Как движки плиток упрощают процесс разработки игр и как применять их к различным типам игр.
Мы нашли этого замечательного автора благодаря FlashGameLicense.com , месту, где можно покупать и продавать флэш-игры!
Окончательный результат предварительного просмотра
Давайте посмотрим на конечный результат, к которому мы будем стремиться:
Переместите мышь, чтобы сделать маленький квадрат, и нажмите, чтобы он прыгнул. Нажмите пробел, чтобы изменить плитку в данный момент под курсором.
Введение: что такое плитка?
Самый простой способ описать плитки — показать вам изображения из игр, которые использовали их в прошлом. Это часть скриншота из The Legend of Zelda:
Когда вы смотрите на старые игры, вы видите, что большая часть искусства в игре используется повторно. Одно и то же дерево появляется дважды, одни и те же камни появляются дважды, дикая трава появляется на экране три раза, а забор виден четыре раза. И все искусство идеально выровнено по сетке. Каждый из маленьких квадратов в сетке, которую я нарисовал на этом скриншоте, является плиткой.
Вместо того, чтобы создавать каждый уровень индивидуально с нуля, люди, которые разработали Legend of Zelda, решили поставить игру в сетку. Затем они прикрепили готовое искусство к этой сетке. Этот метод имеет много преимуществ в игровом дизайне.
Шаг 1: Какие преимущества имеют плитки?
Большинство новичков, использующих Flash, мало знают о методах, основанных на плитках, и о том, какие преимущества они дают дизайнерам. Многие Flash-игры сегодня используют метод, основанный на искусстве. Они рисуют каждый фон и объект вручную, что может занять много времени. Затем функции hitTest()
во Flash используются для обнаружения столкновений. В этом нет ничего плохого, но во многих ситуациях плитки превосходят метод, основанный на искусстве.
Как уже говорилось ранее, рисование вещей снова и снова может занимать много времени. Плитки позволяют вам быстро создавать уровни, и они снимают часть напряжения дизайна уровней с того, кто разрабатывает искусство для игры. Плитка также способна увеличить скорость игры. Метод hitTest()
не очень эффективен. Он проверяет, что векторы двух объектов не соприкасаются, и обнаружение столкновений таким образом занимает много времени. Один или два хит-теста не замедляют вашу игру, но когда вам нужно сотни обнаружений столкновений в секунду, метод тайлов становится намного быстрее. С плитками вам нужно только проверять точки на сетке. Это менее точно (вы больше не проверяете отдельные пиксели или линии фигур друг на друга), но во многих ситуациях проверка столкновений с сеткой работает просто отлично.
Шаг 2: Понимание 2D-массивов
Массивы — это довольно простые объекты в Actionscript. Начнем с создания класса Document (который будет называться Main ) и создания 2D-массива.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
package {
import flash.display.Sprite;
import flash.display.MovieClip;
public class Main extends MovieClip {
public var gameLay:Array = [[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]]
public function Main():void {
if (stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(e:Event=null):void {
}
}
}
|
(Если вы не знаете, как использовать класс документа, ознакомьтесь с этим кратким введением в них. Вам также следует взглянуть на AS3 101: массивы Дрю Кеппл, если вам нужно более четкое представление о том, что такое массивы.)
Мы используем метод init()
чтобы убедиться, что конструктор не запускается до создания экземпляра класса документа. Я просто считаю, что это хорошая форма при кодировании.
В то время как одномерный массив — это просто строка данных, двумерный массив — это массив, который содержит несколько массивов. Это позволяет нам сделать gameLay нашей «картой». Когда мы начнем генерировать плитки на экране, они будут выглядеть так, как выглядит gameLay .
Шаг 3: Понимание данных плитки
2D массив будет нашей «картой». Но сейчас наша карта содержит только два числа: 1 и 0. Когда мы начнем генерировать визуалы, цифры 1 станут твердой землей (черные квадраты в демонстрационном примере выше), а цифры 0 будут пустыми. Для этого урока нам нужно всего три разных числа, чтобы говорить о земле. (Сплошной, пустой, и мы введем третий позже, чтобы сохранить позицию игрока.) Если мы вернемся и посмотрим на скриншот Zelda, вы увидите, что на одном экране должно быть не менее 10+ плиток. ,
Теперь очевидным решением будет добавить больше чисел, но есть о чем подумать. В целом в игре Zelda должно быть как минимум на 100 плиток больше, чем то, что показано на экране в данный момент времени. Профессиональные дизайнеры решают изменить способ интерпретации данных вместо того, чтобы иметь уникальные данные для каждого объекта. Они меняют наборы плиток, а не меняют каждую плитку — так что 7 может означать траву в одной области игры и воду в другой. Сейчас мы не будем вдаваться в подробности, но есть над чем подумать.
Шаг 4: Создание базовой плитки
Откройте Flash и запустите новый документ AS3 — убедитесь, что класс документа установлен в Main . Также убедитесь, что FLA и класс находятся в одной папке. Но это то, о чем вы должны знать перед началом этого урока.
Итак, ваш документ должен быть 600х300. Когда мы оглядываемся назад, gameLay, который действует как наша «карта», имеет размер 10×5. Если мы хотим, чтобы каждая плитка была одинакового размера, и чтобы плитки целиком занимали весь экран, нам нужно сделать каждую плитку шириной (600/10) и высотой (300/5). Таким образом, 60x60px — это то, насколько нам нужна каждая из наших плиток.
Давайте создадим большой черный квадрат размером 60×60 и присвоим ему имя класса Tiles (с заглавной буквой T , в отличие от скриншота). Важно сориентировать его в верхнем левом углу мувиклипа (см. Маленький черный квадрат в разделе «Регистрация»):
После создания мувиклипа убедитесь, что первый кадр листов пуст, а второй содержит черный квадрат.
Шаг 5: Генерация графической плитки
Итак, на данный момент у нас есть данные карты тайлов, которые мы создали на шаге 2. Теперь нам нужно пройтись и интерпретировать эти данные, а также выпустить что-то, что мы можем использовать для вывода плиток на экран. Чтобы упростить его, мы собираемся создать функцию, которая будет добавлять плитки на экран на основе данных в массиве. Эта функция принадлежит классу Main :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public function buildLevel(s:Array){
if(getChildByName(«tileHolder»)!=null){
removeChild(getChildByName(«tileHolder»));
}
var tiles:MovieClip = new MovieClip();
tiles.name = «tileHolder»;
for(var i=0; i<s[0].length;i++){
for(var o=0; o<s.length;o++){
var currentTile:Tiles = new Tiles();
currentTile.x = i*60;
currentTile.y = o*60;
currentTile.name = i+»-«+o;
tiles.addChild(currentTile);
currentTile.gotoAndStop(int(s[o][i])+1);
}
}
addChild(tiles);
}
|
Мы использовали вложенный цикл для прохождения каждого элемента массива. Пустой фрагмент ролика, называемый «плитками» в коде, но с именем «tileHolder», используется для хранения плиток, чтобы мы могли удалять их с экрана или легко перемещать. CurrentTile добавляется в этот фрагмент тайлов . Мы говорим currentTile, чтобы перейти к кадру, указанному элементом массива. Поскольку мы используем 0,1 в наших данных плитки, нам нужно добавить 1 к числу в фактическом массиве, чтобы убедиться, что плитки останавливаются в кадрах 1 и 2. Теперь, даже если мы построили эту функцию, нам все еще нужно призвать его к работе.
1
2
3
|
private function init(e:Event=null):void {
buildLevel(gameLay);
}
|
И вот как это должно выглядеть при экспорте. Любые изменения, которые вы вносите в массив gameLay () , отражаются на том, что происходит при экспорте. Если бы у нас было больше элементов, мы могли бы легко сказать функции buildLevel изменить то, что должно появиться, в зависимости от того, что появилось в массиве.
Попробуйте изменить макет уровня, изменив массив gameLay.
Шаг 6: Уничтожение плитки
В точках, вам нужно удалить все плитки на экране. Даже если что-то изменить на экране не изменит сами данные мозаики, нам все равно нужно удалить графику. Поскольку мы обернули нашу графику в наш клип плиток , мы можем легко удалить их. На самом деле, функция выше удаляет плитки сами.
1
2
3
4
5
|
if(getChildByName(«tileHolder»)!=null){
removeChild(getChildByName(«tileHolder»));
}
var tiles:MovieClip = new MovieClip();
tiles.name = «tileHolder»;
|
Это фрагмент из функции buildLevel () , которую вы уже написали. Я дал плиткам имя «tileHolder», потому что это более простой способ ссылаться на него в списке отображения. Функция buildLevel проверяет, есть ли на экране экземпляр «tileHolder», и, если он есть, удаляет его. Затем он делает еще один фрагмент плитки, чтобы провести следующий раунд графики. Мы могли бы разделить это на две разные функции, одну для уничтожения и одну для создания уровня, но сейчас лучше сохранить компактность.
Шаг 7: Создание и экспорт графики игрока
Итак, у нас есть мозаичная графика, и мы можем изменить данные и увидеть их. Но как мы можем взаимодействовать с окружающей средой? Сначала нам нужно сделать клип для игрока . Мы собираемся сделать это 10х10 и экспортировать его с именем класса «player».
Не стесняйтесь рисовать что-то, кроме коробки!
Шаг 7: Создание класса игрока
Нам нужно сделать класс для нашего игрока . Мы пока оставим функции пустыми, а потом расширим их. Если вы посмотрите, то увидите, что мы не создали событие ENTER_FRAME. Я считаю, что лучше всего создать только одну ENTER_FRAME и вызывать функции для других объектов, для чего мы хотим быть ENTER_FRAME.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package {
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class player extends MovieClip {
public var gameLay:Array = new Array();
public var ySpeed:Number = 0;
public var xSpeed:Number = 5;
public var friction:Number = .7;
public var grav:Number = .5;
public function player():void {
if (stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
public function getTile(xs,ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)]
}
public function onFrameEnters():void {
}
public function jump(e:MouseEvent){
}
private function init(e:Event=null):void {
stage.addEventListener(MouseEvent.CLICK,jump);
}
}
}
|
Мы разберем это в следующих шагах.
Шаг 8: Понимание физики платформера
Прежде чем мы заставим нашего игрока взаимодействовать с окружающей средой, я думаю, что мы должны сначала рассмотреть, как работают платформеры. В большинстве платформеров игрок подпрыгивает, а затем падает. Когда они касаются земли, они теряют скорость и отталкиваются от земли. Чтобы проиллюстрировать это с помощью psuedocode :
1
2
3
4
5
6
7
8
|
while(player.isCollidingWith(ground)){
player.y—;
player.ySpeed = 0;
}
ySpeed+=gravity;
if(player.press(jumpButton)){
ySpeed = -10;
}
|
Это упрощенный способ взглянуть на платформеры, но он дает вам основы. Я действительно не рассматривал платформеры в этом уроке, поэтому я не буду углубляться в эту тему. Это вся логика, которая нам нужна, чтобы заставить нашего игрока работать.
Шаг 9: Проверка положения плиток
Поскольку все визуальные элементы нашей игры основаны на массивах, мы можем использовать массивы для проверки столкновений, а не полагаться на функции Flash hitTest. Мы собираемся создать собственную функцию для обработки коллизий на основе данных плитки:
1
2
3
|
public function getTile(xs,ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)]
}
|
Это наша функция, взятая из класса игрока , и это действительно так просто. Функция принимает два параметра: xs для переменных x и ys для переменных y. Поскольку наши плитки имеют размер 60×60, если мы разделим переменные x и y на 60, а затем округлим их вниз, мы получим «положение» переменных x и y относительно массива. Затем функция возвращает любое значение плитки, находящееся в этой позиции в массиве. Мы можем использовать эту функцию, как если бы мы использовали hitTest сейчас, за исключением того, что эта функция намного быстрее и не использует визуальные эффекты. Давайте идти вперед и интегрировать его в игру.
Шаг 10: Интеграция функции getTile
Итак, теперь у нас есть функция getTile , которая может получить значения плитки для нас. И мы знаем, что каждый раз, когда 1 появляется в массиве, это будет твердое основание. Давайте посмотрим, как выглядит класс player, когда мы интегрируем функцию getTile и добавляем платформенную физику.
01
02
03
04
05
06
07
08
09
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
package {
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class player extends MovieClip {
public var gameLay:Array = new Array();
public var ySpeed:Number = 0;
public var xSpeed:Number = 5;
public var friction:Number = .7;
public var grav:Number = .5;
public function getTile(xs,ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)]
}
public function onFrameEnters():void { //we’ll call this in Main’s ENTER_FRAME handler
xSpeed*=friction;
ySpeed+=grav;
x+=xSpeed;
y+=ySpeed;
//here we are moving the player left or right based on the mouse position
if(stage.mouseX>x+10){
xSpeed = 6
}
if(stage.mouseX<x-10){
xSpeed = -6
}
//next group of while statements are equivalent, together, to our pseudocode’s
//»while(player.isCollidingWith(ground))»
while(getTile(x,y) == 1){
y-=.5;
ySpeed = 0;
}
while(getTile(x,y-10) == 1){
y+=.5;
ySpeed = 0;
}
while(getTile(x+5,y-5) == 1){
x-=.5;
}
while(getTile(x-5,y-5) == 1){
x+=.5;
}
}
//when the player clicks, the character jumps
public function jump(e:MouseEvent){
if(getTile(x,y+2) == 1){
ySpeed = -10;
}
}
public function player():void {
if (stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(e:Event=null):void {
stage.addEventListener(MouseEvent.CLICK,jump);
}
}
}
|
Мы используем getTile в областях, где мы будем использовать hitTests. У нас есть стены, интегрированные в платформерный двигатель. Это обрабатывает всю нашу физику платформера, и нам нужно только прикоснуться к классу игрока еще раз. Мы можем передать массив gameLay игроку через класс документа.
Шаг 11: Защита от ошибочных проверок
Что произойдет, если игрок выйдет из игровой площадки? Данные тайла только 10×5, что соответствует размеру экрана. Если наша функция getTile получает значение выше 600 или ниже 0 для значения x, она выдаст ошибку, потому что эта область не существует в данных тайла. (601/60) = 10,1111, округлите его до 10. Значения в массиве — от 0 до 9, поскольку массивы начинают считать с 0.
Мы должны быть уверены, что в любой момент, когда что-то выйдет за пределы игровой границы, оно будет зарегистрировано как прочное основание. мы можем сделать это легко, изменив функцию getTile .
1
2
3
4
5
6
|
public function getTile(xs,ys){
if(ys>0 && xs>0 && (gameLay[0].length)*60>xs && (gameLay.length)*60>ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)];
}else{
return 1;
}
|
Это гарантирует, что все, что находится за пределами массива, будет зарегистрировано как твердая земля. Теперь вы можете передать массив, полный 0, игроку, и движок сможет справиться сам.
Шаг 12: Размещение персонажа на экране
Итак, наш персонаж весь закодирован, но когда мы показываем его на экране, он ничего не делает. Это потому, что мы на самом деле не вызвали ни одной из функций, необходимых для его перемещения. Мы можем добавить обработчик события ENTER_FRAME в наш класс документа и использовать его:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private function init(e:Event=null):void {
buildLevel(gameLay);
addEventListener(Event.ENTER_FRAME, onFrameEnter);
var p1:player = new player();
p1.name = «p1»
p1.x = 300
p1.y = 100
p1.gameLay = gameLay;
addChild(p1);
}
public function onFrameEnter(e:Event=null):void {
for(var i:int = numChildren-1; i >= 0; i—){
if(getChildAt(i) is player){
MovieClip(getChildAt(i)).onFrameEnters();
}
}
}
|
Это может потребовать много усилий, поэтому давайте сначала рассмотрим функцию init (). В функцию init () мы добавляем обработчик события ENTER_FRAME, который вызывает функцию onFrameEnter () каждый кадр. Кроме того, мы добавляем плеер в кадр с помощью функции init () . Размещаем плеер посередине экрана. В дополнение к этому мы делаем GameLay игрока (который является картой) таким же, как gameLay, используемый классом документа.
В функции onFrameEnter () у нас есть цикл. В цикле мы просматриваем каждого ребенка в списке отображения. Каждый дочерний элемент в списке отображения для определенного объекта имеет определенный порядковый номер в зависимости от того, когда он был добавлен на сцену, и других факторов. Числовые индексы для детей никогда не превысят количество детей, которое имеет объект. Поскольку номера индекса начинаются с 0, мы инициализируем наш цикл в numChildren-1. Когда тогда убедитесь, что i
не опускаюсь ниже 0 и продолжаем наш цикл.
Поскольку мы перебираем каждого ребенка на сцене, мы можем проверить и посмотреть, принадлежат ли определенные дети к определенному классу. После этого мы можем вызывать функции для них, как мы это делали для игрока. Этот метод может показаться сложным, но как только вы его поймете и настроите, он сэкономит немало системных ресурсов по сравнению с выполнением нескольких функций ENTER_FRAME. Если бы у нас было больше классов, мы могли бы добавить их в оператор if
чтобы гарантировать, что каждый объект, к которому мы стремимся, был затронут.
Шаг 13: размещение с использованием данных плитки
Что произойдет, если мы хотим, чтобы игрок вышел на сцену на основе числа в gameLay , а не поместил его, используя прямые значения x и y? Ну, мы можем просто добавить значение в массив, который обрабатывает, где будет появляться игрок, а затем изменить функцию buildLevel () Майна, чтобы обработать это:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public function buildLevel(s:Array){
if(getChildByName(«tileHolder»)!=null){
removeChild(getChildByName(«tileHolder»));
}
var tiles:MovieClip = new MovieClip();
tiles.name = «tileHolder»;
for(var i=0; i<s[0].length;i++){
for(var o=0; o<s.length;o++){
if(int(s[o][i]) == 2){
MovieClip(getChildByName(«p1»)).x = 30+(i*60);
MovieClip(getChildByName(«p1»)).y = 30+(o*60);
}else{
var currentTile:Tiles = new Tiles();
currentTile.x = i*60
currentTile.y = o*60
currentTile.name = i+»-«+o
tiles.addChild(currentTile);
currentTile.gotoAndStop(int(s[o][i])+1);
}
}
}
addChild(tiles);
}
|
Теперь, если бы мы поместили 2 в массив gameLay , это поместило бы игрока в эту позицию в массиве. Мы должны помнить, что он не поместит игрока непосредственно в центр плитки, как мы хотим, поэтому нам нужно добавить несколько чисел-модификаторов, чтобы поместить его в центр (следовательно, добавить 30 к координатам x и y).
Шаг 14: Понимание 3D-массивов
Много раз, когда вы делаете игру, вы хотите иметь более одного уровня. Что произойдет, если мы хотим, чтобы наш игрок мог переключаться между уровнями? Сейчас нам нужно будет хранить несколько уровней данных, и один из самых простых способов сделать это — трехмерный массив.
Если одномерный массив представляет собой линию, а двумерный массив представляет собой квадрат, тогда трехмерный массив представляет собой куб. 3D-массив может содержать несколько 2D-массивов, и поскольку наш уровень является 2D-массивом, мы можем использовать 3D-массив для хранения нескольких карт. Существует множество способов хранения нескольких уровней, заполненных данными листов, один из моих любимых — XML, но трехмерные массивы — самые простые. Нам нужно будет изменить некоторые из наших функций, хотя:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public var gameLay:Array = [[[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,2,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]],
[[1,1,1,1,1,1,1,1,1,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,2,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]]];
private function init(e:Event=null):void {
buildLevel(gameLay[0]);
addEventListener(Event.ENTER_FRAME, onFrameEnter);
var p1:player = new player();
p1.name = «p1»
p1.x = 300
p1.y = 100
p1.gameLay = gameLay[0];
addChild(p1);
}
|
Теперь у нас есть трехмерный массив, который содержит два разных уровня. Если мы хотим переключаться между уровнями, это простой процесс: мы просто используем gameLay [1] вместо gameLay [0] .
Шаг 15: Переключение уровней на лету
То, как мы построили нашу систему тайлов до сих пор, не займет много времени, чтобы переключать уровни на лету. Поскольку наша функция buildLevel обрабатывает всю бэкэнд-работу, изменение уровней так просто:
1
2
|
buildLevel(gameLay[0]);
buildLevel(gameLay[1]);
|
Как вы применяете этот метод для переключения уровней, зависит от вас. На данный момент мы собираемся вернуться к одному 2D-массиву для этого.
Шаг 16: Понимание многократного использования
На данный момент у нас есть этот движок плиток, настроенный для работы с платформером, но для таких движков существует множество различных применений.
Когда вы смотрите на некоторые профессионально разработанные игры, вы можете увидеть приложения движка плитки. Как мы уже продемонстрировали в Zelda, во многих играх используются движки тайлов.
Слева направо показаны игры «Достаточно водопроводчиков», «Украшенные драгоценностями» и «Время Fukc». Каждая игра показывает сетку, что означает, что плитки были использованы для создания игр. Как только вы полностью поймете, как манипулировать данными тайлов, вы можете начать выполнять сложные проверки шаблонов, подобные тем, которые сделаны в Bejeweled для головоломок, что является нашей следующей темой.
Шаг 17: Простая игра-головоломка
Давайте на мгновение отбросим идею платформера и представим, что мы хотим сделать игру-головоломку. Нечто простое Допустим, мы хотим сделать так, чтобы, если все плитки на экране были черными, вы выиграли. Вы сможете добавить или удалить блок в зависимости от положения мыши и нажатия клавиши пробела.
Есть два шага для этого. Нам нужно создать функцию, которая позволяет нам менять блоки, затем нам нужно написать функцию, чтобы проверить и посмотреть, все ли блоки на экране есть. Когда мы говорим, что блоки «есть», это означает, что плитка черная.
01
02
03
04
05
06
07
08
09
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
import Tiles;
import player;
public class Main extends MovieClip {
public var gameLay:Array = [[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]]
public function getTile(xs,ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)]
}
public function onFrameEnter(e:Event=null):void {
for(var i:int = numChildren-1; i >= 0; i—){
if(getChildAt(i) is player){
MovieClip(getChildAt(i)).onFrameEnters();
}
}
}
public function Main():void {
if (stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(e:Event=null):void {
buildLevel(gameLay);
stage.addEventListener(KeyboardEvent.KEY_DOWN,clicks);
addEventListener(Event.ENTER_FRAME, onFrameEnter);
}
public function buildLevel(s:Array){
if(getChildByName(«tileHolder»)!=null){
removeChild(getChildByName(«tileHolder»));
}
var tiles:MovieClip = new MovieClip();
tiles.name = «tileHolder»;
for(var i=0; i<s[0].length;i++){
for(var o=0; o<s.length;o++){
var currentTile:Tiles = new Tiles();
currentTile.x = i*60
currentTile.y = o*60
currentTile.name = i+»-«+o
tiles.addChild(currentTile);
currentTile.gotoAndStop(int(s[o][i])+1);
}
}
addChild(tiles);
}
private function clicks(e:KeyboardEvent):void {
if(e.keyCode== 32){
if(getTile(stage.mouseX, stage.mouseY)==1){
gameLay[Math.floor(stage.mouseY/60)][Math.floor(stage.mouseX/60)] = 0;
MovieClip(MovieClip(getChildByName(«tileHolder»)).getChildByName(Math.floor(stage.mouseX/60)+»-«+Math.floor(stage.mouseY/60))).gotoAndStop(1);
}else{
gameLay[Math.floor(stage.mouseY/60)][Math.floor(stage.mouseX/60)] = 1;
MovieClip(MovieClip(getChildByName(«tileHolder»)).getChildByName(Math.floor(stage.mouseX/60)+»-«+Math.floor(stage.mouseY/60))).gotoAndStop(2);
}
}
var foundZero:Boolean = false;
for(var i=0; i<gameLay[0].length;i++){
for(var o:int = 0; o < gameLay.length; o++){
if(int(gameLay[o][i]) == 0){
foundZero = true;
}
}
}
if(!foundZero){
trace(«won»);
}
}
}
}
|
Для ясности, это весь класс документов, необходимый для создания игры, которую мы обсуждали. Обратите внимание, насколько он похож на наш платформерный код. В функцию init()
мы добавили прослушиватель событий для нажатия клавиш; запускает функцию clicks()
, которая обрабатывает изменение самих плиток. Давайте разберем эту новую функцию.
Сначала мы проверяем, чтобы клавиша была пробелом, проверяя код клавиши. Поскольку мы проверяем положение плиток с помощью мыши и функции getTile, все, что нам нужно сделать, это проверить и посмотреть, равен ли какой-либо элемент мозаики в этой точке карты единица или ноль, чтобы выяснить, что нужно изменить. к.
Затем мы изменим сами данные и изменим визуальное отображение плиток для отображения данных. Требуется много времени для индивидуального таргетинга плиток, но поскольку мы назвали каждую плитку на основе их описания, мы можем получить к ним доступ, используя метод getChildByName.
Как только мы закончим менять плитки, нам нужно проверить и посмотреть, все ли плитки на экране черные. Мы делаем это, просматривая данные и проверяя, появляются ли какие-либо нули. Если никакие нули не появляются, мы знаем, что все равно 1 (потому что наши данные используют только 1 и 0).
Так что у нас это. Очень простая игра-головоломка, которая использует данные плитки. Если бы мы хотели, мы могли бы делать сложные проверки, такие как украшенные драгоценными камнями или другие подобные головоломки, чтобы найти определенные комбинации плиток.
Шаг 18: объединение элементов из обеих игр
Что если бы нам нужен платформер, где вы могли бы легко создавать и разрушать землю, используя пробел? Мы могли бы сделать это легко, просто комбинируя две разные игры, которые мы сделали, простым способом.
Шаг 19: Платформер
Давайте создадим платформер, который позволяет создавать и уничтожать данные. Вот код:
01
02
03
04
05
06
07
08
09
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
import Tiles;
import player;
public class Main extends MovieClip {
public var gameLay:Array = [[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]]
public function getTile(xs,ys){
return gameLay[Math.floor(ys/60)][Math.floor(xs/60)]
}
public function onFrameEnter(e:Event=null):void {
for(var i:int = numChildren-1; i >= 0; i—){
if(getChildAt(i) is player){
MovieClip(getChildAt(i)).onFrameEnters();
}
}
}
public function Main():void {
if (stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(e:Event=null):void {
buildLevel(gameLay);
stage.addEventListener(KeyboardEvent.KEY_DOWN,clicks);
addEventListener(Event.ENTER_FRAME, onFrameEnter);
var p1:player = new player();
p1.name = «p1»
p1.x = 300
p1.y = 100
p1.gameLay = gameLay;
addChild(p1);
}
public function buildLevel(s:Array){
if(getChildByName(«tileHolder»)!=null){
removeChild(getChildByName(«tileHolder»));
}
var tiles:MovieClip = new MovieClip();
tiles.name = «tileHolder»;
for(var i=0; i<s[0].length;i++){
for(var o=0; o<s.length;o++){
var currentTile:Tiles = new Tiles();
currentTile.x = i*60
currentTile.y = o*60
currentTile.name = i+»-«+o
tiles.addChild(currentTile);
currentTile.gotoAndStop(int(s[o][i])+1);
}
}
addChild(tiles);
}
private function clicks(e:KeyboardEvent):void {
if(e.keyCode == 32){
if(!(Math.floor(stage.mouseX/60) == Math.floor(MovieClip(getChildByName(‘p1’)).x/60)&&Math.floor(stage.mouseY/60) == Math.floor((MovieClip(getChildByName(‘p1’)).y-10)/60))){
if(getTile(stage.mouseX, stage.mouseY)==1){
gameLay[Math.floor(stage.mouseY/60)][Math.floor(stage.mouseX/60)] = 0 ;
MovieClip(MovieClip(getChildByName(«tileHolder»)).getChildByName(Math.floor(stage.mouseX/60)+»-«+Math.floor(stage.mouseY/60))).gotoAndStop(1);
}else{
gameLay[Math.floor(stage.mouseY/60)][Math.floor(stage.mouseX/60)] = 1 ;
MovieClip(MovieClip(getChildByName(«tileHolder»)).getChildByName(Math.floor(stage.mouseX/60)+»-«+Math.floor(stage.mouseY/60))).gotoAndStop(2);
}
}
}
}
}
}
|
Мы добавляем игрока обратно в игру и меняем функцию clicks () . Мы изменили его, чтобы гарантировать, что плитки, содержащие игрока, не могут быть заполнены. Делая эти простые изменения, мы можем объединить два движка, чтобы создать движок, который работает хорошо.
Шаг 20: бесконечное использование
Как указывалось ранее, у плиточных двигателей есть множество способов использования. Логические игры, платформеры. Вы можете создать несколько слоев плиток для создания фонов, и как только вы начнете создавать сложные игры, вам придется создавать свои собственные редакторы карт для обработки и создания данных плиток для ваших игр. Лучший способ научиться — это экспериментировать. Напишите свои собственные движки плиток, выясните, что лучше всего подходит для ваших игр и приложений, и выясните, что вам нужно сделать, чтобы заставить работать то, что вы хотите.
Спасибо за прочтение!