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 custom data structures with opaque 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:

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.