A闪的 BLOG 技术与人文
对于程序来讲,我们总希望能够制作一套以不变应万变的方法,可惜世界上并没有这样的东西。对于游戏开发来讲,我们不可能每次更新一些资源就要将程序重新build一边,也没有人会将游戏资源做到程序中,除非你是为一些客户定制某些产品,为了防止客户自行更改程序你需要使用这些手段。一般来讲我们会将游戏的资源放到外部,在使用的时候动态的load进来或者从服务器以流的方式将资源推送到用户的终端中。那么stage3D中最明显的外部操作则是某型文件的操作。我们不会傻傻的将所有模型全部编译到程序的code中。但对于模型文件的格式却大大制约了我们程序的效率以及效果。
举个简单的例子,当你有一个非常复杂的模型时候,你是希望把它变为XML格式自行解析呢?还是想把它保存为一个压缩过的二进制格式去使用呢?我想大家都喜欢使用第二种方式,不过第二种方式的难度颇高。因为二进制文件不直观,调试麻烦。而XML的缺点在于文件体积大,解析慢。对于如何选择,笔者认为选择一个易用,把握性较大的方式最好。本篇我们就来带着大家制作一个非常简单的demo,这个demo中,我们把所有的数据全部二进制话,存储到外部,当程序读取时候进行快速解析。这种方式的好处在于,前期的运行速度全部消耗在加载操作中,一旦加载完成,我们可以很快的看到画面内容。我们制作的效果还是原来第二篇内容的效果,只不过我们将数据全部转为二进制格式。首先我们来编写一个简单的工具一边将直观的数据转化为二进制文件。
先从顶点数据开始,由于我们的二进制格式有四个文件,分变为顶点数据,顶点索引数据和两个AGAL文件。这里我们需要对顶点数据和顶点索引数据的数据格式做一个简单的定义。
由于Flash API中提供了二进制数据的上传接口,所以我们默认将数据格式定义为系统需要的格式。请注意uploadFromByteArray 对于数据的要求是长度为4个字节的数据,同时为了区分顶点数据和索引数据的格式,我们在文件开始写入一个字节,如果字节为0表示顶点数据,如果为1表示索引数据。代码如下:
public static function createVertextData(val:Vector.<Number>,data32PerVertex:uint):ByteArray { var rel:ByteArray; rel = new ByteArray(); rel.endian = Endian.LITTLE_ENDIAN; rel.writeByte(0); //o表示为顶点数据 rel.writeInt( data32PerVertex ); //写入每一个数据的数量 rel.writeInt( val.length / data32PerVertex ); //写入顶点数量 var len:uint = val.length; for( var i:int = 0; i<len; i++ ) { rel.writeFloat( val[i] ); } return rel; }
我们将原来的顶点数据传入,并且将每个顶点的数据长度传入,这样我们得到的数据格式为
数据类型 一个字节 0表示顶点数据
顶点数据数量 四个字节 32位有符号int类型
顶点数量 四个字节 32位有符号int类型
主体数据 长度为4个字节的32位有符号浮点型数据
OK,将上面的代码封装成为一个类之后我们建立一个AIR项目来进行使用。具体使用方法如下:
//三角形顶点数据 var triangleData:Vector.<Number> = Vector.<Number>([ // x, y, r, g, b 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0 ]); var byte:ByteArray = VertexData.createVertextData( triangleData, 5 ); var file:File = File.desktopDirectory; file = file.resolvePath(“ver.stage3dvertex”); var fileStream:FileStream = new FileStream(); fileStream.open(file, FileMode.UPDATE); fileStream.writeBytes( byte ); fileStream.close();
我们直接想文件写入到磁盘的桌面上,并且文件名为ver.stage3dvertex。
下面我们来生成索引数据,具体代码如下:
public static function createIndexData(val:Vector.<uint>):ByteArray { var rel:ByteArray; rel = new ByteArray(); rel.endian = Endian.LITTLE_ENDIAN; rel.writeByte(1); //1表示为顶点索引数据 rel.writeInt( val.length ); //写入顶点索引数量长度 var len:uint = val.length; for( var i:int = 0; i<len; i++ ) { rel.writeShort( val[i] ); } return rel; }
顶点数据的要求是2子节点的数据,那么我们将数据格式定义如下:
数据类型 一个字节 1表示索引数据
索引数据数量 四个字节 32位有符号int类型
主体数据 长度为2个字节的16位有符号int型数据
在刚才的AIR项目中,继续编写代码,以便生成文件
//三角形索引数据 var indexData:Vector.<uint> = Vector.<uint>([ 0, 1, 2 ]); byte = VertexData.createIndexData( indexData ); file = File.desktopDirectory; file = file.resolvePath(“ver.stage3dindex”); fileStream = new FileStream(); fileStream.open(file, FileMode.UPDATE); fileStream.writeBytes( byte ); fileStream.close();
当我们自定义的两组文件都生成好之后,我们来制作AGAL的功能,由于adobe为我们提供了相关的类,我直接将代码贴到下面。
public static function createVAGAL(val:String):ByteArray { var rel:ByteArray; var vagal:AGALMiniAssembler = new AGALMiniAssembler(); vagal.assemble( Context3DProgramType.VERTEX, val ); rel = vagal.agalcode; return rel; } public static function createFAGAL(val:String):ByteArray { var rel:ByteArray; var vagal:AGALMiniAssembler = new AGALMiniAssembler(); vagal.assemble( Context3DProgramType.FRAGMENT, val ); rel = vagal.agalcode; return rel; }
最后,我们通过运行AIR程序得到四个文件,分别为
ver.stage3dvertex
ver.stage3dindex
ver.stage3dfagal
ver.stage3dvagal
我们新疆一个工程,将上面的四个文件拷贝到对应的工程目录下,等待我们程序的调用。现在我们要做的工作就是将我们生成好的文件在程序中进行解析,由于在本身的数据之中我们又添加了一些自定义的数据,所以,这里我们编写两个解析文件的类。两个类的代码如下:
顶点数据解析:
package { import flash.utils.ByteArray; import flash.utils.Endian;public class VertexDataRead { private var _data32PerVertex:uint; private var _numVertices:uint; private var _data:ByteArray; public function VertexDataRead() { } public function get data():ByteArray { return _data; }
public function get numVertices():uint { return _numVertices; }
public function get data32PerVertex():uint { return _data32PerVertex; }
public function read( val:ByteArray ):void { val.endian = Endian.LITTLE_ENDIAN; var format:int = val.readByte(); if( format == 0 ) { trace(“[VertexDataRead 数据正确]”); this._data32PerVertex = val.readInt(); this._numVertices = val.readInt(); this._data = new ByteArray(); this._data.endian = Endian.LITTLE_ENDIAN; val.readBytes( this._data ); } else { throw new Error(“数据操作失败,数据格式不正确!”); } } } }
索引数据解析:
package { import flash.utils.ByteArray; import flash.utils.Endian;public class IndexDataRead { private var _numVertices:uint; private var _data:ByteArray; public function IndexDataRead() { } public function get data():ByteArray { return _data; } public function get numVertices():uint { return _numVertices; } public function read( val:ByteArray ):void { val.endian = Endian.LITTLE_ENDIAN; var format:int = val.readByte(); if( format == 1 ) { trace(“[IndexDataRead 数据正确]”); this._numVertices = val.readInt(); this._data = new ByteArray(); this._data.endian = Endian.LITTLE_ENDIAN; val.readBytes( this._data ); } else { throw new Error(“IndexDataRead::数据操作失败,数据格式不正确!”); } } } }
有了数据解析的功能,我们就可以方便并且放心的来使用这些数据了,在文档类中,我们编写如下代码:
package { import flash.display.Sprite; import flash.display.Stage3D; import flash.display3D.Context3D; import flash.display3D.Context3DRenderMode; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.VertexBuffer3D; import flash.events.ErrorEvent; import flash.events.Event; import flash.events.MouseEvent; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.utils.ByteArray; import flash.utils.Endian;public class FileData extends Sprite { private var _stage3d:Stage3D; private var _context3d:Context3D; private var files:Array = [“ver.stage3dvertex”, “ver.stage3dindex”, “ver.stage3dvagal”, “ver.stage3dfagal”]; private var filesIndex:uint = 0; private var bytes:Array = []; private var _vb:VertexBuffer3D; private var _ib:IndexBuffer3D; private var _pm:Program3D; private var vdr:VertexDataRead = new VertexDataRead(); private var idr:IndexDataRead = new IndexDataRead(); private var vagal:ByteArray; private var fagal:ByteArray; public function FileData() { trace( “stage3D层数量:”,stage.stage3Ds.length ); if( stage.stage3Ds.length > 0 ) { this._stage3d = stage.stage3Ds[0]; this._stage3d.addEventListener(Event.CONTEXT3D_CREATE,created_handler); this._stage3d.addEventListener(ErrorEvent.ERROR, created_error_handler ); this._stage3d.requestContext3D( Context3DRenderMode.AUTO ); } } private function created_error_handler(evt:ErrorEvent):void { trace(“GPU启动失败或设备丢失!”); } private function created_handler (evt:Event):void { trace( “GPU设备类型:”,this._stage3d.context3D.driverInfo ); this._context3d = this._stage3d.context3D; this._context3d.enableErrorChecking = true; this._context3d.configureBackBuffer( 300,300,0,true); this.loadFile( this.files[ this.filesIndex ] ); } private function loadFile( url:String ):void { var data:URLLoader = new URLLoader(); data.dataFormat = URLLoaderDataFormat.BINARY; data.addEventListener(Event.COMPLETE, dataload_complate_handler ); data.load( new URLRequest(url) ); } private function dataload_complate_handler(evt:Event):void { var byte:ByteArray = evt.target.data; byte.endian = Endian.LITTLE_ENDIAN; this.bytes.push( byte ); this.filesIndex++; if( this.filesIndex < this.files.length ) { this.loadFile( this.files[ this.filesIndex ] ); } else { updateAll(); } } private function updateAll():void { this.vdr.read( this.bytes[0] ); this.idr.read( this.bytes[1] ); this.vagal = this.bytes[2] ; this.fagal = this.bytes[3]; addTriangle(); this.addEventListener(Event.ENTER_FRAME, draw ); } private function addTriangle():void { this._vb = this._context3d.createVertexBuffer( vdr.numVertices, vdr.data32PerVertex ); this._vb.uploadFromByteArray( vdr.data, 0, 0, vdr.numVertices ); this._ib = this._context3d.createIndexBuffer( idr.numVertices ); this._ib.uploadFromByteArray( idr.data, 0, 0, idr.numVertices ); this._pm = this._context3d.createProgram(); this._pm.upload( vagal, fagal ); this._context3d.setVertexBufferAt( 0, this._vb, 0, Context3DVertexBufferFormat.FLOAT_2 ); this._context3d.setVertexBufferAt( 1, this._vb, 2, Context3DVertexBufferFormat.FLOAT_3 ); this._context3d.setProgram( this._pm ); } private function draw(evt:Event):void { this._context3d.clear( 0, 0, 0); this._context3d.drawTriangles( this._ib ); this._context3d.present(); } private function click_handler(evt:MouseEvent):void { this._context3d.dispose(); } } }
通过通过代码我们可以看到,原来定义的Vector已经从文件中被我们删除,取而代之的则是loader操作。而数据的上传操作从原来多个复杂的参数变为了数据读取的操作。这样做大大方便了程序的编写,而且从程序的扩展性来说也提高不少。最后我们运行一下看看效果,与原来的效果完全一样,只不过我们将程序的数据生成方式全部改变。
最后我们通过Scout工具来检测一下程序运行速度的明显变化,第一张图是原来程序自己创建数据的运行结果,我么可以看到,在addTriangle方法中由于我们人工创建了数据,导致这一步操作消耗了15ms的时间,第二张图我们使用了文件加载的方式,省去了数据创建,将创建变为了解析,所以可以看到在addTriangle这个方法上我们消耗了2ms的时间。通过这种方法,我们将程序的运行时间大大缩短,也将画面的呈现速度提高。