Для подсчета набранных очков, лучше всего суммировать maxHP уничтоженных астероидов. В игре должно быть ограничение на количество добавляемых турелей, для этого введем деньги. Игроку изначально дается некоторая сумма монет, за каждый уничтоженный астероид так же добавляются монеты. У каждого вида турели будет своя стоимость.
Добавим на панель два текстовых поля для очков score_label и для монет money_label. В класс panel добавляем две переменных
public var score:Number; // сколько игрок набрал очков
public var money:Number; // сколько игрок имеет денег
И в функции обновления статистики Update выводим значения монет и очков.
public function Update(event:Event):void {
. . .
// Счет
this.score_label.text = Math.floor(score);
// деньги
this.money_label.text = money;
}
Для наглядности справа от каждого вида турели на панели управления разместим текстовые поля, стоимости этой турели, поля будем называть money_t1, money_t2, … В базовый класс турелей добавляем новое поле cost – стоимость турели. Во всех потомках, в конструкторе прописываем стоимости. Например, для лазерной (класс turret_laser) cost=20, для ракетной 40, для бастиона 70. Цифры навскидку, их потом нужно будет балансировать при окончательном тесте игры.
В функцию Update добавим подсветку красным цветом турелей и их стоимости, на установку которых в данный момент недостаточно монет.
public function Update(event:Event):void {
. . .
// подсветим красным турели, которые сейчас нельзя поставить из-за нехватки денег
var i:Number = 1;
var ok:Boolean;
while (this["t"+i]){ // есть такая турель
ok = this["t"+i].cost <= money;
this["t"+i].transform.colorTransform = new ColorTransform(1,1,1, 1, ok?0:200,0,0, 0);
this["money_t"+i].textColor = ok ? 0xFFFFFF : 0xFF0000;
i++;
}
Если игрок пытается поставить на поле турель, на покупку которой недостаточно монет, надо дать ему понять, почему турель не добавилась. Для этого в течение секунды включим мерцание текстового поля стоимости турели и поля общего числа монет.
Добавим две переменных: какое поле мерцает и сколько еще мерцать.
var twinkling_cnt:Number; // счетчик мерцания стоимости
var twinkling_nm:String; // для какой турели мерцает стоимость
Для мерцания по событию ENTER_FRAME будем включать и выключать видимость нужных полей.
// Мерцает стоимость указанной турели
function Twinkling(nm:String):void {
if (twinkling_cnt){ // уже что-то мерцает
stopTwinkling(); // прекращаем
}
twinkling_nm=nm;
twinkling_cnt=21; // мерцаем секунду
// добавляем обработку события по ENTER_FRAME
addEventListener(Event.ENTER_FRAME, doTwinkling);
}
// функция мерцания
function doTwinkling(event:Event):void {
if (--twinkling_cnt){ // еще мерцаем
this["money_"+twinkling_nm].visible =
this.money_label.visible = !this.money_label.visible;
} else { // закончили
stopTwinkling();
}
}
// функция окончания мерцания
function stopTwinkling():void {
// возвращаем видимость полей
this.money_label.visible = this["money_"+twinkling_nm].visible = true;
// удаляем событие
removeEventListener(Event.ENTER_FRAME, doTwinkling);
}
В функции добавления новой турели проверяем, достаточно ли монет и если нет, то турель не добавляем, а включаем мерцание стоимости.
Осталось добавить увеличение счета и монет. Пишем функцию:
// Добавляет очки
public function addPoints(asteroid_HP:Number):void {
score += asteroid_HP /10; // добавляем очки
money += Math.floor(asteroid_HP /10); // и монеты
Update(); // обновить статистику
}
А в классе sky, при удалении астероида вызываем эфу функцию, передавая maxHP астероида.
root.panel.addPoints(obj.maxHP);
Осталось добавить начало игры и окончание игры. Делаем следующую раскадровку флешки:
1-й кадр – прелоадер
2-й кадр – кнопка «Start game» и за пределами видимости размещаем все мувиклипы с классами, которые аттачатся в игре (т.к. мы убирали у всех галочку «Export in first frame», то их необходимо разместить на timeline).
3-й кадр – собственно основные классы игры: sky и panel
На втором кадре пишем следующий код:
stop();
play_btn.addEventListener(MouseEvent.CLICK, doPlay);
function doPlay(e:MouseEvent):void {
play();
}
Т.е. добавляем кнопке обработку клика мышкой, чтобы проигрывание флешки перешло на следующий кадр.
И создаем новый мувиклип в котором крупно пишем статичный текст «Game over», ниже разместим динамическое текстовое поле score_label в которое запишем набранные очки. Сделаем, чтобы при добавлении этого класса экран плавно затемнялся и по окончании флешка переходит на второй кадр, т.е. к кнопке «Start game»
Класс GameOver выглядит так:
package main {
import flash.display.Sprite;
import flash.events.*;
dynamic public class GameOver extends Sprite {
var bg:Sprite;
public function init() {
var dx:Number = stage.stageWidth/2;
var dy:Number = stage.stageHeight/2;
// размещаемся в центре экрана
x = dx;
y = dy;
// отобразим набранные очки
this.score_label.text = "Score: "+Math.floor(root.panel.score);
// создадим Sprite под текстом
bg = new Sprite();
addChildAt(bg, 0);
// рисуем на нем черный прямоугольник размером с экран
bg.graphics.beginFill(0);
bg.graphics.drawRect(-dx,-dy, dx*2,dy*2);
bg.graphics.endFill();
// ставим полную прозрачность
bg.alpha=0;
// по ENTER_FRAME будем уменьшать прозрачность
addEventListener(Event.ENTER_FRAME, Update);
}
function Update(e:Event):void {
// уменьшаем прозрачность
bg.alpha+=0.005;
if (bg.alpha>=1){ // если экран полностью затемнился
root.sky.Done(); // у sky вызываем удаление всех объектов
root.gotoAndStop(2); // переходим на второй кард флешки
removeEventListener(Event.ENTER_FRAME, Update); // удаляем обработку события
parent.removeChild(this); // удаляем себя
}
}
}
}
Затемнение сделано уменьшением прозрачности черного прямоугольника во весь экран. В базовый класс sky добавим функцию Done, в которой удалим все созданные и летающие объекты, а так же обработку события ENTER_FRAME:
// закончили
public function Done():void {
removeEventListener(Event.ENTER_FRAME, Update);
for each (var obj:basic_object in all_moving){
removeChild(obj);
}
}
По окончанию игры все.
Пора оживить игру звуковыми эффектами. На самом деле из-за вакуума космоса ничего там услышать нельзя, но нас это не волнует, игра должна звучать! Из звуков нужно подобрать:
1. что-то нейтральное для фоновой музыки
2. столкновение астероидов
3. взрыв астероида
4. падение астероида на землю
5. выстрел лазерной турели
6. выстрел ракетной турели
7. взрыв ракеты
8. взрыв планеты и game-over
Источник звука необходимо правильно позиционировать (звук слева или справа) и откорректировать громкость по удаленности от центра экрана.
Начнем с создания регулятора громкости. Рисуем мувиклип в виде полоски (мувик длиной 100 пикселей с названием "bg"), изображения динамика (преобразуем в мувик и даем название "off" – клик по динамику выключает звук) и ползунка (мувик с названием " track"), размещаем на панели управления.
Создаем класс volume для регулятора. Здесь нам необходимо отловить клик по динамику (off), чтобы выключать/включать звук, клик по фоновой полоске, чтобы передвинуть туда ползунок, и реализовать перетаскивание ползунка. Примерно так:
// регулятор громкости звуков
package main {
import flash.display.Sprite;
import flash.events.*;
import flash.geom.Rectangle;
dynamic public class volume extends Sprite {
static var Volume:Number = 0.7; // громкость звуков
var oldVolume:Number; // громкость звуков до выключения
public function volume() {
// клик по фоновой полоске
this.bg.addEventListener(MouseEvent.CLICK, handleBgClick);
// клик по изображению динамика
this.off.addEventListener(MouseEvent.CLICK, handleOffClick);
this.off.buttonMode=true;
// для перетаскивания ползунка перехватываем нажатие и отпускание кнопки мышки
this.track.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
this.track.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
this.track.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
// Установить ползунок
setVolume(Volume);
}
// Установить громкость
public function setVolume(newVolume:Number):void {
Volume=newVolume;
this.track.x = Volume*100; // предвигаем ползунок
}
// клик по фоновой полоске
function handleBgClick(event:Event):void {
setVolume(this.bg.mouseX/100);
}
// клик по изображению динамика
function handleOffClick(event:Event):void {
if (this.off.currentFrame==1){ // выключаем звук
// запомним текущую громкость
oldVolume = Volume;
// уберем ползунок
this.track.visible=false;
setVolume(0);
// переходим на кадр, где перечеркнутый динамик
this.off.gotoAndStop(2);
} else { // восстанавливаем звук
setVolume(oldVolume);
// восстановим ползунок
this.track.visible=true;
// переходим на кадр, где нормальный динамик
this.off.gotoAndStop(1);
}
}
// начало перетаскивания ползунка
function handleMouseDown(event:Event):void {
var r:Rectangle = new Rectangle(0,0, 100,0);
// начнем перетаскивание
this.track.startDrag(true, r);
}
// при перемещении ползунка меняем громкость
function handleMouseMove(event:Event):void {
setVolume(this.track.x/100);
}
// окончание перетаскивания ползунка
function handleMouseUp(event:Event):void {
// прекратили перетаскивание
this.track.stopDrag();
}
}
}
Здесь мы впервые применили статичную переменную
static var Volume:Number = 0.7; // громкость звуков
Т.к. громкость нужно будет считывать в разных местах: при создании звуковых эффектов (свой класс), при проигрывании фоновой музыки (вставим в panel), то нужен аналог _global, куда можно записать значение переменной и обращаться из любого места. Статичные переменные класса и являются таким аналогом. Получить значение статичной переменной очень просто, нужно обратиться к ней, указав название класса:
trace(volume.Volume);
Теперь займемся непосредственно звуками. Управление звуками в AS3.0 сильно переделали, поэтому сначала знакомимся с документацией. Класс Sound, здесь нам нужно только функция play, видим, что для управления трансформацией звука (громкость и положение левый/правый канал) используется класс SoundTransform. Там есть нужные нам свойства:
pan : Number
The left-to-right panning of the sound, ranging from -1 (full pan left) to 1 (full pan right).
volume : Number
The volume, ranging from 0 (silent) to 1 (full volume).
Вроде бы все просто.
Для того чтобы начать проигрывание какого-то звука его нужно загрузить (внешние файлы мы не используем), значит, ставим в свойствах звуков «Export for ActionScript», класс пусть генерится автоматически, писать там нечего. Убираем галочку с «Export in first frame», нам нужно, чтобы звуки грузились во втором кадре флешки, а не в первом, где у нас прелоадер (первый кадр должен быть максимально легким). Для этого создаем пустой мувиклип, в первом кадре пишем stop(), создаем несколько пустых ключевых кадров и в каждый ставим проигрывание звука, см. скриншот:

Рис. 5
Параметры, и в какой последовательности совершенно не важно, все это проигрываться никогда не будет. Но мы разместим этот мувик на втором кадре флешки, и все звуки, которые в нем присутствуют, будут загружены именно во втором кадре, что нам и нужно.
Теперь создаем класс sounds, который будет отвечать за проигрывание всех звуков в игре.
// Управление пространственными звуками в игре
package main {
import flash.media.SoundTransform;
import main.Vector;
dynamic public class sounds extends Object {
var snd:Object; // хэш со звуками
var stageRadius:Number; // примерный радиус видимой области
public function init(stageWidth:Number, stageHeight:Number) {
// создадим все звуки
snd = new Object();
snd['asteroid_explosion'] = new asteroid_explosion();
snd['asteroids_clash'] = new asteroids_clash();
snd['EarthCrash'] = new EarthCrash();
snd['explode_rocket'] = new explode_rocket();
snd['explode_to_earth'] = new explode_to_earth();
snd['lasersound'] = new lasersound();
snd['launch_rocket'] = new launch_rocket();
// вычислим примерный радиус (в квадрате) видимой области экрана
stageRadius = Math.pow((stageWidth+stageHeight)/4, 2);
}
// Воспроизводит звук с учетом пространства
// dx,dy - смещение источника звука относительно центра экрана
// dv - множитель для громкости
public function Play2DSnd(snd_name:String, dx:Number, dy:Number):void {
var t:SoundTransform, v:Vector;
if (! volume.Volume) return; // запрет звуков
v = new Vector(dx, dy);
t = new SoundTransform();
// Ставим громкость в зависимости от расстояния
// Делаем так, чтобы громкость в углу экрана (stageRadius) была 50% от общей
t.volume = volume.Volume / (1 + v.magnitude2()/stageRadius);
// стерео в зависимости от угла до источника звука
t.pan = 1 - Math.abs(v.getDirection())/90;
// воспроизводим
snd[snd_name].play(0, 0, t);
}
}
}
В панель управления добавляем переменную типа sounds и новую функцию
var game_snd:sounds; // звуки
. . .
// проигрывает указанный звук, местоположение определяется по объекту
public function PlaySnd(snd_name:String, obj:Sprite):void {
var p:Point = obj.localToGlobal(new Point(0,0));
game_snd.Play2DSnd(snd_name, p.x-stage.stageWidth/2, p.y-stage.stageHeight/2);
}
Далее во всех классах, где нужно включать какой-то звук вызываем root.panel.PlaySnd. Например, в лазерной турели в функции Fire в конец добавляем
root.panel.PlaySnd('lasersound', this);
По звукам все.
Теперь осталось сбалансировать игру: сколько давать денег за уничтоженные астероиды, сколько стоят турели, параметры турелей (HP, масса, скорострельность, радиус поражения), как часто влетают новые группы астероидов, какова их средняя скорость и т.п. Параметров много, все их нужно подобрать, чтобы играть было максимально интересно. Менять цифры в коде и каждый раз перекомпилировать игру неудобно, лучше при старте вынести все параметры как текстовые поля, чтобы можно было задать любые значения и тут же сыграть раунд, проверить что получилось.
Рисуем мувиклип, состоящий из черного полупрозрачного прямоугольника, размещаем на втором кадре флешки, там будут динамически созданы все настраиваемые поля, даем имя params и пишем простой класс, основная задача которого возвращать основные игровые константы.
// Настройка игровых констант
package main {
import flash.display.Sprite;
import flash.text.*;
dynamic public class base_params extends Sprite {
// Описание всех коснтант
var all_const:Array = [
{name:'moving_object_MIN_SPEED', txt:'Мин. скорость астероидов', value:2},
{name:'moving_object_MAX_SPEED', txt:'Макс. скорость астероидов', value:7},
. . .
];
var input_fields:Object;
public function base_params() {
var i:Number, y:Number, tf:TextField;
input_fields = new Object();
// динамически создаем все поля
y=1;
for (i=0; i<all_const.length; i++){
// создаем описание параметра
tf = new TextField();
tf.y=y;
tf.width=240;
tf.height=18;
tf.autoSize=TextFieldAutoSize.RIGHT;
tf.selectable=false;
tf.textColor=0xFFFFFF;
tf.text = all_const[i].txt;
addChild(tf);
// создаем редактируемое поле
tf = new TextField();
tf.y=y;
tf.x=240;
tf.width=56;
tf.height=18;
tf.type=TextFieldType.INPUT;
tf.background = tf.border = true;
tf.text = all_const[i].value;
addChild(tf);
// запоминаем ссылку на текстовое поле
input_fields[ all_const[i].name ] = tf;
y+=19;
}
}
// Возвращает значение указанной константы
public function getConst(const_name:String):String {
return input_fields[const_name].text;
}
}
}
Получается вот такая картина:
Рис. 6
Далее во всех классах, вместо констант временно подставляем функции, которые возвращают значение, запрашивая его у класса params. Например, в классе астероидов:
dynamic public class moving_object extends basic_object {
// const MIN_SPEED:Number = 2; // раброс начальной скорости
// const MAX_SPEED:Number = 7;
function get MIN_SPEED():Number {
return Number(root.params.getConst("moving_object_MIN_SPEED"));
}
function get MAX_SPEED():Number {
return Number(root.params.getConst("moving_object_MAX_SPEED"));
}
И раздаем флешку всем своим друзьям с просьбой «протестируй, попробуй поменять параметры»…
Вариант для тестирования и балансировки: перед стартом игры можно изменить игровые константы подбирая оптимальные значения. На панели управления добавлена статистика и регулятор громкости. В игру добавлены еще два вида турелей.
По окончании тестирования прописываем полученные константы вместо класса params.
Игра написана. Окончательный вариант можно посмотреть здесь.
Скачать исходные коды с описанием процесса создания: AsteroidStormSourceCode.zip (3.4 Мб)
Что в итоге? Можно смело утверждать, что ActionScript 3.0 это большой шаг в развитии Flash. В целом впечатления положительные, несмотря на то, что это пока только альфа версия и присутствуют некоторые баги.
На примере созданной игры при большом игровом поле 800x440 и большом количестве одновременно отображаемых и обрабатываемых объектов (более 200), Flash показывает приличную производительность, что было нереально в предыдущих версиях. Полный переход к объектно-ориентированному программированию упрощает написание скриптов, они становятся более структурированные и понятны.
Полагаю, в ближайшем будущем следует ожидать появления больших и серьезных игр, полностью написанных на Flash, таких как онлайн RPG www.timezero.ru
Данную статью и исходные коды разрешаю (и даже настаиваю :) совершенно свободно распространять в интернете.
Июль 2006г.
Алексей (Merlin)
Страницы: 1 2 3 4 [5]