Alternativa 3D Series – Tutorial 1 – Getting Started

Alternativa 是基于flash的3d引擎。使用Alternativa我们可以直接在web页面嵌入3d应用。在链接互联网的pc上,flashplayer的安装率超过了90%。因此使用alternative3d制作的应用,不必考虑麻烦用户安装多余的插件。在接下来的一系列文章中,我将向你展示如何使用alternative3d和flex制作简单的3D应用。

  

本系列教程内容虽然不是“Hello world”,但是他同样很简单。在文章开始部分,我们会着手制作一个后续文章方便扩展的框架。本着这个目标,我们首先会将逻辑关系和引擎的管理部分进行分离。逻辑管理部分仅用来描述程序自身的一些特性。

代码
   
     
1 package
2 {
3 import flash.display.StageAlign;
4 import flash.display.StageScaleMode;
5 import flash.events.Event;
6 import mx.collections.ArrayCollection;
7 import mx.core.Application;
8 import mx.core.UIComponent;
9 import alternativa.engine3d.controllers.CameraController;
10 import alternativa.engine3d.core.Camera3D;
11 import alternativa.engine3d.core.Object3D;
12 import alternativa.engine3d.core.Scene3D;
13 import alternativa.engine3d.display.View;
14 import alternativa.utils.FPS;
15
16 /**
17 * The EngineManager holds all of the code related to maintaining the Alternativa 3D engine.
18 */
19 public class EngineManager extends UIComponent
20 {
21 public var scene:Scene3D;
22 public var view:View;
23 public var camera:Camera3D;
24 public var cameraController:CameraController;
25
26 // a collection of the BaseObjects
27   protected var baseObjects:ArrayCollection = new ArrayCollection();
28 // a collection where new BaseObjects are placed, to avoid adding items
29 // to baseObjects while in the baseObjects collection while it is in a loop
30   protected var newBaseObjects:ArrayCollection = new ArrayCollection();
31 // a collection where removed BaseObjects are placed, to avoid removing items
32 // to baseObjects while in the baseObjects collection while it is in a loop
33 protected var removedBaseObjects:ArrayCollection = new ArrayCollection();
34 // the last frame time
35 protected var lastFrame:Date;
36
37 public function EngineManager()
38 {
39 super ();
40 addEventListener(Event.ADDED_TO_STAGE, init);
41 }
42
43 public function init(e:Event): void
44 {
45 stage.scaleMode = StageScaleMode.NO_SCALE;
46 stage.align = StageAlign.TOP_LEFT;
47
48 // Creating scene
49 scene = new Scene3D();
50 scene.root = new Object3D();
51
52 // Adding camera and view
53 camera = new Camera3D();
54 camera.x = 100 ;
55 camera.y = - 150 ;
56 camera.z = 100 ;
57 scene.root.addChild(camera);
58
59 view = new View();
60 addChild(view);
61 view.camera = camera;
62
63 // Connecting camera controller
64 cameraController = new CameraController(stage);
65 cameraController.camera = camera;
66 cameraController.setDefaultBindings();
67 cameraController.checkCollisions = true ;
68 cameraController.collisionRadius = 20 ;
69 cameraController.controlsEnabled = true ;
70
71 // FPS display launch
72 FPS.init(stage);
73
74 stage.addEventListener(Event.RESIZE, onResize);
75 stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
76 onResize( null );
77
78 // set the initial frame time
79 lastFrame = new Date();
80
81 // start the application
82 ApplicationManager.Instance.startupApplicationManager();
83 }
84
85 private function onResize(e:Event): void
86 {
87 view.width = stage.stageWidth;
88 view.height = stage.stageHeight;
89 Application.application.width = stage.stageWidth;
90 Application.application.height = stage.stageHeight;
91 }
92
93 protected function onEnterFrame(event:Event): void
94 {
95 // Calculate the time since the last frame
96 var thisFrame:Date = new Date();
97 var seconds:Number = (thisFrame.getTime() - lastFrame.getTime()) / 1000.0 ;
98 lastFrame = thisFrame;
99
100 // sync the baseObjects collection with any BaseObjects created or removed during the
101 // render loop
102 removeDeletedBaseObjects();
103 insertNewBaseObjects();
104
105 // allow each BaseObject to update itself
106 for each (var baseObject:BaseObject in baseObjects)
107 baseObject.enterFrame(seconds);
108
109 // User input processing
110 cameraController.processInput();
111 // Scene calculating
112 scene.calculate();
113 }
114
115 public function addBaseObject(baseObject:BaseObject): void
116 {
117 newBaseObjects.addItem(baseObject);
118 }
119
120 public function removeBaseObject(baseObject:BaseObject): void
121 {
122 removedBaseObjects.addItem(baseObject);
123 }
124
125 protected function shutdownAll(): void
126 {
127 // don't dispose objects twice
128 for each (var baseObject:BaseObject in baseObjects)
129 {
130 var found:Boolean = false ;
131 for each (var removedObject:BaseObject in removedBaseObjects)
132 {
133 if (removedObject == baseObject)
134 {
135 found = true ;
136 break ;
137 }
138 }
139
140 if ( ! found)
141 baseObject.shutdown();
142 }
143 }
144
145 protected function insertNewBaseObjects(): void
146 {
147 for each (var baseObject:BaseObject in newBaseObjects)
148 baseObjects.addItem(baseObject);
149
150 newBaseObjects.removeAll();
151 }
152
153 protected function removeDeletedBaseObjects(): void
154 {
155 for each (var removedObject:BaseObject in removedBaseObjects)
156 {
157 var i: int = 0 ;
158 for (i = 0 ; i < baseObjects.length; ++ i)
159 {
160 if (baseObjects.getItemAt(i) == removedObject)
161 {
162 baseObjects.removeItemAt(i);
163 break ;
164 }
165 }
166
167 }
168
169 removedBaseObjects.removeAll();
170 }
171 }
172 }

我们从EngineManager类讲起。这个类用来完成3d引擎的初始化。它继承自UIComponent,这样我们就能像使用其他的flex组件一样,直接把它放到Application标签中。一会我们就可以在Alternativa1.mxml中看到他。

在EngineManager的构造函数中,我们添加了一个事件监听器。它可以在EngineManager放到显示列表时,完成一些初始化操作。有一点我们不必担心,该事件一旦发生,stage属性将不为空。

在init函数中,我们做了一些Alternativa初始化操作。我们创建了4个很重要的东西:Scene3D,Camera3D,CameraController和View。Scene3D本质上是一个容纳其他物体的容器(功能同sprite)。Camera3D摄像机,从名字我们就能知道他的作用,他就像3d世界中的眼睛。CameraController为我们提供了鼠标和键盘控制camera,同时做一些简单的物体碰撞检测。使用CameraController我们只需要六行代码,就可以移动和控制3d世界,这真是一件很棒的事情。最后还有一个View,他用来把3d的影像转化成2d图片显示在显示器上。


你可能注意到,我们使用了一个FPS.init(stage).方法。这个方法用来在舞台上显示每秒的内存使用和帧率。对于程序调试,他是一个很有用的工具,但是在最终的发布版本,这个方法是可以注释掉的。

一旦Alternativa 引擎初始化,我们需要增加两个事件监听。一个是用来响应窗口大小改变(stage.addEventListener(Event.RESIZE, onResize),另一个用来不停渲染( stage.addEventListener(Event.ENTER_FRAME, onEnterFrame)。我们捕捉窗口大小变化事件,以便让View 做出相应大小改变。帧频渲染可以不停渲染当前3d场景。


帧频渲染是3D程序运行的基本概念。渲染包括两个部分,第一部分(游戏逻辑)是程序自身的更新:游戏中的运动和游戏逻辑处理。第二部分(屏幕渲染)就是3d engine的逐帧屏幕渲染,可以显示3d世界的变化。

第一部分的更新是由BaseObject 这个类完成的。你会在onEnterFrame 函数看到,我们用从BaseObjects取出单个BaseObject,并调用它的enterFrame 方法。enterFrame 方法是BaseObject 里重量级方法,他允许继承自BaseObject 的子类方便的进行自我渲染更新。在MeshObject 和RotatingBox 类中会看到他。


 

最后在init方法中我们调用了ApplicationManager.Instance.startupApplicationManager().在讲解这个以前,我们先把程序的逻辑和engine逻辑做分离。EngineManager的重要作用就是管理Alternativa引擎和不停渲染。

下面我们接着做另一件事,创建ApplicationManager 类。

 

代码
   
     
1 package
2 {
3
4 import mx.core.Application;
5
6 /**
7 * The ApplicationManager holds all program related logic.
8 */
9 public class ApplicationManager
10 {
11 protected static var instance:ApplicationManager = null ;
12
13 public static function get Instance():ApplicationManager
14 {
15 if (instance == null )
16 instance = new ApplicationManager();
17 return instance;
18 }
19
20 public function ApplicationManager()
21 {
22
23 }
24
25 public function startupApplicationManager():ApplicationManager
26 {
27 var rotatingBox:RotatingBox = new RotatingBox().startupRotatingBox();
28 Application.application.engineManager.cameraController.lookAt(rotatingBox.model.coords);
29
30 return this ;
31 }
32
33 }
34 }

 

ApplicationManager类是个单例类(不明白的自己百度)。该类中仅有一个名为startupApplicationManager的方法,它会返回类本身的唯一实例,在这个方法中我们还创建了一个RotatingBox(旋转多面体),并把它指向给摄像机。建立一个类,就在里面写了两行代码,似乎有些多余,但是对于一个复杂的程序来说,这样做有助于将程序逻辑和引擎逻辑分离。

 

 

代码
   
     
1 package
2 {
3 import mx.core.Application;
4
5 /**
6 * The BaseObject class allows extending classes to update themselves during the render loop.
7 */
8 public class BaseObject
9 {
10 public function BaseObject()
11 {
12
13 }
14
15 /**
16 * Must be called by all extending classes when being created. Adds this object to the list of BaseObjects maintained
17 * by the EngineManager.
18 */
19 public function startupBaseObject(): void
20 {
21 Application.application.engineManager.addBaseObject( this );
22 }
23
24 /**
25 * Must be called by all extending classes when being destroyed. Removes this object to the list of BaseObjects maintained
26 * by the EngineManager.
27 */
28 public function shutdown(): void
29 {
30 Application.application.engineManager.removeBaseObject( this );
31 }
32
33 /**
34 * This function is called once per frame before the scene is rendered.
35 *
36 * @param dt The time in seconds since the last frame was rendered.
37 */
38 public function enterFrame(dt:Number): void
39 {
40
41 }
42 }
43 }

 

 

 

 

代码
   
     
1 package
2 {
3 import alternativa.engine3d.core.Object3D;
4 import alternativa.engine3d.materials.SurfaceMaterial;
5
6 import mx.core.Application;
7
8 public class MeshObject extends BaseObject
9 {
10 public var model:Object3D = null ;
11
12 public function MeshObject()
13 {
14 super ();
15 }
16
17 override public function shutdown(): void
18 {
19 super .shutdown();
20 Application.application.engineManager.scene.root.removeChild(model);
21 model = null ;
22 }
23
24 public function startupModelObject(object:Object3D): void
25 {
26 model = object;
27 Application.application.engineManager.scene.root.addChild(model);
28 super .startupBaseObject();
29 }
30 }
31 }

RotatingBox继承自MeshObject,而MeshObject继承自BaseObject,MeshObject比BaseObject多了一个叫Object3D的属性。Object3D(3D物体的基类)可以在屏幕上创建带3D网格的物体。

看起来在一个类中只增加了一个属性,还要多次继承来实现,有点多余。但是,你可以想象,不管是在游戏还是在程序中,我们需要很多3D的物体,并且邪王某些类虽然不包含3D物体但是它也需要每帧做更新,我们这样的类层次划分就很合理。这也是我们把MeshObject和BaseObject分离成两个类的原因,继承自BaseObject 的类就可以做自我更新,继承自MeshObject 的类不光可以自我更新,还多了一个3D实物的属性——像RotatingBox

 

 

代码
                       
                         
1 package
2 {
3 import alternativa.engine3d.materials.WireMaterial;
4 import alternativa.engine3d.primitives.Box;
5
6 public class RotatingBox extends MeshObject
7 {
8 protected static const ROTATION_SPEED:Number = 1 ;
9
10 public function RotatingBox()
11 {
12 super ();
13 }
14
15 public function startupRotatingBox():RotatingBox
16 {
17 var box:Box = new Box( 100 , 100 , 100 , 3 , 3 , 3 );
18 box.cloneMaterialToAllSurfaces( new WireMaterial( 1 , 0x000000 ));
19 super .startupModelObject(box);
20 return this ;
21 }
22
23 public override function enterFrame(dt:Number): void
24 {
25 model.rotationX += dt * ROTATION_SPEED;
26 model.rotationY += dt * ROTATION_SPEED;
27 model.rotationZ += dt * ROTATION_SPEED;
28 }
29 }
30 }

 

 

让我们来研究一下RotatingBox 类,它有两个重要的方法:startupRotatingBox 和enterFrame。startupRotatingBox 方法创建一个带材质的3D物体,本例中我们创建了一个原始的四方体,并给四方体使用了一个叫WireMaterial(线形)的材质。enterFrame 对继承自BaseObject的方法做了重写。在这里,每次调用该方法都会让box旋转一定的角度。

 

代码
   
     
1 <? xml version = " 1.0 " encoding = " utf-8 " ?>
2 < mx:Application
3 xmlns:mx = " http://www.adobe.com/2006/mxml "
4 layout = " absolute "
5 xmlns:ns1 = " * "
6 width = " 640 "
7 height = " 480 " color = " #FFFFFF "
8 backgroundGradientAlphas = " [1.0, 1.0] "
9 backgroundGradientColors = " [#FFFFFF, #C0C0C0] " >
10
11 < ns1:EngineManager id = " engineManager " x = " 0 " y = " 0 " width = " 100% " height = " 100% " />
12
13 </ mx:Application >

 

我们将所有东西集合到Alternativa1.mxml文件中,这是程序的入口文档。前文提到的让EngineManager 继承自UIComponent 就是为了,能让它放到主的Application类中。如你所见,我们在主文档中仅需像添加一个文本标签一样简单的添加EngineManager 标签,

就可以完成一个3D控制器。

总结:在EngineManager用来初始化并管理3D引擎,本类在以后几章中将很少变化。ApplicationManager 分离出了程序自身的逻辑,本类将会在后续章节中相应改变。BaseObject 和MeshObject 基类可以让我们很方便的创建一个物体,并实现物体的自我更新。这两个类

以后不会变动。最后RotatingBox 继承自BaseObject 和MeshObject ,创建了一个现实的3D物体。

 

你可能感兴趣的:(alter)