Away3D入门(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/getting-started-with-away3d-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 13 分钟阅读 - 6379 个词 阅读量 0Away3D入门(译文)
原文地址:https://www.codeproject.com/Articles/39229/Getting-Started-with-Away3D
原文作者:MatthewCasperson
译文由本站 robot-v1.0 翻译
前言
See how to create a simple framework for your 3D Flash applications using Away3D.
了解如何使用Away3D为3D Flash应用程序创建简单的框架.
介绍(Introduction)
Away3D(Away3D) 是一款功能强大的Flash 3D引擎,它是从Papervision Flash 3D引擎衍生而来的.从那时起,它就拥有了自己的生命,目前是可用于Flash的少数几个可以利用Flash Player 10的新功能的3D引擎之一.(is a powerful Flash 3D engine which started life as a spin off from the Papervision Flash 3D engine. Since then, it has taken on a life of its own, and is currently one of the few 3D engines available for Flash that can make use of the new features of Flash Player 10.) 在本教程中,我将向您展示如何启动和运行基本的Away3D程序.这将是一系列教程中的第一篇,因此,我们将在此基础上奠定很多基础.这意味着尽管结果将非常简单,但结果是我们将创建一些类.(In this tutorial, I will be showing you how to get a basic Away3D program up and running. This will be the first in a series of tutorials, and as such, we will lay down a lot of foundation here that we can build on. This means that while the outcome will be quite simple, we will be creating a few classes as a result.)
背景(Background)
此代码用作(This code is used as the basis for a) 系列教程(series of tutorials) 炫耀Away3D引擎.(showing off the Away3D engine.)
使用代码(Using the code)
该程序本身将在概念上分为两个区域:“引擎"和"应用程序”.这样做的原因是,将有大量用于创建,运行和清理Away3D"引擎"的代码,并且在大多数情况下,这些代码在教程中是常见的.另一方面,教程之间的"应用程序"将发生重大变化.通过将构成这两个区域的代码分开,我们可以定义一个可重用的基础,并将其与所有即将上架的教程一起使用,它还将帮助将实现本教程结果的代码与运行该模板的样板代码隔离开来. Away3D引擎.(The program itself will be conceptually split into two areas: the “engine” and the “application”. The reason for this is that there will be a great deal of code that is used to create, run, and cleanup the Away3D “engine”, and for the most part, this code will be common amongst the tutorials. On the other hand, the “application” will change significantly between tutorials. By keeping the code that makes up these two areas separate, we can define a reusable base that will be used with all the coming tutorials, and it will also help isolate the code that achieves the result of the tutorial from the boilerplate code that runs the Away3D engine.)
我们需要创建的第一类是(The first class we need to create is the) EngineManager
.顾名思义,该类将处理创建,运行和销毁Away3D引擎.(. As the name suggests, this class will deal with creating, running, and destroying the Away3D engine.)
EngineManager.as(EngineManager.as)
package
{
import away3d.cameras.Camera3D;
import away3d.containers.View3D;
import away3d.core.render.Renderer;
import mx.collections.ArrayCollection;
import mx.core.UIComponent;
import mx.core.Application;
import flash.events.*;
public class EngineManager extends UIComponent
{
public static const version:String = "1.0.0";
protected static const MEASURED_MIN_WIDTH:int = 25;
protected static const MEASURED_MIN_HEIGHT:int = 25;
protected static const MEASURED_WIDTH:int = 100;
protected static const MEASURED_HEIGHT:int = 100;
// Away3D view
internal var view:View3D = null;
// Away3D camera
internal var cam:Camera3D = null;
// a collection of the BaseObjects
protected var baseObjects:ArrayCollection = new ArrayCollection();
// a collection where new BaseObjects are placed, to avoid adding items
// to baseObjects while in the baseObjects collection while it is in a loop
protected var newBaseObjects:ArrayCollection = new ArrayCollection();
// a collection where removed BaseObjects are placed, to avoid removing items
// to baseObjects while in the baseObjects collection while it is in a loop
protected var removedBaseObjects:ArrayCollection = new ArrayCollection();
// the last frame time
protected var lastFrame:Date;
// the application manager
protected var applicationManager:ApplicationManager = null;
// the resource manager
protected var myResourceManager:ResourceManager = null;
// true when we have added the Away3D controls
protected var addedToStage:Boolean = false;
// true if some properties have been modifed
protected var propertiesDirty:Boolean = false;
internal function get MyResourceManager():ResourceManager
{
return myResourceManager;
}
protected function propertyChanged():void
{
propertiesDirty = true;
invalidateProperties();
invalidateDisplayList();
}
public function EngineManager()
{
super();
}
override protected function measure():void
{
super.measure();
// set a bunch of predefined sizes
this.measuredMinWidth = MEASURED_MIN_WIDTH;
this.measuredMinHeight = MEASURED_MIN_HEIGHT;
this.measuredHeight = MEASURED_HEIGHT;
this.measuredWidth = MEASURED_WIDTH;
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (view != null)
{
// resize the viewport to match the new settings
view.x = this.height/2;
view.y = this.width/2;
}
}
override protected function commitProperties():void
{
super.commitProperties();
if (propertiesDirty)
{
propertiesDirty = false;
if (addedToStage)
{
applicationManager.shutdown();
applicationManager.startupApplicationManager();
}
}
}
override protected function createChildren():void
{
super.createChildren();
addEventListener(Event.ADDED_TO_STAGE, createChildrenEx);
addEventListener(Event.REMOVED_FROM_STAGE, shutdown);
}
protected function shutdown(event:Event):void
{
if (applicationManager != null)
applicationManager.shutdown();
shutdownAll();
this.removeChild(view);
applicationManager = null;
addedToStage = false;
view = null;
cam = null;
}
protected function createChildrenEx(event:Event):void
{
if (!addedToStage)
{
cam = new Camera3D();
view = new View3D({x:250,y:150,camera:cam});
view.renderer = Renderer.BASIC;
addChild(view);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
// set the initial frame time
lastFrame = new Date();
// load any resources
myResourceManager = new ResourceManager();
myResourceManager.loadResources();
// start the application manager
applicationManager =
new ApplicationManager(this).startupApplicationManager();
addedToStage = true;
}
}
protected function onEnterFrame(event:Event):void
{
// Calculate the time since the last frame
var thisFrame:Date = new Date();
var seconds:Number = (thisFrame.getTime() -
lastFrame.getTime())/1000.0;
lastFrame = thisFrame;
// sync the baseObjects collection with
// any BaseObjects created or removed during the
// render loop
removeDeletedBaseObjects();
insertNewBaseObjects();
// allow each BaseObject to update itself
for each (var baseObject:BaseObject in baseObjects)
baseObject.enterFrame(seconds);
// render the scene
view.render();
}
public function addBaseObject(baseObject:BaseObject):void
{
newBaseObjects.addItem(baseObject);
}
public function removeBaseObject(baseObject:BaseObject):void
{
removedBaseObjects.addItem(baseObject);
}
public function shutdownAll():void
{
// don't dispose objects twice
for each (var baseObject:BaseObject in baseObjects)
{
var found:Boolean = false;
for each (var removedObject:BaseObject in removedBaseObjects)
{
if (removedObject == baseObject)
{
found = true;
break;
}
}
if (!found)
baseObject.shutdown();
}
}
protected function insertNewBaseObjects():void
{
for each (var baseObject:BaseObject in newBaseObjects)
baseObjects.addItem(baseObject);
newBaseObjects.removeAll();
}
protected function removeDeletedBaseObjects():void
{
for each (var removedObject:BaseObject in removedBaseObjects)
{
var i:int = 0;
for (i = 0; i < baseObjects.length; ++i)
{
if (baseObjects.getItemAt(i) == removedObject)
{
baseObjects.removeItemAt(i);
break;
}
}
}
removedBaseObjects.removeAll();
}
}
}
的(The) EngineManager
涉及很多代码.原因是我们正在创建(involves quite a bit of code. The reason for this is that we are creating the) EngineManager
作为Flash组件(请参阅Adobe文档(as a Flash component (see the Adobe documentation) 这里(here) ).通过扩展(). By extending the) UIComponent
类并覆盖许多关键功能,我们可以创建一个类,该类可以由不了解基础代码的人员拖放到Flash或Flex应用程序上.(class and overriding a number of key functions, we can create a class that can be dropped onto a Flash or Flex application by someone with no knowledge of the underlying code.)
的功能之一(One of the functions of the) EngineManager
将允许任何扩展(will be to allow any class extending the) BaseObject
类(稍后会详细介绍)以在将帧渲染到屏幕之前进行自我更新.为此,我们需要维护一个(class (more on that later) to update itself before a frame is rendered to the screen. For this, we need to maintain a collection of) BaseObject
s,即(s, which is where the) baseObjects
,(,) newBaseObjects
和(, and) removedBaseObjects
属性进来.(properties come in. The) baseObjects
属性保存当前所有活动的(property holds all the currently active) BaseObject
s,而(s, while) newBaseObjects
和(and) removedBaseObjects
包含(contain) BaseObject
刚刚从系统中添加或删除的.(s that were just added or removed from the system.)
我们不只是从中添加和删除对象的原因(The reason why we don’t just add and remove objects from) baseObjects
直接的原因是,当您将一个集合放在上面时,修改一个集合几乎总是一个坏主意.(directly is that it is almost always a bad idea to modify a collection while you are lopping over it.)
// allow each BaseObject to update itself
for each (var baseObject:BaseObject in baseObjects)
baseObject.enterFrame(seconds);
看看我们如何循环(See how we loop over the) baseObjects
收集和致电(collection and call) enterFrame
在各个(on each) BaseObject
.如果我们添加和删除了一个实例(. If we added and removed an instance of a) BaseObject
来自的班级(class from) baseObjects
直接和(directly, and the) BaseObject
我们打电话给(we called) enterFrame
就是要创建一个新的(on was to create a new) BaseObject
或在此调用过程中将其自身从系统中删除,我们会发现自己的集合不一致.您可能会发现(or remove itself from the system during this call, we would find ourselves with an inconsistent collection. You might find the) foreach
循环最终会跳过一条记录,或访问记录两次.实际上,许多编程语言明确地禁止在循环期间通过抛出异常来进行此类集合修改,即使在那些没有的语言中,也最好避免这样做.(loop ends up skipping a record, or visits a record twice. In fact, a lot of programming languages expressly forbid this sort of collection modification during a loop by throwing an exception, and even in those languages that don’t, it’s best avoided.)
一旦了解了为新添加和删除的内容维护单独的集合的原因(Once you understand the reason for maintaining separate collections for newly added and removed) BaseObject
s,您可以在其中解释很多代码(s, you can explain a lot of the code in the) EngineManager
类.的(class. The) addBaseObject
和(and) removeBaseObject
功能添加(functions add a) BaseObject
到(to the) newBaseObjects
和(and) removedBaseObjects
分别收集(collections, respectively, while the) insertNewBaseObjects
和(and) removeDeletedBaseObjects
函数被调用(从(functions are called (from the) onEnterFrame
功能)同步主(function) to synchronise the main) baseObjects
添加和删除的集合(collection with the added and removed) BaseObject
s.(s.)
EngineManager
包含在Flash/Flex组件的生命周期中使用的六个功能.其中的四个在Adobe文档中有详细说明,我将在此处进行遍历.(contains six functions that are used in the lifecycle of a Flash/Flex component. Four of these are detailed in the Adobe documentation, and I’ll run through them here.)
的(The) measure
函数用于定义控件的最小和默认大小.这非常简单,因为我们要做的就是为基础分配四个值(function is used to define the minimum and default size of the control. This is pretty straightforward as all we need to do is assign four values to the underlying) UIComponent
measuredMinWidth
,(,) measuredMinHeight
,(,) measuredHeight
和(, and) measuredWidth
属性.(properties.)
的(The) updateDisplayList
调用函数以调整控件的子级的大小和位置.在这种情况下,我们唯一的子元素是Away3D引擎,特别是View3D对象.我们在这里所做的就是调整view属性的大小,以反映出(function is called to size and position the children of the control. In this case, our only child element is the Away3D engine, specifically the View3D object. All we do here is resize the view property to reflect the changes in the size of the) EngineManager
控制.(control.)
的(The) commitProperties
调用函数以允许控件应用所做的任何属性更改.其背后的想法是,属性可以并且将以任何顺序更改,但是可能必须以特定顺序应用或处理.即使(function is called to allow the control to apply any property changes that have been made. The idea behind this is that properties can and will be changed in any order, but may have to be applied or processed in a specific order. Even though) EngineManager
不公开任何可以更改的属性,此处的代码设置为重新初始化(doesn’t expose any properties that can be changed, the code here is set to re-initialise the) ApplicationManager
(稍后会在该类中提供更多信息),实际上会重新启动应用程序.((more on that class later), which in effect restarts the application.)
的(The) createChildren
当期望控件创建其任何子级时,调用函数.如前所述,(function is called when the control is expected to create any of its children. As mentioned before, the only child of the) EngineManager
控制是Away3D引擎;但是,我们还没有创建引擎. Away3D引擎对(control is the Away3D engine; however, we don’t create the engine just yet. The Away3D engine makes numerous references to the) stage
财产,这是(property, which is) null
直到(until the) ADDED_TO_STAGE
事件已触发.因此,我们将(event has been triggered. So, we attach the) createChildrenEx
函数对此事件进行处理,然后创建Away3D引擎.同样,我们使用(function to this event, and create the Away3D engine then. Likewise, we use the) REMOVED_FROM_STAGE
调用shutdown,这将清理Away3D引擎.(to call shutdown, which will clean up the Away3D engine.)
的(The) updateDisplayList
,(,) commitProperties
,(,) createChildren
和(, and) measure
所有功能均具有由(functions all have functionality defined by the) UIComponent
类.还有两个功能,(class. There are two more functions,) shutdown
和(and) createChildrenEx
,这也起着重要作用.(, that also play an important role.)
如上所述,(As noted above, the) createChildrenEx
功能是Away3D引擎实际启动的地方.对于这个简单的示例,我们只需要两个Away3D类:(function is where the Away3D engine actually starts up. For this simple example, we only need two Away3D classes:) Camera3D
和(and) View3D
.的(. The) Camera3D
类,我们将其分配给(class, which we assign to the) cam
属性,是我们用来观察Away3D世界的摄像机.的(property, is the camera through which we look into the Away3D world. The) View3D
类,我们将其分配给(class, which we assign to the) view
属性,负责将3D世界渲染到2D监视器上.初始化Away3D引擎的实际代码不超过几行.我们创建了一个新的实例(property, takes care of rendering the 3D world onto your 2D monitor. The actual code for initialising the Away3D engine isn’t more than a few lines. We create a new instance of the) Camera3D
类,然后是(class, and then a new instance of the) View3D
类.然后,我们定义所需的渲染器类型(在本例中为基本渲染器),并添加(class. We then define which sort of renderer we want (the basic one in this case), and add the) View3D
作为子元素(as a child element of the) EngineManager
控制.(control.)
除了初始化Away3D引擎外,(In addition to initialising the Away3D engine, the) createChildrenEx
功能还需要创建新的(function also takes create of creating a new) ResourceManager
和(and) ApplicationManager
,并附加(, and attaching the) onEnterFrame
功能(function to the) ENTER_FRAME
事件.(event.)
的(The) shutdown
函数用于清理Away3D引擎. “清理"本质上是指贯穿(function is used to clean up the Away3D engine. “Cleaning up” essentially means running through the) createChildrenEx
相反,删除添加了子项的子项,然后设置为(in reverse, removing children where they have been added, and setting to) null
已初始化的属性.(the properties that had been initialised.)
最后,在(Finally, in the) onEnterFrame
功能,我们管理渲染循环.在这里,我们确定已渲染最后一帧的正弦经过了多少时间,请同步(function, we manage our render loop. It’s here that we determine how much time has passed sine the last frame was rendered, synchronise the) baseObjects
收集,致电(collection, call) enterFrame
在我们所有人(on all of our) BaseObject
s,然后使用(s, and then finally render the frame to the screen with) view.render()
.(.)
BaseObject.as(BaseObject.as)
package
{
internal class BaseObject
{
protected var engineManager:EngineManager = null;
public function BaseObject(engineManager:EngineManager)
{
this.engineManager = engineManager;
}
public function startupBaseObject():void
{
engineManager.addBaseObject(this);
}
public function shutdown():void
{
engineManager.removeBaseObject(this);
}
public function enterFrame(dt:Number):void
{
}
}
}
的唯一目的(The sole purpose of the) BaseObject
类是为了允许扩展类在以下情况下进行自身更新:(class is to allow an extending class to update itself when) enterFrame
叫做.的(is called. The) startupBaseObject
和(and) shutdown
调用函数以添加(functions are called to add the) BaseObject
到(to the) EngineManagers
收集并删除它.然后,我们有(collection and remove it. Then, we have the) enterFrame
函数,为空.的(function, which is empty. The) enterFrame
扩展类将覆盖该函数.(function is expected to be overridden by extending classes.)
网格物体(MeshObject.as)
package
{
import away3d.core.base.Object3D;
import away3d.core.utils.Init;
import away3d.loaders.Collada;
import mx.controls.Alert;
import flash.events.Event;
internal class MeshObject extends BaseObject
{
public var model:Object3D = null;
public function MeshObject(engineManager:EngineManager)
{
super(engineManager);
}
public function startupColladaModelObject(collada:XML, init:Init):MeshObject
{
super.startupBaseObject();
if (collada != null)
model = Collada.parse(collada, init);
engineManager.view.scene.addChild(model);
return this;
}
public override function shutdown():void
{
super.shutdown();
if (model != null)
engineManager.view.scene.removeChild(model);
model = null;
}
}
}
的(The) MeshObject
类扩展(class extends) BaseObject
并增加了对象在屏幕上具有3D网格表示的功能.它包括一个称为(and adds the ability for an object to have a 3D mesh representation on the screen. It includes a function called) startupColladaModelObject
它获取Collada XML文档,将其加载为网格,对其进行纹理处理,然后将结果添加到Away3D场景中.(which takes a Collada XML document, loads it as a mesh, textures it, and adds the result to the Away3D scene.)
RotatingModel.as(RotatingModel.as)
package
{
import away3d.core.utils.Init;
public class RotatingMesh extends MeshObject
{
public function RotatingMesh(engineManager:EngineManager)
{
super(engineManager);
}
public function startupRotatingMesh(collada:XML, init:Init):RotatingMesh
{
super.startupColladaModelObject(collada, init);
return this;
}
public override function enterFrame(dt:Number):void
{
this.model.rotationY += 90 * dt;
}
}
}
的(The) RotatingModel
类是您如何使用(class is an example of how you would use the) MeshObject
(因此((and therefore the) BaseObject
)类.() class.) RotatingModel
延伸(extends) MeshObject
,然后覆盖(, and then overrides the) enterFrame
函数可在每帧中少量旋转模型.如您所见,几乎不需要付出任何努力(function to rotate the model around by a small amount every frame. As you can see, there is very little effort involved to have the) RotatingModel
加载模型,将其添加到场景中,然后在每一帧进行更新:由于(load a model, add it to the scene, and then perform updates every frame: the majority of the work has been taken care of, thanks to the) MeshObject
和(and) BaseObject
类.(classes.)
ResourceManager.as(ResourceManager.as)
package
{
import away3d.materials.BitmapMaterial;
import flash.utils.ByteArray;
public class ResourceManager
{
[Embed (source="../media/fighter1.dae",
mimeType="application/octet-stream")]
public static const Fighter1:Class;
public var Fighter1XML:XML = null;
[Embed (source="../media/sf-02.jpg")]
public static const SF02:Class;
public var SF02_Tex:BitmapMaterial= null;
public function ResourceManager()
{
}
public function loadResources():void
{
Fighter1XML = ConvertToXML(Fighter1);
SF02_Tex = new BitmapMaterial(new SF02().bitmapData);
}
protected function ConvertToXML(data:Class):XML
{
var byteArray:ByteArray = new data() as ByteArray;
return new XML(byteArray.readUTFBytes(byteArray.length))
}
}
}
的(The) ResourceManager
用作容纳应用程序使用的任何资源的区域.作为开发人员,您将面临的问题之一是Flash安全沙箱,其中不能从Web服务器上的SWF加载本地资源,也不能从本地SWF加载Web资源(并非没有无论如何都要乱扔垃圾).的(is used as an area to hold any resources used by the application. One of the problems you will face as a developer is the Flash security sand box, where local resources can’t be loaded from a SWF located on a web server, and web resources can’t be loaded from a local SWF (not without some mucking around anyway). The) ResourceManager
通过以下方式利用资源嵌入(makes use of resource embedding through the) Embed
标签,实际上是在开发PC上获取一个文件,并将数据嵌入到最终的SWF文件中.因为所有数据都包含在一个文件中,所以这使得分发Flash Flash SWF文件变得容易,并且克服了加载资源时的任何安全问题.(tag, which essentially takes a file on your development PC and embeds the data into the final SWF file. This makes it easy to distribute the resulting Flash SWF file because all the data is included in one file, and it overcomes any security issues when loading resources.)
如您所见,我们嵌入了两个文件.的(As you can see, we embed two files. The)**战士1(fighter1.dae)**文件是将在屏幕上显示的Collada网格物体,(file is the Collada mesh that will be displayed on the screen, and the)**sf-02.jpg(sf-02.jpg)**文件将用于纹理化网格.(file will be used to texture the mesh.)
的(The) loadResources
函数用于加载资源.的(function is used to load the resources. The) ConvertToXML
功能需要嵌入式(function takes the embedded)**战士1(fighter1.dae)**文件,嵌入为(file, which is embedded as a) ByteArray
,并将其转换回XML对象.(, and converts it back into an XML object.)
ApplicationManager.as(ApplicationManager.as)
package
{
import away3d.core.math.Number3D;
import away3d.core.utils.Init;
public class ApplicationManager extends BaseObject
{
protected var mesh:MeshObject = null;
public function ApplicationManager(engineManager:EngineManager)
{
super(engineManager);
}
public function startupApplicationManager():ApplicationManager
{
super.startupBaseObject();
mesh = new RotatingMesh(engineManager).startupRotatingMesh(
engineManager.MyResourceManager.Fighter1XML,
new Init({material:engineManager.MyResourceManager.SF02_Tex}));
mesh.model.moveTo(100, -100, 2000);
return this;
}
public override function shutdown():void
{
super.shutdown();
}
public override function enterFrame(dt:Number):void
{
}
}
}
的(The) ApplicationManager
类定义使用我们创建的所有其他类以实际产生所需结果的代码.在这种情况下,所需的结果非常简单:我们只想创建一个实例(class defines the code that makes use of all the other classes we have created to actually produce the desired outcome. In this case, the desired outcome is quite simple: we just want to create an instance of the) RotatingMesh
类.由于我们在上一堂课中所做的工作,所以唯一的事情(class. Because of the work we put into the previous classes, the only thing) ApplicationManager
要做的是创建一个新的实例(has to do is create a new instance of the) RotatingMesh
,通过调用进行初始化(, initialise it with a call to) startupRotatingMesh
,然后在屏幕上稍微重新定位.(, and reposition it slightly on the screen.)
GettingStarted.mxml(GettingStarted.mxml)
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
xmlns:ns1="*"
width="600"
height="400">
<ns1:EngineManager x="0" y="0"
width="100%" height="100%"
id="engineManager"/>
</mx:Application>
最后,我们有(Finally, we have the)**GettingStarted.mxml(GettingStarted.mxml)**文件.如您所见,我们添加了(file. As you can see, we add the) EngineManager
就像是另一个控件,例如按钮或文本框.因为我们已经实现了必要的功能(like it was just another control like a button or a textbox. Because we have implemented the nessessary functions to make) EngineManager
一个Flex组件,这是所有必需的代码.(a Flex component, this is all the code that is required.)
在这里,我们为这样一个简单的程序编写了很多代码,但是创建此初始框架确实可以节省很多时间,因此值得进行额外的初始工作.(We have covered a lot of code here for such a simple program, but creating this initial framework does save a lot of time later on, so it is worth the extra initial effort.)
查看在线演示(Check out the online demo) 这里(here) .(.)
历史(History)
- 2009年8月23日-最初职位.(23 August 2009 - Initial post.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
Flash Dev 新闻 翻译