Читать нас в Telegram

Первый опыт разработки игры на Rust

Рубрика: Переводы
,
7420

Рассказывает Olivia Ifrim

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

Почему Rust?

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

Почему именно игра и какая это игра?

Делать игры — весело! Хотелось бы, чтобы была более веская причина, но для любительских проектов я обычно предпочитаю вещи, которые весьма далеки от того, что я делаю ежедневно на работе. Так какая же игра? Я хотела сделать симулятор на тему тенниса с пиксельной графикой. Я ещё не всё продумала, но по сути это теннисная академия, куда люди приходят и играют в теннис.

Техническое исследование

Я знала, что хочу использовать Rust, но не знала точно, насколько «с нуля» хотела бы это сделать. Писать пиксельные шейдеры не было желания, но использовать “drag and drop” тоже. Поэтому мне нужно было выбрать что-то, что дало бы мне достаточно гибкости, но при этом осталось интересным с инженерной точки зрения, не переходя на слишком низкий уровень.

Я нашла несколько полезных ресурсов, которыми хочу поделиться:

Я провела небольшое исследование игровых движков Rust и остановилась на двух вариантах: Piston и ggez. Я пробовала их в предыдущем небольшом проекте и в итоге выбрала ggez, потому что он кажется более простым для использования в маленькой 2D игре. Модульная структура Piston кажется немного непонятной для новичка.

Архитектура игры

Затем я провела немного времени, размышляя об игре. Первым шагом было бы  сделать некоторую площадку на экране, затем несколько человек и несколько кортов. В конечном счёте, нам нужно будет назначать людей на корты, заставлять их передвигаться и ждать. У людей будут навыки, которые будут улучшаться с увеличением игровой практики. Ещё там должен быть своего рода режим сборки, чтобы вы могли добавлять людей и корты, и в итоге всё это будет стоить денег.

В этот момент у меня появилось достаточно идей, чтобы начать писать код.

Разработка игры

Начало: круги и абстракция

Я стащила готовый образец ggez и получила на экране окно с кружком. Удивительно! Далее немного абстракции. Я подумала, что было бы неплохо абстрагироваться от идеи игрового объекта. Каждый игровой объект можно рендерить и обновлять, что-то вроде этого:

// Трейт игрового объекта
trait GameObject {
    fn update(&mut self, _ctx: &mut Context) -> GameResult<()>;
    fn draw(&mut self, ctx: &mut Context) -> GameResult<()>;
}

// определённый игровой объект - Круг
struct Circle {
    position: Point2,
}

 impl Circle {
    fn new(position: Point2) -> Circle {
        Circle { position }
    }
}
impl GameObject for Circle {
    fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
        Ok(())
    }
    fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
        let circle =
            graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?;

         graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?;
        Ok(())
    }
}

Это позволит иметь хороший список объектов, которые я могла бы обновлять и рендерить в цикле.

impl event::EventHandler for MainState {
    fn update(&mut self, context: &mut Context) -> GameResult<()> {
        // обновляем все объекты
        for object in self.objects.iter_mut() {
            object.update(context)?;
        }

        Ok(())
    }

    fn draw(&mut self, context: &mut Context) -> GameResult<()> {
        graphics::clear(context);

        // Рисуем все объекты
        for object in self.objects.iter_mut() {
            object.draw(context)?;
        }

        graphics::present(context);

        Ok(())
    }
}

На данном этапе main.rs необходим, потому что в нём содержится весь код. Поэтому я потратила некоторое время, разбивая его на отдельные файлы и немного упорядочивая структуру каталогов, так что теперь это выглядит так.

resources -> все ассеты находятся здесь (изображения)
src
-- сущности
---- game_object.rs
---- circle.rs
-- main.rs -> главный цикл

Люди, площадки и изображения

Следующим большим шагом было создание игрового объекта Person и загрузка изображений. Я решила, что все будет на основе плитки (на данный момент 32×32 плитки).

Теннисные корты

Я потратила некоторое время на просмотр изображений теннисных кортов в Интернете и решила, что мой корт должен быть размером 4 * 2. Я могла бы сделать изображение такого размера или оно могло бы иметь 8 отдельных плиток. После дальнейшего изучения вопроса я поняла, что мне нужно только 2 уникальных плитки, чтобы построить весь корт. Сейчас объясню.

Есть 2 уникальных плитки: 1 и 2.

Каждая секция корта состоит из плитки 1 или плитки 2 как они есть или повёрнутых на 180 градусов.

Основной режим сборки

Теперь, когда я могла рендерить площадки, людей и корты, я решила, что мне нужен базовый режим сборки. И я сделала так, что, когда клавиша нажата, — объект выбран, и затем клик разместил бы этот объект. Например, клавиша 1 позволит вам выбрать корт, а клавиша 2 — игрока.

Это было бы не очень удобно, так как вам нужно помнить, что такое 1 и 2. Поэтому я добавила вайрфрейм (от англ. wireframe – каркас) в режиме сборки, чтобы вы могли понять, какой у вас объект. Вот сборка в действии.

Сомнения по поводу архитектуры и рефакторинга

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

Я начала подвергать сомнению архитектуру и смогла увидеть некоторые ограничения:

Я покопалась в Сети и обнаружила ECS (Entity Component System) — архитектура, которая в основном используется в играх. Суть ECS заключается в следующем:

В терминологии ECS есть 3 основных понятия:

Чем больше я читала об этом, тем больше понимала, что это решит мои текущие проблемы:

Вот что я получила после внедрения ECS (что, честно говоря, всё практически переписано).

resources -> 
src
-- components
---- position.rs
---- person.rs
---- tennis_court.rs
---- floor.rs
---- wireframe.rs
---- mouse_tracked.rs
-- resources
---- mouse.rs
-- systems
---- rendering.rs
-- constants.rs
-- utils.rs
-- world_factory.rs -> глобальные фабричные функции
-- main.rs -> главный цикл

Назначение игроков на корты

После перехода на ECS всё стало относительно легко. Теперь у меня был систематический способ добавления данных в мои объекты и добавления логики на основе этих данных. Это позволило мне очень легко назначать людей на корты.

Что было сделано:

Всё это можно увидеть в действии.

Заключение

Я действительно отлично провела время, создавая эту маленькую игру. Но я особенно рада, что написала её с помощью Rust, потому что:

Что касается разработки игр на Rust, я думаю, что это только начало. Но я вижу большое сообщество, активно работающее над тем, чтобы сделать Rust более доступным для разработчиков. Так что я с оптимизмом смотрю в будущее и не могу дождаться, чтобы увидеть, как всё это будет развиваться.

Перевод статьи "24 HOURS OF GAME DEVELOPMENT IN RUST"