Статьи

Tower Defense в JavaFX (2)

В последней части мы создали простой редактор, который позволяет нам размещать турели. Теперь мы добавим точку появления, где появляются враги, и определим для них цель атаки. Сначала я добавлю немного информации на карту через слой объектов. Это стандартный TMX, поэтому мы можем сделать это в редакторе TileMap:

Bildschirmfoto-2013-08-06-эм-17.41.17

Чтобы рассчитать путь атаки для наших врагов, мы будем использовать алгоритм A *, который является частью модуля tilengine:

Итак, давайте получим spawnpoint и target и сохраним их для нашего алгоритма:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
ArrayList objectGroups = tileMap.getObjectGroups();
for (ObjectGroup objectGroup : objectGroups) {
for (final TObject tObject : objectGroup.getObjectLIst()) {
if (tObject.getName().equals("spawnpoint")) {
 
spawnpointX = tObject.getX() / turrets.getTilewidth();
spawnpointY = tObject.getY() / turrets.getTileheight();
 
}
 
if (tObject.getName().equals("target")) {
 
targetX = tObject.getX() / turrets.getTilewidth();
targetY = tObject.getY() / turrets.getTileheight();
 
}
}
}

С этими значениями мы можем инициализировать алгоритм A *, который вычисляет кратчайший путь для наших врагов:

1
2
3
AStar.AStarTile start = new AStar.AStarTile((int) spawnpointX, (int) spawnpointY);
AStar.AStarTile end = new AStar.AStarTile((int) targetX, (int) targetY);
attackPath = AStar.getPath(tileMap, platformLayer, start, end);

Чтобы увидеть результат, мы добавим отладочный слой в GameCanvas:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private class AStarLayer extends Layer {
public AStarLayer() {
}
Color pathColor = Color.rgb(255, 100, 100, .2);
 
@Override
public void draw(GraphicsContext graphicsContext, double x, double y, double width, double height) {
AStar.PathNode start = attackPath;
if (start != null) {
graphicsContext.setFill(pathColor);
graphicsContext.fillRect(start.getX() * tileMap.getTilewidth(), start.getY() * tileMap.getTileheight(), tileMap.getTilewidth(), tileMap.getTileheight());
while (start.getParent() != null) {
start = start.getParent();
graphicsContext.fillRect(start.getX() * tileMap.getTilewidth(), start.getY() * tileMap.getTileheight(), tileMap.getTilewidth(), tileMap.getTileheight());
}
}
}
}

Результат выглядит так:

Bildschirmfoto-2013-08-07-эм-08.12.45

Вы видите кратчайший путь красным цветом. Поскольку алгоритм не «видит» структуры фонового изображения, он соответственно рассчитывает траекторию, и враги просто игнорируют структуры корабля (фон должен быть частью космического корабля). Чтобы исправить это, мы добавим несколько невидимых плиток позже. Для больших игр лучше использовать невидимый слой столкновений, который дает вам лучшую производительность и больше возможностей для реализации таких вещей, как заблокированные проходы. Для нас прозрачный подход к плитке лучше, потому что нам не нужен дополнительный слой, и проще, если пользователь может редактировать макет.

Теперь нам нужно отправить врагов по этому пути. Чтобы оживить Sprite, я объединил фазы анимации в одно изображение:

Bildschirmfoto-2013-08-07-эм-08.05.59

Теперь мы можем использовать Tiled-редактор для создания TileSet из него:

Bildschirmfoto-2013-08-07-эм-08.10.43

Я также использовал Tiled, чтобы добавить два дополнительных свойства в spawnpoint:

Bildschirmfoto-2013-08-06-эм-17.41.271

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

1
2
3
4
5
6
7
8
if (tObject.getName().equals("spawnpoint")) {
 
Properties properties = tObject.getProperties();
evaluationInterval = Long.parseLong(properties.getProperty("delay"));
spawnpointX = tObject.getX() / turrets.getTilewidth();
spawnpointY = tObject.getY() / turrets.getTileheight();
 
}

Пока у нас есть только один тип монстров, поэтому мы можем игнорировать это и использовать только задержку. Сначала мы создадим анимацию Sprite из нашего TileSet:

1
2
final TileSet enemy1 = tileMap.getTileSet("enemy1");
final TileSetAnimation tileSetAnimation = new TileSetAnimation(enemy1, new int[]{0, 1, 2, 3, 4, 5}, 10f);

Чтобы вызвать монстра, мы определим поведение. Это просто синхронизированный вызов метода. API, вероятно, будет немного изменен здесь для поддержки лямбда-выражений:

01
02
03
04
05
06
07
08
09
10
11
Behavior monsterSpawnBehavior = new Behavior() {
int enemyCount = 0;
 
@Override
public boolean perform(GameCanvas canvas, long nanos) {
new Sprite(canvas, tileSetAnimation, "enemy" + (enemyCount++), ((int)spawnpointTileX) * tileMap.getTilewidth(), ((int)spawnpointTileY) * tileMap.getTileheight(), 46, 46, Lookup.EMPTY);
return false;
}
};
monsterSpawnBehavior.setEvaluationInterval(evaluationInterval);
canvas.addBehaviour(monsterSpawnBehavior);

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
sprite.addBehaviour(new SpriteBehavior() {
AStar.PathNode start = attackPath;
 
@Override
public boolean perform(Sprite sprite) {
double x = sprite.getX();
double y = sprite.getY();
double pathX = start.getX() * tileMap.getTilewidth();
double pathY = start.getY() * tileMap.getTileheight();
if (Math.abs(pathX- x) 1) {
sprite.setVelocityX(.5);
} else if (pathX- x < -1) { sprite.setVelocityX(-.5); } else { sprite.setVelocityX(0); } if (pathY - y > 1) {
sprite.setVelocityY(.5);
} else if (pathY - y < -1) {
sprite.setVelocityY(-.5);
} else {
sprite.setVelocityY(0);
}
return true;
}
});

И вот результат:

Это все на данный момент. Как видите, добавить AI в спрайты с помощью Behaviors довольно просто, а AStar очень удобен. В следующей части мы позаботимся о том, чтобы наши враги указывали в правильном направлении, и добавили немного поведения в турели.

Ссылка: Tower Defense в JavaFX (2) от нашего партнера JCG Тони Эппла в блоге Eppleton .