Код данного примера будет довольно сильно переработан.
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;
}
}
}
Это был последний класс в этом примере, откомпилировав его Вы должны получить что-то вроде этого:
Исходники к уроку
После этого урока мы немного отвлечемся от построения моделей механизмов и сделаем упор на векторную математику и тригонометрию, а также рассмотрим довольно интересную математическую библиотеку.
Ну и конечно: С наступившим Новым Годом!





Комментариев нет:
Отправить комментарий