Link

首页

Tweet

书单

about me

Command

Category

Starling外部资源管理


      对于所有程序员来说,外部资源管理意味着你只需要关心你的程序逻辑即可,对于外部的美工动画你可以不用过多的考虑。也正式因为一些不规范的动画或者美工素材导致一些程序员对此有很大的抵触心里。我也曾经见过一些制作的非常混乱的动画内容,导致在程序配合的时候无从下手,这种情况是极为糟糕的。相对于沟通成本来说,你可能更加倾向于位图。现在有了GPU,你也可以强制的在项目中使用位图资源,而放弃混乱不堪的矢量图内容。

     当然,如果是多人开发的话,也需要注意程序员之间的配合,使用统一的接口,统一访问各种资源。否则你的项目可能会陷入进退两难的境地。对于程序规范这方面,本文不深入设计,大家有兴趣可以去搜索一些代码规范一类的文章。

     资源的外部管理另外一个好处就是方便你的国际化,当然,如果你想发展海外市场,你的游戏或者应用还需要考虑到一些当地的法律和风俗习惯,比如在天朝你不可能去制作色情游戏,在一些中东地区国家你需要注意宗教问题。这些都会牵制着你的游戏是否能够得到当地网民的追捧程度。

      在Starling中,我们可以使用系统默认为我们提供的一些sprite sheet技术来方便的制作位图序列动画。当然,你可以编写自己的数据解析逻辑来方便的控制你的可视化内容。在这篇文章中,我将继续编写我的“打飞机”游戏,其中关于界面的部分,我都会抽离成为外部可配置的文件,这样不仅仅方便你游戏中所有UI的调整,同时也方便后期的国际化移植。

      首先,我们来看一看在Starlig中如何使用sprite sheet。你需要的是已经制作好的素材和一个能够生成sprite sheet配置数据的软件,这里笔者使用的是TexturePacker。值得称赞的是,这款软件拥有Mac和Windows两个版本。你可以根据你的需要来进行下载,在试用期过了之后你需要付费并且激活。下面这幅图片是我所生成的图片和配置文件(这里的素材不是最终完整版)。

点击查看原图

生成的XML配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with TexturePacker http://texturepacker.com-->
<!-- $TexturePacker:SmartUpdate:a9479bcf80444cacbc2cb7cb2411210b$ -->
<TextureAtlas imagePath="config_ui.png">
    <SubTexture name="0_num" x="1008" y="42" width="12" height="18" frameX="-10" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="1_num" x="1016" y="82" width="6" height="16" frameX="-12" frameY="-6" frameWidth="30" frameHeight="30"/>
    <SubTexture name="1_panel" x="933" y="263" width="60" height="126" frameX="-55" frameY="-84" frameWidth="200" frameHeight="300"/>
    <SubTexture name="2_num" x="1008" y="62" width="12" height="18" frameX="-10" frameY="-4" frameWidth="30" frameHeight="30"/>
    <SubTexture name="2_panel" x="806" y="425" width="92" height="126" frameX="-52" frameY="-84" frameWidth="200" frameHeight="300"/>
    <SubTexture name="3_num" x="1002" y="138" width="10" height="18" frameX="-10" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="3_panel" x="900" y="485" width="90" height="126" frameX="-52" frameY="-84" frameWidth="200" frameHeight="300"/>
    <SubTexture name="4_num" x="1002" y="102" width="12" height="16" frameX="-10" frameY="-6" frameWidth="30" frameHeight="30"/>
    <SubTexture name="5_num" x="1002" y="158" width="10" height="18" frameX="-11" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="6_num" x="1008" y="2" width="14" height="18" frameX="-8" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="7_num" x="1002" y="120" width="12" height="16" frameX="-10" frameY="-6" frameWidth="30" frameHeight="30"/>
    <SubTexture name="8_num" x="1002" y="82" width="12" height="18" frameX="-10" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="9_num" x="1008" y="22" width="14" height="18" frameX="-8" frameY="-5" frameWidth="30" frameHeight="30"/>
    <SubTexture name="aircraft_enemy_0" x="933" y="194" width="67" height="67"/>
    <SubTexture name="aircraft_enemy_1" x="806" y="194" width="125" height="125"/>
    <SubTexture name="aircraft_enemy_2" x="911" y="391" width="93" height="92"/>
    <SubTexture name="aircraft_player" x="806" y="321" width="103" height="102"/>
    <SubTexture name="and_btn" x="858" y="594" width="39" height="39"/>
    <SubTexture name="back_btn" x="653" y="727" width="247" height="60" frameX="-5" frameY="-5" frameWidth="257" frameHeight="72"/>
    <SubTexture name="blood" x="806" y="553" width="50" height="50"/>
    <SubTexture name="blood_line" x="806" y="2" width="200" height="30"/>
    <SubTexture name="blood_line_context" x="806" y="34" width="200" height="30"/>
    <SubTexture name="change_missile" x="899" y="613" width="50" height="50"/>
    <SubTexture name="get_skill" x="951" y="613" width="50" height="50"/>
    <SubTexture name="go_panel" x="806" y="66" width="194" height="126" frameX="-4" frameY="-82" frameWidth="200" frameHeight="300"/>
    <SubTexture name="help_back_ground" x="404" y="2" width="400" height="600"/>
    <SubTexture name="help_game_btn" x="404" y="666" width="247" height="60" frameX="-5" frameY="-5" frameWidth="257" frameHeight="72"/>
    <SubTexture name="invincible" x="653" y="604" width="50" height="50"/>
    <SubTexture name="setting_back_ground" x="2" y="604" width="400" height="600"/>
    <SubTexture name="setting_game_btn" x="653" y="665" width="247" height="60" frameX="-5" frameY="-5" frameWidth="257" frameHeight="72"/>
    <SubTexture name="start_game_back_ground" x="2" y="2" width="400" height="600"/>
    <SubTexture name="start_game_btn" x="404" y="604" width="247" height="60" frameX="-5" frameY="-5" frameWidth="257" frameHeight="72"/>
    <SubTexture name="sub_btn" x="858" y="553" width="39" height="39"/>
</TextureAtlas>

      好了,到此位置,UI素材部分已经准备妥当,你需要在程序中编写你的代码来获取这些资源。那么在Starling中图片素材资源最终都会被处理为贴图资源,也就是Texture对象。但不幸的是starling.textures.Texture类只能处理一张位图图片,不能够处理sprite sheet类型的素材,此时你就需要使用到starling.textures.TextureAtlas这个类来进行制作,具体代码如下:

var textures:Texture = Texture.fromBitmapData( PublicData.texturesBitmapData );
            PublicData.UI_TEXTURES_ATLAS = new TextureAtlas( textures, PublicData.texturesXML );
            textures = null;

通过这样的操作后,你可以使用类似于下面的语句来方便的获取单独的贴图资源。

PublicData.UI_TEXTURES_ATLAS.getTexture("background");

注意,这里的"background"字符串来自于XML配置表中SubTexture节点的name属性。

      通过这种方法,我们对现在的所有资源进行统一管理,并且能够很好的进行内存控制。当然,我不建议你将所有的资源全部放到一张图片中,你需要根据你的项目需要进行有效的分配管理。

      下面则是使用Starling来制作位图字体。对于制作webgame的同学来说,在网页中使用个性化字体是非常纠结的事情,尤其中中文字体,我们不可能将一个30M+的字体文件打包到swf文件中让用户加载,对于国内的网速来说是非常不乐观的。使用问题字体你会有效的缓解这方面的压力。当然,对于中文还是无能为力。下面的图片是笔者用GlyphDesigner这款软件来制作的,不幸的是这方面的软件我目前找到了三款MAC系统的,而且质量都不错,对于windows平台还没有发现比较好的软件。图片和配置文件如下:

点击查看原图

XML配置文件:

<font>
    <info face="mebius" size="32" bold="0" italic="0" chasrset="" unicode="0" stretchH="100" smooth="1" aa="1" padding="0,0,0,0" spacing="2,2"/>
    <common lineHeight="36" base="29" scaleW="128" scaleH="64" pages="1" packed="0"/>
    <pages>
        <page id="0" file="number text.png"/>
    </pages>
    <chars count="11">
        <char id="57" x="2" y="3" width="20" height="28" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="0" letter="9"/>
        <char id="56" x="24" y="3" width="20" height="28" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="0" letter="8"/>
        <char id="54" x="46" y="3" width="20" height="28" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="0" letter="6"/>
        <char id="48" x="68" y="3" width="19" height="28" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="0" letter="0"/>
        <char id="51" x="89" y="3" width="19" height="28" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="0" letter="3"/>
        <char id="52" x="2" y="33" width="20" height="27" xoffset="0" yoffset="5" xadvance="18" page="0" chnl="0" letter="4"/>
        <char id="53" x="24" y="33" width="20" height="27" xoffset="1" yoffset="5" xadvance="18" page="0" chnl="0" letter="5"/>
        <char id="50" x="46" y="33" width="20" height="27" xoffset="1" yoffset="5" xadvance="18" page="0" chnl="0" letter="2"/>
        <char id="55" x="68" y="33" width="20" height="27" xoffset="2" yoffset="5" xadvance="18" page="0" chnl="0" letter="7"/>
        <char id="49" x="90" y="33" width="13" height="27" xoffset="3" yoffset="5" xadvance="18" page="0" chnl="0" letter="1"/>
        <char id="32" x="105" y="33" width="0" height="0" xoffset="9" yoffset="49" xadvance="9" page="0" chnl="0" letter=" "/>
    </chars>
    <kernings count="1">
        <kerning first="49" second="49" amount="-2"/>
    </kernings>
</font> 

      下面我们将使用Starling为我们提供的位图字体类来实现文图文本。

private function drawBitmapFont():void
        {
            var fontTexture:Texture = Texture.fromBitmapData( PublicData.numbertexturesBitmapData );
            PublicData.BitmapFontToNumber = new BitmapFont(fontTexture, PublicData.numberXML);
            TextField.registerBitmapFont( PublicData.BitmapFontToNumber );
        }

      在后面编写程序的时候,当你实例化一个TextField的时候只需要设置字体为mebius即可。这里你需要注意一个问题,你定义的字体名称千万不能可系统以后或者市面上已有的字体名称想冲突,否则在用户的及其中显示为系统字体样式。

      最后我来介绍一个我自己编写的UI描述格式,这个格式非常的简单,我们只需要在面板中定义资源的id标签和位置即可。配置文件格式如下:

<root>
    <scene name="start">
        <element type="image" name="startbg" texturesname="start_game_back_ground" x="0" y="0" />
        <element type="button" name="start_btn" texturesname="start_game_btn" x="0" y="300" />
        <element type="button" name="setting_btn" texturesname="setting_game_btn" x="0" y="390" />
        <element type="button" name="help_btn" texturesname="help_game_btn" x="0" y="480" />
    </scene>
    <scene name="setting">
        <element type="image" name="setting_bg" texturesname="setting_back_ground" x="0" y="0" />
        <element type="button" name="soundsub_btn" texturesname="sub_btn" x="50" y="100" />
        <element type="numtext" name="Volumetext" texturesname="" x="100" y="100" />
        <element type="button" name="soundand_btn" texturesname="and_btn" x="200" y="100" />
        <element type="button" name="quality_level_sub_btn" texturesname="sub_btn" x="50" y="200" />
        <element type="numtext" name="qualityleveltext" texturesname="" x="100" y="200" />
        <element type="button" name="quality_level_and_btn" texturesname="and_btn" x="200" y="200" />
        <element type="button" name="back_btn" texturesname="back_btn" x="0" y="300" />
    </scene>
    <scene name="help">
        <element type="image" name="helpbg" texturesname="help_back_ground" x="0" y="0" />
        <element type="button" name="back_btn" texturesname="back_btn" x="0" y="300" />
    </scene>
</root>

      这个描述文件中scene标签标识一个场景,里面的element标识一个UI空间,其中type用来分辨UI空间类型。请注意,这仅仅是一个前期模型的简单版本,并非最终版本。最后的数据格式会更加复杂,解析模块如下:

package ashan.res
{
    import ashan.data.PublicData;
    
    import starling.display.DisplayObject;
    import starling.display.DisplayObjectContainer;
    import starling.display.Image;
    import starling.display.Sprite;
    import starling.text.TextField;
    import starling.text.BitmapFont;
    import starling.utils.Color;

    public class SceneRead
    {
        
        private var _mSceneV:Vector.<Sprite>;
        private var _mSceneNameList:Vector.<String>;
        
        public function SceneRead()
        {
            init();
        }
        
        private function init():void
        {
            this._mSceneV = new Vector.<Sprite>;
            this._mSceneNameList = new Vector.<String>;
        }
        
        public function getSceneByName(_name:String):Sprite
        {
            var _scene:Sprite;
            var _isCreated:Boolean = false;
            
            var i:uint = 0;
            var t:uint = this._mSceneNameList.length;
            while( i < t )
            {
                if( this._mSceneNameList[i] == _name )
                {
                    _isCreated = true;
                    break;
                }
                else
                {
                    _isCreated = false;
                }
                i++;
            }
            
            if( !_isCreated )
            {
                this.drawScene( _name );
            }
            
            _scene = this._mSceneV[i];
            return _scene;
        }
        
        private function drawScene(_name:String):void
        {   
            var _scene:Sprite = new Sprite();
            var _xmlElement:XMLList = new XMLList( PublicData.sceneXML.scene.(@name == _name ) );
            //
            var i:uint = 0;
            var t:uint = _xmlElement.element.length();
            while( i < t )
            {
                var _element:DisplayObject = drawElement( new XMLList(_xmlElement.element[i]));
                _scene.addChild( _element );
                //
                i++;
            }
            
            this._mSceneV.push( _scene );
            this._mSceneNameList.push( _name );
        }
        
        private function drawElement(_xmllist:XMLList):DisplayObject
        {
            var _element:DisplayObject;
            var type:String = _xmllist.@type;
            var _texturesNameStr:String = _xmllist.@texturesname;
            switch( type )
            {
                case "image":
                    _element = new Image( PublicData.UI_TEXTURES_ATLAS.getTexture(_texturesNameStr) );
                    break;
                case "button":
                    _element = new Image( PublicData.UI_TEXTURES_ATLAS.getTexture(_texturesNameStr) );
                    break;
                case "numtext":
                    _element = new TextField(100,50,"0","mebius");
                    ( _element as TextField ).fontName = "mebius";
                    ( _element as TextField ).fontSize = BitmapFont.NATIVE_SIZE;
                    ( _element as TextField ).color = Color.WHITE;
                    break;
            }
            _element.x = uint( _xmllist.@x );
            _element.y = uint( _xmllist.@y );
            _element.name = _xmllist.@name;
            //
            return _element;
        }
    }
}

       我们通过这个SceneRead类来将XML数据转化为对象,并且方便我们的代码管理。最终我们来看一下如何实例化操作一个场景,并且为这个场景中的UI添加逻辑(这部分代码也是demo模型,后期会更加复杂)。

package ashan.scene
{
    import ashan.data.PublicData;
    import ashan.events.SceneEvent;
    import ashan.res.SceneRead;
    
    import flash.events.EventDispatcher;
    
    import starling.display.DisplayObject;
    import starling.display.Sprite;
    import starling.events.Touch;
    import starling.events.TouchEvent;
    import starling.events.TouchPhase;
    import starling.text.TextField;
    
    public class SettingScene extends EventDispatcher
    {
        private var _scene:Sprite;
        private var _mSceneRead:SceneRead;
        
        public function SettingScene(_sceneRead:SceneRead)
        {
            this._mSceneRead = _sceneRead;
            init();
        }
        
        private function init():void
        {
            
            //
            this._scene = this._mSceneRead.getSceneByName(PublicData.SETTING_SCENE_NAME);
            this._scene.getChildByName("back_btn").addEventListener(TouchEvent.TOUCH,
                                                                        back_btn_event);
            this._scene.getChildByName("soundsub_btn").addEventListener(TouchEvent.TOUCH,
                                                                        sub_volume);
            this._scene.getChildByName("soundand_btn").addEventListener(TouchEvent.TOUCH,
                                                                        and_volume);
            this._scene.getChildByName("quality_level_sub_btn").addEventListener(TouchEvent.TOUCH,
                                                                        sub_quality);
            this._scene.getChildByName("quality_level_and_btn").addEventListener(TouchEvent.TOUCH,
                                                                        and_quality);
            //
            var txt:TextField = this._scene.getChildByName("Volumetext") as TextField;
            txt.text = PublicData.Volume.toString();
            var qtxt:TextField = this._scene.getChildByName("qualityleveltext") as TextField;
            qtxt.text = PublicData.QualityLevel.toString();
        }
        
        private function sub_quality(evt:TouchEvent):void
        {
            var btn:DisplayObject = this._scene.getChildByName("quality_level_sub_btn");
            var touch:Touch = evt.getTouch(btn, TouchPhase.BEGAN);
            if (touch)
            {
                if( PublicData.QualityLevel != PublicData.QualityLevel_MIN )
                {
                    PublicData.QualityLevel--;
                    var txt:TextField = this._scene.getChildByName("qualityleveltext") as TextField;
                    txt.text = PublicData.QualityLevel.toString();
                }
            }
        }
        
        private function and_quality(evt:TouchEvent):void
        {
            var btn:DisplayObject = this._scene.getChildByName("quality_level_and_btn");
            var touch:Touch = evt.getTouch(btn, TouchPhase.BEGAN);
            if (touch)
            {
                if( PublicData.QualityLevel != PublicData.QualityLevel_MAX )
                {
                    PublicData.QualityLevel++;
                    var txt:TextField = this._scene.getChildByName("qualityleveltext") as TextField;
                    txt.text = PublicData.QualityLevel.toString();
                }
            }
        }
        
        private function sub_volume(evt:TouchEvent):void
        {
            var btn:DisplayObject = this._scene.getChildByName("soundsub_btn");
            var touch:Touch = evt.getTouch(btn, TouchPhase.BEGAN);
            if (touch)
            {
                if( PublicData.Volume != PublicData.Volume_MIN )
                {
                    PublicData.Volume--;
                    var txt:TextField = this._scene.getChildByName("Volumetext") as TextField;
                    txt.text = PublicData.Volume.toString();
                }
            }
            
        }
        
        private function and_volume(evt:TouchEvent):void
        {
            var btn:DisplayObject = this._scene.getChildByName("soundand_btn");
            var touch:Touch = evt.getTouch(btn, TouchPhase.BEGAN);
            if (touch)
            {
                if( PublicData.Volume != PublicData.Volume_MAX )
                {
                    PublicData.Volume++;
                    var txt:TextField = this._scene.getChildByName("Volumetext") as TextField;
                    txt.text = PublicData.Volume.toString();
                }
            }
            
        }
        
        private function back_btn_event(evt:TouchEvent):void
        {
            var btn:DisplayObject = this._scene.getChildByName("back_btn");
            var touch:Touch = evt.getTouch(btn, TouchPhase.BEGAN);
            if (touch)
            {
                var evts:SceneEvent = new SceneEvent( SceneEvent.BACK_START_SCENE_WINDOWS );
                this.dispatchEvent( evts );
            }
        }
        
        public function get scene():Sprite
        {
            return _scene;
        }
        
        
    }
}

       OK!本篇文章就到这里,希望能对你有所帮助。如果你在使用Starling框架的过程中遇到问题或者对此保留意见,欢迎在g新浪微博@我,或者发送email到mebius@ashan.org