Начинаем делать игру

Итак, с основным фишками разобрались, даже что-то удалось написать и это работало :) Пора браться за какую-нибудь серьезную задачу, чтобы получше освоиться в AS3. Думаю, лучше всего создать веселую игрушку. Например, такой вариант. Космическое пространство, планета, на планету летят астероиды и кометы (типа земля попала в полосу невезения :), твоя задача защитить планету от полного уничтожения. Можно ставить в любом месте защитные автоматические турели. Для начала два типа: 1 – лазерная, стреляет часто, но слабо. 2 – ракетная, разносит все в клочья в радиусе взрыва ракеты, но стреляет редко. Добавить еще большие кометы, которые дробятся на мелкие астероиды.
Создаем новую флешку, размером 800x400 (от балды), частота кадров 21 (в какой-то статье было доказано, что такая частота оптимальна).

Прелоадер

Т.к. в игре будет достаточно картинок, звуков и анимации итоговый вес флешки будет приличным, необходимо отобразить процесс загрузки игры. Делаем мувик preloader который состоит из текстового поля (txt) и прогрессбара (progress - мувик состоящий из 100 кадров, будем позиционировать кадр в зависимости от того, сколько процентов флешки загрузилось). Мувиклипу прелоадера прописываем класс main.preloader со следующим кодом:

package main {
     import flash.display.MovieClip;
     import flash.events.*;

     dynamic public class preloader extends MovieClip {
           public function preloader() {
                // Нужно периодически вызывать Update, которая будет обновлять процент загрузки
                // Используем старый-добрый onEnterFrame с учетом событий AS3
                addEventListener(Event.ENTER_FRAME, Update);
           }

           public function Update(e : Event):void {
                this.txt.text="Загрузка...";
           }
          
     }
}

Обратите внимание, класс должен быть динамичным (dynamic) иначе компилятор будет ругаться:
Cannot create property txt on preloader.
Т.е. не может создать новое свойство txt (это наше вложенное текстовое поле) в нединамичном классе.
На первом фрейме флешки пишем

stop();

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

Теперь нужно выяснить, как узнать, сколько процентов нашей флешки в данный момент загрузилось. Смотрим по таблице соответствий AS2-AS3 (http://livedocs.macromedia.com/flex/2/langref/migration.html) куда дели функцию getBytesLoaded(), нас отсылают к классу URLLoader.bytesLoaded, но этот класс используется для загрузки внешних мувиков и картинок, а нам надо узнать процент загрузки самого себя. После внимательного просмотра всех свойств видимых объектов (DisplayObject) обнаружил свойство loaderInfo, это ссылка на того, кто загружал этот объект. А у класса LoaderInfo есть свойства bytesLoaded и bytesTotal . Нам нужно узнать, сколько байт загрузилось у всей флешки, т.е. у stage. Тесты показали, что stage.loaderInfo.bytesLoaded правильно возвращает, сколько байт загрузилось, а вот stage.loaderInfo.bytesTotal стабильно возвращает ноль! Решив, что это глюк при локальной загрузке, залил флешку на сервер и убедился, что при загрузке с сайта bytesTotal нормально возвращает общий размер флешки. Спишем эти глюки на альфаверсию Flash 9.

С проверкой bytesTotal на ноль функция Update принимает следующий вид:

public function Update(e : Event):void {
     var bytesLoaded:Number = stage.loaderInfo.bytesLoaded;
     var bytesTotal:Number = stage.loaderInfo.bytesTotal;
     var s:String = "";
     var percent:Number = 0;
     if (bytesTotal>0){
           percent = Math.floor(bytesLoaded/bytesTotal*100);
           s = percent+"% ("+
           Math.round(bytesLoaded/1024)+"кб / "+
           Math.round(bytesTotal/1024)+"кб)";
     }
     this.txt.text="Загрузка... "+s;
     this.progressbar.gotoAndStop(percent+1);
     // Если полностью загрузились, то переходим на второй кадр
     if (bytesLoaded==bytesTotal || bytesTotal==0){
           removeEventListener(Event.ENTER_FRAME, Update);
           parent.play();
     }
}

Локально увидеть процесс загрузки не удастся, но тесты через сервер показали, что все нормально отображается. Обратите внимание, после того, как прелоадер выполнил свою задачу нужно удалить себя из обработки ENTER_FRAME (функция removeEventListener), иначе Update продолжает вызываться и после перехода на второй кадр (хоть там уже нет нашего мувика, что весьма странно).

Игровое поле

Игровое поле должно быть довольно большим, по нему можно перемещаться «таская» мышкой. В качестве фона на гугле найдем большую картинку звездного неба. Создаем новый MovieClip с именем background и вставляем в него картинку (размер картинки 2000x2000). В Linkage мувика ставим галочку «Export for ActionScript», класс main.background, и убираем галочку с «Export in first frame» (иначе этот мувик и все остальные, у которых будет стоять «Export in first frame», станут грузиться вместе с первым кадром флешки, и прелоадинга как такового не будет).

Напишем класс main.background, в котором реализуем его «таскание» мышкой. Делаем обычный drag:

// Перехватываем нажатие кнопки мыши по нашему мувику
addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
// Отпускание мышки
addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
. . .
// Нажатие кнопки мыши по нашему мувику
function handleMouseDown(event:Event):void {
     startDrag();
}
// Отпустили кнопку мыши
function handleMouseUp(event:Event):void {
     stopDrag();
}

При таком решении будет наблюдаться один маленький баг: нажать кнопку мыши на мувике, вывести мышку за пределы флешки, отпустить кнопку и провести курсор по флешке. Мувик будет продолжать таскаться при отпущенной кнопке мыши! Т.к. событие отпускание кнопки мыши за пределами флешки не обрабатывается и stopDrag() не сработал. Нужно ставить какую-то заглушку, вижу два варианта:

1. Отлавливать выход мышки за пределы флешки (есть такое новое событие mouseLeave в классе Stage)
2. Отлавливать перемещение мышки и если мы находимся в состоянии таскания, а кнопка мыши отпущена, принудительно сделать stopDrag()

Опробуем новые методы:

// При выходе мышки за пределы флешки, делать тоже самое, что и при отпускании кнопки
stage.addEventListener(Event.MOUSE_LEAVE, handleMouseUp);

Интересно, что это событие срабатывает не тогда, когда реально мышка вышла за пределы флешки, а только тогда, когда отпустят кнопку мыши за пределами… но это уже мелочи.
И нужно задать пределы перетаскивания, а то можно утащить наше небо вообще за край. Для этого в startDrag задаем пределы перетаскивания:

// Нажатие кнопки мыши по нашему мувику
function handleMouseDown(event:Event):void {
     var dx:Number = width-stage.stageWidth;
     var dy:Number = height-stage.stageHeight;
     var dragRect:Rectangle = new Rectangle(-dx,-dy, dx,dy);
     startDrag(false, dragRect);
}

Отлично, с перетаскиванием неба закончили.  

Объекты космоса

Теперь необходимо создать базовый класс любого объекта, который будет в нашей игре. Т.к. в космосе в основном сферические объекты, то основными параметрами нашего объекта должны быть: координата и радиус. В качестве координаты используем x,y мувика. И нужна функция проверки столкновения двух объектов. Т.к. любой объект в игре будет иметь изображение и, скорее всего анимированное, то наследуемся от класса MovieClip (если нет анимации, то лучше наследоваться от класса Sprite).

package main {
     import flash.display.MovieClip;

     dynamic public class basic_object extends MovieClip {
           public var radius:Number; // радиус

           // Вычисляет расстояние до другого объекта в квадрате
           // Функция sqrt довольно медленная и быстрее оперировать расстоянием в квадрате
           public function distance2(obj2:basic_object):Number {
                var dx:Number = x - obj2.x;
                var dy:Number = y - obj2.y;
                return dx*dx + dy*dy;
           }
           // Вычисляет расстояние до другого объекта
           public function distance(obj2:basic_object):Number {
                return Math.sqrt( distance2(obj2) );
           }
           // Определяет, столкнулись два объекта или нет
           public function CheckCollision(obj2:basic_object):Boolean {
                var d2:Number = distance2(obj2); // квадрат расстояния
                // сумма радиусов - минимально возможное расстояние между объектами
                var dr:Number = radius + obj2.radius;
                // если расстояние между объектами меньше суммы их радиусов - столкновение
                return (d2 < dr*dr);
           }
     }
}

Теперь из этого класса создадим первый статичный космический объект – класс static_object

package main {
     import main.*;

     dynamic public class static_object extends basic_object {
           public function static_object() {
                // вычислим наш радиус, основываясь на размере картинки
                radius = Math.floor((width+height)/4);
           }
     }
}

Наверное, позже статичным объектам добавим какие-то свойства, например, гравитацию, а пока он пустой, инициализируем только радиус. Создадим мувик Earth, имя класса main.static_object (экспорт в первом фрейме не забываем отключать) и вставляем туда картинку нашей планеты (google рулит!). Если мувик с землей просто положить на сцену поверх мувика космоса, то они никак не будут взаимодействовать и при перетаскивании звездного неба земля остается на месте. Космических объектов будет много их необходимо перемещать, рассчитывать столкновения и т.п., поэтому необходимо создать базовый класс (мувиклип), в котором все и будет размещаться и рассчитываться. Создаем мувиклип sky, класс main.sky в него кладем мувиклип со звездным небом (назовем bg) и мувик с землей (назовем earth). Маленькая поправка в классе background (со звездным небом), т.к. двигаться должно не только небо, но и все объекты над ним, то startDrag делаем у родителя:

parent.startDrag(false, dragRect);

И аналогично stopDrag()
Запускаем, тестируем. Небо есть, земля есть, все нормально таскается мышкой.

Рис. 2  

Пора создать первый астероид. Это будет продолжение нашего базового класса basic_object, движущийся объект moving_object. У него должно быть направление движения и скорость. Это лучше определить, как вектор. Не изобретая велосипед, берем готовый класс Vector из примера Macromedia, про рыбок Boids.

package main {
     public class Vector {
           var x:Number;
           var y:Number;

           function Vector(setX:Number=0, setY:Number=0){
              x = setX;
              y = setY;
           }
           function copyVector( v:Vector ):void {
                x = v.x;
                y = v.y;
           }
           function setMembers( x:Number, y:Number ):void {
                x = x;
                y = y;
           }
           function addVector( v:Vector ):void {
                x += v.x;
                y += v.y;
           }
           function subVector( v:Vector ):void {
                x -= v.x;
                y -= v.y;
           }
           function mulScalar( i:Number ):void {
                x *= i;
                y *= i;
           }
          function magnitude():Number {
                return Math.sqrt( x*x + y*y );
           }
           function magnitude2():Number {
                return x*x + y*y;
           }
     }
}

Создаем новый мувиклип asteroid, класс main.moving_object. В него вставляем несколько изображений астероидов по одному на каждый кадр (если все астероиды будут выглядеть одинаково, будет совсем некрасиво). При инициализации рандомно выбираем изображение астероида и делаем gotoAndStop() на выбранный кадр:

package main {
     import flash.display.MovieClip;
     import main.*;

     dynamic public class moving_object extends basic_object {
           public var Velocity:Vector; // вектор движения

           public function moving_object() {
                var num:Number;
                num = Math.floor(Math.random()*totalFrames)+1; // выбираем случайный кадр
                gotoAndStop(num); // и переходим на него
                Velocity = new Vector(); // создаем вектор
                // вычислим наш радиус, основываясь на размере картинки
                radius = Math.floor((width+height)/4);
           }
     }
}

Бросаем мувик астероида внутрь нашего основного sky, запускаем… стоит астероид на месте, ошибок компиляции нет, что уже хорошо. Астероиды будут появляться рандомно за границей игрового поля и лететь в сторону земли. Если это будет полный рандом – не интересно, лучше если астероиды будут прилетать небольшой группой, поэтому делаем функцию drop, вбросить астероид рядом с указанной координатой и двигаться в указанном направлении:

dynamic public class moving_object extends basic_object {
     const DROP_RADIUS:Number = 200;     // разброс при вбрасывании
     const MIN_SPEED:Number = 1;  // раброс начальной скорости
     const MAX_SPEED:Number = 10;

     ...

     // Вбросить объект рядом с указанной точкой x1:y1 и двигаться к x2:y2
     public function drop(x1:Number,y1:Number, x2:Number,y2:Number):void {
          // координата вброса
          x = x1 + (Math.random()-0.5)*DROP_RADIUS;
          y = y1 + (Math.random()-0.5)*DROP_RADIUS;
          // направление
          Velocity.setMembers(x2-x, y2-y);
          // скорость
          var spd:Number = MIN_SPEED + (MAX_SPEED-MIN_SPEED)*Math.random();
          // приведем длину вектора к выбранной скорости
          Velocity.mulScalar( spd / Velocity.magnitude() );
     }
}

Цифры констант поставлены от балды, когда астероиды реально полетят, то подгоним под нормальные значения. Для теста, попробуем вбросить десять астероидов в видимой области (200:200), код в главном классе sky будет следующий

package main {
     import flash.display.MovieClip;
     import main.*;

     dynamic public class sky extends MovieClip {
           public function sky() {
                var new_asteroid:moving_object;
                var i:Number;
                for (i=0; i<10; i++){
                     new_asteroid = new moving_object();
                     addChild(new_asteroid);
                     new_asteroid.drop(100,100, 200,200);
                }
           }
     }
}

Запускаем – пусто… Что не так? В мувике астероида убрана галочка «Export in first frame», т.е. мувик не грузится в первом кадре флешки, а будет загружаться в том кадре, в котором он поставлен на TimeLine. Я забыл добавить мувик астероида на TimeLine и соответственно он так и не был загружен вообще. Исправляюсь, бросаю его за пределами звездного поля, чтобы не был виден. Запускаем:

Рис. 3

Ура, астероиды появились, рандомно скучковались. То, что есть астероиды, которые пересекаются не страшно, у нас все равно будет проверка столкновений и отскоков, а вбрасываться астероиды будут за областью видимости и когда влетят в поле зрения, они уже растолкаются. Это даже хорошо, создаст дополнительный рандом в траекториях их движений.

Ну, пора уже заставить их летать! Для этого в главном классе sky нужно создать массив, в который будут заноситься все движущиеся объекты. Сделать функцию, которая вызывается по EnterFrame и поставить обход всего массива с вызовом функции move объектам. В классе moving_object пишем:

// Переместиться
public function move():void {
     x += Velocity.x;
     y += Velocity.y;
}

А главный класс sky приобретает следующий вид.

package main {
     import flash.display.MovieClip;
     import flash.events.*;
     import main.*;

     dynamic public class sky extends MovieClip {
           var MIN_DROP:Number, MAX_DROP:Number; // пределы количества вбрасываемых астероидов
           var all_moving:Array; // здесь все движущиеся объекты

           public function sky() {
                all_moving = new Array();
                // сколько астероидов вбрасывается
                // это не константы, т.к. со временем количество астероидов должно увеличиваться
                MIN_DROP=5; MAX_DROP=10;

                addEventListener(Event.ENTER_FRAME, Update);
                // тестовый вброс
                DropSeveralAsteroids();
           }

           public function DropSeveralAsteroids():void {
                var x1:Number, y1:Number; // точка вброса
                var x2:Number, y2:Number; // куда двигаться
                var cnt:Number;    // количество вбрасываемых встероидов
                var new_asteroid:moving_object; // объект астероида

                x1=0; y1=0; // пока так, для теста, из левого верхнего угла
                x2=this.earth.x; y2=this.earth.y; // двигаться к земле
                // выбираем рандомное количество
                cnt = MIN_DROP + Math.floor((MAX_DROP-MIN_DROP)*Math.random());
                while (cnt--){
                     new_asteroid = new moving_object();     // создаем новый астероид
                     addChild(new_asteroid);           // добавляем его на наш мувиклип
                     all_moving.push(new_asteroid);    // запоминаем в массиве
                     new_asteroid.drop(x1,y1, x2,y2)// бросаем
                }
           }

           public function Update(e : Event):void {
                // Проходим по всему массиву созданных объектов
                // и заставляем каждого сдвинуться в своем направлении
                for each (var obj:moving_object in all_moving){
                     obj.move();
                }
           }

     }
}

Летают! Сразу видны недочеты:

1. все летят строго в центр планеты. И в конце просто выстраиваются в одну прямую. Стоит при вбрасывании добавить рандом к x2:y2.
2.астероидам стоит придать небольшое вращение. Поскольку это не 3D объект, то просто будем поворачивать картинку вокруг своей оси.

При инициализации moving_object:

// зададим случайное направление вращения
rot = 2*(Math.random()-0.5);
а при перемещении
// Переместиться
public function move():void {
     x += Velocity.x;
     y += Velocity.y;
     rotation += rot; // и немного повернуться
}

Можно посмотреть, что же получилось. Первый результат: нажать правой кнопкой мыши и выбрать play, должны появиться пролетающие астероиды. Небо можно "таскать" мышкой

 


Страницы: 1 [2] 3 4 5