Статьи

Tower Defense в JavaFX (3)

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RotatingTileSetAnimation extends TileSetAnimation {
 
private double angle = 0;
 
public RotatingTileSetAnimation(TileSet set, int[] indices, float speed) {
super(set, indices, speed);
}
 
public void setAngle(double angle) {
this.angle = angle;
}
 
@Override
public void render(Sprite sprite, GraphicsContext context, float alpha, long delta) {
context.save();
context.translate(sprite.getWidth() / 2, sprite.getHeight() / 2);
context.rotate(angle);
context.translate(-sprite.getWidth() / 2, -sprite.getHeight() / 2);
super.render(sprite, context, alpha, delta); //To change body of generated methods, choose Tools | Templates.
context.restore();
}
}

Мы можем вычислить угол поворота по скорости x и y и установить его в нашем GraphicsContext перед рендерингом. Итак, вот подкласс, который делает это:

01
02
03
04
05
06
07
08
09
10
11
12
public class LookAheadTileSetAnimation extends RotatingTileSetAnimation {
 
public LookAheadTileSetAnimation(TileSet set, int[] indices, float speed) {
super(set, indices, speed);
}
 
@Override
public void render(Sprite sprite, GraphicsContext context, float alpha, long delta) {
setAngle(Math.toDegrees(Math.atan2(sprite.getVelocityY(), sprite.getVelocityX())));
super.render(sprite, context, alpha, delta); //To change body of generated methods, choose Tools | Templates.
}
}

Вот результат:

Довольно просто, не правда ли? Теперь следующим шагом будет добавление некоторого поведения к самим туретам. Я хочу, чтобы они всегда проверяли ближайшего врага и наводили на него пушку. Сначала я немного изменил код и снова разделил башни на базы и пушки. Поэтому, когда вы выбираете пушку сейчас, в TileLayer будет размещена турель-база с названием «турели-базы». Я просто изменил класс TurretView для поддержки этого:

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
class TileSetView extends StackPane {
 
Canvas canvas;
TileSet cannons;
TileSet bases;
int selectedIndex = 0;
Color selected = Color.rgb(100, 100, 255, .2);
 
public TileSetView() {
}
 
public void setTileSet(final TileSet bases, final TileSet cannons) {
this.cannons = cannons;
this.bases = bases;
getChildren().clear();
ImageView turretBases = new ImageView();
turretBases.setImage(bases.getTileImage());
 
ImageView turretCannons = new ImageView();
turretCannons.setImage(cannons.getTileImage());
 
getChildren().addAll(turretBases, turretCannons);
 
canvas = new Canvas(cannons.getTileImage().getWidth(), cannons.getTileImage().getHeight());
getChildren().add(canvas);
canvas.setOnMouseClicked(new EventHandler() {
@Override
public void handle(MouseEvent t) {
double x = t.getX();
double y = t.getY();
selectedIndex = (int) ((int) x / cannons.getTilewidth() + (((int) y / cannons.getTileheight()) * cannons.getNumColumns()));
updateCanvas();
}
});
updateCanvas();
}
 
public int getSelectedGid() {
if (bases == null) {
return -1;
}
return bases.getFirstgid() + selectedIndex;
}
 
public int getSelectedIndex(){
return selectedIndex;
}
 
public void updateCanvas() {
GraphicsContext graphicsContext2D = canvas.getGraphicsContext2D();
graphicsContext2D.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
if (selectedIndex >= 0) {
graphicsContext2D.setFill(selected);
int x = selectedIndex % cannons.getNumColumns();
int y = selectedIndex / cannons.getNumColumns();
graphicsContext2D.fillRect(x * cannons.getTilewidth(), y * cannons.getTileheight(), cannons.getTilewidth(), cannons.getTileheight());
}
}
}

Так вот как это выглядит сейчас:

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

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
public class CannonSprite extends Sprite {
 
RotatingTileSetAnimation rotateAnimation;
 
public CannonSprite(GameCanvas parent, RotatingTileSetAnimation animation, String name, double x, double y, int width, int height) {
super(parent, animation, name, x, y, width, height, Lookup.EMPTY);
this.rotateAnimation = animation;
addBehaviour(new SpriteBehavior() {
@Override
public boolean perform(Sprite sprite) {
Sprite closest = null;
double dist = Double.MAX_VALUE;
Collection sprites = sprite.getParent().getSprites();
for (Sprite sprite1 : sprites) {
if (sprite1 instanceof EnemySprite) {
double distance = distance(getX(), getY(), sprite1.getX(), sprite1.getY());
if (distance < dist) {
dist = distance;
closest = sprite1;
}
}
}
if (closest != null) {
rotateAnimation.setAngle(Math.toDegrees(Math.atan2(closest.getY() - sprite.getY(),closest.getX() - sprite.getX())));
}
 
return true;
}
});
}
 
public double distance(double x1, double y1, double x2, double y2) {
return Math.sqrt(
(x1 - x2) * (x1 - x2)
+ (y1 - y2) * (y1 - y2));
}
}

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

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

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