#graphics #retro #raycasting #3d-rendering #game-engine #engine #game

raycaster

A multi-threaded raycaster engine for creating game graphics similar to Wolfenstein3D

4 releases (2 breaking)

0.3.1 Mar 18, 2023
0.3.0 Jan 7, 2023
0.2.0 Jan 5, 2023
0.1.0 Jan 4, 2023

#609 in Game dev

MIT license

59KB
930 lines

A Rust based Raycaster engine

This is a full featured raycaster engine to produce game graphics similar to Wolfenstein 3D. I need it to create 3D dungeons for my retro RPG creator Eldiron.

The caster renders into a Vec<u8> frame. Next to rayon, which is needed for multithreading, the only other dependency of the crate right now is rustc-hash for a fast HashMap.

For single threaded rendering enable the "single_threaded" feature (for example for WASM targets). Multi threaded rendering is about 2-4 times faster than single threaded on my machine.

A demo application using pixels is available in the demo directory.

Features

  • Textured or colored walls, ceiling and floor
  • Adjustable fog color and distance
  • Sprites
  • Animation support
  • Multi-threaded or single-threaded rendering
  • Tile based lighting

Todo

  • Doors

Multi-threaded Rendering

As a raycaster works with stripes of pixels (instead of slices) the internal rendering stores the image 90 percent rotated so that it can work with slices. This helps with memory access and makes it possible to use rayon for multithreading. The image is than rotated back into the destination frame, this too is done in parallel.

Multithreaded rendering of an 1280x800 image is done in about 2-3 ms on my machine. Single threaded rendering takes about 7-8 ms. The renderer should be fast enough to handle 4k resolutions.

Usage

Create a world map:

use raycaster::prelude::*;

let mut world = WorldMap::new();

// Add an image containing the tilemap to the world
let image_id = world.add_image(tilemap, tilemap_width, tilemap_height);

// Create a textured tile and use it for the ceiling default
// The rectangle defines the tile in the tilemap
let ceiling_tile = Tile::textured(image_id, (0, 0, 24, 24));
world.set_ceiling_tile();

// Set a colored tile for the floor
world.set_floor_tile(Tile::colored([50, 50, 50, 255]));

// Add a wall with a tile at the given location
// Add as many walls as you like
world.set_wall(5, 7, tile...);

// Add a bat sprite at the given location.
// You can manage the sprites yourself as WorldMap::sprites is public.
let sprite = Sprite::new(7.0, 7.0, tile...);
world.add_sprite(sprite);

// Torch Sprite
let mut sprite = Sprite::new(4.1, 6.1, Tile::textured_anim(image_id, calc_tile_rect(14, 14, 24,), 2));
sprite.shrink = 2; // Scale the sprite down
sprite.move_y = -100.0; // Move the sprite up
world.add_sprite(sprite);
world.add_light(4, 6, 2); // Add a light source at the torch position

// Set the fog color and the fog distance, the distance is in tiles.
world.set_fog([10, 10, 10, 255], 6.0);

When we have set up the world we can render it:


const width: usize = 800;
const height: usize = 600;

let frame = vec![0; width * height * 4];

let mut caster = Raycaster::new();

// Set the position pf the player
caster.set_pos(9, 7);

// Render into the given rectangle inside the frame (here the full frame), the stride (i.e. the width of the frame) and the world.
caster.render(&mut frame[..], (0, 0, width, height), width, &mut world);

Acknowledgements

Dependencies

~265–600KB
~11K SLoC