Game development

Содержание серии «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.

2 комментариев

  1. 01.11.2012    

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

    • Trost Trost
      01.11.2012    

      Прорывы будут случатся постоянно. У самого очень много вопросов возникало при изучении as3. Даже не проходя уроки, в сотый раз написав (вроде бы) знакомые строчки, можно понять что-то новое.

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

Добавить комментарий