Упячка давит

Давно горел идеей разработать прототип игры Super Mario. Поэтому в этой статье мы рассмотрим создание 2D-платформера с анимацией, взрывами, полосой здоровья и движущимися врагами — полностью на Rust с использованием библиотеки macroquad.

В качестве примера мы покажем процесс сборки игры, в которой главный герой — персонаж по имени Упячка (он же Upyachka) преодолевает препятствия, уклоняется от врагов-айтишников-hbr и сражается с бомбами на пути.

GamePlay

1. Планирование архитектуры игры

Архитектура
  • TileMap — карта из тайлов (земля, ловушки, платформы)

  • Player — персонаж игрока с состояниями (передвижение, прыжки, урон, здоровье)

  • Enemy — враги с логикой движения и столкновений

  • Explosion — эффект анимированного взрыва

  • UI — отрисовка интерфейса: полосы здоровья, текст, уведомления

  • Audio — звуки прыжков, взрывов и музыки

2. Графика и структура ресурсов

Игра использует:

  • Спрайты PNG 64x64 (герой, враг, земля, бомба, взрыв)

  • Аудио в формате WAV

  • Карта уровня — двумерный массив Vec<Vec<u8>>, где 0 — воздух, 1 — земля, 2 — бомба

Пример карты:

let tiles = vec![
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
    vec![1; 20],
];

3. Игрок: движение, прыжок, урон, анимация

В Player реализовано:

  • передвижение и прыжок по нажатию клавиш

  • влияние гравитации

  • проверка столкновений с землёй

  • анимация при движении (смена кадров)

  • обработка урона через hurt_timer

Игрок может мигать при получении урона:

let alpha = if self.hurt_timer > 0.0 {
    ((get_time() * 15.0).sin() * 0.5 + 0.5) as f32
} else { 1.0 };

Метод draw() отрисовывает спрайт нужного кадра с эффектом прозрачности.

4. Игровая логика: столкновения и урон

if enemy.collides_with(player.pos) && player.hurt_timer <= 0.0 {
    player.damage();
}

if tilemap.is_bomb_tile(tile_x, tile_y) && player.hurt_timer <= 0.0 {
    explosions.push(Explosion::new(player.pos));
    play_sound_once(&explosion_sound);
    player.damage();
    screen_shake = 10.0;
}

Игрок мигает при получении урона, имеет hurt_timer, чтобы предотвратить спам урона.

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

Враги двигаются влево-вправо по экрану. Если они сталкиваются с игроком, вызывается метод collides_with, и игрок получает урон. Если игрок приземляется сверху — враг уничтожается, появляется взрыв и звук:

if player.get_velocity().y > 0.0 && player_bottom <= enemy_top + 20.0 {
    explosions.push(Explosion::new(enemy.pos));
    play_sound_once(&explosion_sound);
}

6. Анимация и эффекты

Взрыв — это 8 кадров по горизонтали. Каждый Explosion содержит frame и timer, и обновляется:

if self.timer > 0.05 {
    self.frame += 1;
    self.timer = 0.0;
    if self.frame >= 8 {
        self.finished = true;
    }
}

Игрок также мигает:

if self.hurt_timer > 0.0 && (get_time() * 10.0).floor() as i32 % 2 == 0 {
    return; // пропускаем кадр

7. Звук и дрожание камеры

if screen_shake > 0.0 {
    offset = vec2(rand::gen_range(-2.0, 2.0), rand::gen_range(-2.0, 2.0));
    screen_shake -= get_frame_time() * 50.0;
}

8. UI: полоса здоровья

draw_rectangle(10.0, 10.0, 200.0, 20.0, DARKGRAY);
draw_rectangle(10.0, 10.0, 200.0 * (player.health as f32 / 3.0), 20.0, RED);

Полностью исходный код: https://github.com/digkill/RustyMario

Видео геймплея: https://t.me/notecto/3

Следующим шагом может стать редактор уровней, меню, или экспорт в Android, IOS через cargo mobile.