Game development

Записи в категории Avoider game уроки

Avoider Game tutorial на русском. Часть 4: Меню и кнопки

Содержание серии «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: Меню и кнопки
 (Оригинал на английском языке здесь)
 

Введение

В этой части урока avoider game tutorial AS3  мы добавим кнопки, чтобы позволить игроку перезапустить игру, если он проиграл, и добавим главное меню.

Кликните картинку ниже, чтобы увидеть, что мы получим:

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

Попробуй еще раз

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

Создайте новый символ типа Button (Insert> New Symbol и выберите Button). Назовите его RestartButton, и нажмите кнопку OK.

Вы автоматически войдете в режим редактирования вашей кнопки. Нарисуйте кнопку, на которую хочется нажать и переиграть. Вот моя:

Теперь взгляните на таймлайн в нижней части экрана:

Кнопка вместо кадров содержит четыре отдельных состояния:

  • Up — Обычный вид кнопки.
  • Over — Как выглядит кнопка при наведении на нее курсора.
  • Down — Внешний вид кнопки при нажатии.
  • Hit — область кнопки, которая является «чувствительной» к курсору (подробнее об этом чуть позже).

Затемненный серый прямоугольник с точкой в колонке UP означает, что мы нарисовали изображение для этого состояния. Давайте нарисуем вид кнопки для других состояний. Щелкните правой кнопкой мыши по каждой из пустых клеточек, и выберите Insert Keyframe:

 

Ок, отлично. Нажмите на сером прямоугольнике с точкой в нем (далее — ключевой кадр) в колонке Over. Изображение из состояния Up должно было скопироваться в колонку over, если его там нет, вы можете скопировать и вставить его из одного столбца в другой. Измените изображение кнопки подходящим способом — я просто инвертировал цвета моей кнопки:

Теперь измените кадр down:

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

Во всяком случае, я не хочу оставлять на кнопкt некликабельные участки, так что я сделаю фигуру такой же формы, как и кнопка:

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

Теперь нам нужно добавить эту кнопку на игровой на экран. Сохраните все, что вы сделали, затем дважды щелкните GameOverScreen в библиотеке.

Найдите RestartButton в библиотеке и перетащите её в экран(мувиклип) Game Over.

(Ой. я забыл, что черных границ кнопки не будет видно на черном фоне. Ну да ладно.)

Помните еще в первой части этого урока, когда я говорил о экземплярах и классах? Вот, кнопка на экране gameOver — экземпляр класса RestartButton. Сейчас этот экземпляр в не имеет имени экземпляра. Если мы хотим, получить к нему доступ кодом (а мы хотим), мы должны дать кнопке instance name (имя экземпляра).

Кликните на кнопку, которую вы только что добавили, и посмотрите на панели свойств:

Видите поле instance name? Введите restartButton (как обычно, будьте осторожны с заглавными буквами!)

Ладно, теперь мы можем написать код, чтобы заставить игру делать что-то, когда кнопка нажата.

Эй! … Слушай!

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

Так как кнопка существует в GameOverScreen, соответствующий код пишем в GameOverScreen класс. Но … мы же не создавали такого класса. Так давайте сделаем это сейчас! Создайте новый AS файл и введите следующее:

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

		}
	}
}

Сохраните GameOverScreen.as в папке классов (Classes).

Надеюсь, этот код выглядит знакомо. Сохраните все и протестируйте игру. Она выдаст вам ошибку:

1
GameOverScreen.as, Line 2 1046:  Type was not found or was not a compile-time constant: SimpleButton.
GameOverScreen.as, Line 2 1046:  Type was not found or was not a compile-time constant: SimpleButton.

или на русском:

1
 GameOverScreen.as, строка 2 1046: Тип не найден или не является константой во время компиляции: SimpleButton.
 GameOverScreen.as, строка 2 1046: Тип не найден или не является константой во время компиляции: SimpleButton.

Что? Флеш обнаружил, что есть кнопка на GameOverScreen, но говорит: «Что это такое? Я никогда не слышал об этой «кнопке». Флеш иногда бывает довольно глупым.

Мы должны указать флешу, что такое button. Вернитесь к GameOverScreen.as и измените его, чтобы импортировать определения класса SimpleButton (строка 4):

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

	public class GameOverScreen extends MovieClip 
	{
		public function GameOverScreen() 
		{

		}
	}
}

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

Так как этот код похож на то, что мы добавляли в предыдущих частях, я просто вставлю все сразу и объясню:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 
{
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.events.MouseEvent;
 
    public class GameOverScreen extends MovieClip 
    {
        public function GameOverScreen() 
        {
            restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
        }
 
        public function onClickRestart( mouseEvent:MouseEvent ):void 
        {
 
        }
    }
}
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.events.MouseEvent;

	public class GameOverScreen extends MovieClip 
	{
		public function GameOverScreen() 
		{
			restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
		}

		public function onClickRestart( mouseEvent:MouseEvent ):void 
		{

		}
	}
}
  • public function onClickRestart( mouseEvent:MouseEvent ) — это функция, которую мы хотим запускать при нажатии кнопки перезапуска. Как и прежде, мы передаем информацию о событии (мыши), потому что так положено.
  • restartButton.addEventListener (MouseEvent.CLICK, onClickRestart); — Это добавляет слушатель события к кнопке и говорит ему, что, если нажата кнопка (да-да, Flash действительно требует от вас, писать CLICK большими буквами), то должна сработать функция onClickRestart.
  • import flash.events.MouseEvent; — Без этой строки, Flash не знает значение слова CLICK.

Чтобы запустить игру снова, мы должны удалить экран gameOver и перезагрузить игровой экран. В части 3 мы изменили структуру проекта так, что класс документа обрабатывает все манипуляции экранами, по-этому будем править его. Откройте DocumentClass.as и добавьте новую функцию:

24
25
26
27
28
29
30
31
public function restartGame():void
{
    playScreen = new AvoiderGame();
    playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
    addChild( playScreen );
 
    gameOverScreen = null;
}
public function restartGame():void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	addChild( playScreen );

	gameOverScreen = null;
}

Строки 26 и 27 создают игровой экран в точности так же, как это делал конструктор. Когда игрок попадает на экран GameOver, строкой playScreen = null мы удалаем слушатели событий и сбрашиваем х и у координаты игрового экрана, что я и объяснял в Части 3.

Строка 30 уберет из памяти gameOverScreen, но есть одна проблема. GameOverScreen определен только в функции onAvatarDeath(), так что он не доступен для функции restartGame(). Давайте сделаем gameOverScreen доступным для всего класса документа, как и playScreen:

1
2
3
4
5
6
7
package 
{
    import flash.display.MovieClip;
    public class DocumentClass extends MovieClip 
    {
        public var playScreen:AvoiderGame;
        public var gameOverScreen:GameOverScreen;
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;

Теперь нам нужно удалитьvar из var gameOverScreen в функции onAvatarDeath, так как мы уже определили gameOverScreen ранее:

16
17
18
19
20
21
22
23
24
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
    gameOverScreen = new GameOverScreen();
    gameOverScreen.x = 0;
    gameOverScreen.y = 0;
    addChild( gameOverScreen );
 
    playScreen = null;
}
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
	gameOverScreen = new GameOverScreen();
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );

	playScreen = null;
}

Вот как выглядит мой класс документа на данный момент:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package 
{
    import flash.display.MovieClip;
    public class DocumentClass extends MovieClip 
    {
        public var playScreen:AvoiderGame;
        public var gameOverScreen:GameOverScreen;
 
        public function DocumentClass() 
        {
            playScreen = new AvoiderGame();
            playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
            playScreen.x = 0;
            playScreen.y = 0;
            addChild( playScreen );
        }
 
        public function onAvatarDeath( avatarEvent:AvatarEvent ):void
        {
            gameOverScreen = new GameOverScreen();
            gameOverScreen.x = 0;
            gameOverScreen.y = 0;
            addChild( gameOverScreen );
 
            playScreen = null;
        }
 
        public function restartGame():void
        {
            playScreen = new AvoiderGame();
            playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
            playScreen.x = 0;
            playScreen.y = 0;
            addChild( playScreen );
 
            gameOverScreen = null;
        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;

		public function DocumentClass() 
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
		}

		public function onAvatarDeath( avatarEvent:AvatarEvent ):void
		{
			gameOverScreen = new GameOverScreen();
			gameOverScreen.x = 0;
			gameOverScreen.y = 0;
			addChild( gameOverScreen );

			playScreen = null;
		}

		public function restartGame():void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );

			gameOverScreen = null;
		}
	}
}

Я добавил строки 13 и 14, чтобы расположить игровой экран в точке (0,0), так же, как и gameOverScreen, и дублировал эти строчки в restartGame (). В следующей части, я упрощу код так, что нам не придется писать один и тот же код дважды.

ПЕРЕЗАГРУЗКА!

Теперь у нас есть функция в gameOverScreen, которая запускается при нажатии кнопки «рестарт», и функция в классе документа, которая выполняет перезапуск игры. Возникает желание соединить их друг с другом непосредственно, но, как я говорил в предыдущей части , это приводит к проблемам в будущем. Мы будем использовать тот же подход, который мы использовали со смертью аватара: создание нового пользовательского события.

Вот что произойдет:

  • Игрок нажимает кнопку «рестарт»
  • Кнопка отсылает MouseEvent типа CLICK
  • MouseEvent.CLICK поймает(услышит) слушатель события в gameOverScreen и запустит onClickRestart ()
  • onClickRestart (), в свою очередь, отсылает наше новое пользовательское событие — назовем его NavigationEvent — типа RESTART
  • NavigationEvent.RESTART поймает слушатель события в классе документа, и запустит новую функцию: onRequestRestart ()
  • onRequestRestart () запустит функцию restartGame ()

Тьфу! Много букв, но все на самом деле довольно просто. Это также дает нам возможность встроить рестарт игры разными способами: нажатием «R» или по прошествии некоторого времени. В любом случае, нам нужно просто отослать событие NavigationEvent.RESTART, и остальное уже готово.

Наше новое пользовательское событие выглядят так же, как и AvatarEvent:

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

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

(Сохраните файл как NavigationEvent.as в папке Classes). Создать событие также просто: (этот кусок кода пишем в GameOverScreen):

14
15
16
17
public function onClickRestart( mouseEvent:MouseEvent ):void
{
    dispatchEvent( new NavigationEvent( NavigationEvent.RESTART ) );
}
public function onClickRestart( mouseEvent:MouseEvent ):void
{
	dispatchEvent( new NavigationEvent( NavigationEvent.RESTART ) );
}

А учитывая, что это ваш третий слушатель события, не должно возникнуть проблем с добавлением его в класс документа (строка 21):

18
19
20
21
22
23
24
25
26
27
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
    gameOverScreen = new GameOverScreen();
    gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart );
    gameOverScreen.x = 0;
    gameOverScreen.y = 0;
    addChild( gameOverScreen );
 
    playScreen = null;
}
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
	gameOverScreen = new GameOverScreen();
	gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart );
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );

	playScreen = null;
}

Не забудьте создать функцию onRequestRestart () внутри класса документа, и указать ей, что делать:

1
2
3
4
public function onRequestRestart( navigationEvent:NavigationEvent ):void
{
    restartGame();
}
public function onRequestRestart( navigationEvent:NavigationEvent ):void
{
	restartGame();
}

Теперь, после того, как мы нажали кнопку «рестарт» (и после длинной цепочки событий), функция restartGame() класса документа будет выполнена, что в свою очередь удалит экран gameOver и загрузит игровой экран. Сохраняем и тестируем. Наконец, игрок может перезапустить игру не обновляя страницы!

Главное меню

Добавить меню в начало игры теперь будет легко. Код практически такой же, как в gameOverScreen. Давайте рассмотрим следующие действия очень быстро.

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

Вернитесь в файл FLA и найдите GameOverScreen в библиотеке. Щелкните по нему правой кнопкой мыши и выберите Duplicate(Дублировать). Нужно будет ввести имя для дубликата. Я написал имя MenuScreen, оставив тип Movie Clip. Дважды щелкните на новом MenuScreen в библиотеке, чтобы войти в режим редактирования, и удаление кнопку рестарт. Сделайте все необходимые изменения в интерфейсе. Вот мой результат:

Истинное произведение искусства.

Теперь о кнопке. Опять же, вы можете создать с нуля, как мы делали раньше (помните, она должен быть типа Button!), или вы можете дублировать и править существующую RestartButton. Полностью ваш выбор. В любом случае, назовите новую кнопку StartButton и отредактируйте ее соответствующим образом. Вот моя:

Я не лентяй, я поддерживаю постоянный стиль на протяжении всего моего проекта. Добавьте новую кнопку на MenuScreen и задайте имя экземпляра «startButton».

Теперь давайте писать код. Щелкните правой кнопкой мыши на MenuScreen и выберите Properties. Поставьте галочку Export for ActionScript, введите MenuScreen в поле Class и нажмите кнопку OK. Как обычно, вас предупредят:

… Поэтому давайте создадим класс для MenuScreen.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 
{
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.events.MouseEvent;
 
    public class MenuScreen extends MovieClip 
    {
        public function MenuScreen() 
        {
            startButton.addEventListener( MouseEvent.CLICK, onClickStart );
        }
 
        public function onClickStart( event:MouseEvent ):void
        {
            dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
        }
    }
}
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.events.MouseEvent;

	public class MenuScreen extends MovieClip 
	{
		public function MenuScreen() 
		{
			startButton.addEventListener( MouseEvent.CLICK, onClickStart );
		}

		public function onClickStart( event:MouseEvent ):void
		{
			dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
		}
	}
}

Сохраните файл в папке классы, «MenuScreen.as».

Я даже не собираюсь объяснить большую часть этого кода, потому что это почти тот же код, что и в GameOverScreen.as. Заметим, однако, что в строке 16 я отправляю NavigationEvent типа START, — но сейчас класс NavigationEvent имеет только тип RESTART. Давайте добавим новый тип в NavigationEvent.as прямо сейчас:

1
2
3
4
5
6
7
package  
{
    import flash.events.Event;
    public class NavigationEvent extends Event 
    {
        public static const RESTART:String = "restart";
        public static const START:String = "start";
package  
{
	import flash.events.Event;
	public class NavigationEvent extends Event 
	{
		public static const RESTART:String = "restart";
		public static const START:String = "start";

Легко.

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

6
7
8
9
10
11
12
13
14
15
16
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;
 
public function DocumentClass() 
{
    playScreen = new AvoiderGame();
    playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
    playScreen.x = 0;
    playScreen.y = 0;
    addChild( playScreen );
}
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;

public function DocumentClass() 
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
}

на это:

6
7
8
9
10
11
12
13
14
15
16
17
public var menuScreen:MenuScreen;
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;
 
public function DocumentClass() 
{
    menuScreen = new MenuScreen();
    menuScreen.addEventListener( NavigationEvent.START, onRequestStart );
    menuScreen.x = 0;
    menuScreen.y = 0;
    addChild( menuScreen );
}
public var menuScreen:MenuScreen;
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;

public function DocumentClass() 
{
	menuScreen = new MenuScreen();
	menuScreen.addEventListener( NavigationEvent.START, onRequestStart );
	menuScreen.x = 0;
	menuScreen.y = 0;
	addChild( menuScreen );
}

Обратите внимание, что я добавил новый menuScreen, и я больше не создаю игровой экран в конструкторе DocumentClass().

Теперь нам нужно написать функцию onRequestStart () (строка 13, выше), и мы закончим:

35
36
37
38
39
40
41
42
43
44
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
    playScreen = new AvoiderGame();
    playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
    playScreen.x = 0;
    playScreen.y = 0;
    addChild( playScreen );
 
    menuScreen = null;
}
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );

	menuScreen = null;
}

Сохраните все файлы и тестируйте игру и … успех!

Заключение

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

Как всегда, вы можете скачать архив со всеми наработками здесь, и вы можете подписаться на мою рассылку RSS (чтобы получить извещение, как только появится новая часть) здесь .

В части 5 я покажу вам, как добавить часы и очки(счет). А пока, почему бы вам не попробовать добавить кнопку на экране GameOver, которая отправляет вас обратно в главное меню?

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

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.

Avoider Game tutorial на русском. Часть 2: Несколько врагов

Содержание серии «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: Меню и кнопки
 (Оригинал на английском языке здесь)
 

Введение

В этой части урока avoider game tutorial я покажу Вам, как добавить несколько врагов в вашу игру. Посмотрите, что должно выйти к концу этой части, нажав на картинку ниже.

Avoider game tutorial part 2

Подготовка

Если вы не читали первую часть, скачайте файлы первой части отсюда и распакуйте их на жесткий диск.

Если же вы проходили первый урок, я рекомендую сделать резервную копию существующих файлов игры, скопировав основную папку и переименовав копию, например в AvoiderGame-part1. Таким образом, вы сможете взглянуть назад на старые версии и увидеть, на сколько вы продвинулись!

В любом случае, откройте файл FLA. Откройте файл класса документа (мой называется AvoiderGame.as) и файл Enemy.as. Приступим.

Контролируем врагов

Взгляните еще раз на конструктор врага (он находится в файле Enemy.as):

6
7
8
9
10
        public function Enemy() 
        {
            x = 100;
            y = -15;
        }
		public function Enemy() 
		{
			x = 100;
			y = -15;
		}

Этот кусочек кода означает, что каждый экземпляр класса Enemy будет появляться в точке (100, -15). Вряд-ли так у нас выйдет интересная игра «Avoider». Также бестолково будет создавать врагов, которые двигаются вверх — если они появятся над экраном, они никогда не попадут в кадр!

Измените функцию конструктора следующим образом:

6
7
8
9
10
public function Enemy( startX:Number, startY:Number ) 
{
    x = startX;
    y = startY;
}
public function Enemy( startX:Number, startY:Number ) 
{
	x = startX;
	y = startY;
}

Мы добавили параметры к функции конструктора. Теперь, когда мы создаем экземпляр Enemy в классе документа, мы можем передать значения X и Y, чтобы указать, где он должен появиться.

Переключитесь на класс документа (AvoiderGame.as) и измените код, который создает нового врага с:

15
enemy = new Enemy();
enemy = new Enemy();

на:

15
enemy = new Enemy( 100, -15 );
enemy = new Enemy( 100, -15 );

Если вы запустите игру (Control> Test Movie, помните?), вы увидите, что враг появляется в точке (100, -15) — как мы и ожидали. Мы передали значения 100 и -30 конструктору класса Enemy, который сначала установил startX = 100 и startY = -15, а затем установил х = startX и y = startY. Теперь, вы можете изменять значение в «Enemy (100, -15)», чтобы создавать врагов в разных местах. Попробуйте.

Безобразное клонирование

Как добавить больше, чем одного врага? Позвольте мне показать вам пример того, что не нужно делать. Это самый простой метод: мы буквально «копируем и вставляем» врага, пока не получим столько, сколько нам нужно.

Сперва, определить еще несколько врагов, как public переменные в классе документа (строки 10-12):

7
8
9
10111213
14
public class AvoiderGame extends MovieClip 
{
    public var enemy:Enemy;
    public var eric:Enemy;    public var ernie:Enemy;    public var emily:Enemy;    public var avatar:Avatar;
    public var gameTimer:Timer;
public class AvoiderGame extends MovieClip 
{
	public var enemy:Enemy;
	public var eric:Enemy;
	public var ernie:Enemy;
	public var emily:Enemy;
	public var avatar:Avatar;
	public var gameTimer:Timer;

Теперь установите им разные стартовые позиции в конструкторе класса документа, и добавьте их (с помощью AddChild) в игру (строки 20-25):

16
17
18
19
202122232425
public function AvoiderGame() 
{
    enemy = new Enemy( 100, -15 );
    addChild( enemy );
    eric = new Enemy( 160, -120 );    addChild( eric );    ernie = new Enemy( 205, -60 );    addChild( ernie );    emily = new Enemy( 317, -85 );    addChild( emily );
public function AvoiderGame() 
{
	enemy = new Enemy( 100, -15 );
	addChild( enemy );
	eric = new Enemy( 160, -120 );
	addChild( eric );
	ernie = new Enemy( 205, -60 );
	addChild( ernie );
	emily = new Enemy( 317, -85 );
	addChild( emily );

Далее, в функции «OnTick», заставьте их все двигаться:

37
38
39
40
41
42
public function onTick( timerEvent:TimerEvent ):void 
{
    enemy.moveDownABit();
    eric.moveDownABit();
    ernie.moveDownABit();
    emily.moveDownABit();
public function onTick( timerEvent:TimerEvent ):void 
{
	enemy.moveDownABit();
	eric.moveDownABit();
	ernie.moveDownABit();
	emily.moveDownABit();

Наконец, проверьте каждый из них на наличие столкновения с игроком:

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
if ( avatar.hitTestObject( enemy ) ) 
{
    gameTimer.stop();
}           
if ( avatar.hitTestObject( eric ) ) 
{
    gameTimer.stop();
}           
if ( avatar.hitTestObject( ernie ) ) 
{
    gameTimer.stop();
}           
if ( avatar.hitTestObject( emily ) ) 
{
    gameTimer.stop();
}
if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( eric ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( ernie ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( emily ) ) 
{
	gameTimer.stop();
}

Запустите игру. Конечно, все работает, но это довольно очень кривое решение. Откат! Отменяйте все, что вы копировали и вставляли, в этом нет необходимости.

Формируем армию

Копирование и вставка врагов, как показано выше, позволяет нам обратиться к каждому из них по определенному имени. Но … нужно ли нам это? Бывают ли ситуации, когда мы должны сказать только одному врагу действовать по-другому? Только тогда, когда что-то происходит с этим врагом — когда он касается игрока, возможно, или когда он покидает экран, — но в этих случаях мы выделим противника по его его ситуации, а не по имени. Скоро я покажу вам, что я имею в виду.

Мы собираемся создать группу(отряд) врагов, и использовать класс документа в качестве «генерала», который говорит группе, как себя вести. Я буду называть эту группу army(армия, войско), по очевидным причинам. Мы будем использовать массив в качестве структуры для группы, это по-сути упорядоченный список объектов.

Сначала мы должны определить новое войско, так же, как мы делали это для врага. Нужно, чтобы войско было доступными во время игры, поэтому мы определяем его внутри класса документа, но снаружи его конструктора (строка 9):

7
8
910
11
12
public class AvoiderGame extends MovieClip 
{
    public var army:Array;    public var enemy:Enemy;
    public var avatar:Avatar;
    public var gameTimer:Timer;
public class AvoiderGame extends MovieClip 
{
	public var army:Array;
	public var enemy:Enemy;
	public var avatar:Avatar;
	public var gameTimer:Timer;

И еще удалите строчку public var enemy:Enemy; (строка 10 выше). Так как все враги будут частью войска, и армия является публичной переменной, нам больше не нужно иметь для врагов публичные переменные.

Теперь, измените конструктор игры вот так:

14
15
16
17
18
19
public function AvoiderGame() 
{
    army = new Array();
    var newEnemy = new Enemy( 100, -15 );
    army.push( newEnemy );
    addChild( newEnemy );
public function AvoiderGame() 
{
	army = new Array();
	var newEnemy = new Enemy( 100, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

Что мы сделали?

  • Строка 16 — Создать «army» как новый(new) экземпляр класса Array, так же, как мы делали это для врага.
  • Линия 17 — Чтобы не запутаться, я переименовал enemy в newEnemy, так как эта переменная будет использоваться только для создания новых врагов. Поскольку мы еще не писали «var newEnemy …» чтобы объявить эту переменную, мы просто добавим «var» в начале этой строчки.
  • Строка 18 — Добавляю нового врага в войско. («впихнул»(push) его в конец списка врагов).
  • Строка 19 — добавил нового врага в игру (здесь просто поменялось имя enemy на newEnemy).

Теперь нам нужно(снова) заставить противника двигаться. Помните, что это делается в функции OnTick. Мы собираемся использовать цикл for each…in(для каждого … в), который является нововведением в ActionScript 3.

Измените функцию OnTick, так чтобы вместо:

31
32
33
34
35
public function onTick( timerEvent:TimerEvent ):void 
{
    enemy.moveDownABit();
    avatar.x = mouseX;
    avatar.y = mouseY;
public function onTick( timerEvent:TimerEvent ):void 
{
	enemy.moveDownABit();
	avatar.x = mouseX;
	avatar.y = mouseY;

было:

31
32
33
34
35
36
37
38
public function onTick( timerEvent:TimerEvent ):void 
{
    for each ( var enemy:Enemy in army ) 
    {
        enemy.moveDownABit();
    }
    avatar.x = mouseX;
    avatar.y = mouseY;
public function onTick( timerEvent:TimerEvent ):void 
{
	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
	}
	avatar.x = mouseX;
	avatar.y = mouseY;

Мне кажется, что не сложно понять что делают новые строчки. Цикл «для каждого … в» проходит через все элементы в списке войск(army) и командует им перемещаться вниз.

Стоит отметить один момент. Может показаться, что мы говорим циклу что он должен выбирать только элементы типа Enemy — но это не так. Мы просто говорим циклу ожидать все что внутри армии причисленным к типу Enemy. Мы могли бы добавить аватар в армию в функции конструктора — и тогда получили бы ошибку, потому что класс «Avatar» не содержит функцию под названием moveDownABit (). Во всяком случае, не стоит чересчур об этом беспокоится, со временем это станет интуитивным для вас.

Еще одно изменение, которое мы должны сделать, прежде чем мы сможем запустить игру: проверка столкновений. Мы можем просто переместить код обнаружения столкновений, внутрь цикла «for each…in» (строки 36-39):

31
32
33
34
35
3637383940
41
42
43
public function onTick( timerEvent:TimerEvent ):void 
{
    for each ( var enemy:Enemy in army ) 
    {
        enemy.moveDownABit();
        if ( avatar.hitTestObject( enemy ) )         {            gameTimer.stop();        }    }
    avatar.x = mouseX;
    avatar.y = mouseY;
}
public function onTick( timerEvent:TimerEvent ):void 
{
	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
		if ( avatar.hitTestObject( enemy ) ) 
		{
			gameTimer.stop();
		}
	}
	avatar.x = mouseX;
	avatar.y = mouseY;
}

Обратите внимание: мы проверяем на столкновение раньше, чем мы передвигаем аватара, то есть мы проверяем, сталкивается ли противник с местом, где был игрок 1 тик(tick) назад. Вряд ли это справедливо. По-этому, сделаем небольшую перестановку:

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public function onTick( timerEvent:TimerEvent ):void 
{
    avatar.x = mouseX;
    avatar.y = mouseY;
 
    for each ( var enemy:Enemy in army ) 
    {
        enemy.moveDownABit();
        if ( avatar.hitTestObject( enemy ) ) 
        {
            gameTimer.stop();
        }
    }
}
Сохраните и запустите. Вы увидите, что все работает именно так, как раньше. Зато теперь мы с легкостью сможем добавить множество врагов.
public function onTick( timerEvent:TimerEvent ):void 
{
	avatar.x = mouseX;
	avatar.y = mouseY;

	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
		if ( avatar.hitTestObject( enemy ) ) 
		{
			gameTimer.stop();
		}
	}
}
Сохраните и запустите. Вы увидите, что все работает именно так, как раньше. Зато теперь мы с легкостью сможем добавить множество врагов.

Набираем войска:

Попробуйте сделать так:

1
2
3
4
5
public function onTick( timerEvent:TimerEvent ):void 
{
    var newEnemy:Enemy = new Enemy( 100, -15 );
    army.push( newEnemy );
    addChild( newEnemy );
public function onTick( timerEvent:TimerEvent ):void 
{
	var newEnemy:Enemy = new Enemy( 100, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

Вы должны узнать строчки 33-35. Мы просто вставили код для создания нового врага в функцию OnTick. Теперь каждый тик будет создан новый враг. Что вы ожидаете увидеть, если это запустить?

Интересно. Новые враги создаются в одном месте и в таком быстром темпе, что они перекрывают друг друга. Давайте попробуем это исправить, создавая новых врагов в самых разных местах.

Для этого нам пригодится функция Math.random (). Она возвращает случайное значение в интервале от 0 до 1. Моя игра 400 пикселей в ширину, поэтому я хотел бы, чтобы враги появлялись с координатой X от 0 до 400. Соответственно (строки 33-34):

31
32
33
34
35
36
public function onTick( timerEvent:TimerEvent ):void 
{
    var randomX:Number = Math.random() * 400;
    var newEnemy:Enemy = new Enemy( randomX, -15 );
    army.push( newEnemy );
    addChild( newEnemy );
public function onTick( timerEvent:TimerEvent ):void 
{
	var randomX:Number = Math.random() * 400;
	var newEnemy:Enemy = new Enemy( randomX, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

Теперь враги будут создаваться в случайной точке над экраном. Сохранить и запустить. Что мы получим на этот раз?

Ой. Думаю при такой частоте появления играть будет сложновато.

Меньше врагов в секунду, пожалуйста

Сейчас, враги появляются с частотой одного нового врага за тик. Так как тик = 25мс, есть 40 тиков в секунду, поэтому и 40 новых врагов в секунду. Я думаю, что если уменьшить до 4 врагов в секунду, будет нормально. Так как 4 равно 1/10 от 40, мы должны добавлять новых врагов с частотой 1/10 (врагов/тик).

Мы могли бы сделать так, чтобы новый враг появлялся каждые десять тиков. Но я думаю, было бы веселее, если бы мы сказали, что 1/10 — шанс на появление нового врага в любой тик, таким образом, иногда у нас будет больше, чем четыре врага в секунду, а иногда меньше , но в среднем будет 4 врага в секунду. Случайность, делает игру немного более захватывающей (хотя слово «захватывающая» немного не подходит на этом раннем этапе развития игры …).

Как это сделать? Ну, мы знаем, что Math.random () генерирует случайное число между 0 и 1. Так как эти случайные числа равномерно распределены, это означает, что есть шанс 1/10 на что, что выпадет число между 0 и … 1/10!. То есть, шанс Math.random () <0,1 составляет 1/10.

Итак, давайте изменим наш код, чтобы использовать этот факт (строчки 29 и 34):

28
2930
31
32
33
3435
36
public function onTick( timerEvent:TimerEvent ):void 
{    if ( Math.random() < 0.1 )
    {
        var randomX:Number = Math.random() * 400;
        var newEnemy:Enemy = new Enemy( randomX, -15 );
        army.push( newEnemy );        addChild( newEnemy );
    }
public function onTick( timerEvent:TimerEvent ):void 
{
	if ( Math.random() < 0.1 )
	{
		var randomX:Number = Math.random() * 400;
		var newEnemy:Enemy = new Enemy( randomX, -15 );
		army.push( newEnemy );
		addChild( newEnemy );
	}

(Примечание: При каждом вызове Math.random () генерирует новое число, так что новый код не мешает коду для случайного расположения новых врагов)

Сохраните все и запустите.

Намного лучше! Даже, может быть, стало слишком легко. Почему бы не поиграться с числом в выражении (Math.random() < 0.1), чтобы подобрать сложность под себя?

Заключение

На этом вторая часть заканчивается. Вы можете скачать ZIP с моим результатом здесь.

В следующей части мы добавим экран «Game Over», чтобы игра имела цель!

Хотите испытать свои знания? Вот вам «домашнее задание»: попытайтесь сделать появление врагов в случайных местах. Уверен, теперь вы можете сделать это. Кроме того, иногда враги создаются на половину «обрезанные» одной из сторон экрана. Сможете исправить?

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

Avoider Game tutorial на русском. Часть 1: Введение

Содержание серии «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: Меню и кнопки
 (Оригинал на английском языке здесь)
 

Введение

Начать проходить урок можно без наличия опыта работы с AS2 или flash. Вам будет легче, если вы уже знакомы с основами языков программирования, такими как переменные, операторы «if», циклы и функции, но не волнуйтесь, если это не так. Также урок пригодится тем, кто хочет совершить переход с AS2 на AS3.

Примеры в данном уроке будут следовать полезным традициям AS3, то есть:

  • Один кадр
  • Один слой
  • Нет объектов на сцене
  • Нет кода в кадрах
  • Нет кода в символах

(Если вы не использовали флэш раньше, все это может быть абсолютно не понятным для вас. Поверьте мне, что это хорошие привычки!)

Эти правила, возможно, придется нарушить пару раз, когда речь пойдет о добавлении загрузчика (preloader). Но не волнуйтесь, это будет в далекой восьмой части :)

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

Чтобы знать, что мы должны получить в конце урока, посмотрите пример, нажав на картинку:

avoider game tutorial

А по окончанию 12-и уроков, это будет выглядеть так, с управлением клавиатурой, несколькими уровнями, звуками и многим другим:

avoider game tutorial

Подготовка

Давайте начнем с простого. Запускайте ваш Adobe Flash (CS3 или новее) и выберите File > New > Flash File (ActionScript 3.0).

Выберите File > Save и создайте новую папку где-нибудь, где вы хотите хранить все файлы игры. Создайте новую папку, внутри этой папки, с именем «Classes». Теперь введите имя файла FLA (к примеру мояИгра.fla) и сохраните его в основной папке (рядом с папкой classes).

Нам нужно изменить несколько настроек, перед тем как мы сможем приступить к созданию игры. Выберите File > Publish Settings. Перейдите на вкладку Flash, если она еще не выбрана, а затем нажмите на кнопку настройки рядом с выпадающим списком версий ActionScript:

Нажмите на кнопку «плюс» над списком путей к классам, и напишите ./Classes/ в текстовое поле, которое появилось. Так мы сказали флешу, что мы собираемся хранить код в папке Classes, которую мы сделали раньше.

Кроме того, поставьте флажок «Automatically declare stage instances», если он еще не стоит. Жмем «ок».

Далее мы будем изменять свойства нашего проекта. Выберите в верхнем меню Modify > Document и настройте его, как вам хочется. Я выбрал серый фон и разрешение 400 на 300 пикселей. Вы всегда сможете изменить это позже, так что не беспокойтесь. Я рекомендую оставить частоту кадров 24 кадров в секунду. Вот что я сделал:

Хорошо, теперь все скучное позади. Давайте создадим нашего врага!

Подготовка графики врага:

Враг — объект, которого вы, как игрок, не должны прикасаться. (т.к. жанр игры avoider) Давайте нарисуем его, прежде чем оживить. Нажмите кнопку Insert > New Symbol и в появившемся окне, дайте ему имя Enemy (с большой буквы E!) а также тип MovieClip (чтобы оживить его позже). Нажмите кнопку ОК. Библиотека теперь будет иметь свой новый(пока что пустой) объект «Enemy».

Если вы не видите библиотеки, убедитесь, что стоит галочка на пункте Window > Library. По умолчанию вы автоматически перейдете в режим редактирования Enemy. Вы можете посмотреть, что вы редактируете в полоске над окном редактирования:

Если вы не попали в режим редактирования врага, просто дважды щелкните по нему в библиотеке (или щелкните правой кнопкой мыши и выберите пункт Edit). Нарисуйте вашего врага. Я выбрал смайлик на роль плохого парня. Вы можете нарисовать все, что угодно, но постарайтесь сделать это круглым. Вот мой злодей:

Круто. Хотя уже есть небольшая проблема. Вы видите маленький черный крестик с белым фоном? Это называется точка регистрации, и когда мы начинаем писать код, скажем, «создать противника в точке 0,0», в этот момент, флеш выставит смайлик так, что его «крестик» окажется в точке 0,0. (Это трудно объяснить словами. Позже это станет понятней.) По этой причине я передвину смайлик так, что точка регистрации станет его центром. Вот как это сделать:

Во-первых, убедитесь, что вы редактируете вашего врага, а затем выберите все (нажмите Edit > Select All). Появятся штриховки:

Теперь, сгруппируем все вместе, выбрав Modify > Group Together(или ctrl+g). Появится синяя рамочка. Если теперь нажать и перетащить что-нибудь в этой рамочке, весь смайлик будет двигаться как единое целое. (Если бы вы попробовали это раньше, вы бы могли ненароком вытащить ему глаз.)

Открываем панель выравнивания. Если вы не можете ее найти, убедитесь, что пункт Window> Align отмечен. Не снимая выделения с врага (т.е. с синей рамочкой вокруг него), убедитесь, что выбран пункт to stage, и нажмите две кнопки для выравнивания по центру(горизонтально и вертикально):

Теперь, когда мы закончили с дизайном противника, мы можем выйти из режима редактирования. Кликните на Scene 1(сцена 1) на полоске сверху зоны редактирования. Самое время, чтобы сохранить нашу работу, поэтому жмем File > Save.

Создадим врага

Пришло время написать код для управления поведением противника. Вот первый момент, когда можно заметить отличия as3 от as2. Как я уже говорил в начале, этот код не будет находится ни на таймлайне ни в кадрах внутри символа. Вместо этого, код будет в отдельном файле. У такого подхода есть ряд преимуществ:

  • Код полностью отделен от графики, так что вы можете дать вашему художнику файл FLA , без исходного кода.
  • Кроме того, вы можете дать другому программисту части кода, без необходимости отправлять ему ваши графику и звуки — или даже остальные части кода.
  • Если у вас несколько программистов, каждый из которых работает над отдельными частями кода, будет намного проще собрать весь код вместе.

Жмем File> New и выберите файл ActionScript. Появится пустое окно редактора кода. Сразу сохраните файл, как Enemy.as, в папку Classes, созданную ранее. Этот файл будет содержать класс — шаблон кода — для наших смайликов-злодеев.

К этому моменту урока, большинство AS2 программистов начнут жаловаться о том, насколько труднее использовать AS3, потому что нужно написать намного больше кода, чтобы заставить объект что-то сделать. Потерпите немного и вы увидите, что «лишние» строчки кода не так сложны для понимания. Также такой подход с лихвой окупится в долгосрочной перспективе.

Начнем с ввода следующей команды:

1
2
3
4
package 
{
 
}
package 
{

}

Ключевое слово package просто говорит, что всё, что все между следующей парой скобок является частью одного… пакета. В этом случае, все в этом пакете касается объекта Enemу (нашего врага).

Внутри скобок мы должны определить класс противника — шаблон, который задает, что он может сделать, и что мы можем сделать с ним:

1
2
3
4
5
6
7
package 
{
    public class Enemy extends MovieClip 
    {
 
    }
}
package 
{
	public class Enemy extends MovieClip 
	{

	}
}

Давайте рассмотрим это по частям:

  • class Enemy — «все, что между следующей парой скобок составляет класс «Enemy»».
  • public (общедоступный) — «доступ к классу Enemy можно будет получить из любого отрывка кода, который знает о классе Enemy». Если бы мы написали internal (внутренний), это означало бы, что только код внутри этого пакета может получить доступ к этому классу.
  • extends MovieClip — «Я могу сделать что-либо возможное для класса MovieClip  — и добавить свой функционал» Например, мы можем написать код, который командует MovieClip перейти к следующему кадру своей анимации. Это значит, что мы можем сделать то же самое и с Еnemy (или, по крайней мере, мы могли бы, если бы он содержал больше, чем один кадр). Кроме того, мы можем дать этому врагу кол-во здоровья (HP), которого MovieClip не имеет, таким образом, расширить функциональность.

Хотя MovieClip довольно важный и часто используемый класс объектов, Flash все-равно нужно указать, где он. Итак, в строке 3 мы пишем следующее:

1
2
34
5
6
7
8
package 
{
    import flash.display.MovieClip;    public class Enemy extends MovieClip 
    {
 
    }
}
package 
{
	import flash.display.MovieClip;
	public class Enemy extends MovieClip 
	{

	}
}

Наконец, классу Enemy (как и любому другому) нужно то, что известно как конструктор. Это всего лишь функция, которая автоматически выполняется каждый раз, когда новый враг будет создан. Функция конструктора должна иметь то же имя, что и класс, к которому она принадлежит:

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

		}
	}
}

Опять видим это слово public. Обратите внимание, что функция Enemy имеет пару пустых круглых скобок непосредственно после нее; подробнее об этом позже.

Теперь у нас есть класс Enemy, мы должны связать его с графикой смайлика. Сохраните AS файл и возвращайтесь к файлу FLA (он будет на панели вкладок в верхней части окна, если вы не закрыли его). Щелкните правой кнопкой мыши по смайлику (Enemy) в библиотеке, и выберите Properties. Нажмите кнопку Advanced, если она есть, чтобы показать панель привязки. Ставим галочку возле Export for ActionScript и проверяем, что класс называется: «Enemy». Если вы все связали правильно — нажмите на иконку карандаша. Должен открыться редактор класса Enemy, где мы только что писали код.

Жмем OK, чтобы закрыть окно свойств.

Теперь нам нужно закодить(написать) поведение врага. Что он должен делать? На данный момент, все что он будет делать, это:

  • Появляться над верхней частью экрана при создании
  • Непрерывно двигаться вниз

У нас уже есть место, чтобы поместить код, который необходимо выполнить при создании Enemy — функция конструктора. Вот:

1
2
3
4
5
6
7
8
9
10
11
12
package
{
    import flash.display.MovieClip;
    public class Enemy extends MovieClip
    {
        public function Enemy()
        {
            x = 100;
            y = 0;          
        }
    }
}
package
{
	import flash.display.MovieClip;
	public class Enemy extends MovieClip
	{
		public function Enemy()
		{
			x = 100;
			y = 0;			
		}
	}
}

Так класс MovieClip имеет свойства х и у, у класса Enemy они тоже есть.

Те из вас, кто знаком с декартовой системой координат , наверное, скажут: «Я думал, что мы хотели, чтобы враг появлялся в верхней части экрана, но мы установили у-координату ноль, что, безусловно, внизу». Флеш немного странноват в этом отношении. Координата Y увеличивается, когда вы идем вниз экрана. Для моих 400 х 300 пикселей, координаты выглядеть следующим образом:

Привыкайте к этому.

Я буду передвигать врагов так: другой класс будет им командовать «двигаться вниз!», каждую долю секунды. Нам нужно добавить функцию «движение вниз» в класс Enemy, так как у MovieClip нет такой функции :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package 
{
    import flash.display.MovieClip;
    public class Enemy extends MovieClip 
    {
        public function Enemy() 
        {
            x = 100;
            y = 0;          
        }
 
        public function moveDownABit():void 
        {
            y = y + 3;
        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class Enemy extends MovieClip 
	{
		public function Enemy() 
		{
			x = 100;
			y = 0;			
		}

		public function moveDownABit():void 
		{
			y = y + 3;
		}
	}
}

Давайте разберем новые строчки:

  • Функция moveDownABit () — все внутри следующей пары скобок составляет функцию под названием moveDownABit (англ. сдвинутьВнизНемного). Опять же, ставим пару пустых круглых скобок после имени функции.
  • public — другая часть механизма игры будет запускать эту функцию, поэтому она должна быть «публичной».
  • :void — эта функция не возвращает информации. Не будем останавливаться на этом, я объясню это, когда мы сделаем функцию, которая возвращает что-то.
  • y = y + 3; — увеличение у-координаты противника на 3 пикселя (помните, что это будет двигать противника вниз, а не вверх).

Добавим врага в игру

Сохраняем Enemy.as и возвращаемся к файлу FLA. Пора бы уже добавить врагов в игру, ведь мы до сих пор просто редактировали шаблон(класс) врага, а не самого врага. Представьте что мы работали над чертежом дома, и теперь мы должны использовать этот чертеж, чтобы построить настоящий, физической дом.

Важно помнить, что flash в начале был инструментом для создания анимации. Несмотря на то, что он много развивался с тех пор, flash по-прежнему считает все, что вы создаете объектом MovieClip. Как нам известно, у MovieClip есть функция-конструктор, которая выполняется, когда он создан, соответственно у вашей игры тоже есть конструктор. Мы собираемся расширить эту функцию, чтобы создавать врага, когда игра началась. Но как мы получим доступ к конструктору игры? Если вы сказали «через другой AS файл» — вы правы.

Создайте новый AS файл и сохраните его в нашей папке для классов (Classes) с именем AvoiderGame.as. Добавьте следующие строки в файл и сохраните его:

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

		}
	}
}

 

Выглядит знакомо? Помимо имен класса и конструктора, это точно то же, с чего мы начали писать «Enemy.as». Давайте свяжем его с нашим файлом FLA.

Вернитесь в файл FLA и убедитесь, что вы уже не редактирование смайлик. Найдите панель свойств(Properties) — если вы не можете её найти, убедитесь, что Window > Properties > Properties отмечено галочкой. На этой панели есть поле «Document class«(класс документа), введите AvoiderGame. Важно обращать внимание на регистр букв — мы назвали файл «AvoiderGame.as» и класс внутри файла «AvoiderGame», мы должны точно также назвать класс документа. При нажатии на иконку карандаша должен открыться наш AvoiderGame.as.

Круто, теперь мы можем изменить конструктор самой игры, чтобы заставить его создавать врага при запуске:

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

Следите за высотой букв! Enemy, написанное с большой буквы E = класс-файл, который определяет особенности, общие для всех врагов, enemy с маленькой буквы e = конкретный враг, «экземпляр» класса Enemy. Для наглядности, пусть «Человек» это класс, тогда я — экземпляр этого класса, вы — еще один экземпляр этого класса.

Сейчас, этот экземпляр врага(enemy) доступен только для функции конструктора, потому что он впервые упоминается в этой функции. Нам нужно, чтобы он был доступен на протяжении всей игры, так что добавьте эту строку кода (строка 6):

1
2
3
4
5
67
8
9
10
11
12
13
package 
{
    import flash.display.MovieClip;
    public class AvoiderGame extends MovieClip 
    {
        public var enemy:Enemy; 
        public function AvoiderGame() 
        {
            enemy = new Enemy();
        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class AvoiderGame extends MovieClip 
	{
		public var enemy:Enemy;

		public function AvoiderGame() 
		{
			enemy = new Enemy();
		}
	}
}

 

  • public — вы уже знаете, что это значит.
  • var — «я собираюсь определить переменную» (от variable => переменная)
  • enemy:Enemy «эта переменная с названием enemy, и это экземпляр класса Enemy.»

Так как переменная определена внутри класса AvoiderGame, она будет доступна внутри всего класса.

Теперь мы создали врага, но он существует только в памяти. Увидеть на экране мы его пока не можем. Давайте это исправим:

8
9
10
11
12
public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
        }
public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );
		}

 

Новая для нас функция: AddChild() (добавить потомка). Мы используем ее, чтобы добавлять объекты на экран. Любой объект, который добавлен с помощью «AddChild()» к классу документа (в нашем случае к AvoiderGame) появится на экране, если его X-и Y-координаты будут внутри области, которую можно увидеть!

Считаем, что enemy теперь потомок  класса AvoiderGame, и что класс AvoiderGame является родителем объекта enemy. Объект может иметь только одного родителя, но он может иметь столько потомков, сколько вам захочется.

Наконец, мы можем запустить игру и ожидать результатов! Сохраните все, затем нажмите Control> Test Movie. Вы получите что-то вроде этого:

Ура! Только … мы хотим, чтобы в начале весь враг находился над окном, а не его половина. Flash выставил положение противника так, что его точка регистрации расположена в точке (100,0). Мы могли бы переместить точку регистрации к нижней части противника, но я думаю, что это грубое решение. Вместо этого, давайте просто изменим, стартовою у-позицию врага, изменив конструктор внутри Enemy.as файла, например так:

6
7
8
9
10
        public function Enemy() 
        {
            x = 100;
            y = -15;
        }
		public function Enemy() 
		{
			x = 100;
			y = -15;
		}

Вам нужно будет изменить значение Y в зависимости от высоты вашего смайлика (мой 30 пикселей в высоту).

Оживим врага

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

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

Редактируем конструктор игры, чтобы создать новый экземпляр «gameTimer» класса Timer. Это встроенный класс, так что вам не нужно создавать новый AS файл, просто измените AvoiderGame.as вот так :

8
9
10
11
12
13
14
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
        }
		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
		}

Обратите внимание, что на этот раз круглые скобки после имени класса не пусты. Число 25 говорит, что мы хотим установить интервал таймера на 25 мс (миллисекунды; 1000 мс = 1 секунда), т.е. мы хотим, что-то делать каждые 25 мс.

Мы хотим иметь доступ к gameTimer на протяжении всей игры, так что мы должны сделать его доступным для всего класса (строка 7):

1
2
3
4
5
6
78
9
10
11
12
13
14
15
16
17
package 
{
    import flash.display.MovieClip;
    public class AvoiderGame extends MovieClip 
    {
        public var enemy:Enemy;
        public var gameTimer:Timer; 
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
        }
    }
}
package 
{
	import flash.display.MovieClip;
	public class AvoiderGame extends MovieClip 
	{
		public var enemy:Enemy;
		public var gameTimer:Timer;

		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
		}
	}
}

Как и MovieClip, Timer требует, чтобы мы сделали import его библиотек перед использованием: (строка 4):

1
2
3
45
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 
{
    import flash.display.MovieClip;
    import flash.utils.Timer; 
    public class AvoiderGame extends MovieClip 
    {
        public var enemy:Enemy;
        public var gameTimer:Timer;
 
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
        }
    }
}
package 
{
	import flash.display.MovieClip;
	import flash.utils.Timer;

	public class AvoiderGame extends MovieClip 
	{
		public var enemy:Enemy;
		public var gameTimer:Timer;

		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
		}
	}
}

(Откуда я знаю, что нужно писать flash.utils.Timer и flash.display.MovieClip? Можно найти в google. Или же пишите код в FlashDefevlop — он добавляет такие строки автоматически).

Хорошо, у нас есть таймер,  который срабатывает каждые 25 мс. Но в данный момент он не подключен ни к чему; он не говорит врагу двигаться вниз по экрану. Добавим еще одну строчку (17) в конструктор вашей игры:

11
12
13
14
15
16
17
18
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
            gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
        }
		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
		}
  • gameTimer.addEventListener — «добавить слушатель события к gameTimer». Слушатель события, как робот, который постоянно проверяет, произошло ли указанное событие и запускает другую функцию, если это так. В нашем случае, событие это …
  • TimerEvent.TIMER — это событие случается каждый раз, когда таймер завершает интервал, в нашем случае, это событие будет срабатывать каждые 25 мс, потому что это интервал gameTimer.
  • moveEnemy — это функция, которая будет запускаться каждый раз, когда случается TimerEvent происходит. Мы еще не написали эту функцию, мы вернемся к этому через минуту.

TimerEvent тоже нужно импортировать (строка 5):

1
2
3
4
5
package 
{
    import flash.display.MovieClip;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
package 
{
	import flash.display.MovieClip;
	import flash.utils.Timer;
	import flash.events.TimerEvent;

Теперь нам нужно написать функцию moveEnemy, которая будет запускаться каждые 25мс (строки 21-24):

7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122232425
    public class AvoiderGame extends MovieClip 
    {
        public var enemy:Enemy;
        public var gameTimer:Timer;
 
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
            gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
        }
 
        public function moveEnemy( timerEvent:TimerEvent ):void         {         }    }
	public class AvoiderGame extends MovieClip 
	{
		public var enemy:Enemy;
		public var gameTimer:Timer;

		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
		}

		public function moveEnemy( timerEvent:TimerEvent ):void 
		{

		}
	}

Опять же, у нас есть кое-что внутри скобок. Это позволяет слушателю события передать информацию о событии функции moveEnemy, в виде экземпляра (TimerEvent) класса TimerEvent. Нам не нужна эта информация, поэтому мы не будем трогать на это. Кроме того, здесь опять есть void.

Все, что мы должны сейчас сделать, это использовать эту новую функцию чтобы двигать противника. Помните, мы писали функцию moveDownABit внутри класса Enemy? Пришло время нею воспользоваться:

21
22
23
24
        public function moveEnemy( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
        }
		public function moveEnemy( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
		}

Это говорит экземпляру enemy, выполнять его фунуцию moveDownABit. Сохраните все и запустите игру (Control> Test Movie или ctrl+enter).

Ничего не произошло.

Мы должны запустить gameTimer! Измените конструктор вашей игры, вот так (строка 19):

12
13
14
15
16
17
18
1920
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            gameTimer = new Timer( 25 );
            gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
            gameTimer.start();        }
		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
			gameTimer.start();
		}

Сохранить все, и запускайте снова. Успех! Враг движется вниз, со скоростью 3 пикселя за 25 мс (или 120 пикселей / сек).

Теперь нам нужно добавить интерактивности, в противном случае это просто кинцо, а не игра.

Добавим главгероя

Сначала мы должны нарисовать персонажа для наших игроков. В файле FLA создайте новый символ, типа MovieClip под названием Avatar. (Посмотрите разделом выше, если вы не помните, как это сделать.) К злодеям смайликам подойдет главгерой в виде черепа.

Avatar

Наш Avatar

Хорошо, когда вы нарисовали вашего героя (дальше -Avatar), центрируйте точку регистрации, выходите из режима редактирования и сохраните FLA. Что дальше? Вряд ли вы будете удивлены, услышав, что мы собираемся создать новый AS файл для Avatar. Сохраните его как Avatar.as в папке classes, и начните редактировать его:

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

		}
	}
}

Знакомая история. Я думаю вы сможете сами привязать этот класс к персонажу Avatar в библиотеке.

Что наш Аватар должен делать? На данный момент, просто будет следовать за курсором. Мы можем контролировать положение Аватара из класса AvoiderGame, так что нам не нужно добавлять кода для класса Avatar прямо сейчас.

Перейдите к редактированию AvoiderGame.as. Давайте добавим экземпляр класса Avatar в игру. Я покажу весь новый код сразу, так как это почти то же самое, что мы делали для врага. (Ниже содержимое файла AvoiderGame.as) (Новый код: строчки 10, 18, и 19):

7
8
9
1011
12
13
14
15
16
17
181920
21
22
23
24
25
26
27
28
29
30
    public class AvoiderGame extends MovieClip 
    {
        public var enemy:Enemy;
        public var avatar:Avatar;        public var gameTimer:Timer;
 
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            avatar = new Avatar();            addChild( avatar ); 
            gameTimer = new Timer( 25 );
            gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
            gameTimer.start();
        }
 
        public function moveEnemy( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
        }
    }
	public class AvoiderGame extends MovieClip 
	{
		public var enemy:Enemy;
		public var avatar:Avatar;
		public var gameTimer:Timer;

		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			avatar = new Avatar();
			addChild( avatar );

			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
			gameTimer.start();
		}

		public function moveEnemy( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
		}
	}

Если вы запустите игру сейчас, вы увидите, что avatar (экземпляр) появится в верхнем левом углу экрана — то есть, в позиции (0,0). Мы хотим, чтобы он появлялся там, где курсор мыши. Flash имеет два свойства, которые мы можем использовать для этого: mouseX, который дает нам х-координаты курсора мыши, и mouseY, роль которого не сложно угадать. Как уже упоминалось выше, все, что наследует(extends) MovieClip будет иметь параметры х и у, которые мы можем изменять. Таким образом, (строки 20 и 21):

13
14
15
16
17
18
19
202122
23
24
25
26
        public function AvoiderGame() 
        {
            enemy = new Enemy();
            addChild( enemy );
 
            avatar = new Avatar();
            addChild( avatar );
            avatar.x = mouseX;            avatar.y = mouseY; 
            gameTimer = new Timer( 25 );
            gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
            gameTimer.start();
        }
		public function AvoiderGame() 
		{
			enemy = new Enemy();
			addChild( enemy );

			avatar = new Avatar();
			addChild( avatar );
			avatar.x = mouseX;
			avatar.y = mouseY;

			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, moveEnemy );
			gameTimer.start();
		}

Если теперь запустить, аватар появится на курсоре мыши (это легче увидеть, если вы используете комбинацию клавиш Ctrl-Enter для запуска). Так как мы используем gameTimer для изменения позиции противника каждые долю секунды, давайте также использовать его для позиции аватара (строки 31 и 32):

28
29
30
313233
        public function moveEnemy( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
            avatar.x = mouseX;            avatar.y = mouseY;        }
		public function moveEnemy( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
			avatar.x = mouseX;
			avatar.y = mouseY;
		}

Ой, подождите, moveEnemy теперь не очень подходящее название для функции. Как насчет moveEnemyAndAvatar?

28
29
30
31
32
33
        public function moveEnemyAndAvatar( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
            avatar.x = mouseX;
            avatar.y = mouseY;
        }
		public function moveEnemyAndAvatar( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
			avatar.x = mouseX;
			avatar.y = mouseY;
		}

Нам придется обновить слушатель события gameTimer, чтобы он указывал на эту функцию с новым именем:

24
gameTimer.addEventListener( TimerEvent.TIMER, moveEnemyAndAvatar );
gameTimer.addEventListener( TimerEvent.TIMER, moveEnemyAndAvatar );

Сохраните и запустите игру. Греемся в лучах великолепия своего нового курсора в форме черепа!

Нам не страшен.. желтый враг.

Вы, должно быть, заметили, что если вы столкнетесь своим аватаром с противником… абсолютно ничего не происходит. Не очень хорошо для игры жанра Avoider, так что давайте это исправим.

Как мы знаем, что аватар коснулся врага? У MovieClip есть встроенная функция, называется hitTestObject, которая определяет, касается ли MovieClip другого указанного MovieClip. Естественно, классы Avatar и Enemy тоже так умеют. Так как объекты двигаться только, когда срабатывает gameTimer, мы будем проверять столкновения сразу после передвижений (строки 34-37):

28
29
30
31
32
33
3435363738
        public function moveEnemyAndAvatar( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
            avatar.x = mouseX;
            avatar.y = mouseY;
 
            if ( avatar.hitTestObject( enemy ) )             {             }        }
		public function moveEnemyAndAvatar( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
			avatar.x = mouseX;
			avatar.y = mouseY;

			if ( avatar.hitTestObject( enemy ) ) 
			{

			}
		}

 

  • avatar.hitTestObject( enemy ) — возвратит значение true(истина), если аватар касается противника.
  • if — «если выражение между следующей парой круглых скобок верно, то выполнить код внутри фигурных скобок»

Сейчас у нас нет ничего между фигурными скобками по-этому ничего не произойдет при столкновении. Перед тем, как добавить что-нибудь, посмотрите еще раз на название функции: moveEnemyAndAvatar(двигать врага и аватар). Оно опять не описывает роль функции. Мы будем добавлять все больше и больше кода к этой функции в будущих частях этого урока, так что давайте не возиться с громоздким названием, например: moveEnemyAndAvatarAndAlsoCheckToSeeIfTheyHaveCollided. Вместо этого, я собираюсь ввести новое понятие:

  • Tick — каждый раз, когда gameTimer срабатывает, мы будем называть Tick(тик). То есть каждый тик мы обновляем и проверяем все что нам нужно.

Теперь мы можем изменить имя функции на «onTick»:

28
29
30
31
32
33
34
35
36
37
38
        public function onTick( timerEvent:TimerEvent ):void 
        {
            enemy.moveDownABit();
            avatar.x = mouseX;
            avatar.y = mouseY;
 
            if ( avatar.hitTestObject( enemy ) ) 
            {
 
            }
        }
		public function onTick( timerEvent:TimerEvent ):void 
		{
			enemy.moveDownABit();
			avatar.x = mouseX;
			avatar.y = mouseY;

			if ( avatar.hitTestObject( enemy ) ) 
			{

			}
		}

Не забудьте изменить слушатель(listener) события gameTimer, чтобы он указывал на новое имя. Нам больше не нужно будет менять его снова.

Наконец, давайте заставим игру что-то делать, когда противник касается аватара. Так как я еще не рассказал про систему жизней, экран «game over», и в игре нет очков, я использую это как возможность показать вам кое-что.

Вы помните, как мы должны были запустить gameTimer, прежде чем что-нибудь произошло? Вот, добавьте в ваш OnTick следующее (строка 34):

28
29
30
31
32
33
3435
36
        public function onTick(event:TimerEvent):void {
            enemy.moveDownABit();
            avatar.x=mouseX;
            avatar.y=mouseY;
 
            if (avatar.hitTestObject(enemy)) {
                gameTimer.stop();            }
        }
		public function onTick(event:TimerEvent):void {
			enemy.moveDownABit();
			avatar.x=mouseX;
			avatar.y=mouseY;

			if (avatar.hitTestObject(enemy)) {
				gameTimer.stop();
			}
		}

Теперь сохраните и запустить игру. Смотрите, что происходит, когда вы столкнетесь с врагом. Не волнуйтесь, игра не зависла, мы просто остановили таймер. Из-за этого не выполняется функция onTick. Делаем два вывода:

  1. Обнаружение столкновений ужасно
  2. Функцию паузы будет очень легко встроить позже

Заключение

Мы получили прототип игры «Avoider». Если вы хотите поделиться своим проектом с кем-то, просто заархивируйте основную папку (моя называется AvoiderGame-MJW) и отправьте архив. Мой архив можно скачать отсюда . Если же вы просто хотите показать игру, не отправляя исходники, откройте свой FLA файл и выберите File> Export> Export Movie и отправляйте один swf файл.

Я понимаю, что полученная «игра» очень сырая, как с точки зрения кода так и геймплея. Мы написали «движок» для того, чтобы в следующих уроках мы смогли добавлять нововведения куда быстрей. В следующей части мы добавим орду врагов, и после этого мы будем добавлять часы, экран «game over», и систему жизней, и очки. В общем оставайтесь с нами, еще много интересного впереди.

Спасибо за внимание!

При возникновении вопросов — не стесняйтесь спрашивать в комментариях.

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

Avoider game tutorial : Теперь и на русском

Avoider Game tutorial на русском

Avoider game tutorial — серия уроков, выполнив которые вы получите знания, необходимые для создания игр в ActionScript3. Начинать можно «с нуля».

Читать далее »