Это второе из двух руководств, в которых подробно рассказывается, как использовать Flixel для создания Frogger для Интернета и AIR на Android.
Вступление
Добро пожаловать во второй из двух руководств, в которых подробно рассказывается, как создать Frogger для Интернета и AIR на Android. В предыдущей части я говорил о том, как настроить Flixel, изменить состояния, и мы создали первый уровень для Frogger. К сожалению, мы мало что можем сделать с нашей игрой без игрока. В этом руководстве будут рассмотрены основы движения, обнаружения столкновений, управления состоянием игры и развертывания нашего кода на Android. Есть много чего, так что давайте начнем!
Окончательный результат предварительного просмотра
Вот предварительный просмотр того, что мы будем строить в этом уроке:
Шаг 1: Создание плеера
Наш игрок представляет последний актерский класс, который нам нужно построить. Создайте класс Frog
и настройте его следующим образом:
Вам также нужно добавить следующий код в наш новый класс:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package
com.flashartofwar.frogger.sprites
{
import com.flashartofwar.frogger.enum.GameStates;
import com.flashartofwar.frogger.states.PlayState;
import flash.geom.Point;
import org.flixel.FlxG;
import org.flixel.FlxSprite;
public class Frog extends FlxSprite
{
private var startPosition:Point;
private var moveX:int;
private var maxMoveX:int;
private var maxMoveY:int;
private var targetX:Number;
private var targetY:Number;
private var animationFrames:int = 8;
private var moveY:Number;
private var state:PlayState;
public var isMoving:Boolean;
/**
* The Frog represents the main player’s character.
* and some special collision logic for the Frog.
*
* @param X start X
* @param Y start Y
*/
public function Frog(X:Number, Y:Number)
{
super(X, Y);
// Save the starting position to be used later when restarting
startPosition = new Point(X, Y);
// Calculate amount of pixels to move each turn
moveX = 5;
moveY = 5;
maxMoveX = moveX * animationFrames;
maxMoveY = moveY * animationFrames;
// Set frog’s target x,y to start position so he can move
targetX = X;
targetY = Y;
// Set up sprite graphics and animations
loadGraphic(GameAssets.FrogSpriteImage, true, false, 40, 40);
addAnimation(«idle» + UP, [0], 0, false);
addAnimation(«idle» + RIGHT, [2], 0, false);
addAnimation(«idle» + DOWN, [4], 0, false);
addAnimation(«idle» + LEFT, [6], 0, false);
addAnimation(«walk» + UP, [0,1], 15, true);
addAnimation(«walk» + RIGHT, [2,3], 15, true);
addAnimation(«walk» + DOWN, [4,5], 15, true);
addAnimation(«walk» + LEFT, [6,7], 15, true);
addAnimation(«die», [8, 9, 10, 11], 2, false);
// Set facing direction
facing = FlxSprite.UP;
// Save an instance of the PlayState to help with collision detection and movement
state = FlxG.state as PlayState;
}
/**
* This manage what direction the frog is facing.
*
* @param value
*/
override public function set facing(value:uint):void
{
super.facing = value;
if (value == UP || value == DOWN)
{
width = 32;
height = 25;
offset.x = 4;
offset.y = 6;
}
else
{
width = 25;
height = 32;
offset.x = 6;
offset.y = 4;
}
}
/**
* The main Frog update loop.
*/
override public function update():void
{
//Default object physics update
super.update();
}
/**
* Simply plays the death animation
*/
public function death():void
{
}
/**
* This resets values of the Frog instance.
*/
public function restart():void
{
}
/**
* This handles moving the Frog in the same direction as any instance it is resting on.
*
* @param speed the speed in pixels the Frog should move
* @param facing the direction the frog will float in
*/
public function float(speed:int, facing:uint):void
{
}
}
}
|
Я не собираюсь проходить весь код, так как он прокомментирован, но вот несколько ключевых моментов, которые нужно проверить. Сначала мы устанавливаем все значения для движения лягушки в конструкторе вместе с его анимацией. Затем мы создаем установщик для обработки изменения ориентации лягушки при смене направления. Наконец, у нас есть несколько вспомогательных методов для управления циклом обновления, смертью, перезапуском и перемещением.
Теперь мы можем добавить нашего игрока на игровой уровень. Откройте класс PlayState
и поместите следующий код между журналами и машинами, которые мы настроили в первой части.
1
2
|
// Create Player
player = add(new Frog(calculateColumn(6), calculateRow(14) + 6)) as Frog;
|
Вам также нужно будет импортировать класс Fog и добавить следующее свойство:
1
|
private var player:Frog;
|
Важно расположить игрока на правильном уровне глубины в игре. Он должен быть выше бревен и черепах, но под машинами, поэтому, когда его сбивают, он не появляется на крыше машины. Теперь мы можем проверить, что наш игрок находится на уровне, перекомпилировав игру. Вот что вы должны увидеть:
Пока мы не добавим некоторые элементы управления клавиатурой, мы не сможем ничего сделать с плеером, поэтому давайте перейдем к следующему шагу.
Шаг 2: Управление с клавиатуры
В Frogger игрок может двигаться влево, вправо, вверх и вниз. Каждый раз, когда игрок движется, лягушка прыгает на следующую позицию. В обычных играх на основе плиток это легко настроить. Мы просто придумываем следующую плитку для перемещения и добавляем к значению x или y, пока она не достигнет нового пункта назначения. Однако Frogger добавляет некоторую сложность движению, когда дело доходит до бревен и черепах, на которых вы можете плавать. Поскольку эти объекты не движутся в соответствии с сеткой, нам нужно уделить дополнительное внимание тому, как работают границы нашего уровня.
Давайте начнем с основных элементов управления. Откройте класс Frog
и добавьте следующий блок кода в наш метод update()
перед super.update()
:
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
|
if (state.gameState == GameStates.PLAYING)
{
// Test to see if TargetX and Y are equal.
if (x == targetX && y == targetY)
{
// Checks to see what key was just pressed and sets the target X or Y to the new position
// along with what direction to face
if ((FlxG.keys.justPressed(«LEFT»)) && x > 0)
{
targetX = x — maxMoveX;
facing = LEFT;
}
else if ((FlxG.keys.justPressed(«RIGHT»)) && x < FlxG.width — frameWidth)
{
targetX = x + maxMoveX;
facing = RIGHT;
}
else if ((FlxG.keys.justPressed(«UP»)) && y > frameHeight)
{
targetY = y — maxMoveY;
facing = UP;
}
else if ((FlxG.keys.justPressed(«DOWN»)) && y < 560)
{
targetY = y + maxMoveY;
facing = DOWN;
}
// See if we are moving
if (x != targetX || y != targetY)
{
//Looks like we are moving so play sound, flag isMoving and add to score.
FlxG.play(GameAssets.FroggerHopSound);
// Once this flag is set, the frog will not take keyboard input until it has reacged its target
isMoving = true;
}
else
{
// Nope, we are not moving so flag isMoving and show Idle.
isMoving = false;
}
}
// If isMoving is true we are going to update the actual position.
if (isMoving == true)
{
if (facing == LEFT)
{
x -= moveX;
} else if (facing == RIGHT)
{
x += moveX;
} else if (facing == UP)
{
y -= moveY;
} else if (facing == DOWN)
{
y += moveY;
}
// Play the walking animation
play(«walk» + facing);
}
else
{
// nothing is happening so go back to idle animation
play(«idle» + facing);
}
}
|
Здесь много кода, поэтому давайте пройдемся по нему блок за блоком, начиная с нашего первого условия. Нам нужен способ узнать, настроено ли игровое состояние на игру. Если вы посмотрите в конец конструктора класса Frog
, то увидите, что мы сохраняем ссылку на текущее состояние воспроизведения в локальной переменной. Это хитрый прием, который мы можем использовать, чтобы помочь нашим игровым объектам прочитать состояние игры из активного FlxState. Поскольку мы знаем, что лягушка всегда будет использоваться в PlayState
можно с уверенностью предположить, что для правильного состояния игры установлено PlayState
. Теперь, когда у нас есть этот FlxState, мы можем проверить фактическое состояние игры, прежде чем делать что-либо в FrogClass
.
Я просто хочу потратить секунду, чтобы прояснить терминологию. Слово « государство» имеет несколько коннотаций в нашей игре FlxFrogger. Существуют FlxStates
представляют текущий экран или отображение игры, а затем состояние игры. Состояние игры представляет собой фактическую активность, в которой в данный момент находится игра. Каждое из состояний игры можно найти в классе GameState
внутри пакета com.flashartofwar.frogger.enum
. Я сделаю все возможное, чтобы два использования состояния были как можно более ясными.
Итак, вернемся к нашей лягушке. Если игровое состояние установлено как «играющий», то можно безопасно обнаружить любой запрос движения для лягушки, а также обновить ее анимацию. Позже вы увидите, как мы справляемся со смертельными анимациями и замораживаем игрока на основе столкновений. Наш первый блок кода определяет, достигла ли Frog цели targetX и targetY. Второй блок кода фактически обрабатывает увеличение значений x и y лягушки.
Теперь мы можем поговорить о том, как на самом деле двигать лягушку. Это следующее условие внутри блока targetX / Y. После того, как было обнаружено нажатие клавиши и установлены новые значения targetX / Y, мы можем сразу же подтвердить, что мы движемся. Если это так, нам нужно воспроизвести звук лягушачьего прыжка и установить для флага frog isMoving
значение true. Если эти значения не меняются, мы не движемся. После того, как это установлено, мы можем обработать логику движения в последнем условии.
Наконец, мы проверяем, isMoving
ли isMoving
и видим, в каком направлении нам нужно двигаться, основываясь на том, в какую сторону смотрит лягушка. Лягушка может двигаться только в одном направлении за один раз, поэтому ее очень легко настроить. Мы также вызываем метод play()
для анимации лягушки. Последнее, что вы должны знать, это как мы рассчитываем значения moveX
и moveY
. Если вы заглянете в конструктор, то увидите, что мы определим, что лягушка переместится на X пикселей по Y кадрам. В этом случае мы хотим, чтобы наша анимация длилась 8 кадров, поэтому для того, чтобы каждый раз перемещать нужные нам 40 пикселей, мы будем двигаться на 5 пикселей каждый кадр на 8 кадров. Вот так мы получаем плавную анимацию прыжка и не позволяем игроку постоянно нажимать клавиши и прерывать анимацию.
Шаг 3: Обнаружение столкновения автомобиля
Flixel обрабатывает всю логику столкновения, которая нам понадобится. Это значительно FlxU
время, и все, что вам нужно сделать, это попросить класс FlxU
обнаружить любые перекрывающиеся спрайты. Что хорошего в этом методе, так это то, что вы можете тестировать FlxSprites или FlxSpriteGroups. Если вы помните, мы настроили наши автомобили в отдельную carGroup
что позволит невероятно легко проверить, столкнулись ли они с лягушкой. Чтобы начать, нам нужно открыть PlayState
и добавить следующий код:
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
|
/**
* This is the main game loop.
*/
override public function update():void
{
if (gameState == GameStates.GAME_OVER)
{
}
else if (gameState == GameStates.LEVEL_OVER)
{
}
else if (gameState == GameStates.PLAYING)
{
// Do collision detections
FlxU.overlap(carGroup, player, carCollision);
}
else if (gameState == GameStates.DEATH_OVER)
{
}
// Update the entire game
super.update();
}
|
Это включает в себя немного больше логики состояния ядра игры, которую мы будем заполнять на следующих нескольких шагах, но давайте посмотрим на код под комментарием «Делать обнаружение столкновений». Как видите, у нас есть новый класс FlxU
который помогает управлять обнаружением столкновений в игре, как я уже упоминал выше. Этот класс принимает targetA, targetB и метод обратного вызова. Метод overlap()
проверит всех потомков targetA и потомков targetB, а затем передаст результаты в предоставленный метод обратного вызова. Теперь нам нужно добавить carCollision
вызов carCollision
в наш класс:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
/**
* This handles collision with a car.
* @param target this instance that has collided with the player
* @param player a reference to the player
*/
private function carCollision(target:FlxSprite, player:Frog):void
{
if (gameState != GameStates.COLLISION)
{
FlxG.play(GameAssets.FroggerSquashSound);
killPlayer();
}
}
|
С этим обратным вызовом мы берем несколько свобод, поскольку знаем, что первым параметром будет FlxSprite
а вторым — Frog
. Обычно это анонимный и нетипизированный метод, когда он вызывается после обнаружения коллизии. Вы заметите, что мы проверяем, что gameStates
настроен на столкновение. Мы делаем это потому, что когда происходит столкновение, вся игра останавливается, чтобы позволить анимации смерти играть, но технически лягушка все еще сталкивается с carGroup. Если бы мы не добавили это условие, мы бы застряли в бесконечном цикле, пока анимация пыталась воспроизвести.
Теперь нам просто нужно добавить логику для убийства игрока. Это будет выполнено в killPlayer()
и вот код:
1
2
3
4
5
6
7
8
|
/**
* This kills the player.
*/
private function killPlayer():void
{
gameState = GameStates.COLLISION;
player.death();
}
|
Наконец, нам нужно заполнить логику death()
в нашем классе Frog
. Добавьте следующий код в метод смерти:
1
|
play(«die»);
|
Здесь мы говорим лягушатному спрайту воспроизвести анимацию кристалла, которую мы настроили в конструкторе. Теперь скомпилируйте игру и проверьте, что игрок может столкнуться с любым из автомобилей или грузовиков.
Шаг 4: перезапуск после смерти
Теперь, когда у нас есть наш первый набор обнаружения столкновений вместе с анимацией смерти, нам нужен способ перезапустить игровой уровень, чтобы игрок мог продолжить попытки снова. Чтобы сделать это, нам нужно добавить способ уведомления игры, когда анимация смерти закончится. Давайте добавим следующее в наш метод Frog update()
чуть выше, где мы проверяем, равен ли gameState
игре:
1
2
3
4
5
6
7
|
// Test to see if the frog is dead and at the last death frame
if (state.gameState == GameStates.COLLISION && (frame == 11))
{
// Flag game state that death animation is over and game can perform a restart
state.gameState = GameStates.DEATH_OVER;
}
else
|
Обратите внимание на трейлинг, который должен встретиться с if (state.gameState == GameStates.PLAYING)
так что мы проверяем столкновение, а затем играем.
Теперь нам нужно вернуться в наш класс PlayState
и добавить следующий вызов метода к else if (gameState == GameStates.DEATH_OVER)
:
1
|
restart();
|
Теперь мы можем добавить метод перезапуска:
1
2
3
4
5
6
7
8
9
|
/**
* This handles resetting game values when a frog dies, or a level is completed.
*/
private function restart():void
{
// Change game state to Playing so animation can continue.
gameState = GameStates.PLAYING;
player.restart();
}
|
Последнее, что нам нужно сделать, это добавить этот код в метод restart
класса Frog
.
1
2
3
4
5
6
7
8
|
isMoving = false;
x = startPosition.x;
y = startPosition.y;
targetX = startPosition.x;
targetY = startPosition.y;
facing = UP;
play(«idle» + facing);
if (!visible) visible = true;
|
Теперь мы должны быть готовы скомпилировать и проверить, что все это работает. Вот что вы должны увидеть: когда машина врезается в игрока, все останавливается, пока воспроизводится анимация смерти. Когда анимация закончится, все должно перезапуститься, и игрок снова появится внизу экрана. Как только вы это настроите, мы готовы приступить к обнаружению столкновения с водой.
Шаг 5: Обнаружение столкновения с водой
При наличии надежной системы для обработки столкновений, анимации смерти и перезапуска ее будет действительно легко добавить на следующих нескольких этапах обнаружения столкновений. Однако обнаружение воды немного отличается. Так как мы всегда знаем, где находится вода, мы можем проверить положение игрока на y. Если значение y игрока больше, чем там, где заканчивается земля, мы можем предположить, что игрок столкнулся с водой. Мы делаем это, чтобы сократить количество обнаруженных столкновений, которое нам нужно было бы сделать, если бы мы проверяли каждое открытое пространство воды, когда лягушка приземляется. Давайте добавим следующее в наш PlayState
где мы тестируем автомобиль на столкновение:
01
02
03
04
05
06
07
08
09
10
11
12
|
// If nothing has collided with the player, test to see if they are out of bounds when in the water zone
if (player.y < waterY)
{
if (!player.isMoving && !playerIsFloating)
waterCollision();
if ((player.x > FlxG.width) || (player.x < -TILE_SIZE ))
{
waterCollision();
}
}
|
Вам также нужно будет добавить два свойства: первое позволяет нам узнать, где начинается вода на оси Y, а другое — логическое значение, чтобы сообщить нам, если игрок плывет. Мы будем использовать это плавающее логическое значение на следующем шаге.
1
2
|
private var waterY:int = TILE_SIZE * 8;
private var playerIsFloating:Boolean;
|
Определить начальную координату воды легко, учитывая, что все является частью сетки. Далее нам нужно добавить наш waterCollision()
:
01
02
03
04
05
06
07
08
09
10
11
|
/**
* This is called when the player dies in water.
*/
private function waterCollision():void
{
if (gameState != GameStates.COLLISION)
{
FlxG.play(GameAssets.FroggerPlunkSound);
killPlayer();
}
}
|
Скомпилируйте и проверьте, что если мы пойдем в воду, игрок умрет.
Далее мы рассмотрим, как позволить лягушке плавать на бревнах и черепахах, когда они находятся в пределах акватории уровня.
Шаг 6: Обнаружение плавающего столкновения
Чтобы выяснить, может ли лягушка плавать, нам нужно проверить наличие столкновений в logGroup
или turtleGroup
. Давайте добавим следующий код в наш класс PlayState
прямо под тем местом, где мы тестируем автомобиль на столкновение. Убедитесь, что оно выше условного столкновения с водой. Это важно, потому что, если мы проверяем игрока в воде перед бревнами или черепахами, мы никогда не сможем правильно справиться с плаванием.
1
2
|
FlxU.overlap(logGroup, player, float);
FlxU.overlap(turtleGroup, player, turtleFloat);
|
Вот два метода, которые нам нужны для обработки столкновения:
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
|
/**
* This handles floating the player sprite with the target it is on.
* @param target this is the instance that collided with the player
* @param player an instance of the player
*/
private function float(target:WrappingSprite, player:Frog):void
{
playerIsFloating = true;
if (!(FlxG.keys.LEFT || FlxG.keys.RIGHT))
{
player.float(target.speed, target.facing);
}
}
/**
* This is called when a player is on a log to indicate the frog needs to float
* @param target this is the instance that collided with the player
* @param player an instance of the player
*/
private function turtleFloat(target:TimerSprite, player:Frog):void
{
// Test to see if the target is active.
// is in the water
if (target.isActive)
{
float(target, player);
}
else if (!player.isMoving)
{
waterCollision();
}
}
|
Вам также нужно будет импортировать WrappingSprite
и TimerSprite
. Как только вы это сделаете, нам нужно вернуться в наш класс Frog
и добавить следующее в наш метод float()
:
1
2
3
4
5
6
|
if (isMoving != true)
{
x += (facing == RIGHT) ?
targetX = x;
isMoving = true;
}
|
Мы просто добавили много кода, и я прокомментировал большую его часть, но я хотел поговорить об этой последней части прямо здесь. Этот код на самом деле обрабатывает перемещение лягушки в том же направлении с той же скоростью, что и бревно или черепаха, на которой игрок находится. Мы используем несколько трюков, чтобы убедиться, что когда игрок пытается сдвинуться с плавающего объекта, он не будет переопределен тем, на чем он плавает. Большая часть разработки игр — управление состоянием, и использование флагов, таких как isMoving
чтобы помочь другим частям кода знать, что можно и что нельзя делать, — огромная помощь.
Давайте скомпилируем игру и проверим, может ли игрок плавать по логам.
Вы могли заметить одну вещь: когда вы приземлитесь на бревно или черепаху, вы больше не утонете. Это потому, что нам нужно сбросить флаг playerIsFloating
прежде чем мы сделаем все наши обнаружения столкновений. Вернитесь в PlayState
и добавьте следующее непосредственно перед тем, как мы начнем тестирование на автомобильное столкновение.
1
2
|
// Reset floating flag for the player.
playerIsFloating = false;
|
Итак, ваш тестовый блок должен выглядеть так:
Как я уже упоминал, существует тонкий баланс поддержания состояния и того, чтобы вы устанавливали и отменяли эти флаги состояния в нужное время, чтобы избежать большого разочарования при создании собственных игр. Теперь вы можете сделать новую компиляцию, чтобы убедиться, что все работает правильно, и мы можем перейти к следующему шагу.
Шаг 7: Обнаружение столкновения дома
Добавление в обнаружение столкновений для домашних баз должно быть очень прямым. Добавьте следующий тест столкновения в конец кода обнаружения столкновений в нашем классе PlayState
и выше, где мы проверяем, прыгнул ли игрок в воду:
1
|
FlxU.overlap(homeBaseGroup, player, baseCollision);
|
Теперь нам нужно создать наш baseCollision()
для обработки того, что происходит, когда вы приземляетесь на дом:
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
|
/**
* This handles collision with a home base.
* @param target this instance that has collided with the player
* @param player a reference to the player
*/
private function baseCollision(target:Home, player:Frog):void
{
// Check to make sure that we have not landed in a occupied base
if (target.mode != Home.SUCCESS)
{
// Increment number of frogs saved
safeFrogs ++;
// Flag the target as success to show it is occupied now
target.success();
}
// Test to see if we have all the frogs, if so then level has been completed.
if (safeFrogs == bases.length)
{
levelComplete();
}
else
{
restart();
}
}
|
Нам также необходимо добавить следующее свойство в наш класс:
1
|
private var safeFrogs:int = 0;
|
Здесь вы можете увидеть, что мы тестируем, чтобы увидеть, был ли дом уже засажен, затем мы проверяем, есть ли во всех домах лягушки, и, наконец, мы просто запускаем перезапуск при успешной посадке на домашней базе. Важно отметить, что в этом уроке мы не проверяем состояние дома. Помните, что есть бонусная муха и аллигатор, на которого вы могли бы приземлиться. Позже, если вы хотите добавить это в, вы можете сделать это здесь.
Теперь нам нужно добавить логику для того, когда уровень будет завершен:
01
02
03
04
05
06
07
08
09
10
11
12
|
/**
* This is called when a level is completed
*/
private function levelComplete():void
{
// Change game state to let system know a level has been completed
gameState = GameStates.LEVEL_OVER;
// Hide the player since the level is over and wait for the game to restart itself
player.visible = false;
}
|
Нам нужно добавить следующий код в метод restart()
выше, где мы сбрасываем состояние игры:
1
2
3
|
// Test to see if Level is over, if so reset all the bases.
if (gameState == GameStates.LEVEL_OVER)
resetBases();
|
Наконец, нам нужно добавить метод resetBases()
в наш класс PlayState
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
/**
* This loops through the bases and makes sure they are set to empty.
*/
private function resetBases():void
{
// Loop though bases and empty them
for each (var base:Home in bases)
{
base.empty();
}
// Reset safe frogs
safeFrogs = 0;
}
|
После того, как все базы были включены, мы перебираем их и вызываем метод empty()
который сбрасывает графическое и дополнительное значение. Наконец, нам нужно установить лягушек, которые были сохранены на ноль. Давайте скомпилируем игру и проверим, что происходит, когда вы приземляетесь на всех базах. После того как вы приземлились на домашней базе, она должна измениться на значок лягушки, указывающий, что вы уже приземлились там.
Как видите, когда мы сохранили все лягушки, уровень зависает, потому что нет логики для повторного запуска уровня. Нам также необходимо добавить некоторые игровые сообщения, чтобы игрок знал, что происходит, и использовать его в качестве системы уведомлений в игре, чтобы мы могли определить, когда нужно перезапустить все анимации. Это то, что мы добавим на следующем шаге.
Шаг 8: Добавление игровых сообщений
Многое происходит в игре, и одним из лучших способов донести до игрока то, что происходит, является добавление в игру сообщений. Это поможет проинформировать игрока о приостановке игры, чтобы активировать игровое состояние, например, завершенный уровень или смерть. В нашем классе PlayState
добавьте следующий код выше, где мы создаем наши домашние базы в методе create()
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// Create game message, this handles game over, time, and start message for player
gameMessageGroup = new FlxGroup();
gameMessageGroup.x = (480 * .5) — (150 * .5);
gameMessageGroup.y = calculateRow(8) + 5;
add(gameMessageGroup);
// Black background for message
var messageBG:FlxSprite = new FlxSprite(0, 0);
messageBG.createGraphic(150, 30, 0xff000000);
gameMessageGroup.add(messageBG);
// Message text
messageText = new FlxText(0, 4, 150, «TIME 99»).setFormat(null, 18, 0xffff00000, «center»);
gameMessageGroup.visible = false;
gameMessageGroup.add(messageText);
|
Вам также нужно будет добавить следующие свойства:
1
2
3
|
private var messageText:FlxText;
private var gameMessageGroup:FlxGroup;
private var hideGameMessageDelay:int = -1;
|
Вам также нужно будет импортировать класс FlxText
. Теперь давайте перейдем к нашему методу update()
и добавим следующий код в gameState == GameStates.LEVEL_OVER
:
1
2
3
4
5
6
7
8
|
if (hideGameMessageDelay == 0)
{
restart();
}
else
{
hideGameMessageDelay -= FlxG.elapsed;
}
|
Основная идея здесь заключается в том, что мы используем таймер для отсчета времени отображения игрового сообщения. Когда таймер достигает нуля, мы можем перезапустить уровень. Это дает игроку некоторое время для отдыха между уровнями. Также нам нужно добавить следующий блок кода чуть ниже, где мы тестируем столкновение воды вокруг линии 203:
01
02
03
04
05
06
07
08
09
10
11
|
// Manage hiding gameMessage based on timer
if (hideGameMessageDelay > 0)
{
hideGameMessageDelay -= FlxG.elapsed;
if (hideGameMessageDelay < 0) hideGameMessageDelay = 0;
}
else if (hideGameMessageDelay == 0)
{
hideGameMessageDelay = -1;
gameMessageGroup.visible = false;
}
|
Здесь мы можем управлять видимостью игрового сообщения. В нашем baseCollision()
нам нужно добавить следующий код ниже, где мы проверяем target.mode != Home.success
вокруг строки 317:
1
2
3
4
|
// Regardless if the base was empty or occupied we still display the time it took to get there
messageText.text = «TIME » + String(gameTime / FlxG.framerate — timeLeftOver);
gameMessageGroup.visible = true;
hideGameMessageDelay = 200;
|
Добавьте следующие свойства, которые мы будем использовать на следующем шаге:
1
2
|
private var gameTime:int = 0;
private var timer:int = 0;
|
Затем добавьте эту строку кода вокруг строки 328 внутри условной области, под которой мы вызываем success()
для цели:
1
|
var timeLeftOver:int = Math.round(timer / FlxG.framerate);
|
Это позволяет нам рассчитать общее время, необходимое для завершения метки, которую мы подключим на следующем шаге. Теперь в resetBases()
добавьте следующий код в конце, где мы установили safeFrogs
в 0:
1
2
3
4
|
// Set message to tell player they can restart
messageText.text = «START»;
gameMessageGroup.visible = true;
hideGameMessageDelay = 200;
|
Скомпилируйте игру, и теперь вы должны увидеть сообщения о состоянии игры, когда вы приземлитесь в доме или перезапустите уровень.
Шаг 9: Таймер игры
Теперь мы готовы добавить логику для таймера игры. В методе create()
класса PlayState
добавьте следующий код, в котором мы создаем изображение bg:
1
2
3
|
// Set up main variable properties
gameTime = 60 * FlxG.framerate;
timer = gameTime;
|
Вам понадобится следующее свойство и константа:
1
2
|
private var timeAlmostOverWarning:int;
private const TIMER_BAR_WIDTH:int = 300;
|
И ниже последней машины, добавленной в carGroup
добавьте следующее:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// Create Time text
timeTxt = new FlxText(bg.width — 70, LIFE_Y + 18, 60, «TIME»).setFormat(null, 14, 0xffff00, «right»);
add(timeTxt);
// Create timer graphic
timerBarBackground = new FlxSprite(timeTxt.x — TIMER_BAR_WIDTH + 5, LIFE_Y + 20);
timerBarBackground.createGraphic(TIMER_BAR_WIDTH, 16, 0xff21de00);
add(timerBarBackground);
timerBar = new FlxSprite(timerBarBackground.x, timerBarBackground.y);
timerBar.createGraphic(1, 16, 0xFF000000);
timerBar.scrollFactor.x = timerBar.scrollFactor.y = 0;
timerBar.origin.x = timerBar.origin.y = 0;
timerBar.scale.x = 0;
add(timerBar);
|
Вам также понадобятся следующие свойства:
1
2
3
4
5
6
7
8
|
private const LIFE_X:int = 20;
private const LIFE_Y:int = 600;
private const TIMER_BAR_WIDTH:int = 300;
private var timerBarBackground:FlxSprite;
private var timeTxt:FlxText;
private var timerBar:FlxSprite;
private var timeAlmostOverFlag:Boolean = false;
|
Теперь давайте добавим следующий код в нашу функцию обновления выше, где нам удастся скрыть gameMessage в строке 234:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
// This checks to see if time has run out.
// sine the last update.
if (timer == 0 && gameState == GameStates.PLAYING)
{
timeUp();
}
else
{
timer -= FlxG.elapsed;
timerBar.scale.x = TIMER_BAR_WIDTH — Math.round((timer / gameTime * TIMER_BAR_WIDTH));
if (timerBar.scale.x == timeAlmostOverWarning && !timeAlmostOverFlag)
{
FlxG.play(GameAssets.FroggerTimeSound);
timeAlmostOverFlag = true;
}
}
|
И этот метод вызывается, когда время истекло:
01
02
03
04
05
06
07
08
09
10
11
|
/**
* This is called when time runs out.
*/
private function timeUp():void
{
if (gameState != GameStates.COLLISION)
{
FlxG.play(GameAssets.FroggerSquashSound);
killPlayer();
}
}
|
Наконец, нам нужно сбросить таймер, когда время истекает, игрок приземляется на домашнюю базу или уровень перезапускается. Мы можем сделать это в нашем методе restart()
непосредственно перед вызовом player.restart()
:
1
2
|
timer = gameTime;
timeAlmostOverFlag = false;
|
Вы можете скомпилировать игру сейчас, чтобы проверить все это. Мы только что добавили много кода, но, надеюсь, код и комментарии достаточно просты, поэтому нам не нужно слишком много объяснять.
Шаг 10: Жизни
Какая игра будет полна без жизней? В frogger вы получаете 3 жизни. Давайте добавим следующий вызов функции в наш метод create()
в котором мы устанавливаем timeAlmostOverWarning = TIMER_BAR_WIDTH * .7
в строке 69:
1
|
createLives(3);
|
И вот методы, которые будут управлять жизнью для нас:
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
|
/**
* This loop creates X number of lives.
* @param value number of lives to create
*/
private function createLives(value:int):void
{
var i:int;
for (i = 0; i < value; i++)
{
addLife();
}
}
/**
* This adds a life sprite to the display and pushes it to teh lifeSprites array.
* @param value
*/
private function addLife():void
{
var flxLife:FlxSprite = new FlxSprite(LIFE_X * totalLives, LIFE_Y, GameAssets.LivesSprite);
add(flxLife);
lifeSprites.push(flxLife);
}
/**
* This removes the life sprite from the display and from the lifeSprites array as well.
* @param value
*/
private function removeLife():void
{
var id:int = totalLives — 1;
var sprite:FlxSprite = lifeSprites[id];
sprite.kill();
lifeSprites.splice(id, 1);
}
/**
* A simple getter for Total Lives based on life sprite instances in lifeSprites array.
* @return
*/
private function get totalLives():int
{
return lifeSprites.length;
}
|
Также убедитесь, что вы добавили следующее свойство:
1
|
private var lifeSprites:Array = [];
|
Опять же, эти методы хорошо прокомментированы, но основная идея заключается в том, что мы сохраняем всю нашу жизнь в массиве. Мы начинаем с добавления жизни в массив на основе переданного значения. Чтобы удалить жизни, мы просто склеиваем 1 из массива. Это быстрый способ управлять жизнями, используя преимущества некоторых нативных классов, таких как Array. Теперь нам просто нужно настроить логику, чтобы убрать жизнь, когда ты умрешь.
Добавьте следующий вызов в наш killPlayer()
где мы устанавливаем player.death()
:
1
|
removeLife();
|
Теперь вы можете проверить, что после смерти жизнь должна быть удалена с дисплея.
Удостоверьтесь, что вы не проходите мимо всех жизней, иначе вы получите ошибку. На следующем шаге мы добавим логику игры, чтобы эта ошибка не возникала.
Шаг 11: Экран Game Over
Мы можем быстро проверить, не вышел ли игрок из жизни и закончилась ли игра в нашем методе restart()
. Давайте заменим весь метод следующим:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* This handles resetting game values when a frog dies, or a level is completed.
*/
private function restart():void
{
// Make sure the player still has lives to restart
if (totalLives == 0 && gameState != GameStates.GAME_OVER)
{
gameOver();
}
else
{
// Test to see if Level is over, if so reset all the bases.
if (gameState == GameStates.LEVEL_OVER)
resetBases();
// Change game state to Playing so animation can continue.
gameState = GameStates.PLAYING;
timer = gameTime;
player.restart();
timeAlmostOverFlag = false;
}
}
|
Здесь вы видите, что мы тестируем, чтобы увидеть, равняется ли общее количество жизней нулю, а состояние игры установлено на игру окончена. Если это так, мы можем назвать метод игры окончен. Если нет, то это обычный бизнес, и уровень перезапускается. Теперь нам нужно добавить метод gameOver()
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
/**
* This is called when a game is over.
* back to the start screen
*/
private function gameOver():void
{
gameState = GameStates.GAME_OVER;
gameMessageGroup.visible = true;
messageText.text = «GAME OVER»;
hideGameMessageDelay = 100;
}
|
Теперь, прежде чем мы сможем увидеть это в действии, нам просто нужно добавить еще несколько строк кода в наш метод update()
для обработки удаления сообщения об окончании игры и возвращения игрока в StartState
. Найдите, где мы тестируем gameState == GameStates.GAME_OVER
и добавьте в условный код следующий код:
1
2
3
4
5
6
7
8
|
if (hideGameMessageDelay == 0)
{
FlxG.state = new StartState();
}
else
{
hideGameMessageDelay -= FlxG.elapsed;
}
|
Теперь вы можете проверить игру над сообщением, убив игрока 3 раза. Вы должны увидеть это, прежде чем вернуться в StartState
.
Теперь, когда все части на месте, мы можем легко добавить в выигрыш.
Шаг 12: Оценка
Нам нужно сложить счет, когда игрок прыгает, приземляется на базе и заканчивает уровень.Flixel позволяет легко запомнить счет, и вы можете получить к нему доступ в любое время, используя FlxG.score
свойство на FlxG
синглтоне. Сначала нам нужно создать класс для хранения некоторых значений для нас. Создайте класс с именем ScoreValues
и настройте его следующим образом:
Вот код для класса:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
package
com.flashartofwar.frogger.enum {
/**
* These represent the values when scoring happens. */
public class ScoreValues {
public static const STEP: uint = 10 ; public static const REACH_HOME: uint = 50 ; public static const FINISH_LEVEL: uint = 1000 ; public static const TIME_BONUS: uint = 10 ; }
}
|
Теперь вернитесь в PlayState
класс и добавьте приведенный выше наш код gameMessageGroup в create()
метод вокруг строки 77:
1
2
3
4
|
FlxG.score = 0 ; var scoreLabel:FlxText = add( new FlxText( 0 , 30 , 100 , "Score" ).setFormat( null , 10 , 0xffffff , "right" )) as FlxText; scoreTxt = add( new FlxText( 0 , scoreLabel.height, 100 , "" ).setFormat( null , 14 , 0xffe00000 , "right" )) as FlxText; scoreTxt.text = FlxG.score.toString(); |
Вам понадобится следующее свойство:
1
|
private var scoreTxt:FlxText; |
Давайте добавим немного очков в нашу игру в следующих местах.
После строки 407, где мы вычисляем время, оставшееся в target.mode != Home.Success
условном выражении:
1
2
|
// Increment the score based on the time left FlxG.score += timeLeftOver * ScoreValues.TIME_BONUS; |
Убедитесь, что вы импортируете ScoreValues
класс. Далее мы добавим следующее к нашему levelComplete()
методу:
1
2
|
//Increment the score based on FlxG.score += ScoreValues.FINISH_LEVEL; |
Теперь нам нужно перейти в наш Frog
класс и добавить следующее в наш update()
метод, где мы установили isMoving
значение true в строке 141:
1
2
|
// Add to score for moving FlxG.score += ScoreValues.STEP; |
Прежде чем мы сможем проверить это, нам нужно обновить счет в нашем игровом цикле. Давайте вернемся в PlayState
класс и добавим следующее к нашему update()
методу в строке 281 непосредственно перед тем, где мы проверяем gameState == GameStates.DEATH_OVER
:
1
2
|
// Update the score text scoreTxt.text = FlxG.score.toString(); |
Скомпилируйте игру и убедитесь, что счет работает.
Шаг 13: Сборка для мобильных
Теперь, когда все готово, мы можем легко скомпилировать и развернуть FlxFrogger на устройстве Android, на котором установлен AIR. Вместо того, чтобы рассказывать, как настроить предварительную версию AIR и Android SDK, я просто хочу сосредоточиться на том, как подготовить этот проект к компиляции и развертыванию. Прежде чем мы начнем, вам следует ознакомиться с этим прекрасным постом Карла Фримена о том, как настроить FDT для сборки Air for Android.
Вам нужно будет установить AIR 2.5 в своем каталоге Flex SDK и копию Android SDK на своем компьютере. Если вы помните в первой части, наш скрипт Ant указывает на Flex SDK, поэтому мы можем скомпилировать. Нам нужно установить, где находится Android SDK, чтобы мы могли установить наш Air apk на телефон. Откройте build.properties
файл и найдите android.sdk
свойство.
Вам нужно будет указать, где вы скачали свой Android SDK. Вот как выглядит мой путь:
1
|
android.sdk= /Users/jfreeman/Documents/AndroidDev/android-sdk-mac_86- 2.2 |
Как видите, я переименовал его, чтобы сообщить, что это сборка 2.2. Мне нравится хранить резервные копии моих SDK на основе номера версии, так что это хорошая привычка. Давайте откроем конфигурацию Run External Tools, где мы установили нашу первоначальную цель сборки в первой части.
Щелкните правой кнопкой мыши по нашей сборке и выберите «дублировать». Переименуйте копию сборки в FlxFrogger Android Build
. Теперь нажмите на него и давайте изменим цель по умолчанию на deploy-to-phone
.
Теперь у вас есть новый запуск для компиляции и развертывания на телефон Android. Если ваш телефон подключен и вы хотите проверить его работоспособность, просто запустите задачу Ant и подождите, пока он не будет перенесен. Помните, что для его работы на вашем телефоне должен быть установлен Air.
Последняя вещь.Возможно, вы заметили, что есть и deploy-to-emulator
цель. Вы можете использовать это, если хотите протестировать с эмулятором Android, но имейте в виду, что эмулятор невероятно медленный. Я обнаружил, что это почти невыносимо для того, чтобы получить реальное представление о том, как Flash работает на телефоне, так что не удивляйтесь, если у вас меньше 3-4 кадров в секунду.
Шаг 14: Сенсорное управление
По умолчанию Flixel настроен для работы с клавиатурой, но на мобильных устройствах у вас может быть только сенсорный экран для работы. Настроить сенсорное управление очень просто, FlxSprites
на следующем шаге мы создадим собственные сенсорные кнопки . Создайте новый класс с именем TouchControls
и настройте его следующим образом:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package
com.flashartofwar.frogger.controls {
import flash.events.KeyboardEvent;
import org.flixel.FlxG; import org.flixel.FlxGroup; import org.flixel.FlxState; import org.flixel.FlxText; import org.flixel.FlxSprite; public class TouchControls extends FlxGroup {
/*private var spriteButtons[0]:FlxSprite; private var spriteButtons[1]:FlxSprite; private var spriteButtons[2]:FlxSprite; private var spriteButtons[3]:FlxSprite;*/ private var spriteButtons: Array ; /**
* Touch controls are special buttons that allow virtual input for the game on devices without a keyboard. *
* @param target Where should the controls be added onto * @param xx position to display the controls * @param yy position to display the controls * @param padding space between each button */
public function TouchControls(target:FlxState, x: int , y: int , padding: int ) {
this .x = x; this .y = y; var txt:FlxText; spriteButtons = new Array ( 4 ); //spriteButtons[0] = new FlxSprite(x, y) spriteButtons[ 0 ] = new FlxSprite( 0 , 0 ) spriteButtons[ 0 ].color = 0x999999 ; spriteButtons[ 0 ].createGraphic( 100 , 100 ); add(spriteButtons[ 0 ]); txt = new FlxText( 0 , 30 , 100 , "UP" ).setFormat( null , 20 , 0xffffff , "center" ); add(txt); spriteButtons[ 1 ] = new FlxSprite(spriteButtons[ 0 ].right + padding, 0 ) spriteButtons[ 1 ].color = 0x999999 ; spriteButtons[ 1 ].createGraphic( 100 , 100 ); add(spriteButtons[ 1 ]); txt = new FlxText(spriteButtons[ 1 ].x, 30 , 100 , "DOWN" ).setFormat( null , 20 , 0xffffff , "center" ); add(txt); spriteButtons[ 2 ] = new FlxSprite(spriteButtons[ 1 ].right + padding, 0 ) spriteButtons[ 2 ].color = 0x999999 ; spriteButtons[ 2 ].createGraphic( 100 , 100 ); add(spriteButtons[ 2 ]); txt = new FlxText(spriteButtons[ 2 ].x, 30 , 100 , "LEFT" ).setFormat( null , 20 , 0xffffff , "center" ); add(txt); spriteButtons[ 3 ] = new FlxSprite(spriteButtons[ 2 ].right + padding, 0 ) spriteButtons[ 3 ].color = 0x999999 ; spriteButtons[ 3 ].createGraphic( 100 , 100 ); add(spriteButtons[ 3 ]); txt = new FlxText(spriteButtons[ 3 ].x, 30 , 100 , "RIGHT" ).setFormat( null , 20 , 0xffffff , "center" ); add(txt); }
public function justPressed(button: Number ): Boolean {
return FlxG.mouse.justPressed() && spriteButtons[button].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y); }
public function justReleased(button: Number ): Boolean {
return FlxG.mouse.justReleased() && spriteButtons[button].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y); }
override public function update(): void {
if (FlxG.mouse.justPressed()) {
if (spriteButtons[ 0 ].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y)) {
spriteButtons[ 0 ].color = 0xff0000 ; }
else if (spriteButtons[ 1 ].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y)) {
spriteButtons[ 1 ].color = 0xff0000 ; }
else if (spriteButtons[ 2 ].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y)) {
spriteButtons[ 2 ].color = 0xff0000 ; }
else if (spriteButtons[ 3 ].overlapsPoint(FlxG.mouse.x, FlxG.mouse.y)) {
spriteButtons[ 3 ].color = 0xff0000 ; }
}
else if (FlxG.mouse.justReleased()) {
spriteButtons[ 0 ].color = 0x999999 ; spriteButtons[ 1 ].color = 0x999999 ; spriteButtons[ 2 ].color = 0x999999 ; spriteButtons[ 3 ].color = 0x999999 ; }
super .update(); //Passing update up to super }
}
}
|
Зайдите в Frog
класс и замените блок кода, в котором мы проверяем нажатия клавиш, следующим кодом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
if ((FlxG.keys.justPressed( "LEFT" ) || (touchControls != null && touchControls.justPressed( 2 ))) && x > 0 ) {
targetX = x - maxMoveX; facing = LEFT; }
else if ((FlxG.keys.justPressed( "RIGHT" ) || (touchControls != null && touchControls.justPressed( 3 ))) && x < FlxG.width - frameWidth) {
targetX = x + maxMoveX; facing = RIGHT; }
else if ((FlxG.keys.justPressed( "UP" ) || (touchControls != null && touchControls.justPressed( 0 ))) && y > frameHeight) {
targetY = y - maxMoveY; facing = UP; }
else if ((FlxG.keys.justPressed( "DOWN" ) || (touchControls != null && touchControls.justPressed( 1 ))) && y < 560 ) {
targetY = y + maxMoveY; facing = DOWN; }
|
Как вы можете видеть, в дополнение к тестированию нажатий клавиш мы будем непосредственно проверять наши нажатия, TouchControls
чтобы увидеть, были ли они нажаты. Вам также нужно будет импортировать TouchControls
класс и добавить следующее свойство:
1
|
public var touchControls:TouchControls; |
Теперь, чтобы показать сенсорное управление при компиляции для мобильного устройства, мы собираемся использовать условный компилятор. Добавьте следующий код к PlayState
классу в create()
методе непосредственно перед тем, как мы установили gameState
для воспроизведения:
01
02
03
04
05
06
07
08
09
10
11
|
// Mobile specific code goes here /*FDT_IGNORE*/ CONFIG::mobile {
/*FDT_IGNORE*/ touchControls = new TouchControls( this , 10 , calculateRow( 16 ) + 20 , 16 ); player.touchControls = touchControls; add(touchControls); /*FDT_IGNORE*/ }
/*FDT_IGNORE*/ |
Я добавил несколько специальных комментариев, чтобы FDT не выдавал ошибку, поскольку он еще не понимает условия компилятора. Вот как выглядит код без этих специальных FDT игнорирующих комментарии:
1
2
3
4
5
6
|
CONFIG::mobile {
touchControls = new TouchControls( this , 10 , calculateRow( 16 ) + 20 , 16 ); player.touchControls = touchControls; add(touchControls); }
|
Также убедитесь, что вы добавили следующее свойство и импортировали TouchControls
:
1
|
private var touchControls : TouchControls; |
Как видите, условный компилятор похож на обычный if
оператор. Здесь мы просто тестируем, что если значение CONFIG::mobile
установлено в true, то мы создаем для мобильных устройств, и если это так, он должен отображать элементы управления. Сообщение компилятору об этой переменной конфигурации все обрабатывается в нашем скрипте сборки, поэтому вам не о чем беспокоиться. В зависимости от того, какой тип цели сборки вы вызываете, значение изменяется при компиляции. Это не может быть проще. Это отличный метод для использования, когда у вас есть специальный код для мобильных устройств, который нужно выполнить, и который вы не захотите запускать в веб-версии.
Вы можете проверить эти элементы управления, развернув их на телефоне, вот что вы должны увидеть:
Если вы тестируете в браузере, вы не увидите элементы управления. Надеюсь, вы сможете увидеть всю мощь условных компиляторов и преимущества, которые они получат при развертывании контента на нескольких устройствах и платформах.
Шаг 15: Оптимизация
Оптимизация под мобильные устройства — очень трудоемкий процесс. К счастью, эта игра очень хорошо работает на любом телефоне Android с процессором 1 ГГц или быстрее. Также помогает то, что мы решили создать игру с очень низкой частотой кадров. Несколько вещей, которые я заметил, которые могут создать впечатление, что игра на самом деле работает быстрее, — это ускорить время, необходимое для перемещения лягушки с плитки на плитку, а также ускорить время игры и время ожидания игры.
Еще можно попробовать сгруппировать объекты, для которых требуется обнаружение столкновений, по строкам, а не по типу. Поэтому сейчас все легковые и грузовые автомобили проходят тестирование как одна большая группа. Поскольку лягушка движется вертикально вдоль сетки, мы могли ускорить обнаружение столкновений, проверяя только строку за раз. Несмотря на то, что это намного лучший подход к обработке большого количества обнаружений столкновений, я был бы удивлен, если бы вы получали дополнительные кадры в секунду. Air на Android выполняет код на удивление хорошо, реальное узкое место в рендере.
Чтобы обратиться к медленному рендереру проигрывателя Flash, вы можете уменьшить изображение. Мы создали эту игру с полным разрешением 480 х 800. Если мы настроим это для работы с половиной размера пикселя, то для рендеринга Flash будет меньше накладных расходов, что может дать нам несколько дополнительных кадров в секунду. Выполнение чего-то подобного было бы невероятно трудоемким и, возможно, не стоило бы дополнительной работы. При любом типе оптимизации у вас должен быть установлен список устройств или спецификаций компьютеров для тестирования и попытка построить наименьший общий знаменатель. При создании Flash-игр для веб, настольных и мобильных устройств всегда лучше начинать с мобильной версии, так как вы вряд ли увидите значительные изменения в воспроизведении на рабочем столе и в Интернете.
Заключение Что дальше?
Прямо сейчас у вас есть полный игровой движок Frogger. Вы можете создать новый уровень, перемещаться в пределах уровня, оценивать и определять, когда уровень завершен. Следующее, что вы должны добавить самостоятельно — это дополнительные уровни. Игра настроена так, чтобы создание новых уровней на основе этого шаблона не требовало особых усилий. Возможно, вы захотите разбить код создания уровней на его собственный класс, чтобы вы могли загружать определенные уровни, когда они вам нужны.
Также вы можете добавить множитель к скорости по умолчанию, которую получает каждый объект, когда он создается внутри, PlayState
так, чтобы каждый новый уровень говорил игровым акторам двигаться быстрее и быстрее.
Наконец, вы можете полностью обновить игру и сделать ее самостоятельно. Существует множество клонов Frogger, и, поскольку все тяжелые работы были сделаны для вас, небо — это предел. Если вы хотите добавить в этот проект и заполнить некоторые из недостающих функций, я предлагаю вам раскошелиться на исходный код на GitHub и сообщить мне, какие дополнения вы делаете. Я буду рад добавить их в базовый проект и дать вам кредит.
Если у вас возникнут какие-либо проблемы или возникнут вопросы, оставьте комментарий ниже, и я сделаю все возможное, чтобы ответить на них. Спасибо за прочтение 🙂