Скачать, Flash-плеер для просмотра
После конференции Adobe очень заинтересовался отечественной разработкой в области 3-мерной графики для Flash движком Alternativa3D. Помимо русскоязычных ресурсов, нашел на просторах интернета обучающий курс от Мэтью Касперсена и решил сделать доброе дело для тех, у кого с английским не очень, — перевести эти уроки. Не уверен, что сил хватит на все уроки, но начало положено.
За перевод судить строго меня не следует — он достаточно вольный в литературном плане. Техническую сторону я старался сохранить максимально достоверной.
Оригинал статьи находится здесь.
Для работы вам понадобится например FlashDevelop, а так же Adobe Flex SDK, который, к слову, подгружается при установке последней версии FlashDevelop. А как настроить FlashDevelop можно прочитать здесь. Также необходимо скачать SWC-библиотеки Alternativa3D и подключать к каждому вашему проекту.
Подключается библиотека swc тривиально. В среде FlashDevelop жмите правую кнопку мыши в окне Project на той папке, в которой хотите разместить библиотеку (например libs) и выбирайте Add->Library Asset... Затем выбирайте файл *.swc и всё, библиотека подключена.
Итак, перевод.
Alternativa — 3D движок для платформы Flash, который позволяет размещать трехмерные объекты прямо на ваших веб-страницах. Учитывая, что Flash player установлен примерно но 90% всех подключенных к Интернету устройств, Alternativa предоставляет вам 3D платформу, которая не требует никаких усилий по установке от конечных пользователей. В этом уроке я покажу вам, как создавать простые 3D-приложения с использованием Альтернатива и Flex.
Хотя конечный результат этого урока прост, тем не менее это не приложение «hello world». С самого начала мы сосредоточимся на том, чтобы создать прочную основу, которую впоследствии можно будет легко расширить. С этой целью один из принципов, который я применю здесь — разделение логики, связанной с управлением 3D движком (т.е. почти весь код, созданный Альтернативой), и логики, которая определяет конечный результате самого приложения.
// EngineManager.as package { import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import mx.collections.ArrayCollection; import mx.core.Application; import mx.core.UIComponent; import alternativa.engine3d.controllers.CameraController; import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.Scene3D; import alternativa.engine3d.display.View; import alternativa.utils.FPS; /** * The EngineManager holds all of the code related to maintaining the Alternativa 3D engine. */ public class EngineManager extends UIComponent { public var scene:Scene3D; public var view:View; public var camera:Camera3D; public var cameraController:CameraController; // 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; public function EngineManager() { super(); addEventListener(Event.ADDED_TO_STAGE, init); } public function init(e:Event): void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; // Creating scene scene = new Scene3D(); scene.root = new Object3D(); // Adding camera and view camera = new Camera3D(); camera.x = 100; camera.y = -150; camera.z = 100; scene.root.addChild(camera); view = new View(); addChild(view); view.camera = camera; // Connecting camera controller cameraController = new CameraController(stage); cameraController.camera = camera; cameraController.setDefaultBindings(); cameraController.checkCollisions = true; cameraController.collisionRadius = 20; cameraController.controlsEnabled = true; // FPS display launch FPS.init(stage); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); onResize(null); // set the initial frame time lastFrame = new Date(); // start the application ApplicationManager.Instance.startupApplicationManager(); } private function onResize(e:Event):void { view.width = stage.stageWidth; view.height = stage.stageHeight; Application.application.width = stage.stageWidth; Application.application.height = stage.stageHeight; } 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); // User input processing cameraController.processInput(); // Scene calculating scene.calculate(); } public function addBaseObject(baseObject:BaseObject):void { newBaseObjects.addItem(baseObject); } public function removeBaseObject(baseObject:BaseObject):void { removedBaseObjects.addItem(baseObject); } protected 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(); } } }
Мы начнем с класса EngineManager. Этот класс будет содержать весь код, необходимый для инициализации и запуска движка Alternativa3D (далее A3D, моё прим.). Класс EngineManager расширяет класс UIComponent, который мы раместим в корне Flex-приложения как любой другой GUI-контрол. Мы увидим это позже в файле Alternativa1.mxml.
В конструктор EngineManager мы добавили приемник событий, который будет вызывать функцию инициализации каждый раз, когда EngineManager будет добавлен в список отображения. Если фраза "добавить к списку отображения" ничего не значит для вас, не волнуйтесь — все, что вам нужно знать, это то, что когда произошло это событие объект stage больше не NULL.
Функция инициализации выполняет большинство кода инициализации A3D. Мы создаем 4 важнейших объекта A3D: Scene3D, Camera3D, CameraController и View. Scene3D, по существу, контейнер в котором размещаются все наши объекты сцены. Camera3D, как следует из названия, точка обзора для нашего 3D-мира. CameraController — удобный класс, который позволяет нам двигать камеру клавиатурой и мышью, а также определяет столкновения с объектами сцены. CameraController дает Вам возможность передвигаться и интерактивно взаимодействовать с 3D-миром написав всего 6 строк кода. Это круто! Наконец, есть объект View, принимающий наше 3D-окружение, который видит камера и переводит его в 2D-изображение, которое будет отображаться на мониторе.
Отметим также метод, названный FPS.init(stage), который размещает счетчик FPS (кадров в секунду) на экране. Это полезно для мониторинга вашего приложения, но его нужно закомментировать при окончательном развертывании вашего приложения.
После инициализации движка A3D мы добавим еще два слушателя событий: один — для реакции на изменение размеров окна ( stage.addEventListener(Event.RESIZE, OnResize):void ), второй — для реакции на событие перерисовки кадра ( stage.addEventListener (Event. ENTER_FRAME, onEnterFrame):void ). Мы перехватываем изменение размеров окна с целью обновления размеров нашего View. Событие же перерисовки кадра — наш основной цикл растеризации кадра (далее "основной цикл", моё прим.).
Основной цикл — общий термин, который обозначает цикл, определяющий работу вашего приложения. Цикл состоит из двух частей. Первая — когда приложение обновляется. Любое движение или логика вашей игры просчитывается в первой чаксти цикла, скажем, расчет перемещения ракеты в пространстве. Вторая — где 3D-движок растеризует кадр на экране, демонстрируя тем самым изменения, которые были сделаны в 3D-мире.
Обновление приложения (первая часть цикла) осуществляется в классе BaseObject. Отметим, что функция onEnterFrame нашего приложения вызывает функцию enterFrame коллекции BaseObject-ов. Единственная цель функции enterFrame класса BaseObject — позволить любому классу, расширяющему BaseObject легко обновлять себя внутри основного цикла. Мы увидим как это работает, чуть позже в классах MeshObject и RotatingBox.
Наконец, в функции инициализации мы вызываем ApplicationManager.Instance.startupApplicationManager(). Я говорил, что мы будем отделять логику приложения от логики движка. Класс EngineManager заботится об управлении движком A3D и об основном цикле, чтобы наша программа на самом деле что-то делала. Для этого мы создаем класс ApplicationManager.
// ApplicationManager.as package { import mx.core.Application; /** * The ApplicationManager holds all program related logic. */ public class ApplicationManager { protected static var instance:ApplicationManager = null; public static function get Instance():ApplicationManager { if (instance == null) instance = new ApplicationManager(); return instance; } public function ApplicationManager() { } public function startupApplicationManager():ApplicationManager { var rotatingBox:RotatingBox = new RotatingBox().startupRotatingBox(); Application.application.engineManager.cameraController.lookAt(rotatingBox.model.coords); return this; } } }
ApplicationManager разработан как Singleton. И хотя ActionScript не имеет private и protected конструкторов (и, следовательно, не позволяет реализовывать синглтоны по-настоящему), что имеют некоторые негативные последствия, я по-прежнему нахожу этот шаблон проектирования полезным как инструмент для самодокументирования. Если класс имеет свойство Instance, то он — синглтон, и вы не должны создавать объекты с помощью new.
ApplicationManager в этом примере имеет только одну функцию (помимо Синглтон-свойств): startupApplicationManager. В этой функции мы размещаем код, который относится к самому приложению, в отличие от кода инициализации движка A3D. В нашем случае мы создаем RotatingBox и направляем на него камеру. Это может показаться излишним, определять класс из 2 строк кода, но разделение логики приложения и движка будет полезным в более сложных приложениях.
// BaseObject.as package { import mx.core.Application; /** * The BaseObject class allows extending classes to update themselves during the render loop. */ public class BaseObject { public function BaseObject() { } /** * Must be called by all extending classes when being created. Adds this object to the list of BaseObjects maintained * by the EngineManager. */ public function startupBaseObject():void { Application.application.engineManager.addBaseObject(this); } /** * Must be called by all extending classes when being destroyed. Removes this object to the list of BaseObjects maintained * by the EngineManager. */ public function shutdown():void { Application.application.engineManager.removeBaseObject(this); } /** * This function is called once per frame before the scene is rendered. * * @param dt The time in seconds since the last frame was rendered. */ public function enterFrame(dt:Number):void { } } }
// MeshObject.as package { import alternativa.engine3d.core.Object3D; import alternativa.engine3d.materials.SurfaceMaterial; import mx.core.Application; public class MeshObject extends BaseObject { public var model:Object3D = null; public function MeshObject() { super(); } override public function shutdown():void { super.shutdown(); Application.application.engineManager.scene.root.removeChild(model); model = null; } public function startupModelObject(object:Object3D):void { model = object; Application.application.engineManager.scene.root.addChild(model); super.startupBaseObject(); } } }
Класс RotatingBox является примером того, как расширить класс BaseObject, хотя, если вы посмотрите внимательно, мы фактически создали промежуточный класс MeshObject. MeshObject расширяет BaseObject и добавляет свойства класса Object3D, который представляет собой 3D-сетку на экране. Это может показаться излишним — создавать еще один класс ради одного свойства, но правда в том, что вам может понадобиться обновлять объекты сцены вашего приложения или игры каждый кадр и они не обязательно должны иметь свойства 3D-сетки. Именно поэтому мы создали BaseObject и MeshObject как отдельные классы: расширение BaseObject позволяет объекту обновить себя, а MeshObject будет общим базовым классом для тех объектов, которые имеют свойства 3D-сетки, такие как RotatingBox, например.
// RotatingBox.as package { import alternativa.engine3d.materials.WireMaterial; import alternativa.engine3d.primitives.Box; public class RotatingBox extends MeshObject { protected static const ROTATION_SPEED:Number = 1; public function RotatingBox() { super(); } public function startupRotatingBox():RotatingBox { var box:Box = new Box(100, 100, 100, 3, 3, 3); box.cloneMaterialToAllSurfaces(new WireMaterial(1, 0x000000)); super.startupModelObject(box); return this; } public override function enterFrame(dt:Number):void { model.rotationX += dt * ROTATION_SPEED; model.rotationY += dt * ROTATION_SPEED; model.rotationZ += dt * ROTATION_SPEED; } } }
Итак, давайте взглянем на класс RotatingBox. У нас есть две важные функции: startupRotatingBox и enterFrame. Функция StartupRotatingBox отвечает за создание и текстурирование 3D-сетки, которая будет отображаться на экране. Мы используем встроенный в движок Box-примитив, и текстуру проволочного каркаса WireMaterial. Мы переопределяем функцию enterFrame класса BaseObject, и именно здесь мы вращаем наш объект изменяя немного угол в каждом кадре.
<!-- Alternativa1.mxml --> <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:ns1="*" width="640" height="480" color="#FFFFFF" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FFFFFF, #C0C0C0]"> <ns1:EngineManager id="engineManager" x="0" y="0" width="100%" height="100%"/> <mx:Image x="10" y="10" id="imgAlternativa" source="@Embed(source='../media/alternativa.jpg')"/> </mx:Application>
Собираем все вместе в файле Alternativa1.mxml. Это точка входа приложения. Как я упоминал ранее, EngineManager класс расширяет UIComponent класс, что позволяет ему быть добавленым в качестве дочернего в главный класс приложения. Вы можете заметить, что мы добавили EngineManager как дочерний элемент, как Button или Label.
Ну, и что же у нас получилось? Класс EngineManager выполняет код, необходимый для инициализации и управления движком A3D. Этот код меняет очень не много в этом примере. Класс ApplicationManager выполняется код, который относится к логике самого приложения. Вы увидите, что этот код будет меняться почти во всех примерах. Классы BaseObject и MeshObject дают нам простой способ создать объект, который может обновлять себя в основном цикле. Эти классы претерпят значительные изменения в будущих уроках. И, наконец, у нас есть класс RotatingBox, который показывает как расширить класс MeshObejct для создания самообновлеющегося 3D-объекта.
8 комментариев:
Только вот Касперсон писал урок по Alternativa3D 5, а не 7 :)
Ссылка не та... да... исправил. Спасибо.
Пермские ребята движок делают)
Вот прям в точку...)))
У движка есть серьезный минус - отсутствие освещения и, соответственно, теней =(
Обещают в 8-версии поправить и в разы увеличить производительность =)
Производительность, к тому времени когда выйдет 8 версия Альтернативы, увеличится не только у них. Все с нетерпением ждут Molehill с поддержкой API для прямого обращения к ресурсам GPU. Так что, если всё получится, то нас ждут интереснейшие события в области браузерных технологий и в частности 3D.
А отсутствие освещения и теней не является минусом Альтернативы, т.к. основной упор разработчиками был сделан на производительности, поэтому функционал теней и освещения был не реализован намеренно.
Появились тени )))
http://blog.alternativaplatform.com/ru/2011/03/12/alternativa3d-7_7_0-update/
Известно, спасибо, но сейчас более интересное направление для изучения API Molehill. Всем рекомендую!
Отправить комментарий