Код данного примера будет довольно сильно переработан.
1. Чтобы добавить графику, нужно сначала ее нарисовать. Здесь я не стал сильно мудрить, а просто взял картинку из предыдущего поста и разбил ее на несколько частей:
Корпус двигателя |
Коленвал с маховиком |
Шатун |
Поршень |
Сохранять эти изображения нужно в формате png, поддерживающем прозрачный фон.
2. Создаем новый AS3 проект во FlashDevelop, задавая размеры выходного файла равными 192х410 пикселей. Переносим файлы изображений деталей двигателя в папку lib нашего проекта.
3. В данном примере мы разделим кода на 3 логические части: первая будет отвечать за расчет положений механизма (своего рода модель), вторая – за отображение механизма на экране (вид) и третья – выполняет роль инициализатора приложения (создает модель и виды механизма) и контроллера для модели.
3.1. Сначала разберемся с кодом модели механизма, за нее будет отвечать класс MechModel:
package { import flash.events.Event; import flash.events.EventDispatcher; import flash.geom.Point; // Данный класс мы наследуем от EventDispatcher, // т.к. нам нужно чтобы он мог создавать события // (как бы «выкрикивать» во внешний мир // «Эй, интересно вам или нет, но я изменился»), // а другие классы, подписанные на эти события // могли бы как-нибудь на это реагировать // (например, изменять отображение механизма на экране) public class MechModel extends EventDispatcher { // Здесь мы объявляем константу события, // создаваемого данным классом public static const MODEL_CHANGED:String = "modelChanged"; // Основные размеры механизма: // Длина кривошипа АВ private var a:Number; // Длина шатуна ВС private var b:Number; // Угловые параметры, характеризующие положение механизма: // Угол поворота кривошипа private var _phi:Number = 0; // Угол поворота шатуна private var _psi:Number; // Угловая скорость кривошипа public var angularVelocity:Number = 0.1; // Положения основных точек механизма public var pointA:Point; public var pointB:Point; public var pointC:Point; public function MechModel(a:Number = 50, b:Number = 200) { pointA = new Point(0, 0); pointB = new Point(); pointC = new Point(); this.a = a; this.b = b; _phi = 0; calculatePositions(); } // Обновляем положение точек механизма public function update():void { // Обновление значения угла поворота кривошипа _phi += angularVelocity; calculatePositions(); } // Расчет текущего положения механизма // в зависимости от угла поворота кривошипа. private function calculatePositions():void { pointB.x = pointA.x + a * Math.cos(_phi); pointB.y = pointA.y + a * Math.sin(_phi); _psi = Math.asin(a * Math.sin(_phi ) / b); var ac:Number = a * Math.cos(_phi ) + b * Math.cos(_psi); pointC.x = pointA.x + ac; pointC.y = pointA.y; // Т.к. положение механизма изменилось, то надо // “оповестить” всех тех, кому это “интересно”: dispatchEvent(new Event(MODEL_CHANGED)); } // Данный метод пришлось добавить из-за появления // ошибки при подготовке этого урока. // Если брать в качестве угла поворота кривошипа // просто значение угла _phi // (а не остаток от его деления на 2 пи), то через // некоторое время после запуска приложения // переставал вращаться кривошип. // Видимо, во флэше существует ограничение на значение // свойства rotation public function get phi():Number { return _phi % (2 * Math.PI); } public function get psi():Number { return _psi; } } }
3.2. В этом примере мы увидим, что одно и то же явление можно увидеть по-разному (у одной модели может быть несколько видов), для этого создадим 2 класса видов: один с использованием векторной графики (как в предыдущем уроке), а второй – с растровой графикой, подготовленной мной заранее.
Начнем с более простого класса, т.к. его код мы уже частично знаем из предыдущего урока. Его мы будет использовать в качестве “тестового” режима отрисовки:
package { // Импорт необходимых классов import flash.display.Bitmap; import flash.display.Sprite; import flash.events.Event; // Объявление класса public class MechViewVector extends Sprite { // Ссылка на модель отрисовываемого механизма private var model:MechModel; // Символы для отображения характерных точек механизма // // Точка А public var sharnirOne:Sprite; // Точка В public var sharnirTwo:Sprite; // Точка С public var sharnirThree:Sprite; // Ползун private var polzun:Sprite; // Конструктор класса public function MechViewVector(model:MechModel):void { this.model = model; initView(); // Добавление обработчика события изменения // модели механизма model.addEventListener(MechModel.MODEL_CHANGED, modelChangedHandler); } // Построение начального положения механизма private function initView():void { sharnirOne = makeSharnir(model.pointA.x, model.pointA.y); addChild(sharnirOne); sharnirTwo = makeSharnir(model.pointB.x, model.pointB.y); addChild(sharnirTwo); polzun = makePolzun(model.pointC.x, model.pointC.y); addChild(polzun); sharnirThree = makeSharnir(model.pointC.x, model.pointC.y); addChild(sharnirThree); render(); } // Обработчик события, вызываемый при изменении модели private function modelChangedHandler(e:Event):void { render(); } // Отрисовка текущего положения механизма private function render():void { // Обновление положения точки A: sharnirOne.x = model.pointA.x; sharnirOne.y = model.pointA.y; // Обновление положения точки В: sharnirTwo.x = model.pointB.x; sharnirTwo.y = model.pointB.y; polzun.x = model.pointC.x; polzun.y = model.pointC.y; // Обновление положения точки С: sharnirThree.x = model.pointC.x; sharnirThree.y = model.pointC.y; // Очистка экрана this.graphics.clear(); this.graphics.lineStyle(2, 0x000000); // Отрисовка звеньев АВ и ВС: drawStergen(sharnirOne, sharnirTwo); drawStergen(sharnirTwo, sharnirThree); } // Отрисовка ползуна private function makePolzun(x:Number, y:Number):Sprite { var polzun:Sprite = new Sprite(); polzun.graphics.lineStyle(2, 0x000000); polzun.graphics.beginFill(0x0099ff); polzun.graphics.drawRect( -15, -8, 30, 16); polzun.graphics.endFill(); polzun.x = x; polzun.y = y; return polzun; } // Отрисовка стержневых звеньев private function drawStergen(spr1:Sprite, spr2:Sprite):void { this.graphics.moveTo(spr1.x, spr1.y); this.graphics.lineTo(spr2.x, spr2.y); } // Отрисовка характерных точек механизма private function makeSharnir(x:Number, y:Number):Sprite { var sharnir:Sprite = new Sprite(); sharnir.graphics.lineStyle(2, 0x000000); sharnir.graphics.beginFill(0x0099ff); sharnir.graphics.drawCircle(0, 0, 4); sharnir.graphics.endFill(); sharnir.x = x; sharnir.y = y; return sharnir; } } }
Во втором классе вида мы, наконец, будем использовать подготовленную для этого урока графику. Если Вы еще не умеете встраивать собственную графику с помощью FlashDevelop, то рекомендую почитать статью “Где спрятана библиотека FlashDevelop и как ей пользоваться?”. Код нашего второго класса вида:
package { import flash.display.Bitmap; import flash.display.Sprite; import flash.events.Event; import flash.geom.Point; public class MechViewRaster extends Sprite { // Графика для нашего механизма [Embed(source = '../lib/flywheel.png')] private var FlywheelPNG:Class; [Embed(source = '../lib/housing.png')] private var HousingPNG:Class; [Embed(source = '../lib/piston.png')] private var PistonPNG:Class; [Embed(source = '../lib/stone.png')] private var StonePNG:Class; // Спрайты для элементов механизма public var housingSpr:Sprite; public var flywheelSpr:Sprite; public var pistonSpr:Sprite; public var stoneSpr:Sprite; // Регистрационные точки для графических элементов механизма private var housingRegPoint:Point = new Point(70, 94); private var stoneRegPoint:Point = new Point(25, 44); private var flywheelRegPoint:Point = new Point(50, 48); private var pistonRegPoint:Point = new Point(34, 30); // Ссылка на модель механизма private var model:MechModel; public function MechViewRaster(model:MechModel) { this.model = model; model.addEventListener(MechModel.MODEL_CHANGED, modelChangedHandler); // Создание графики для деталей механизма // // Корпус двигателя var housingBmp:Bitmap = new HousingPNG(); housingSpr = new Sprite(); housingSpr.addChild(housingBmp); housingBmp.x = -housingRegPoint.x; housingBmp.y = -housingRegPoint.y; addChild(housingSpr); // Коленвал с маховиком var flywheelBmp:Bitmap = new FlywheelPNG(); flywheelBmp.smoothing = true; flywheelSpr = new Sprite(); flywheelBmp.x = -flywheelRegPoint.x; flywheelBmp.y = -flywheelRegPoint.y; flywheelSpr.addChild(flywheelBmp); addChild(flywheelSpr); // Шатун var pistonBmp:Bitmap = new PistonPNG(); pistonBmp.smoothing = true; pistonSpr = new Sprite(); pistonBmp.x = -pistonRegPoint.x; pistonBmp.y = -pistonRegPoint.y; pistonSpr.addChild(pistonBmp); addChild(pistonSpr); // Рисуем маркер регистрационной точки шатуна // (просто для примера) drawMark(pistonSpr); // Поршень var stoneBmp:Bitmap = new StonePNG(); stoneSpr = new Sprite(); stoneBmp.smoothing = true; stoneBmp.x = -stoneRegPoint.x; stoneBmp.y = - stoneRegPoint.y; stoneSpr.addChild(stoneBmp); addChild(stoneSpr); render(); } private function modelChangedHandler(e:Event):void { render(); } // Отрисовка текущего положения механизма private function render():void { housingSpr.y = model.pointA.y; housingSpr.x = model.pointA.x; housingSpr.rotation = 0; flywheelSpr.y = model.pointA.y; flywheelSpr.x = model.pointA.x; flywheelSpr.rotation = model.phi * 180 / Math.PI; pistonSpr.x = model.pointB.x; pistonSpr.y = model.pointB.y; pistonSpr.rotation = -model.psi * 180 / Math.PI; stoneSpr.y = model.pointC.y; stoneSpr.x = model.pointC.x; stoneSpr.rotation = 0; } // Функция для отрисовки маркера (для тестовых нужд) private function drawMark(spr:Sprite):void { var markSpr:Sprite = new Sprite(); markSpr.graphics.lineStyle(2, 0x000000); markSpr.graphics.moveTo( -15, 0); markSpr.graphics.lineTo(15, 0); markSpr.graphics.moveTo(0, -15); markSpr.graphics.lineTo(0, 15); spr.addChild(markSpr); } } }
private var housingRegPoint:Point = new Point(70, 94);
Найти координаты “регистрационной” точки можно воспользовавшись любым графическим редактором, даже Paint подойдет :)) |
//В конструкторе мы создаем графический элемент для корпуса var housingBmp:Bitmap = new HousingPNG(); // Затем “оборачиваем” его внутрь созданного специально // для этого спрайта housingSpr = new Sprite(); housingSpr.addChild(housingBmp); // А потом размещаем его внутри этого спрайта housingBmp.x = -housingRegPoint.x; housingBmp.y = -housingRegPoint.y; // И добавляем в список отображения не сам объект Bitmap, // а спрайт addChild(housingSpr);
3.3. Модель и виды у нас готовы, теперь осталось собрать это воедино. Данную роль будет выполнять класс Main (он же основной класс проекта):
package { import com.bit101.components.PushButton; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; public class Main extends Sprite { private var model:MechModel; private var vectorView:MechViewVector; private var rasterView:MechViewRaster; // Логический “флаг”, говорящий о том, работает ли механизм // в данный момент private var running:Boolean = false; private var pButton:PushButton; public function Main() { stage.align = StageAlign.TOP_LEFT stage.scaleMode = StageScaleMode.NO_SCALE; // Создаем модель и виды для данной модели model = new MechModel(20, 120); vectorView = new MechViewVector(model); vectorView.x = stage.stageWidth / 2; vectorView.y = 305; vectorView.rotation = -90; // Изначально тестовый вид не отображается // Его можно включить, щелкнув мышью по сцене vectorView.visible = false; rasterView = new MechViewRaster(model); rasterView.x = stage.stageWidth / 2; rasterView.y = 305; rasterView.rotation = -90; addChild(rasterView); addChild(vectorView); // Кнопка запуска-остановки механизма pButton = new PushButton(this, 10, 385, "Play", onClick); pButton.width = 172; // Добавляем возможность включения-выключения // тестового режима отрисовки механизма stage.addEventListener(MouseEvent.CLICK, clickHandler); } protected function onClick(event:Event):void { running = !running; if(running) { event.target.label = "Stop"; addEventListener(Event.ENTER_FRAME, enterFrameHandler); } else { event.target.label = "Play"; removeEventListener(Event.ENTER_FRAME, enterFrameHandler); } // Данная строка нужна для того, чтобы событие // щелчка мышью не распространялось дальше // по списку отображения // Если эту строку закомментировать, то при щелчке // мышью по кнопке будет включаться и выключаться // тестовый режим отрисовки механизма, // что является нежелательным поведением event.stopPropagation(); } // Обновление состояния модели private function enterFrameHandler(e:Event):void { model.update(); } // Включение-отключение тестового режима отрисовки private function clickHandler(e:MouseEvent):void { vectorView.visible = !vectorView.visible; } } }
Это был последний класс в этом примере, откомпилировав его Вы должны получить что-то вроде этого:
Исходники к уроку
После этого урока мы немного отвлечемся от построения моделей механизмов и сделаем упор на векторную математику и тригонометрию, а также рассмотрим довольно интересную математическую библиотеку.
Ну и конечно: С наступившим Новым Годом!
Комментариев нет:
Отправить комментарий