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

Программирование игр на Flash/Flex


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

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

Создайте новый проект ActionScript и присвойте ему имя SimpleMove. Для нашей простой задачи можно обойтись всего одним модулем, в котором будут реализованы все функции. Внутри той папки, в которой размещаются файлы - результаты компиляции проекта (во Flex это папка bin-debug) рекомендую создать папку images, а в ней - папку forGames. Такую же структуру папок, начиная от корневого каталога, должен иметь соответствующий раздел Вашего сайта. Тогда откомпилированный ролик не потребует никакой настройки при публикации его во Всемирной сети. Полный текст модуля можно получить здесь. Читателям рассылки, к сожалению, нельзя выслать этот архив вместе с выпуском: таковы правила ведения рассылок. Пожалуйста, получите файл, воспользовавшись этой ссылкой.

Создание кнопок

Нам понадобятся примерно 15 кнопок для управления объектом. В проектах ActionScript нельзя использовать стандартные компоненты Flex. Кроме того, нам нужны маленькие, круглые кнопки. По этим причинам создадим нужное количество экземпляров класса SimpleButton:
private function cr_button():void{ // Равномерное размещение кнопок по правой стороне рабочего поля
var dist:uint = ( HEIGHT1 - BORDER - BORDER ) / DIRSLENGTH;
_yy = BORDER + dist;
for (var i:uint = 0;i <= DIRSLENGTH;i++){
var r1:Sprite = circle(0xFA000F);
var r2:Sprite = circle(0xFD0000);
var r3:Sprite = gradCircle(0xFFFFFF,0x0000FF,1,1);
var button:SimpleButton = new SimpleButton(r3,r1,r2,r1); // обращение к конструктору класса
SimpleButton.
button.name = "bt" + i.toString(); // суффикс имени будет использоваться для выбора варианта функционирования кнопки
addChild(button);
button.addEventListener(MouseEvent.CLICK,btnClick); // Два обработчика различных событий кнопки.
button.addEventListener(MouseEvent.MOUSE_OVER,btnOver);
_yy+=dist;
}
}
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;
}

Как видите, две функции circle и gradCircle возвращают экземпляры класса Sprite. В обеих функциях с использованием графической библиотеки рисуется окружность заданного радиуса с центром, находящимся в заданной точке. В первой из этих функций окружность заливается однородной заливкой, во второй - градиентной.
Конструктор класса SimpleButton принимает 4 параметра, каждый из которых соответствует стандартному кадру библиотечного символа "Button" в программе Flash. Нужное количество кнопок получается с использованием цикла.

Ограничивающие объекты для проверки столкновений

К счастью, проверка столкновений в программах Flash и Flex - несложное дело, если, конечно, не стремиться к большой точности. В книге Джоба Макара описывается метод hitTest, реализованный в языке программирования ActionScript 2.0. В современной версии этого языка (3.0) имеется два метода, разработанные для решения этой задачи для различных вариантов: hitTestObject и hitTestPoint. Кроме того, имеется еще и свойство hitArea, относящееся к тому же вопросу. Подробное рассмотрение этих методов и этого свойства отложим на некоторое время, в этом проекте используем hitTestObject.

Создадим 4 экземпляра класса Sprite по краям нашего рабочего поля. Присвоим им имена: _border1, _border2, _border3 и _border4. Если имя подвижного объекта - _auto, то можно использовать 4 булевых переменных:

var c1:Boolean = !_auto.hitTestObject(_border1);
var c2:Boolean = !_auto.hitTestObject(_border2);
var c3:Boolean = !_auto.hitTestObject(_border3);
var c4:Boolean = !_auto.hitTestObject(_border4);

которые будут принимать значение Истина, если подвижный объект не столкнулся с соответствующим ограничителем. Если ни с одним из ограничителей столкновения не произошло, то произведение: c1&&c2&&c3&&c4 будет также иметь значение Истина, а если произошло столкновение хотя бы с одним из них - то Ложь. Напомню, что знак операции "двойной амперсанд" для всех Си-подобных языков используется для обозначения операции "Логическое И" (Конъюнкция).

Соответствующие операции реализованы в функции tick, которая является обработчиком события "Прерывание таймера".

И что же должна делать программа, если столкновение обнаружено? В некоторых случаях нужно разрушить, или деформировать объект(ы), участвующие в столкновении. В данном проекте будем считать, что наш автомобиль затормозил несколько раньше, чем столкнулся с препятствием, и никаких последствий для его внешнего вида это событие не вызвало.

Мы будем использовать три варианта обработки данного события:

  1. Автомобиль после столкновения останавливается
  2. Автомобиль меняет направление движения на противоположное
  3. Автомобиль отступает на небольшое расстояние, и выбирает новое (случайное) направление движения.

Описание компонент движения с помощью вектора

В виртуальном двумерном пространстве движение объекта в каждый момент времени описывается тремя составляющими: dx - вектор скорости по оси ОХ,dy - вектор скорости по оси OY, и dr - вектор поворота. Проще говоря, каждый раз, когда таймер "тикает", свойство x объекта изменяется на dx, свойство y - на dy, и свойство rotation - на dr. Величины dx,dy и dr могут быть положительными, отрицательными и нулевыми.

Удобно поместить все эти переменные в одномерный массив (вектор). В данном проекте в том же векторе удобно хранить также информацию о дальнейшем направлении движения. Будем считать, что эта компонента вектора принимает следующие значения: 0 - соответствует остановке, 1 - смене движения на противоположное, 2 - случайному выбору дальнейшего направления. Заметим, что последний вариант, как было сказано выше, потребует небольшого обратного сдвига объекта в направлении, противоположном прежнему движению, иначе новая проверка столкновения приведет к тому же результату, и движение не будет продолжено. Все эти соображения и правила реализованы в следующем фрагменте того же обработчика события - в функции tick:

with (_auto)
if ((c1&&c2&&c3&&c4)){ //если столкновения не произошло, то
x += _drive[0]; // движение по трем составляющим
y += _drive[1];
rotation += _drive[2];
}else{
if(!c1) y ++; // если столкновение с верхним объектом, сдвиг вниз
if(!c2) y --; // остальные аналогично
if(!c3) x ++;
if(!c4) x --;
switch( _drive[3] ){
case(0): _timer.stop();break;
case(1): Drive = [-_drive[0],-_drive[1],-_drive[2],_drive[3],]; break;
default: newDir();break;

}
}

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

private function newDir():void{
var dr:Array = [0,0,0,2];
dr[0] = (Math.random()-.5)*2; //компоненты вектора получают случайные значения в диапазоне от -1 до 1
dr[1] = (Math.random()-.5)*2;
dr[2] = (Math.random()-.5)*2;

Drive = dr;
}

А что это за переменная Drive, которой присваивается значение полученного вектора? Это свойство класса, точнее, это переменная класса, которая ведет себя как свойство. Иначе говоря, при изменении значения этой переменной автоматически происходит вызов метода, именуемого на программистском сленге сеттер. В языке ActionScript такие методы имеют простой синтаксис: перед именем функции надо написать ключевое слово set. В нашем случае этот метод имеет такой вид:

private function set Drive(newDrive:Array):void{
_timer.stop();
_txt.text = newDrive.toString(); //Запишем в верхнее текстовое поле значение вектора
_txtExpl.text = "Движение "; // и начнем заполнять нижнее текстовое пол
_drive = newDrive; // значение вектора занесем во внутреннюю переменную класса
if (!newDrive[0] && !newDrive[1] && !newDrive[2] && !newDrive[3] ){ // Если все компоненты вектора нулевые
_auto.x = _auto.y = 50; // то установим объект в начальное положение. Таймер запускать не будем
_auto.rotation = 0;
_txtExpl.text = "Стартовая позиция";
}else{
for (var i:uint = 0; i < 4; i++){ // Иначе выведем информацию о направлении движения в нижнее текстовое поле (
_txtExpl)
if(newDrive[i] < 0) _txtExpl.appendText(_tx_1[i]);
else if (newDrive[i] == 0) _txtExpl.appendText(_tx[i])
else _txtExpl.appendText(_tx1[i]);
}
_txtExpl.textColor = 0x999999;
_timer.start(); // и запустим таймер. При очередном "тике" будет вызван соответствующий метод.
}
}

Как видите, в данном случае сеттер почти ничего не делает: всего лишь останавливает таймер, записывает новое значение в поле _drive, и выводит поясняющие тексты. Если нужно продолжать движение, он снова запускает таймер.

Создание текстовых полей, определение их свойств и добавление к списку отображения описаны в конструкторе класса, и особых пояснений не требуют.

Заключение

Как Вы думаете, уважаемые читатели, смогли бы Вы самостоятельно создать на основе этого материала игру "Въезд в гараж". Идея, полагаю, понятна: играющий управляет автомобилем, задавая значения вектора Drive. Как создать препятствия (стенки гаража и соседних строений) Вы знаете. За каждое столкновение играющему начисляется штрафное очко. Время въезда тоже учитывается. Кто заинтересуется, задавайте вопросы, пожалуйста. Мой адрес доступен читателям рассылки и зарегистрированным посетителям моего сайта. Успехов Вам!


В избранное