Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование игр на Flash/Flex Космический корабль, управляемый кнопками


Здравствуйте, уважаемые читатели!

Попробуем создать простую модель космического корабля, управляемого с пульта. Читатели рассылки, а также все остальные любопытные могут ознакомиться с результатом выполнения этого упражнения, зайдя на мой сайт. Затем, в Главном меню выбрать позицию Flash/Flex игры, и перейти по гиперссылке Космический корабль. Можно также воспользоваться Прямой ссылкой.

В этой статье (выпуске рассылки) мы рассмотрим следующие вопросы:

  1. Создание простых кнопок;
  2. Управление движением и поворотом объекта с помощью кнопок;
  3. Загрузка растровых изображений

Создание и использование кнопок

Стандартные кнопки, естественно, присутствуют среди компонентов Flash, но это не мешает нам создавать свои. Опыт показывает, что если обойтись без использования стандартных компонентов, то Флеш-ролик становится заметно компактнее. Для описания такой кнопки нужно создать три визуальных объекта (обычно используются объекты класса Sprite) и вызвать конструктор класса SimpleButton, у которого 4 параметра. Эти параметры описывают соответственно:

  • состояние кнопки в ситуации, когда указатель находится вне чувствительной зоны кнопки
  • состояние кнопки при нахождении указателя мыши в чувствительной зоне;
  • Состояние кнопки при нажатии на нее
  • Чувствительную область

Отмечу, что эти параметры в точности соответствуют четырем кадрам в библиотечном символе Button всех версий программы Flash.
Поскольку чувствительная область все равно не видна, достаточно построить всего три визуальных объекта, и использовать любой из них дважды: по прямому назначению и для описания чувствительной области.

Ниже следует текст модуля Button - создание прямоугольной, или круглой кнопки с надписью. Текст сопровождается подробными комментариями:

// Класс Button - круглая, или прямоугольная кнопка. Может содержать надпись

package Button
{
import flash.display.GradientType;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.text.TextField;
public class Button extends Sprite
{
private var _xx:uint; //координаты
private var _yy:uint;
private var _rad:uint; // Радиус кнопки
public const COL1:uint = 0xFADE10; // Цвета кнопки в трех состояниях
public const COL2:uint = 0xAFDD94;
public const COL3:uint = 0x0000FF;
public function Button(type:String,label:String,xx:uint,yy:uint,rad:uint,fn:Function)
// Параметры конструктора: тип: "c" - круглая кнопка, "r" - прямоугольная
// label - надпись, далее: координаты, радиус, или высота
//fn - имя функции-обработчика события click
{
_xx = xx;
_yy = yy;
_rad = rad;
if (type == "c"){ // Круглая кнопка
var s1:Sprite = circle(COL1);
var s2:Sprite = circle(COL2);
var s3:Sprite = gradCircle(0x0000FF,0xFFFFFF,1,1);
}else{
s1 = rect(COL1); //Прямоугольная кнопка
s2 = rect(COL2);
s3 = rect(COL3);
}
var btn:SimpleButton = new SimpleButton(s3,s1,s2,s1); //Конструктор класса SimpleButton. 3 состояния кнопки и чувствительная область
btn.addEventListener(MouseEvent.CLICK,fn); //Связь с обработчиком события
var txt:TextField = new TextField(); // Надпись на кнопке
txt.x = xx - 4;
txt.y = yy + rad;
txt.text = label;
addChild(btn); // Добавление к области видимости
addChild(txt);
}
private function rect(col:uint):Sprite{ // Рисует прямоугольник
var c_rect:Sprite = new Sprite();
with ( c_rect.graphics){ 
lineStyle(1,0);
beginFill(col);
drawRect(_xx,_yy,_rad*3,_rad);
endFill();
}
return c_rect;
}
private function circle(col:uint):Sprite{ // рисует круг с однородной заливкой
var c_circ:Sprite = new Sprite();
with ( c_circ.graphics){ 
lineStyle(1,0);
beginFill(col);
drawCircle(_xx,_yy,_rad);
endFill();
}
return c_circ;
}
private function gradCircle(col1:uint,col2:uint,alpha1:uint,alpha2:uint):Sprite{
var c_circ:Sprite = new Sprite(); //Рисует круг с градиентной заливной
var matrix:Matrix = new Matrix();
matrix.createGradientBox(_rad*4,_rad*4,0,_xx-_rad,_yy-_rad);
var colors:Array = [col1,col2];
var alphas:Array = [alpha1,alpha2];
var ratios:Array = [0,0xFF];
with (c_circ.graphics){
lineStyle(1);
beginGradientFill(GradientType.RADIAL,colors,alphas,ratios,matrix);
drawCircle(_xx,_yy,_rad);
endFill();
}
return c_circ;
}
}
}

Полагаю, что если Вы читали предыдущие статьи (выпуски), то с помощью комментариев в этом тексте разобрались без особого труда.

Модуль Device: Модель стрелочного прибора

Так же подробно прокомментирован и текст модуля Device.

// Класс Device - простой стрелочный прибор с тремя кнопками.
package
{
import Button.Button;

import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;
public class Device extends Sprite
{
private var _arrow:Arrow;
private var _value:Number = 0;
private var _price:Number = 0;
private var _txt:TextField = new TextField;
private var _textFormat:TextFormat = new TextFormat("Arial",14,0,true);
public function Device(pa:Object,mean:String,xx:uint,yy:uint,rad:uint,min:Number,max:Number,nPoints:uint)
{
_price = 180 / nPoints;
var dr:uint = 4;
var rad1:uint = rad - dr;
var rad2:uint = rad + dr;
graphics.lineStyle(2,0xFFFFFF); //Внешняя окружность
graphics.beginFill(0xFFFFFF); //с заливкой белым цветом
graphics.drawCircle(xx,yy,rad2);
graphics.endFill();
graphics.lineStyle(2,0);
graphics.beginFill(0xFFFFFF);
graphics.drawCircle(xx,yy,rad); // Заготовка шкалы
graphics.endFill();
var tMean:TextField = new TextField; //Надпись на приборе
with (tMean){
x = xx - rad / 2;
y = yy - rad /3;
text = mean;
setTextFormat(_textFormat);
}
_txt.x = xx + rad /2 ;
_txt.y = yy - rad / 3;
addChild(tMean);
addChild(_txt);

var dAngle:Number = Math.PI / nPoints; // Угловое расстояние между двумя штрихами шкалы
var beta:Number = 0; // "бегущий" угол.
graphics.lineStyle(1); // Штрихи будут тонкими
for (var i:uint = 0; i <= nPoints; i++){ // Рисуем шкалу справа налево
var xc:Number = xx + rad1 * Math.cos(beta); // штрихи шкалы вычерчиваются между двумя невидимыми окружностями
var yc:Number = yy + rad1 * Math.sin(beta); // радиуси окружностей rad1 и rad2
graphics.moveTo(xc,yc); // переместимся к началу штриха
xc = xx + rad2 * Math.cos(beta);
yc = yy + rad2 * Math.sin(beta);
graphics.lineTo(xc,yc); //Нанесем штрих
beta -= dAngle; // и перейдем к новому значению "бегущего" угла.
}
_arrow = new Arrow(this,xx,yy,4,rad,0xFF0000); // Стрелка прибора
var signs:Array = ["--","0","+"]; // Надписи у кнопок
var rb:uint = rad / 8; //Радиус кнопки
var xb:uint = xx - rb*3; //Координата кнопки по OX
for (i = 0; i< signs.length; i++){ 
var bt:Button = new Button("c",signs[i],xb,yy+rb*2,rb,btClick); // Параметры конструктора описаны в модуле класса
bt.name = "bt" + i.toString(); // по третьему символу имени будет определяться действие кнопки
addChild(bt); // Добавим кнопку к списку видимости
xb += rb * 3; // и получим координату следующей кнопки.
}
Value = 0; // Обращение к Сеттеру (см. ниже)
pa.addChild(this); // Добавит себя к списку видимости родителя
}
private function btClick(e:MouseEvent):void{ // Обработчик события Нажатие на кнопку прибора
var nm:String = e.target.parent.name; 
// Объект, вызвавший событие - кнопка, а его родитель - объект Button с соответствующим именем
var dd:int = int(nm.charAt(2)); // на третьем месте (от 0) номер объекта в порядке создания
switch(dd){
case(0):
if (_arrow.rotation > -90) 
Value = _value - _price; // Обращение к Сеттеру (см. ниже)
break;
case(1): Value = 0; break; //Средняя кнопка устанавливает прибор в 0.
case(2):
if (_arrow.rotation < 90) //Левая и правая кнопки изменяют угол поворота стрелки
Value = _value + _price; 
break;
}
}
public function set Value(newValue:Number):void{ // Сеттер. При изменении свойства Value изменяется угол поворота стрелки
_arrow.rotation = _value = newValue;
_txt.text = (newValue / _price).toString(); // текст тоже изменяется
_txt.setTextFormat(_textFormat); // и форматируется

}
public function get Value():Number{ // Геттер. Возвращает значение свойства для внешнего модуля.
return _value;
}
}
}

Модуль Arrow - Немного измененная версия того, который использовался в проекте "Стрелочные часы":

// Рисует залитый треугольник
package{
import flash.display.Sprite;
import flash.display.GradientType;
import flash.geom.Matrix;

public class Arrow extends Sprite{
public function Arrow(pa:Object,xx:uint,yy:uint,ww:uint,hh:uint,clr:uint){
pa.addChild(this); // Добавит себя в область видимости вызывающего модуля
//Рисуем треугольник
with(graphics){
lineStyle(1,clr);
moveTo(0, 0);
beginFill(clr);
lineTo(-ww / 2,0);
lineTo(0, - hh);
lineTo(ww / 2, 0);
lineTo(0, 0);
endFill();
}
x = xx; // и смещаем его в нужную позицию
y = yy; 
var c_circ:Sprite = new Sprite(); //в нижней части треугольника (стрелки) нарисуем круг с градиентной заливкой
var matrix:Matrix = new Matrix();
var rad:uint = ww /2 + 1;
matrix.createGradientBox(rad*4,rad*4,0,xx-rad,yy-rad);
var colors:Array = [0xFFFFFF,0x0000FF];
var alphas:Array = [1,1];
var ratios:Array = [0,0xFF]; // Параметры подобраны экспериментально
with (c_circ.graphics){
lineStyle(1);
beginGradientFill(GradientType.RADIAL,colors,alphas,ratios,matrix);
drawCircle(xx,yy,ww /2 + 1);
endFill();
}
pa.addChild(c_circ);// В область видимости вызывающего модуля добавим круг в нижней части стрелки.
}
}
}

Использование растровых изображений в Flash-роликах

С момента появления технологии Flash ролики, написанные с использованием этой технологии, были векторными. Напомню, что векторные объекты описываются математическими выражениями. Растровое изображение строится на основе прямоугольной матрицы, каждый элемент которой описывает один пиксель. Преимущество векторной графики, используемой в Flash, сыграли значительную роль в популяризации данной технологии во Всемирной паутине.

Однако, у растровой графики есть свои положительные стороны, особенно для передачи фотографических и других изображений.
В версиях Flash вплоть до версии 8 поддержка растровых изображений была минимальной. В современных версиях имеется новый класс BitmapData, который содержит целый набор средств для работы с растровыми изображениями во Flash во время выполнения.
Мы рассмотрим некоторые из этих возможностей в данном выпуске.

Для работы с растровым изображением прежде всего необходимо создать объект BitmapData. Однако, здесь есть одна тонкость: Для того, чтобы сделать объект видимым нужно добавить этот объект в список отображения. Для решения этой задачи используется метод AddChild(). Однако, этот метод принимает только объекты, являющиеся потомками класса flash.display.DisplayObject. Класс BitmapData происходит непосредственно от класса Object, поэтому его нельзя напрямую добавить в список отображения.

Проблема решается с помощью класса flash.display.Bitmap, который является потомком требуемого класса.

Остальные особенности данного модуля - в его комментариях.

// Космический корабль, управляемый кнопками.
package {
[SWF(width='550',height='400',backgroundColor='#ffffff')] //Это описано в литературе, но не работает, почему-то...
import Button.Button; // Модуль класса Button помещен в папку с тем же названием. Поэтому такая ссылка.
import flash.display.Bitmap;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.URLRequest;
import flash.text.TextField;
public class cosm_ship extends Sprite
{
private var _loader:Loader = new Loader(); //Загрузчик. В данном случае - загрузчик изображений
private var _image:Bitmap = new Bitmap(); // Контейнер для растрового изображения
private var _sprite:Sprite = new Sprite(); // изображение нужно поместить в объект этого класса. Иначе будет трудно им управлять
private var _xs:uint = 0;
private var _ys:uint =0;

private var _xp:int = 0;
private var _yp:int = 0;
private var _devX:Device = new Device(this,"Гориз",100,250,50,-10,10,10);// создаем 3 прибора для управления движением
private var _devR:Device = new Device(this,"Угол",220,250,50,-10,10,10); // по двум направлениям и углу.
private var _devY:Device = new Device(this,"Верт",340,250,50,-10,10,10);
private var _btGo:Button = new Button("c","Дв.",160,_devR.y+300,15,btClick); // кнопка. При нажатии считываются показания приборов и изменяются свойства объекта.
private var _bt0:Button = new Button("c","Вернись",280,_devR.y+300,15,btClick); //Кнопка устанавливает изображение в начальное положение

// (Извините за невольную рифму!)
public function cosm_ship()
{
_sprite.x = _sprite.y =0;
_sprite.graphics.moveTo(0,0);
var xx:uint = 220;
var yy:uint = 10;
bitmapLoad("images/cosm_ship/cosm_ship.gif"); // Загрузка файла изображения
addChild(_sprite);
addChild(_bt0);
addChild(_btGo);

}
private function btClick(e:MouseEvent):void{
if (e.target.parent == _btGo){
_sprite.x += _devX.Value;
_sprite.y += _devY.Value;
_sprite.rotation += _devR.Value;
}else{
_sprite.x = _sprite.y = _sprite.rotation = 0;
}
}
private function bitmapLoad(imgName:String):void{
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE,onComplete); //Связь со слушателем события "Завершение загрузки"
_loader.load(new URLRequest(imgName));
}

private function onComplete(e:Event):void{//Вызывается при завершении загрузки
_image = Bitmap(_loader.content); // Запишем результат загрузки в объект
_sprite.addChild(_image); // и свяжем его с управляемым объектом _sprite.
}
}
}

Заключение

Модель космического корабля создана, осталось приделать к ней ракетные двигатели, чтобы корабль летал в соответствии с законами физики, а не хвостом вперед. Но это уже потребует знания и использования законов физики, как и советовал Джоб Макар.


В избранное