Let’s Make: Dangerous Dave

A Dangerous Dave tutorial using C and SDL.

Overview

Waaaay back in the dark ages of junior high school, I lost many hours to this classic 2D platformer from John Romero. My tribute to these good times is this tutorial for beginner programmers.

The ten-part video series recreates the game using C and SDL with a few interesting limitations: I’ve kept a static design using basic features of the C language. No function pointers, memory allocators, or customer data structures or operations. We fit around 1,000 lines in to a single file. Total run time of the videos is just under two hours.

Happy learning!

GitHub Repository
YouTube Playlist


Video series

Click on the picture to go to the video. Click on the title to go to the project page.

Part 1: Playthrough & Plan

A playthrough of the original Dangerous Dave, pointing out gameplay and features. Brief discussion about tools and development strategy.

  • DOSBox
  • IDA Pro
Video runtime: 12:49

Part 2: Extracting the Tileset


We need to pull out the tileset from the original Dangerous Dave Binary. We’ll create a utility to pull out the tiles in to bitmaps. Thanks to levellass and Malvineous at shikadi.net for reverse engineering the original binary.

  • C Programming
  • Notepad++
  • PowerShell
Video runtime: 11:39

Part 3: Extracting Level Data


The original binary contains the level information that we’ll need. We’ll built a utility to extract these data in to separate data files. Again, many thanks to levellass and Malvineous at shikadi.net.

  • C Programming
  • Notepad++
Video runtime: 5:15

Part 4: The Game Loop


We’ll import the assets we’ve already extracted and build a simple game loop: Checking input, updating game state, and rendering output. Then we set up a fixed time step at 30 frames per second before test drawing the world in the renderer.

  • C Programming
  • Powershell
Video runtime: 12:58

Part 5: Adding Dave


We’ll add Dave to the world and give him basic movement abilities: Left, right, jumping, and picking up items.

  • C Programming
  • Powershell
Video runtime: 17:34

Part 6: Building Dave’s Capabilities


Dave needs more features to get him through the game. We’ll start by finishing levels, then adding features like screen scrolling, weapons, and the jetpack.

  • C Programming
  • Powershell
Video runtime: 16:27

Part 7: Adding Monsters


Beyond level two, each level will challenge Dave with monsters. We’ll get the monsters in to the world and get them moving and shooting.

  • C Programming
  • Powershell
Video runtime: 16:27

Part 8: Game Animations


Let’s make the game more visually complete by adding animations. We’ll implement a game tick clock and use it to offset tile drawing.

  • C Programming
  • Powershell
Video runtime: 7:13

Part 9: User Interface


The original Dangerous Dave has a status bar and several banners. We’ll bring those in to our implementation.

  • C Programming
  • Powershell
Video runtime: 8:13

Part 10: Loose Ends


We need to add a few more things to make the game winnable. We’ll allow Dave to climb trees and stars. Then we need to add level wrapping so Dave can fall through the floor.

  • C Programming
  • Powershell
Video runtime: 10:13

Design

The goal is a reasonably complete (and winnable) version of Dangerous Dave with the minimum amount of design. I've skipped many best-practices for scalability and maintainable design in order to keep this accssible for beginners. The code comes in at around 1000 lines in which we define 30 procedures. Below, we'll review the namespace of the project, which includes C standard library functions, SDL functions, and game state variables. I'll break down the game loop as it developed over the course of the project.

Standard C Functions

Here are the 7 functions you need to know or learn to understand this project:

Function Purpose
fclose Closes a file previously opened using fopen
fgetc Reads the next byte from a file
fopen Opens a file
free Releases memory previously allocated by malloc
malloc Allocates a block of memory
sprintf Outputs a string to a variable. This function is unsafe.
strcat Concatenates two strings. This function is unsafe.

SDL Functions

SDL is a development library that connects program logic to output devices. We'll use 19 functions (of the 536 SDL2 defines) to bring Dangerous Dave to life.

Function Purpose
SDL_CreateTextureFromSurface Creates an SDL_Texture from an SDL_Surface. We manipulate surfaces before conversion
SDL_CreateWindowAndRenderer Sets up our output devices (the screen) during initialization
SDL_Delay Stops game processing for a time. Blocking (non-busy) waiting
SDL_FreeSurface Releases resources used by SDL_Surface
SDL_GetKeyboadState Reads the keyboard state directly. Returns a state array
SDL_GetTicks Records the current time stamp for the app
SDL_Init Starts SDL within the application
SDL_LoadBMP Reads a bitmap file and returns a surface
SDL_Log Sends a string to the SDL log
SDL_MapRGB Converts an input RGB value to the desired format
SDL_PollEvent Checks if there is an SDL_Event waiting
SDL_Quit Ends the SDL context
SDL_RenderClear Clears the back buffer with the current color
SDL_RenderCopy Pushes a texture to the renderer (similar to blitting surfaces)
SDL_RenderFillRect Draws a filled rectangle
SDL_RenderPresent Swaps back buffer to the front
SDL_RenderSetScale Scales the scaling for renderer output
SDL_SetColorKey Sets the transparency for a surface
SDL_SetRenderDrawColor Sets the current drawing color

Our Procedures

We define 30 procedures that make up the heart of our game. All procedures are composed from the above functions and various flow control options in C (loops, conditions, etc). Our procedures are:

Procedures Purpose
add_score Adds points to score and checks for extra lives earned
apply_gravity Applies gravity to Dave
check_collision Updates collision point status for Dave
check_input Reads keyboard state and sets update flags
clear_input Removes keyboard state flags
draw_dave Renders Dave to the screen
draw_dave_bullet Renders Dave's bullets to the screen
draw_monster_bullet Renders enemy shots to the screen
draw_monsters Renders enemies to the screen
draw_ui Renders UI elements, like score.
draw_world Renders the world environment
fire_monsters Fires enemy weapons
init_assets Loads level data and tiles
init_game Sets up game state
is_clear Checks if a level square is empty and passable
is_visible Checks if a level square is visible
main Entry point
move_dave Moves Dave depending on input and state
move_monsters Moves monsters along their path
pickup_item Picks up an item for Dave
render Main render routine
restart_level Moves Dave back to starting position
scroll_screen Moves the screen if necessary
start_level Sets up level start state
update_dbullet Moves Dave's bullet
update_ebullet Moves enemy bullets
update_frame Allows animation through sprite frames
update_game Main game loop update routine
update_level Checks level-wide game state events
verify_input Checks keyboard input for validity

Game Loop

Most of our development time is spent building features and plugging them in to our game loop. The first development video includes us setting up the fixed 30 FPS game loop with empty functions for checking input, updating game, and rendering. Ultimately, we build towards the final result which looks like the diagram below:

Our Dangerous Dave game loop

Game State

The final piece to our design puzzle is the game state. This is what we update every game loop iteration using our procedures above. Since we're using C, I've stashed the game state in one large struct and two small structs. We pass the struct around in each step of the loop and update as needed. The elements of our game state structs include:

Game State Variable Purpose
can_climb Flag set if Dave is standing on a climbable tile (trees, stars, etc)
check_door Flag triggered if Dave is standing on the level exit door
check_pickup_x World grid X position to trigger a pickup
check_pickup_y World grid X position to trigger a pickup
collision_point A set of nine points that hold a collision state (clear or not)
current_level The level Dave is on
dave_[some_action] A flag set after an action has verified and triggers it in the game loop
dave_dead_timer A countdown timer when Dave dies. Controls animation an level reset
dave_px Dave's pixel X position in the world
dave_py Dave's pixel Y position in the world
dave_tick Timer used to control Dave's animations
dave_x Dave's grid X position
dave_y Dave's grid Y position
dbullet_dir Direction of Dave's bullet
dbullet_px Pixel X position of Dave's bullet
dbullet_py Pixel Y position of Dave's bullet
dead_timer A dead timer used for each monster. Controls animation
ebullet_dir Direction of monster bullets
ebullet_px Pixel X position of monster bullet
ebullet_py Pixel Y position of monster bullet
gun Flag set if Dave has a weapon
jetpack Value set if Dave has the jetpack. Double's as the fuel level
jetpack_delay Timer used to prevent repeated toggling of the jetpack
jump_timer Used to control Dave's jump characteristics
last_dir The direction Dave is facing
lives Current lives left
monster_px Monster pixel X position
monster_py Monster pixel Y position
monster_x Monster world grid X position
monster_y Monster world grid Y position
next_px Monster movement waypoint pixel X position
next_py Monster movement waypoint pixel X position
on_ground Flag set if Dave is on the ground
path_index Monster's current position on it's predefined path
quit Game loop quit flag
score Player's current score
scroll_x Amount of screen scroll required
tick Global game timer. Seeds tile animations
trophy Flag set if Dave has the level trophy
try_[some_action] Flag set if player has pushed a key for an action
type Monster type. Doubles as tileset index
view_x World grid X position of the screen
view_y World grid Y position of the screen


Put everything together and the result is: Dangerous Dave!


FAQ

How long did it really take?
Two days, about 4 hours each. Day one was asset extraction and putting together the world…roughly the first 4 videos. Day two covered the game play. Setting up the videos, github, and this website took about a week. Overall, this entire project took about 10 days. Short enough to keep the fun alive.

Is this really a beginner programming project?
Conceptually, yes this is very much a beginner project. The biggest hurdle might be using C. I’m not sure how common C programming is among beginners these days. I’d also debate the value for a beginner to start with C…OOP designs reign supreme in the 21st century.

What features are missing from this version?
A few things are missing. I didn’t try to work on the PC speaker sound effects. Also, the original game had level transitions that I didn’t implement. High scores are not tracked between game runs. Finally, the main menu and victory splash screen are not here. I didn’t feel that these features contribute enough to the core game to include. If there is enough interest, I’ll consider adding another hour of video to fit them in.