На уроке мы сделали игру "Перейди дорогу". Задача игрока пройти снизу вверх поля избегая монстров. У нас будут два вида монстров - одни будут двигаться быстро, другие чуть-чуть медленнее.
level.txt
Уже ни для кого не секрет, как создать такой уровень: добавляем нужные символы в level.txt и регистрируем спрайты.
##################################
# * #
# #
# #
# 2 #
# #
# #
# 1 #
# #
# #
# 2 #
# #
# #
# 1 #
# #
# #
# 2 #
# #
# #
# 1 #
# #
# #
# m #
##################################
Звездочк - * - это портал
Цифры 1 и 2 - это монстры
Буква m - человечек, которым управляет игрок.
Регистрация спрайтов
register('#', WallSprite::new)
.onInit(w -> w.setColor(WallColor.BLUE));
register('*', PortalSprite::new);
register('m', ManSprite::new);
register('1', ChickenSprite::new);
register('2', GhostSprite::new);
load("/level.txt");
Движение монстра
Монстр должен двигаться от стенки до стенки. Алгоритм его движения выглядит примерно так:
Следуя этому алгоритму монстр с самого начала летит в одну сторону и на каждом кадре проверяет, а не врезался ли он в стенку. Если врезался, то направление должно поменятся на противоположное. С проверкой врезался ли монстр в стенку все просто - используем событие onCollision. Осталось придумать как поменять направление. Для этого у меня есть еще один алгоритм:
В коде это выглядит очень просто:
.onCollision(c -> {
if (c.getDirection() == Direction.E) {
c.setDirection(Direction.W);
} else {
c.setDirection(Direction.E);
}
}, '#');
Здесь новая конструкция - блок кода. Если нам нужно выполнить несколько строчек кода в одном событии, то эти строчки надо заключить в фигурные скобки - это будет расцениваться программой как одно действие.
Оператор if проверяет что написано у него в скобках. В нашем случае там написано c.getDirection() == Direction.E. getDirection - "спросить" у спрайта в какую сторону он двигается. "==" - это проверить на равенство с направлениеа E. И если направление является E, тогда выполнить следующий лок кода - c.setDirection(Direction.W), а если какое-нибудь другое направление, то сработает блок кода в else - c.setDirection(Direction.E);
Таким образом врезаясь в стенку монстр будет "отпрыгивать" и лететь в другую сторону и так до следующей стенки.
Вот код одного монстра:
register('2', GhostSprite::new)
.onInit(c -> {
c.setDirection(Direction.E);
c.setSpeed(8);
c.setRotation(Direction.E);
})
.onLoop(ai::followDirection)
.onCollision(c -> {
if (c.getDirection() == Direction.E) {
c.setDirection(Direction.W);
c.setRotation(Direction.W);
} else {
c.setDirection(Direction.E);
c.setRotation(Direction.E);
}
}, '#');
Код второго монстра надо написать аналогичным этому.
Движение человечка
На предыдущих уроках (например 18 декабря 2016 г) мы рассматривали как управлять человечком при помощи клавиш. Код выглядит примерно так:
register('m', ManSprite::new)
.onKeyHold(m -> move(m, Direction.N), KeyCode.UP)
.onKeyHold(m -> move(m, Direction.S), KeyCode.DOWN)
.onKeyHold(m -> move(m, Direction.W), KeyCode.LEFT)
.onKeyHold(m -> move(m, Direction.E), KeyCode.RIGHT)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.UP)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.DOWN)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.LEFT)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.RIGHT);
Чтобы человечек не смог проходить через стенки:
.onCollision(ai::stopXY, '#')
И чтобы человечек "боялся" монстров:
.onCollision(ai::halt, '1', '2')
Домашнее задание
Дома доделать игру так, чтобы загружался следующий уровень, когда человечек "берет" портал. Как это делать читай на страничке урока 13 ноября 2016 г
Код целиком
public class MyGame extends Game {
@Override
public void setup() {
setBackground(new DesertBackground());
register('#', WallSprite::new)
.onInit(w -> w.setColor(WallColor.BLUE));
register('*', PortalSprite::new);
register('m', ManSprite::new)
.onCollision(ai::stopXY, '#')
.onCollision(ai::halt, '1', '2')
.onKeyHold(m -> move(m, Direction.N), KeyCode.UP)
.onKeyHold(m -> move(m, Direction.S), KeyCode.DOWN)
.onKeyHold(m -> move(m, Direction.W), KeyCode.LEFT)
.onKeyHold(m -> move(m, Direction.E), KeyCode.RIGHT)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.UP)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.DOWN)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.LEFT)
.onKeyReleased(m -> m.setSpeed(0), KeyCode.RIGHT);
register('1', ChickenSprite::new)
.onInit(c -> {
c.setDirection(Direction.E);
c.setSpeed(4);
c.setRotation(Direction.E);
})
.onLoop(ai::followDirection)
.onCollision(c -> {
if (c.getDirection() == Direction.E) {
c.setDirection(Direction.W);
c.setRotation(Direction.W);
c.setReverse(true);
} else {
c.setDirection(Direction.E);
c.setRotation(Direction.E);
c.setReverse(false);
}
}, '#');
register('2', GhostSprite::new)
.onInit(c -> {
c.setDirection(Direction.E);
c.setSpeed(8);
c.setRotation(Direction.E);
})
.onLoop(ai::followDirection)
.onCollision(c -> {
if (c.getDirection() == Direction.E) {
c.setDirection(Direction.W);
c.setRotation(Direction.W);
} else {
c.setDirection(Direction.E);
c.setRotation(Direction.E);
}
}, '#');
load("/level.txt");
}
private void move(ManSprite sprite, Direction dir) {
sprite.setSpeed(5);
sprite.setDirection(dir);
ai.followDirection(sprite);
}
@Override
public void loop() {
}
}