Прогресс, сложности... Ну, как обычно...

Всем привет!

Хочу поделиться своим текущим прогрессом и проблемами — никуда от них не деться! В прошлый раз я рассказывал о начале разработки своего инструмента для игры на базе библиотеки для 3D-рендера Three.js.

  HaQOA-ITTA28N0Ikg1R36g.png

Сначала я решил создать менеджер состояний. В этот класс добавляются состояния, которые наследуются от базового класса State:

export class State extends ObjectBase {
    // Инициализация
    init(): boolean {
        return true;
    }
    // Загрузка/подготовка ресурсов 
    load(): boolean {
        return true;
    }
    // Выгрузка ресурсов
    unload(): boolean {
        return true;
    }
    // Вход в состояние
    enter(): boolean {
        return true;
    }
    // Выход из состояния
    exit(): boolean {
        return true;
    }
    // Отрисовка игры 
    render() {}
}

В свою очередь он наследуется от класса ObjectBase, который даёт свойство идентификатора (ID) для быстрой определения в списках. Они попадают в менеджер, назначается текущее состояние и выполняется его обработка. Тут ничего необычного: создаём состояние меню, игры… Описываем логику его работы и переключения. Но внутри текущего состояния у нас могут быть субсостояния: пауза в игре, настройки в меню и т. п. Поэтому в состоянии может быть свой экземпляр менеджера, обеспечивающий «внутреннее» переключение. Такая иерархия проще в разработке и сопровождении для меня, чем создание множества состояний в одном управляющем классе.

Следующим на очереди стала система обработки пользовательского ввода. За отлов событий отвечает браузер; задача разработчика — подписаться на них и обработать результаты.

// Пример
window.addEventListener('keydown', (event) => {
	// Нажали кнопку на клавиатуре
});
window.addEventListener('keyup', (event) => {
	// Отпустили кнопку на клавиатуре
});

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

if (inputManager.isKeyboardDown(KeyCode.A)) {}
if (inputManager.isMouseDown(MouseKeyCode.Left)) {}
if (inputManager.isTouchDown(0)) {}
if (inputManager.isKeyboardUp(KeyCode.A)) {}
if (inputManager.isMouseUp(MouseKeyCode.Left)) {}
if (inputManager.isTouchUp(0)) {}
// События Pressed (зажата) в браузере нет, это уже моя обработка
if (inputManager.isKeyboardPressed(KeyCode.A)) {}
if (inputManager.isMousePressed(MouseKeyCode.Left)) {}
if (inputManager.isTouchPressed(0)) {}

Такие возможности хоть и есть, но они не совсем удобны. Поэтому я добавил создание Action. Это знакомо пользователям игровых движков Godot Engine, Defold и других.

interface Action {
    // Наименование
    name: string;
    // Клавиатура
    keys?: string[];
    // Мышь
    mouseButton?: number[];
    // Касание
    touch?: number;
    // Обработчики
    onDown?: () => void;
    onPressed?: () => void;
    onUp?: () => void;
}

В таком виде проще манипулировать вводом, сохранять, назначать и обрабатывать. Можно назначить сразу обработчики, а можно считывать состояние:

inputManager.addAction({
    name: 'jump',
    keys: [KeyCode.W, KeyCode.Space, KeyCode.ArrowUp],
    mouseButton: [MouseKeyCode.Left],
    onDown: () => console.log('Jump down!'),
});

// Обработка
if (inputManager.isActionDown('jump')) {}
if (inputManager.isActionUp('jump')) {}
if (inputManager.isActionPressed('jump')) {}

Далее кратко опишу остальные классы:

  • Менеджер событий: регистрация, подписка на события и их вызов с параметрами.
  • Менеджер команд: создание команд и групп команд, выполнение, отмена действий (с историей).
  • Менеджер задач: похож на менеджер команд, но без истории; задачи могут выполняться с задержкой, иметь приоритеты, могу быть постоянными.
  • Менеджер поведений: создание и привязка поведений к объектам (аналогично скриптам и поведениям в Construct/GDevelop).
  • Класс работы со звуками.
  • Класс работы с ресурсами.
  • Система частиц (собственная реализация, так как в Three.js её нет). В ней ещё нет абсолютно всех возможностей, которые есть в игровых движках, но в зависимости от необходимости буду реализовать.
  • Менеджер пользовательского интерфейса (также собственная реализация). Текущий функционал позволяет создавать надписи, кнопки и размещать в соответствии в указанными якоря на экране игрока. Будет расширяться по мере необходимости.

Вот такой набор в данный момент у меня получился. Некоторые классы похожи даже, а некоторыми может и пользоваться я не буду, всё зависит от проекта. Конечно, к этому прибавляются библиотеки по реализации шаблона ECS (miniplex), анимации (tween.js) и другие. Этого всего мне вполне хватает для разработки игры. Для проверки этого я решил сделать простую игру, как я делаю при изучении игровых движков. Взял за основу клон игры Stack, что я делал на Ct.js. Но я остановился…

Во время всего этого у меня возникли проблемы связанные с дизайном игры Взлом Эвереста. Графика для меня самый сложный вопрос, я не художник и не могу позволить себе изобразить всё, что захочу. В прошлых проектах мне удавалось находить решения.

my_graphics.png

Я пытался экспериментировать в разных направлениях. Был и пиксель-арт (участие в мероприятии по созданию обложки игры для приставки GameBoy):

image.png image.png

Использование прошлых моделей:

Попытки в 3D с программой Blockbench:

EnemyTank-loop.gif
Простой юнит: открылся — выстрелил — закрылся

Планировал, помимо такой простой анимации и визуальных эффектов, добиться приемлемой картинки. Но всё это не вызывает у меня чувства удовлетворения и радости. Поэтому я решил вернуться к направлению, в котором, по моему мнению, у меня лучше получается — это 2D с векторной графикой. Есть идеи по отрисовке юнитов, интерфейса, анимаций. Буду пробовать, но однозначно возвращаюсь в 2D.

А что с инструментам? Я буду использовать те наработки, которые сделал за это время. За исключением того, что я меняю библиотеку для рендера: вместо Three.js буду использовать Pixi.js. Это легко меняется, так как у моих классов почти нет привязки к классам библиотеки, и можно легко жонглировать (в случае необходимости заменить её на Phaser или другой фреймворк или библиотеку, что не повлияет на функционал игры). Я пишу «почти», потому что менеджер пользовательского интерфейса и система частиц уже привязаны к Three.js. Но для Pixi.js есть отдельные библиотеки для этих функций, поэтому эти классы исключаются. Сейчас занимаюсь маленьким проектом для знакомства с новыми возможностями и прорабатываю стиль игры. Такой прогресс у меня получился на текущий момент.

Спасибо, что дочитали до конца! =)