Game development

Архивы по месяцам Сентябрь, 2012

Avoider Game tutorial на русском. Часть 3: Game Over

Содержание серии «Avoider Game Tutorial»:

  1. Avoider Game tutorial на русском. Часть 1: Введение
  2. Avoider Game tutorial на русском. Часть 2: Несколько врагов
  3. Avoider Game tutorial на русском. Часть 3: Game Over
  4. Avoider Game tutorial на русском. Часть 4: Меню и кнопки
 (Оригинал на английском языке здесь)
 

Введение

В этой части урока я покажу Вам, как добавить экран «game over» при проигрыше. Нажмите на картинку, чтобы увидеть, что мы получим к концу урока:

Avoider Game tutorial (Game over)

Как я уже говорил в первой части этого урока, мы будем использовать только один кадр на таймлайне. Это означает, что мы не можем просто вставить квадрат с надписью «Game Over» на кадре 2, а затем выполнить команду nextFrame () (след. кадр), когда наш герой умирает. Что же мы можем сделать?

Ответ довольно прост: мы просто добавляем большой квадрат с надписью «Game Over» поверх всего остального, когда нам это нужно. Работает как занавес в театре.

Создание Overlay

Приступим к работе. Если вы проходили предыдущие части урока, сделайте резервную копию папки с игрой и откройте FLA файл. В противном случае, скачайте ZIP файл из части 2 , распакуйте его куда-нибудь, и откройте AvoiderGame-MJW.fla.

Создайте новый мувиклип (Insert> New Symbol, moiveClip) назовем его GameOverText, и нарисуйте что-то, что даст понять игроку, что он проиграл. Вот мой вариант:

Это шрифт Arial Black, цвет темно-красный: чтобы выделялся на сером фоне. Обратите внимание, что я центрировал текст относительно точки регистрации — см. Часть 1 если забыли, как это сделать.

Когда вы создаете текстовое поле, Flash может выставить его динамичным — то есть, с учетом под изменение этого текста с помощью кода. Пока мы не добавим счет (очки), нам не будет нужна эта функция, поэтому щелкните на тексте и в панели Properties, убедитесь, что в выпадающем списке выбрано static text, а не dynamic или input text. В противном случае вы получите такую вот ошибку:

1046: Type was not found or was not a compile-time constant: TextField.

Теперь нам нужно сделать этот символ доступным для нашего кода. Найдите его в библиотеке, щелкните правой кнопкой мыши и выберите пункт Properties. В панели Linkage, ставим галочку «Export for ActionScript«, обратите внимание на имя класса (должно быть GameOverText) и нажмите OK.

Откройте класс документа, либо непосредственно открыв AS файл в папке Classes (мой назван AvoiderGame.as), либо нажав на иконку карандаша рядом с полем document class в панели Properties. Найдите функцию OnTick, а затем часть кода, определяющую, что произойдет, если противник столкнется с игроком:

46
47
48
49
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
}

На данный момент, код просто останавливает всю игру, если игрок сталкивается с врагом. Выглядит, как будто игра просто зависла. Давайте сделаем, чтобы появлялся текст «Game Over» :

46
47
48
49
50
51
52
53
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
    var gameOverText:GameOverText = new GameOverText();
    gameOverText.x = 200;
    gameOverText.y = 150;
    addChild( gameOverText );
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
	var gameOverText:GameOverText = new GameOverText();
	gameOverText.x = 200;
	gameOverText.y = 150;
	addChild( gameOverText );
}

Такой код должен быть знаком вам после прочтения частей 1 и 2 . Помните, что вы можете изменить значения 200 и 150, чтобы изменить, где на экране будет размещен текст «Game Over».

Сохраните все и запустите (Control> Test Movie), и когда вы «встретите» врага, вы должны увидеть что-то вроде этого:

Отлично.

Доводим до ума

Наложения текста «конец игры» вполне может хватить — так было в Sonic — но как насчет полноценной заставки «game over» на весь экран?

Мы можем повторить наши действие, но сделать символ, покрывающий весь экран. Создайте новый мувиклип, на этот раз назовите его GameOverScreen, и нарисуйте внутри большой черный прямоугольник, любого размера. Кликните на прямоугольник, и измените его размер в соответствии с размерами вашей сцены. (Вы можете изменить размер прямоугольника, изменяя его W и H — ширину и высоту — в панели Properties. Посмотреть размеры сцены можно, нажав кнопку Modify> Document).

На этот раз мы выставим точку регистрации символа в верхнем левом углу. Почему? Потому что тогда мы сможем просто добавить мувиклип в точку (0, 0) и квадрат совпадет со сценой. Вы можете изменить точку регистрации, используя панель Align:

Альтернативно, вы можете изменить свойства X и Y прямоугольника на 0,0, как вы изменили Ширину и Высоту.

Давайте добавим текст:

Сойдет. Когда будете готовы, подготовьте GameOverScreen для работы с ActionScript, так же, как мы это делали с GameOverText.

Откройте документ класса снова, и найдите код, который мы изменяли ранее:

46
47
48
49
50
51
52
53
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
    var gameOverText:GameOverText = new GameOverText();
    gameOverText.x = 200;
    gameOverText.y = 150;
    addChild( gameOverText );
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
	var gameOverText:GameOverText = new GameOverText();
	gameOverText.x = 200;
	gameOverText.y = 150;
	addChild( gameOverText );
}

Довольно просто внедрить наш новый вариант. Попытайтесь сделать это самостоятельно, если хотите. Вот мой код:

46
47
48
49
50
51
52
53
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
    var gameOverScreen:GameOverScreen = new GameOverScreen();
    gameOverScreen.x = 0;
    gameOverScreen.y = 0;
    addChild( gameOverScreen );
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
	var gameOverScreen:GameOverScreen = new GameOverScreen();
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );
}

Обратите внимание, что я изменил координаты х и у на нули. Если вы сохраните и запустите игру и коснетесь врага, вы увидите что-то такое:

Но так продолжать нельзя. Мы должны поместить игру в свой собственный отдельный символ. Таким образом, вместо того, чтобы бросать занавес перед сценой и оставлять всех актеров стоять позади него, мы можем поместить всех актеров в стеклянный коробок, и убирать этот коробок со сцены, когда мы будем опускать занавес. Это сделает сцену гораздо чище, и за актерами будет легче уследить. Единственный вопрос, не зашел ли я слишком далеко с этой аналогией?

Добавление игрового экрана (game screen)

Позвольте мне все разъяснить. Пока что, наша игра работает следующим образом:

Класс документа контролирует почти все. Если мы будем так продолжать, то скоро, когда мы захотим добавить название, главное меню, несколько уровней, экран выбора персонажа, и так далее, то класс документа будет очень и очень раздутым.

Давайте разделим все по группам. Вот что я предлагаю:

Гораздо чище! Обязанности класса документа — управление «экранами» (play, game over). Каждый отдельный экран управляет своим содержимым. Позже мы могли бы разбить это еще и на подгруппы, но это пока не нужно.

Как мы можем осуществить это? Вы, вероятно, не будет удивлены, если я скажу, что мы должны сделать новый мувиклип — на этот раз, назовем его PlayScreen.

Этот новый экран будет делать примерно то же самое, что и вся наша игра делала в конце предыдущей части. То есть, он будет содержать аватар, и он будет генерировать врагов и заставить их двигаться внутри него. Одним из важных отличий между использованием Movie Clip в роли контейнера, в сравнении с документом (как мы делали раньше) в том, что документ имеет фон по умолчанию (наш серый), а Movie Clip — нет, мы должны добавить его сами.

Давайте же сделаем это! Начните править ваш  PlayScreen и нарисуйте прямоугольник залитый каким-то приятным цветом. Так же, как и для GameOverScreen, сделайте прямоугольник такого же размера, как сцена, и установите его точку регистрации в левом верхнем углу. Я сделал мой светло-голубым, чтобы выделялся на фоне редактора:

Мы не ограничены в использовании одной только заливки на этот раз. К примеру, можно усыпать фон своими инициалами:

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

Поговорим о коде. Я уже говорил, что этот экран будет делать то же самое, что наша игра уже делала, так что вы можете себе представить, что он будет требовать почти такой же код. Мы могли бы переписать этот код с нуля. Мы могли бы скопировать и вставить его, переименовав что-то по мере необходимости. Или … мы могли бы воспользоваться удобствами объектно-ориентированного проектирования.

Помните, что вся игра, весь документ, по-сути тоже Movie Clip. Когда мы указали класс документа, это то же самое, что выбрать мувиклип из библиотеки и указать ему класс в диалоге «Export for ActionScript». Таким образом, нет никакой причины, по которой мы не могли бы назначить нашему PlayScreen тот класс, который, до сих пор, был классом документа.

Щелкните правой кнопкой мыши на PlayScreen в библиотеке, выберите пункт Properties и поставьте галочку напротив «Export for ActionScript«. На этот раз, вместо того чтобы оставить имя класса по умолчанию, введите имя вашего класса документа. Нажмите кнопку ОК.

Ах, да. Во-первых, мы должны сделать новый класс для документа, так как два разных объекта не могут использовать один и тот же класс. Жмем отмена, чтобы закрыть окно.

Новый класс документа

Нажмите File> New и выберите файл ActionScript. Введите (уже знакомый вам) код:

1
2
3
4
5
6
7
8
9
10
11
package 
{
    import flash.display.MovieClip;
    public class DocumentClass extends MovieClip 
    {
        public function DocumentClass() 
        {
 
        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public function DocumentClass() 
		{

		}
	}
}

Сохраните файл в папке Classes, под именем DocumentClass.as. (Назначение этого файла теперь очевидно!) Теперь, вернувшись в FLA, измените класс документа на DocumentClass.

Проверьте, все ли правильно, нажав значок карандаша — если все ОК, то должен открыться AS файл, который вы только что создали.

Теперь вы можете установить PlayScreen-у класс AvoiderGame. Действуйте!

Если вы сохраните и запустите игру сейчас, ничего не произойдет — это потому, что класс документа не добавляет на сцену игровой экран (PlayScreen). Вернитесь к DocumentClass.as и измените его следующим образом (строки 6, 10 и 11):

1
2
3
4
5
67
8
9
101112
13
14
package 
{
    import flash.display.MovieClip;
    public class DocumentClass extends MovieClip 
    {
        public var playScreen:AvoiderGame; 
        public function DocumentClass() 
        {
            playScreen = new AvoiderGame();            addChild( playScreen );        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public var playScreen:AvoiderGame;

		public function DocumentClass() 
		{
			playScreen = new AvoiderGame();
			addChild( playScreen );
		}
	}
}

Похожий кусок кода в AvoiderGame.as мы использовали для создания нового аватара, но здесь мы используем его для создания всей игры. Сохраните и запустите игру:

Супер.

Каждому классу — свое назначение

На самом деле мы еще не закончили. Вот что я хочу получить:

А вот что имеем мы:

У нас все та же проблема что и прежде — раздутый класс с кучей кода — только теперь это не класс документа. Мы хотим, чтобы класс документа вызывал экран GameOver, когда игрок умирает. Как мы можем это сделать?

Еще в первой части , когда мы добавляли игре таймер, я сказал:

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

Для этого мы использовали строчку:

1
gameTimer.addEventListener( TimerEvent.TIMER, onTick );
gameTimer.addEventListener( TimerEvent.TIMER, onTick );

(Напомню — эта строка запускала функцию «OnTick» всякий раз, когда таймер «тикал»).

Теперь я хочу добавить аналогичный слушатель события на игровом экране(PlayScreen) (строка 11):

8
9
10
1112
13
public function DocumentClass() 
{
    playScreen = new AvoiderGame();
    playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );    addChild( playScreen );
}
public function DocumentClass() 
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	addChild( playScreen );
}

«AvatarEvent.DEAD» не является встроенным в флэш событием, очевидно, но мы вернемся к этому позже. Сейчас давайте напишем функцию onAvatarDeath в классе документа:

15
16
17
18
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
 
}
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{

}

Добавим внутрь функции некий код:

1
2
3
4
5
6
7
8
9
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
    var gameOverScreen:GameOverScreen = new GameOverScreen();
    gameOverScreen.x = 0;
    gameOverScreen.y = 0;
    addChild( gameOverScreen );
 
    playScreen = null;
}
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
	var gameOverScreen:GameOverScreen = new GameOverScreen();
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );

	playScreen = null;
}

Строки 17-20 скопированы прямо из AvoiderGame.as. Строка 22 использует новое ключевое слово: null (нулевой, несуществующий). Присваиваю объекту значение null, вы по-сути «сбрасываете» его. Все слушатели событий удаляются, все содержащиеся в нем объекты будут удалены, все функции перестают существовать. После установки на null, PlayScreen находится в том же состоянии, что и до строки playScreen = new PlayScreen(); — это значит, что в следующий раз, когда мы захотим его использовать, нам нужно будет выполнить эту строку еще раз. Обнуляя PlayScreen, мы также уберем его с экрана.

Теперь нам нужно как-то вызвать это событие.

Большое событие

Вы помните, что наши классы Avatar, Enemy и AvoiderGame расширяют функционал (extend) MovieClip, так что они могут делать все, что может MovieClip? Вот, TimerEvent расширяет класс Events (события) таким же образом. Это означает, что мы можем сделать наш собственный вид событий (AvatarEvent) используя слово extend.

Итак, давайте попробуем. Точно так же, как мы расширяли MovieClip ранее. Создайте новый AS файл, AvatarEvent.as:

1
2
3
4
5
6
7
8
9
10
11
package  
{
    import flash.events.Event;
    public class AvatarEvent extends Event 
    {
        public function AvatarEvent()
        {
 
        }
    }
}
package  
{
	import flash.events.Event;
	public class AvatarEvent extends Event 
	{
		public function AvatarEvent()
		{

		}
	}
}

Все выглядит правильно, но на самом деле, есть одна проблема. При создании события любого вида, оно ожидает, что вы передаете тип события. Точно так же, как и наш класс «Enemy» ожидает, что вы передадите ему координаты х и у.

Мы не предусмотрели способ определения типа события в нашем коде, так что давайте это исправим:

1
2
3
4
5
6
7
8
9
10
11
package  
{
    import flash.events.Event;
    public class AvatarEvent extends Event 
    {
        public function AvatarEvent( type:String )
        {
 
        }
    }
}
package  
{
	import flash.events.Event;
	public class AvatarEvent extends Event 
	{
		public function AvatarEvent( type:String )
		{

		}
	}
}

Теперь мы можем добавить собственный тип события. Мы можем использовать приставку «public const«. Это как «public var«, только const не может быть измен после того, как вы нажали Test Movie:

1
2
3
4
5
6
7
8
9
10
11
12
13
package  
{
    import flash.events.Event;
    public class AvatarEvent extends Event 
    {
        public static const DEAD:String = "dead";
 
        public function AvatarEvent( type:String )
        {
 
        }
    }
}
package  
{
	import flash.events.Event;
	public class AvatarEvent extends Event 
	{
		public static const DEAD:String = "dead";

		public function AvatarEvent( type:String )
		{

		}
	}
}

Отлично, теперь мы сможем написать:

1
new AvatarEvent( AvatarEvent.DEAD )
new AvatarEvent( AvatarEvent.DEAD )

Все почти готово, но мы должны выяснить, что дальше делать с этим типом «DEAD», который передается в new AvatarEvent. Ну, на самом деле … нам все-равно! Класс Event уже знает, что делать с типом, который ему передали, и поэтому нам просто необходимо «одолжить» его код.

Мы можем сделать это с помощью функции super(), вот так:

1
2
3
4
5
6
7
8
9
10
11
12
13
package  
{
    import flash.events.Event;
    public class AvatarEvent extends Event 
    {
        public static const DEAD:String = "dead";
 
        public function AvatarEvent( type:String )
        {
            super( type );
        }
    }
}
package  
{
	import flash.events.Event;
	public class AvatarEvent extends Event 
	{
		public static const DEAD:String = "dead";

		public function AvatarEvent( type:String )
		{
			super( type );
		}
	}
}

super( type ) выполняет код внутри функции конструктора класса Event и передает тип(type) туда же. Это значит, что мы позволяем существующему коду (которого мы не видим) внутри класса Event со всем разобраться. Меня это вполне устраивает :)

(Мы столкнемся с super() более подробно в Части 5 . Тем временем, не стесняйтесь прочитать пост MJW про extends, override, и super .)

Теперь, когда у нас есть слушатель события и класс события, остается лишь спровоцировать событие, когда аватар умирает (то есть, когда он коснется врага). Вернитесь к AvoiderGame.as и измените это:

46
47
48
49
50
51
52
53
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
    var gameOverScreen:GameOverScreen = new GameOverScreen();
    gameOverScreen.x = 0;
    gameOverScreen.y = 0;
    addChild( gameOverScreen );
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
	var gameOverScreen:GameOverScreen = new GameOverScreen();
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );
}

на это:

46
47
48
49
50
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
    dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
	dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
}

Строка 49 провоцирует AvatarEvent типа DEAD. Вот и все. Я удалил весь код о GameOverScreen, потому что мы теперь управляем им в документе класса.

Сохраните и запустите. Ура, все работает как надо!

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

Avoider game tutorial перевод от Trost.