Найти в Дзене
ZDG

Пишу игру Robots на JavaScript и Python. Часть 8: Обработка действий игрока

Предыдущие части: Давайте уже рисовать, Чёрный квадрат, Графический контекст, Расстановка объектов, Инициализация, Функции и библиотеки, Проектирование

В предыдущей части я дошел до места, где игрок уже видит перед собой игровое поле. Он видит себя, он видит роботов, стенки, и всё остальное. Игрок решает, в какую сторону ему нужно пойти, чтобы спастись от роботов. Приняв решение, игрок нажимает какую-то клавишу.

Программа должна понять, какую клавишу игрок нажал. Но проблема стоит гораздо шире. Откуда программа вообще узнает, что хоть какая-то клавиша была нажата?

Ожидание ввода изначально делалось очень просто. Давайте я расскажу про это на примере почты.

  1. Вы заходите на почту и спрашиваете – пришло какое-нибудь письмо?
  2. Вам отвечают – нет.
  3. Вы выходите из почты.
  4. Вы сразу поворачиваетесь и заходите обратно.

То есть вы делаете опрос в цикле. Есть письмо? – Нет. Повторим. Есть письмо? – Нет. Повторим. И так до тех пор, пока не придет письмо.

В чём недостаток данного метода?

Посмотрите сами: если вы всё время заходите обратно на почту, то вы не можете заниматься никакими другими делами. Процессор крутится в цикле, не делая больше ничего. Раньше, когда компьютеры были однозадачными, это было нормой. Одна программа – одна задача. Если программа крутится в цикле, ей всё равно больше нечего делать, а другие программы в это время не работают.

Возможен и другой вариант.

  1. Вы заходите на почту и спрашиваете – пришло какое-нибудь письмо?
  2. Вам отвечают – нет.
  3. Вы выходите из почты и некоторое время делаете какие-то свои дела.
  4. Поделав дела, вы снова идете на почту.

В этом варианте программа тоже ожидает ввода, но между опросами она может делать что-то ещё. Этот метод подходит для динамичных игр, где что-то происходит без действия пользователя (как Tetris или Doom), но недостаток тоже есть: если свои дела вы делаете слишком долго, то на почту будете приходить редко. И если письмо придет, пока вы заняты, то вы получите его с опозданием. Для пользователя это будет выражаться в том, что игра будет с задержкой реагировать на его действия. Сейчас мне это, конечно, совсем не грозит.

Наконец, третий вариант.

  1. Вы приходите на почту и говорите – Вот мой телефон. Когда придет письмо, позвоните.
  2. Вы отправляетесь делать свои дела или просто спать, и на почту больше не приходите.

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

В JavaScript возможен только третий вариант. Поэтому я буду делать более-менее общую реализацию игры на его основе.

Как я уже говорил, JavaScript выполняется в среде HTML-документа, загруженного в браузер. Поэтому просить "позвонить" надо у этой среды. Но сначала мне нужен собственно "телефон", куда можно звонить. Я напишу функцию, которая должна обрабатывать состояние игры, получив событие:

function process_game(event) {}

Я не буду пока реализовывать её тело. Достаточно договориться, что в функцию передается событие event, которое было получено от среды. Надо теперь объяснить среде, чтобы она "звонила" в эту функцию:

document.addEventListener('keydown', process_game);

Что произошло: у объекта document, являющегося средой, я вызвал функцию addEventListener (что значит "добавить обработчик события") и передал в неё два параметра – строку 'keydown' и название моей функции process_game. Так я сообщил документу:

"document, когда ты там у себя получишь событие с именем 'keydown', то отдай его в мою функцию process_game".

Фактически я попросил документ позвонить мне.

Теперь каждый раз, когда нажимается клавиша, моя функция будет вызываться и ей будет передаваться объект-событие. Мне не нужно её вызывать самому.

Посмотрим, как организовать ожидание ввода в Python.

Я уже использую библиотеку pygame, которая, кроме графических функций, также имеет функции для работы с событиями.

Я могу получить событие из функции:

pygame.event.wait()

Она удобна тем, что сама, внутри себя, организует какой-то цикл ожидания, который не вредит никому, поэтому я могу смело вызвать эту функцию и ждать, когда она вернет мне событие (так как мне в это время всё равно больше нечем заниматься, игра стоит). Условно говоря, я пришел на почту и спросил, есть ли письмо, а они ушли искать. Я стою и жду, но никуда из почты не ухожу.

Но эта функция возвращает любые события (например, движения мыши), а не только нажатия клавиш. Ситуация похожа на то, как если бы я на почте попросил найти письмо, а мне нашли бы письма, открытки, посылки и бандероли, а также предложили купить конверты.

– Тут событий куча, брать будете?
– А давайте!
– Тут событий куча, брать будете? – А давайте!

Поэтому события надо фильтровать. Я могу сделать это, проверив тип события:

У объекта event есть атрибут type, который можно сравнить с константой pygame.KEYDOWN, то есть "клавиша нажата". И если они равны, значит меня интересует это событие и я передам его в функцию process_game.

Теперь у меня есть общая точка: и в Javascript, и в Python есть моя (ещё не написанная) функция process_game, в которую передается событие. Значит, начиная с этой функции я могу делать одинаковую реализацию игровой логики, так как владею информацией о событии и уже не нуждаюсь в услугах среды или библиотеки.

Сейчас, без дополнительных уточнений и разбиений, process_game является моей главной функцией: получив управление, она должна сделать всю работу – проверить, что было нажато, передвинуть игрока, передвинуть монстров и завершиться.

Да, именно завершиться. Так проходит один шаг игры. После его завершения я должен принять решение о продолжении и повторить всё сначала, то есть снова получить событие и снова отправить его в process_game.

В JavaScript функция завершается и всё, мне не нужно больше ничего делать. Когда игрок нажмет на клавишу, обработчик этого события в документе-среде автоматически запустит мою функцию process_game, которая осуществит очередной шаг игры. Цикл существует, но неявно. Игрок буквально подталкивает его вручную.

В Python после завершения функции я должен сам перейти к ожиданию события, то есть задать явный цикл:

Цикл будет повторять опрос событий и вызов process_game, пока логическая переменная is_playing равна "истине". Её я и буду использовать для того, чтобы решить, надо продолжать игру или нет.

В следующей части я полностью опишу логику обработки игры, и надеюсь, что в целом уложусь в 10 частей, потому что, во-первых, это красиво.

Читайте дальше: Эпичный провал

Наука
7 млн интересуются