15 января 2017г

Отправлено 19 янв. 2017 г., 13:37 пользователем Dimitrijs Fedotovs   [ обновлено 20 янв. 2017 г., 09:27 ]

На уроке мы сделали игру "Перейди дорогу". Задача игрока пройти снизу вверх поля избегая монстров. У нас будут два вида монстров - одни будут двигаться быстро, другие чуть-чуть медленнее.

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() {

}
}
Comments