StarlingMobile:iOS游戏开发中的材质适配方案
一个游戏适配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写两套代码就有点太苦逼了。
下面我们给出一个简单的思路。
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,一起交流分享。
本文涉及的素材和源码下载: