Posted on

Zombie shooter


Project name Zombie shooter (previously hacknslash) is a real-time isometric shooting game. Player is supposed to survive while zombies try to kill the player. Hopefully project will become something more, but I want to build the basics first.

Graphic libraries

My previous game development project was a browser game. I felt confident enough to try out the same without browser. I had toyed around with Rust earlier and it was pleasant after struggling with C/C++ code. Switching to Rust meant that new graphics library has to be chosen. I had used PixiJS in the previous game project and I looked for something more expressive and ended up to old acquaintance SDL. Initial proof of concept version was written with rust-sdl2 graphics library.

SDL version

Rust-sdl2 was fast to develop with, but shortly the scarce documentation and weird bugs made development a constant pain. I didn’t manage to construct a clear separation of concerns in the code base and most of the logic was in the render loop. It was time to look at alternative approaches. I didn’t want to use pure OpenGL since I feared it would slow down the development too much.

I decided to try gfx-rs. It is a graphics abstraction library, which offers backends for Direct3D, Vulkan, Metal and OpenGL. It’s a bit overkill for my use case and I only use the OpenGL “backend” of the gfx-rs for the sake of simplicity. Gfx-rs offers moderate documentation and several examples, but still the implementation of tile map rendering took a few months to complete. I used Tiled to construct a tile map. It provides a nice terrain editor GUI which exports to XML-file and texture sheet for the game.

tile map

Entity Component System

SDL version’s code base was a tightly coupled mess and adding new things slowly got laborious. It was all too easy to implement a huge render method(s) where the game logic was written. Strictness of the Rust compiler didn’t ease the situation. I decided to try Entity Component System (ECS) library to split the code base to components and entities. ECS offed better structure for the source code and “plug-and-play” module structure where each component only knows the required data and that data is passed between components via channels.

use std::sync::mpsc;

fn channel_example() {
  let (sender, receiver) = mpsc::channel();
  sender.send(123).unwrap();
  println!("message: {}", receiver.recv().unwrap());
}

Each component listens to the components it’s interested in. Game engine constructs entities from the components during the init phase and dynamically after the init phase for example when player decides to shoot a bullet.

  • List of current components:
    • Character
    • Zombie
    • Terrain
    • Bullet
    • Camera
    • Mouse

Short-term future

Currently player character is able to shoot around with sound effects, but the bullets won’t collide with zombies. Next I’m focusing to implement collision detection. Check out the current status of Zombie shooter in Github https://github.com/Laastine/zombie-shooter


Current situation

Further reading