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

Программирование игр на Flash/Flex Игра 'Путешествие на воздушном шаре (продолжение)'


Игра "Путешествие на воздушном шаре" (продолжение)

Добрый день, уважаемые читатели!

В этом выпуске рассылки мы продолжим разработку игры ball, начатой в предыдущем. Рассмотрим следующие вопросы, относящиеся к проектированию игр на Flash/Flex (Язык программирования ActionScript 3.0). Рассмотрим следующие вопросы:

  1. Полиморфизм
  2. Управление работой приложения с помощью меню
  3. Разработка специального класса для отображения текста.

Желающие могут получить архив с полным текстом модулей проекта здесь.Отдельные функции изменились по сравнению с предыдущим вариантом.

Полиморфизм

В начальном варианте игры мы сконструировали меню, структура которого была описана средствами языка XML. В этом выпуске мы заставим это меню работать. Вам уже известно, что для этой цели нужно связать какое-либо событие с соответствующей функцией - слушателем этого события. Для меню, как и для кнопок, событие - это, как правило, щелчек мышкой. Слушатель назначается вызовом метода addEventListener. Например:
bt.addEventListener(MouseEvent.CLICK,show);
назначает кнопке с именем bt в качестве слушателя события CLICK функцию show.

Возникает вопрос: можно ли для нескольких (в идеале - для всех) элементов меню назначить один и тот же слушатель. Почему это необходимо, или, по крайней мере, желательно легко понять. Ведь элементы меню создаются в цикле.
Использование одного и того же метода для различных объектов, вызвавших какое-либо событие основывается на одном из базовых принципов объектно-ориентированного программирования - принципе полиморфизма.Этот метод (слушатель события) должен работать по-разному, в зависимости от объекта, вызвавшего событие. Значит, он должен "знать", чье событие он обрабатывает. Стандартный способ передачи этой информации состоит в следующем: в качестве параметра метода передается указатель на соответствующий объект. Например, так выглядит заголовок метода show, ссылка на который приведена выше:
private function show(e:MouseEvent):void


А вот и текст данного метода:
private function show(e:MouseEvent):void{ // Вывод на экран подменю на время, ограниченное "тиком" таймера
var subMenu:Sprite = _subMenus[(e.target as SimpleButton).name]; // Обращение к ассоциативному массиву
// для получения информации, какой из элементов Главного меню вызвал событие
hide(); // Если одно из подменю уже показано, скрыть его.
addChild(subMenu); // и показать нужное подменю.
_timer.start(); // Ограничить время показа этого подменю
}

Использование ассоциативного массива (в нашем примере массива _subMenus) для связи имени элемента меню с указателем на этот элемент рассмотрено в предыдущем выпуске рассылки. Обратите внимание на использование синтаксической конструкции с элементом as для указания класса, от которого происходит элемент, вызвавший событие.

Управление работой приложения с помощью меню

Элементы меню нижнего уровня в нашей игре будут связаны с изменением состояния программы, как это в начальном виде было реализовано в предыдущем выпуске рассылки. Напомню, что для решения этой задачи мы ввели закрытую переменную _state и связали с ней метод, именуемый Сеттер:
public function set State(newState:String):void{

Остается дописать этот метод так, чтобы он работал для обработки события, вызванного любым элементом меню второго уровня. Выше мы разобрались с тем, как получить указатель на объект, вызвавший событие. Однако, удобнее для идентификации соответствующего элемента меню использовать надпись на нем. При этом нужно также учесть, что некоторые элементы могут иметь одинаковые надписи. Чтобы в этом разобраться, рассмотрим фрагмент XML-документа, описывающего меню:
<menus>
<menu name="Установка" func="show">
<item name="Правила"/>
<item name="Выполнить" />
<item name="Сохранить" />
</menu>
<menu name="Игра" func="show">
<item name="Правила"/>
<item name="Начать" />
<item name="Продолжить" />
<item name="Следующая" />
<item name="Завершить" />
</menu>
</menus>

Как видите, здесь описано меню, состоящее из двух элементов верхнего уровня: <menu name="Установка" func="show"> и <menu name="Игра" func="show">. В той и в другой группе (подменю) имеется позиция Правила: <item name="Правила"/>. При выборе пользователем первого из этих вариантов ему будет выведен текст с правилами игры, во втором - с правилами установки начальных позиций динамических элементов нашей игры.
Возможно, этот случай нетипичный, но все же его лучше учесть, тем более, что это совсем несложно сделать. Отметим, что в современной версии Adobe Flash можно использовать кириллицу в именах объектов. В нашем случае имя элемента подменю будет состоять из двух частей: в качестве префикса используем надпись соответствующей кнопки верхнего меню, а в качестве суффикса - надпись на данной кнопке. Например,

Вот как это сделано у меня:
private function btn(namePrefix:String,item:XML,xx:uint,yy:uint):SimpleButton{ // создание кнопки с надписью
var caption:String = item.@name;
var tx:TextField = txt(caption,xx,yy);
var tx1:TextField = txt(caption,xx,yy);
tx1.borderColor = 0xFF0000;
var tx2:TextField = txt(caption,xx,yy);
tx2.borderColor = 0x00FF00;
tx1.width = tx2.width = Constants.MENUWIDTH
var bt3:Sprite = rect(0xDDDDDD,xx,yy);
var bt:SimpleButton = new SimpleButton(tx,tx1,tx2,bt3);
if(namePrefix){ // если префикс определен
bt.name = namePrefix + "." + caption;// Имена кнопок подменю содержат имя "родительской" кнопки в качесте префикса.
}else{
bt.name = caption;
}
var func:String = item.@func;
switch (func){ // Для некоторых кнопок обработчик события определяется из XML-файла
case("show"): bt.addEventListener(MouseEvent.CLICK,show); break;
default: bt.addEventListener(MouseEvent.CLICK,btClick);
}
return bt;
}

Картина будет неполной, если здесь же не привести код метода, из которого вызывается данная функция:
public function getXml(xml:XML,xx:uint,yy:uint):void{
var xxx:uint = xx; // Аргументы функций не следует изменять внутри функций, поэтому пользуемся локальной переменной.
for each (var item:XML in xml..menu){
var bt:SimpleButton = btn("",item,xxx,yy);
__parent.addChild(bt);
if (item.children().length()){ // Если у данного узла есть потомки
var yyy:uint = yy + bt.height;
var subMenu:Sprite = new Sprite; // то создать подменю
_subMenus[bt.name] = subMenu; // Записать указатель на объект в ассоциативный массив
for each (var subItem:XML in item.children()){
var re:Sprite = rect(0xFFFFFF,xxx,yyy);
subMenu.addChild(re);
var sbt:SimpleButton = btn(bt.name,subItem,xxx,yyy);
subMenu.addChild(sbt);
yyy += sbt.height + 1;
}

}
xxx += bt.width+5; // Х-Координата следующей кнопки
}
}

Итак, имеем набор элементов меню/подменю, причем каждому из элементов набора присвоено уникальное имя. Имена элементов главного меню состоят из одного слова, элементы подменю обозначены так: имя_элемента_меню.имя_данного_элемента.
При щелчке мышкой по элементу главного меню вызывается уже рассмотренный выше обработчик show. Выбор элемента подменю всегда изменяет состояние программы: ее свойство Set. Это сделано в функции, создающей кнопку (элемент меню/подменю):

private function btn(namePrefix:String,item:XML,xx:uint,yy:uint):SimpleButton{ // создание кнопки с надписью
var caption:String = item.@name;
var tx:TextField = txt(caption,xx,yy);
var tx1:TextField = txt(caption,xx,yy);
tx1.borderColor = 0xFF0000;
var tx2:TextField = txt(caption,xx,yy);
tx2.borderColor = 0x00FF00;
tx1.width = tx2.width = Constants.MENUWIDTH
var bt3:Sprite = rect(0xDDDDDD,xx,yy);
var bt:SimpleButton = new SimpleButton(tx,tx1,tx2,bt3);
if(namePrefix){ // если префикс определен
bt.name = namePrefix + "." + caption;// Имена кнопок подменю содержат имя "родительской" кнопки в качесте префикса.
}else{
bt.name = caption;
}
var func:String = item.@func;
switch (func){ // Для некоторых кнопок обработчик события определяется из XML-файла
case("show"): bt.addEventListener(MouseEvent.CLICK,show); break;
default: bt.addEventListener(MouseEvent.CLICK,btClick);
}
return bt;
}

А текст фунции btClick, на которую ссылается данный метод default: bt.addEventListener(MouseEvent.CLICK,btClick); следующий:
private function btClick(e:MouseEvent):void{// Обработчик события, связанного с выбором позиции подменю
if (numChildren){ // Если показано меню
removeChildAt(0); //то спрятать его
}
_timer.stop();// и в любом случае остановить таймер
__parent.State = (e.target as SimpleButton).name; // и изменить состояние объекта-родителя, вызвав Сеттер.
}

Обратите внимание на использование метода removeChildAt(0). Его использование позволяет удалить из списка видимости объект, не зная его имени. Подробнее об этом - в одном из следующих выпусков.

Разработка специального класса для отображения текста

Класс flash.text.TextField - текстовое поле с набором необходимых методов для отображения и форматирования текста. В некоторых случаях этих стандартных методов недостаточно. Например, при выводе текста, поясняющего какие-либо возможности программы (фунция помощи пользователю) желательно кроме текста добавить еще и привычный элемент управления - маленькую кнопку с крестиком, щелчек по которой скроет текстовое поле. Разумеется, можно обработать и событие "Щелчек по текстовому полю", но такую возможность нужно сообщить пользователю, иначе он, скорее всего, не догадается.
Итак, разработаем класс - потомок класса TextField. Присвоим новому классу имя SpTextBox. Описание класса выглядит так:
// Класс: демонстрация текстов с кнопкой для закрытия.
package
{
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
public class SpTextBox extends Sprite
{
private var _txt:TextField = new TextField;
private var __parent:ball;
public const BTHW:uint = 8; // Размеры кнопки
public function SpTextBox(pa:Object,w:uint,h:uint)
{
__parent = pa as ball;
_txt.width = w;
_txt.height = h;
_txt.border = true;
_txt.wordWrap = true;
addChild(_txt);
var sp1:Sprite = rect();
var btn:SimpleButton = new SimpleButton(sp1,sp1,sp1,sp1);
addChild(btn);
btn.name = "Start"; // Имя кнопки используется для указания нового состояния родительского объекта при щелчке
btn.addEventListener(MouseEvent.CLICK,__parent.btClick); //Родительский объект должен иметь этот метод
}
private function rect():Sprite{ // Рисует прямоугольник с крестиком
var c_rect:Sprite = new Sprite();
with ( c_rect.graphics){
lineStyle(1,0);
beginFill(0xFFFFFF);
var left:uint = _txt.width - BTHW-1;
var right:uint = _txt.width;
var top:uint = 1;
var bottom:uint = BTHW;
drawRect(left,top,BTHW,BTHW);
endFill();
moveTo(left,top);
lineTo(right,bottom);
moveTo(right,top);
lineTo(left,bottom);
}
return c_rect;
}
public function set Text(tx:String):void{
_txt.text = tx;
}
}
}

Обратите внимание на простой алгоритм рисования крестика, примененный в функции rect.

В следующем выпуске рассылки мы продолжим разработку игры ball. По всем вопросам прошу обращаться по почте, или через любой Интернет-браузер. Мой адрес и другие контактные данные доступны зарегистрированным пользователям сайта. Желаю успехов!


В избранное