StarlingMobile:iOS游戏开发中的材质适配方案

来自Starling中文站
跳转到: 导航, 搜索

作者:京辉(icekiller)

一个游戏适配ipad和iphone的话一般会出两套素材,如果整个游戏的素材在一张材质集上的话还好办。如果素材分布在多张材质集上呢(starling单张素材限制是2048*2048)?比如说ipad的素材需要2张材质集(下图中的ipad_asset1,ipad_asset2),iphone的素材需要1张材质集(下图中的iphone_asset1)。我们假设这样一个情景,材质A(画红色圆圈的)在ipad素材库中是在asset2材质集上,而在iphone素材库中则是在asset1材质集上(这个很容易理解,因为iphone的素材大小要比ipad的小很多,pad两张材质集的phone一般一张就能搞定)。我们为了获取材质A写两套代码就有点太苦逼了。

Ios ipad apder.png

下面我们给出一个简单的思路。

ps : 用到的工具是TexturePacker.

starling中的每个材质集都会对应一个xml文件,如果开始我们把所有用到的材质集xml放到一个字典里,需要获取每个材质的时候,通过材质名称从字典里找到材质所在的材质集,然后返回材质对象,这样用自动查找代替人工赋值。我们就不用关心所需的材质到底在哪个材质集里了。iphone和ipad的材质只要名称一样就OK 了。

下面直接上代码

我们先定义一个接口IAssetFactory

package com.pamakids.assetmanager 
{
    import starling.extensions.ATextureAtlas;
    import starling.textures.Texture;
 
    public interface IAssetFactory
    {
        function init():void;
        function addTextureAtlas(name : String, texture : Texture, atlasXml : XML,textureName : String):void;
        function getTexture(name : String):Texture;
        function getTextures(name : String):Vector.<Texture>;
        function getTextureAtls(name : String) : ATextureAtlas;
    }
}

然后修改了下starling中的TextureAtlas.as文件,作为一个扩展命名为ATextureAtlas.as.主要是加入了一个存储材质集上材质名称的字典.

// ================================================================================================= 
//
//    Starling Framework
//    Copyright 2011 Gamua OG. All Rights Reserved.
//
//    This program is free software. You can redistribute and/or modify it
//    in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
 
package starling.extensions
{
    import flash.geom.Rectangle;
    import flash.utils.Dictionary;
 
    import starling.textures.Texture;
    public class ATextureAtlas
    {
        public var mTextureNames : Dictionary = new Dictionary();
        public var mAtlasTexture : Texture;
        public var mTextureRegions:Dictionary;
        private var mTextureFrames:Dictionary;
 
        /** Create a texture atlas from a texture by parsing the regions from an XML file. */
        public function ATextureAtlas(texture:Texture, atlasXml:XML=null)
        {
            mTextureRegions = new Dictionary();
            mTextureFrames = new Dictionary();
            mAtlasTexture = texture;
 
            if (atlasXml)
                parseAtlasXml(atlasXml);
        }
 
        /** Disposes the atlas texture. */
        public function dispose():void
        {
            mAtlasTexture.dispose();
        }
 
        private function parseAtlasXml(atlasXml:XML):void
        {
            var scale:Number;
            if(mAtlasTexture){
                scale = mAtlasTexture.scale;
            }else {
                scale = 1;
            }
 
            for each (var subTexture:XML in atlasXml.SubTexture)
            {
                var name:String = subTexture.attribute("name");
                var x:Number = parseFloat(subTexture.attribute("x")) / scale;
                var y:Number = parseFloat(subTexture.attribute("y")) / scale;
                var width:Number = parseFloat(subTexture.attribute("width")) / scale;
                var height:Number = parseFloat(subTexture.attribute("height")) / scale;
                var frameX:Number = parseFloat(subTexture.attribute("frameX")) / scale;
                var frameY:Number = parseFloat(subTexture.attribute("frameY")) / scale;
                var frameWidth:Number = parseFloat(subTexture.attribute("frameWidth")) / scale;
                var frameHeight:Number = parseFloat(subTexture.attribute("frameHeight")) / scale;
 
                var region:Rectangle = new Rectangle(x, y, width, height);
                var frame:Rectangle = frameWidth > 0 && frameHeight > 0 ?
                        new Rectangle(frameX, frameY, frameWidth, frameHeight) : null;
 
                addRegion(name, region, frame);
            }
        }
 
        /** Retrieves a subtexture by name. Returns <code>null</code> if it is not found. */
        public function getTexture(name:String):Texture
        {
            var region:Rectangle = mTextureRegions[name];
 
            if (region == null) return null;
            else return Texture.fromTexture(mAtlasTexture, region, mTextureFrames[name]);
        }
 
        /** Returns all textures that start with a certain string, sorted alphabetically
         * (especially useful for "MovieClip"). */
        public function getTextures(prefix:String=""):Vector.<Texture>
        {
            var textures:Vector.<Texture> = new <Texture>[];
            var names:Vector.<String> = new <String>[];
            var name:String;
 
            for (name in mTextureRegions)
                if (name.indexOf(prefix) == 0)                
                    names.push(name);                
 
            names.sort(Array.CASEINSENSITIVE);
 
            for each (name in names) 
                textures.push(getTexture(name)); 
 
            return textures;
        }
 
        /** Creates a region for a subtexture and gives it a name. */
        public function addRegion(name:String, region:Rectangle, frame:Rectangle=null):void
        {
            mTextureNames[name] = name;
            mTextureRegions[name] = region;
            if (frame) mTextureFrames[name] = frame;
        }
 
        /** Removes a region with a certain name. */
        public function removeRegion(name:String):void
        {
            delete mTextureRegions[name];
        }
    }
}

接着就是AssetFactory.as,负责材质的初始化,获取材质,以及材质序列等。

package com.pamakids.assetmanager 
{
    import com.adobe.utils.DictionaryUtil;
 
    import flash.display.Bitmap;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;
    import flash.utils.getDefinitionByName;
    import flash.utils.getTimer;
 
    import starling.extensions.ATextureAtlas;
    import starling.textures.Texture;
    import starling.textures.TextureAtlas;
 
    /**
     * @example
     *         var assetFactory : AssetFactory = new AssetFactory();
     *         assetFactory.registerAssets("Assets");
     *         assetFactory.addTextureAtlas("asset1",assetFactory.getMTexture("Asset1GFX"),assetFactory.getMXml("Asset1XML"),"Asset1GFX");
     *         assetFactory.addTextureAtlas("asset2",assetFactory.getMTexture("Asset2GFX"),assetFactory.getMXml("Asset2XML"),"Asset2GFX");
     *        //如果只想初始化话数据,不像初始化材质的话,直接把材质为null就好,用到时实时渲染材质
     *         assetFactory.addTextureAtlas("asset2",null,assetFactory.getMXml("Asset2XML"),"Asset2GFX");
     * @example
     */
    public class AssetFactory implements IAssetFactory
    {        
        private var mTextureAtlasDic : Dictionary;
        private var mTextureDic : Dictionary;
        private var mTexturesDic : Dictionary;
        private var sTextures : Dictionary;
        private var mtextureXmlDic : Dictionary;
        private var mTextureNameDic : Dictionary;
        private var assetClassName : String;
        private var assetClassReference : Class;
 
        public function AssetFactory()
        {
            mTextureAtlasDic = new Dictionary();
            mTextureDic = new Dictionary();
            mTexturesDic = new Dictionary();
            sTextures = new Dictionary();
            mTextureNameDic = new Dictionary();
            mtextureXmlDic = new Dictionary();
        }
 
        /**
         * you can override init() 
         */
        public function init():void {
 
        }
 
        /**
         * 
         * @param    name
         * @return
         */
        public function getTextureAtls(name : String) : ATextureAtlas {
            if (mTextureAtlasDic[name]) {
                return mTextureAtlasDic[name];
            }
            return null;
        }
 
        /**
         * 注册材质源
         * @param    name
         */
        public function registerAssets(classname : Class):void {
            assetClassReference = classname;
        }
 
        /**
         * 根据名称获得相应的材质
         * @param    name
         * @return
         */
        public function getMTexture(name:String) : Texture
        {
            if (mtextureXmlDic[name] == undefined)
            {
                var data:Object = new assetClassReference[name]();
                if (data is Bitmap)
                    sTextures[name] = Texture.fromBitmapData((data as Bitmap).bitmapData,false);
                else if (data is ByteArray)
                    sTextures[name] = Texture.fromAtfData(data as ByteArray);
            }
            return sTextures[name];
        }
 
        /**
         * 根据名称获得xml
         * @param    xname
         * @return
         */
        public function getMXml(xname : String):XML {
            if (mtextureXmlDic[xname] == undefined)
            {
                var data:Object = new assetClassReference[xname]();
                mtextureXmlDic[xname] = XML(data);
            }
            return mtextureXmlDic[xname];
        }
 
 
        /**
         * 添加材质集数据
         * @param    name 名称
         * @param    texture 对应的材质
         * @param    atlasXml xml文件
         */
        public function addTextureAtlas(name:String, texture:Texture, atlasXml:XML,textureName : String):void
        {    
            mTextureAtlasDic[name] = new ATextureAtlas(texture, atlasXml);
            mTextureNameDic[name] = textureName;
        }
 
        /**
         * 根据名称获得库中的材质
         * @param    name
         * @return
         */
        public function getTexture(name:String):Texture
        {
            if (mTextureDic[name]) {
 
            }else {
                var taltsName : String = getTexturesAtlasName(name);
                if ((mTextureAtlasDic[taltsName] as ATextureAtlas).mAtlasTexture == null) {
                    (mTextureAtlasDic[taltsName] as ATextureAtlas).mAtlasTexture = getMTexture(mTextureNameDic[taltsName]);
                }
                mTextureDic[name] = (mTextureAtlasDic[taltsName] as ATextureAtlas).getTexture(name);
            }
            return mTextureDic[name];
        }
 
        /**
         * 根据名称获得材质序列
         * @param    name
         * @return
         */
        public function getTextures(name:String):Vector.<Texture>
        {
            if (mTexturesDic[name]) {
 
            }else {
                mTexturesDic[name] = new Vector.<Texture>();
                var taltsNames : Vector.<String> = getTexturesName(name);
                trace("taltsNames : " + taltsNames);
                if (taltsNames) {
                    for (var i : int = 0; i < taltsNames.length; i ++ ) {
                        trace("i : " + taltsNames[i]);
                        if ((mTextureAtlasDic[taltsNames[i]] as ATextureAtlas).mAtlasTexture == null) {
                            (mTextureAtlasDic[taltsNames[i]] as ATextureAtlas).mAtlasTexture = getMTexture(mTextureNameDic[taltsNames[i]]);
                        }
                        mTexturesDic[name] = mTexturesDic[name].concat((mTextureAtlasDic[taltsNames[i]] as ATextureAtlas).getTextures(name));
                    }
                }
            }
            return mTexturesDic[name];
        }
 
        /**
         * 根据材质名称获得所属材质集的名称
         * @param    name
         * @return
         */
        private function getTexturesAtlasName(name : String):String {
            var dic : Dictionary;
            for (var k : String in mTextureAtlasDic) {
                dic = (mTextureAtlasDic[k] as ATextureAtlas).mTextureNames;    
                if (dic[name]) {
                    return k;
                    break;
                }
            }
            return "undefined";
        }
 
        /**
         * 根据材质序列名称获得所属材质集的名称
         * @param    name
         * @return
         */
        private function getTexturesName(name : String) : Vector.<String> {
            var tlist : Vector.<String> = new Vector.<String>();
            var dic : Dictionary;
            var len : int = 0;
            var oldTime : uint = getTimer();
            len = name.length;
            for (var k : String in mTextureAtlasDic) {
                dic = (mTextureAtlasDic[k] as ATextureAtlas).mTextureNames;    
                for (var m : String in dic) {
                    if (m.substr(0, len) == name) {
                        tlist.push(k);
                        break;
                    }
                }
            }
            if (tlist.length > 0) {
                return tlist;
            }else{
                return null;
            }
        }
 
    }
}

最后呢,就是AssetManager.as,负责管理各材质工厂了。

package com.pamakids.assetmanager 
{
    import flash.utils.Dictionary;
    import starling.extensions.ATextureAtlas;
    import starling.textures.Texture;
    import starling.textures.TextureAtlas;
 
    /**
     * @example
     * AssetManager.getInstance().reregisterAssetFactory("ipad",ipadAssetFactory);
     * AssetManager.getInstance().reregisterAssetFactory("iphone",iphoneAssetFactory);
     * AssetManager.getInstance().setDefaultAssetFactory("ipad");
     * AssetManager.getInstance().init();
     * @example
     */
    public class AssetManager
    {
        private static var instance : AssetManager;
        private var curAssetFactory : IAssetFactory;
        private var assetFactoryDic : Dictionary = new Dictionary();
        public function AssetManager()
        {
 
        }
 
        public static function getInstance():AssetManager {
            if (instance == null) {
                instance = new AssetManager();
            }
            return instance;
        }
 
        /**
         * 注册
         * @param    name
         * @param    factory
         */
        public function reregisterAssetFactory(name : String,factory : IAssetFactory):void{
            assetFactoryDic[name] = factory;
        }
 
        /**
         * 设置默认仓库
         * @param    name
         */
        public function setDefaultAssetFactory(name : String):void{
            if(assetFactoryDic[name]){
                curAssetFactory = assetFactoryDic[name] as IAssetFactory;
            }
        }
 
        /**
         * 初始化
         */
        public function init():void {
            if(curAssetFactory){
                curAssetFactory.init();
            }
        }
 
        public function getTextureAtls(name : String) : ATextureAtlas {
            if (curAssetFactory) {
                return curAssetFactory.getTextureAtls(name);
            }
            return null;
        }
 
        public function getTexture(name : String):Texture {
            if (curAssetFactory) {
                return curAssetFactory.getTexture(name);
            }
            return null;
        }
 
        public function getTextures(name:String):Vector.<Texture>
        {
            if (curAssetFactory) {
                return curAssetFactory.getTextures(name);
            }
            return null;
        }
    }
}

下面写个简单的用例。

IpadAssetFactory.as

package  
{
    import com.pamakids.assetmanager.AssetFactory;
 
    public class IpadAssetFactory extends AssetFactory
    {
        public function IpadAssetFactory()
        {
            super();
        }
 
        override public function init():void {
            this.registerAssets(Assets);
            this.addTextureAtlas("asset2", null, this.getMXml("AtlasXml2Game"), "AtlasTexture2Game");
            this.addTextureAtlas("asset1",null,this.getMXml("AtlasXmlGame"),"AtlasTextureGame");
 
        }
 
    }
}

IphoneAssetFactory.as

package  
{
    import com.pamakids.assetmanager.AssetFactory;
 
    /**
     * ...
     * @author icekiller
     */
    public class IphoneAssetFactory extends AssetFactory 
    {
 
        public function IphoneAssetFactory() 
        {
            this.registerAssets(Assets);
            this.addTextureAtlas("iphoneAsset1",null,this.getMXml("AssetXmlIphone1"),"AssetIphone1");
        }
 
    }
 
}

Assets.as

package  
{
    import flash.display.Bitmap;
    import flash.utils.Dictionary;
 
    import starling.textures.Texture;
    import starling.textures.TextureAtlas;
 
    public class Assets extends Object
    {
        [Embed( source = "../sprrow/asset1.png" )]
        public static const AtlasTextureGame:Class;
        [Embed(source = "../sprrow/asset1.xml", mimeType = "application/octet-stream")]
        public static const AtlasXmlGame:Class;
 
        [Embed( source = "../sprrow/asset2.png" )]
        public static const AtlasTexture2Game:Class;
        [Embed(source = "../sprrow/asset2.xml", mimeType = "application/octet-stream")]
        public static const AtlasXml2Game:Class;
 
        [Embed( source = "../sprrow/iphone_asset.png" )]
        public static const AssetIphone1:Class;
        [Embed(source = "../sprrow/iphone_asset.xml", mimeType = "application/octet-stream")]
        public static const AssetXmlIphone1:Class;
    }
}

最后就是在主类里应用了

var ipadAssetFactory : IpadAssetFactory = new IpadAssetFactory(); 
var iphoneAssetFactory : IphoneAssetFactory = new IphoneAssetFactory();
AssetManager.getInstance().reregisterAssetFactory("ipad", ipadAssetFactory);
AssetManager.getInstance().reregisterAssetFactory("iphone",iphoneAssetFactory);
//根据设备类型设置默认的素材库
AssetManager.getInstance().setDefaultAssetFactory("iphone");
AssetManager.getInstance().init();
var texture :  = AssetManager.getInstance().getTexture("huabing005");
var pig : Image= new Image(texture);
addChild(pig);

ps:

暂时没解决的问题是如果一个动画序列分布在不同材质集上的话,获取这个序列的时候,顺序会不正确,这个会有解决方案的。所以暂时使用的时候尽量确保一个动画序列在同一个材质集上。

this.addTextureAtlas("asset2", null, this.getMXml("AtlasXml2Game"), "AtlasTexture2Game");

这块尽量确保进入游戏的第一个画面的所有素材在一个材质集上。如果只想初始化化数据,不想初始化材质的话,直接把材质为null就好,用到时实时渲染材质.

对OOP理解的很浅显,如果哪位对类的结构有更好的组织或写更改的话,欢迎分享。

如果有关于Starling或者移动开发方面的交流的话可以到新浪微博@icekiller_cn,一起交流分享。


本文涉及的素材和源码下载:

http://vdisk.weibo.com/s/5nNAw

个人工具
名字空间

变换
操作
导航
Starling中文资料
Starling原创教程
论坛
友链
工具箱