The code was starting to get a bit complex, in part 2 of this challenge. Despite my efforts, code for different tasks were getting mixed. It's clearly time to add some structure to the code, or it'll turn into an unmanageable mess. That's today's task.

Click here to watch the video on Odysee.

Switching to C++

I decided to switch from C to C++. Why? The biggest reason is that I'm most familiar with C++; it's currently my preferred too. If I'd learnt Rust (which I hope to), then I'd probably use that.

C++ does offer two advantages over C that are useful for games:

  • It allows you to work at a higher abstraction level. E.g., Object Orientated Programming (OOP)
  • C++ automatically calls destructors at the end of an object's lifetime, and also has smart pointers. This reduces the memory management burden, so we can focus on the game code

You could definitely use C if you wanted to, but would have to write more boiler-plate code. I'd rather not, so C++ it is...

Kickstarting By Borrowing Code

We don't get bonus points for doing everything ourselves. So, I used the Makefile from Ramon's own game template as a starting point. It's worth looking at the rest of the game template too, to see how he structured the code.

I also used the raylib-cpp wrapper written by Rob Loach. It's a thin C++ wrapper for RayLib, which saved me from having to write more low-level code. 

Separating the Code Into Modules

Well structured code separates everything into logical blocks. This makes it easier to understand by reducing the amount of data you need to keep track of at any one time. It also makes finding the code for specific tasks much easier.

After untangling the code, and separating everything into modules, here's the new main method:

int main(void)
{
	int retVal = EXIT_SUCCESS;
		
	try {
		// Initialization
		
		raylib::Window window(screenWidth, screenHeight, "RayLib - 2D Character Animation");
		raylib::AudioDevice audioDevice(false);
		InputHandler inputHandler;
		
		std::shared_ptr<Scene> currScene = std::make_shared<ScarfyScene>();
		currScene->loadResources();
		currScene->start();

		SetTargetFPS(60);
		

		// Main game loop
		while (!WindowShouldClose())    // Detect window close button or ESC key
		{
			// Update
			inputHandler.handleInput(*currScene);
			auto nextScene = currScene->update();
			if(nextScene) {
				nextScene->loadResources();
				nextScene->start();
				currScene = nextScene;
			}
			
			// Draw
			BeginDrawing();
			currScene->draw();
			EndDrawing();
		}
	} catch(std::runtime_error &e) {
		showErrorAndExit(e.what());
		retVal = EXIT_FAILURE; 
	}
	
    return retVal;
}

This is what you want as much of your code to look like as possible. Short, focused on 1-2 (related) tasks, and easy to follow. It's a big contrast from the original code (watch the video for the comparison). You can clearly see the two tasks that main() performs: initialization, and running the game loop.

Looking at the code above, you should notice the following object types (i.e., classes, in OOP speak):

  • InputHandler - handles user input from the keyboard & gamepad
  • Scene - a place where action happens
  • ScarfyScene - our current demo's scene, containing scarfy (and nothing else)

Behind the scenes there are also:

  • CommandListener - an interface for anything that needs to listen to user input or Artificial Intelligence (AI) commands (e.g., actors)
  • Actors - characters/objects in a scene that move and interact (including the player)
  • Scarfy - our scarfy actor, i.e., the character that we control

This new code structure forms the foundation for building something much more complex, like an actual game instead of a tiny demo.

Programming Patterns

The more code you read, the more you'll see certain programming patterns. These patterns are used, because they work (when applied correctly). I've used a variation of the "command pattern" for handling user input (and the listener pattern too). The InputHandler, reads the keyboard and gamepad, and translates it into commands such as goUp(). The commands can be sent to any CommandListener.

This code pattern provides multiple advantages. CommandListeners don't need to know anything about the keyboard or gamepad, so I could completely change which keys do what simply by changing the InputHandler class. Likewise, the InputHandler doesn't need to know anything about the CommandListeners that it sends commands to. So, changing from controlling scarfy to some other character is a one line change. All "Actors" in the scene are CommandListeners, so any of them could be player controlled (or, controlled by AI).

I recommend the book "Game Programming Patterns," by Robert Nystrom** for more about programming patterns. It covers multiple design patterns that are useful for game programming. You can buy it here (link). Or, the author graciously allows you to read it online here (link).

Download the Code

Click here to download the code.

Click here for Part 4 of the RayLib 2D Challenge.

** NOTE: Affiliate link. I receive a commission should you buy via this link, which will go to fund future projects.