Разработка игры на ggez, часть 1: Настройка и запуск

ggez- это легковесный фреймворк для создания 2D игр. Он направлен на реализацию API игрового движка LÖVE и был им вдохновлен. ggez поддерживает кроссплатформенную 2D графику, звук и обработку событий.

В этом небольшом руководстве мы рассмотрим создание простой игры с помощью этого фреймворка.

Создание проекта

Надеюсь, к этому моменту у вас уже установлен компилятор Rust (на текущий момент актуальная версия 1.40.0) и cargo.

Для пользователей Linux потребуется установить дополнительные библиотеки, к примеру, для Debian это будут libasound2-dev libudev-dev pkg-config, подробнее о системных зависимостях можно узнать тут.

Создадим новый проект:

cargo new --bin game01

Перейдем в созданный каталог и обновим Cargo.toml, а именно добавим в зависимости ggez (актуальная версия 0.5.1):

[package]
name = "game01"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
ggez = "0.5.1"

Готово! Мы можем начать работать над своей игрой.

Создание глобального состояния (game state)

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

struct GameState;

Данная структура должна реализовывать чертуEventHandler. Это основной интерфейс взаимодействия с циклом событий ggez. Черта определяет несколько методов, но обязательными являются два: update() и draw(). Первый вызывается при каждом обновлении игры и обычно сдержит игровую логику. Второй отвечает за отрисовку кадра. Каждый из этих методов принимает один аргумент - Context. Context - это структура, которая отвечает за взаимодействие с различными ресурсами: экран, аудиосистема, таймеры и так далее. Context может быть только один, попытка создать второй приведет к панике.

Давайте создадим эти методы:

impl EventHandler for GameState {
    fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
        Ok(())
    }

    fn draw(&mut self, _ctx: &mut Context) -> GameResult<()> {
        Ok(())
    }
}

Создание цикла событий

Реализуем функцию main, которая будет отвечать за запуск нашей игры. Для начала нам потребуется сам цикл событий, который и будет вызывать наши методы, а также Context. Для их создания лучше всего использовать ContextBuilder. Он принимает различные параметры, с помощью которых описывается окно игры, а по окончанию возвращает Context и цикл. Давайте дадим имя нашей игре, и установим минимальный размер окна (500px на 500px):

let mut cb = ContextBuilder::new("game01", "author")
    .window_setup(
        conf::WindowSetup::default()
            .title("Game 01!")
    )
    .window_mode(
        conf::WindowMode::default()
            .dimensions(500.0, 500.0)
    );

Теперь можно создать Context и цикл событий:

let (ctx, event_loop) = &mut cb.build().unwrap();

Конечно же, создаем наш GameState:

let mut state = GameState {};

Осталось дело за малым, запустить цикл, для этого нужно вызвать event::run() и передать созданные элементы:

match event::run(ctx, event_loop, &mut state) {
    Ok(_) => println!("Exited cleanly."),
    Err(e) => println!("Error occured: {}", e),
}

Готово! Теперь мы можем скомпилировать и запустить наш код с помощью cargo run и получить окно нашей будущей игры.

Полный пример:

use ggez::event::{self, EventHandler};
use ggez::{conf, Context, ContextBuilder, GameResult};

struct GameState {}

impl EventHandler for GameState {
    fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
        Ok(())
    }

    fn draw(&mut self, _ctx: &mut Context) -> GameResult<()> {
        Ok(())
    }
}

fn main() {
    let mut cb = ContextBuilder::new("game01", "author")
        .window_setup(
            conf::WindowSetup::default()
                .title("My game!")
        )
        .window_mode(
            conf::WindowMode::default()
                .dimensions(500.0, 500.0)
        );

    let (ctx, event_loop) = &mut cb.build().unwrap();

    let mut state = GameState {};

    match event::run(ctx, event_loop, &mut state) {
        Ok(_) => println!("Exited cleanly."),
        Err(e) => println!("Error occured: {}", e),
    }
}