Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Setting Up the Game

README.MD File

Your starting repo has a README.md file which contains placeholders for your name, email address, section, and platform. Edit this file to contain your information.

You should then commit and push your changes back to the GitHub server.

How you will commit and push will depend on your client:

  • On GitHub Desktop, the steps are:

    1. Click the “Changes” tab
    2. Check all the files you want to commit (usually it’ll be all of them)
    3. Type something where it says “Add information about the purpose of this change”
    4. Click “Commit to main”. This will make the commit locally.
    5. Then on the top bar you should see a “Push origin” button, which will push the changes to the GitHub server.
  • If you’re using the command line (either Terminal on Mac or Git Bash on Windows), you need to add the files you want to commit then commit and finally push. For example, if you changed your README file and want to send the changes to GitHub, you would:

    git add README.md
    git commit -m "Updated README file"
    git push origin main
    

You need to always make sure you are pushing your code to the GitHub server or we will not be able to view it or grade it. You can double-check to make sure that the code was pushed by going to the GitHub website and checking that your repository shows the updated files.

Starting Code Files

The starting Lab 1 code has several files, although many of the files are nearly empty that you will fill in, and some of the files you should never edit. Here is a quick list of the files:

  • Actor.h/cpp - You will create an Actor class that goes in these files
  • CMakeLists.txt - This is a file that tells CMake (and CLion) how to build the game code. You should NEVER edit these files in any lab assignments
  • Core.cpp - You should NEVER edit this file
  • Game.h/cpp - You will create a Game class that goes into these files
  • Main.cpp - This contains mostly empty or “stub” implementations of the SDL3 callback functions we discussed in lecture. You will modify this
  • Math.h/cpp - This is a custom math library that we use in this class. You should NEVER edit these files
  • Transform.h/cpp - You will create a Transform class that goes into these files

Creating the Game Class

Because this is the very first lab assignment of the semester, the instructions are very detailed. As we progress further in the course, you will be expected to be able to add features to your game with increasingly limited instructions. So make sure you take your time to understand why we are doing certain things in a specific way as opposed to just blindly following the instructions.

In Game.h, after the #pragma once line, add an include for "SDL3/SDL.h". We need this because we will be using a lot of SDL types.

Next, declare a Game class. Inside the class, you will need the following public member functions:

// Initialize the game
// Returns true if successful
bool Initialize();

// Runs an interation of the game loop
// Returns true if the game loop should continue
bool RunIteration();

// Called when the game gets shutdown
void Shutdown();

// Called when the game receives an event from SDL
void HandleEvent(const SDL_Event* event);

Then in Game.cpp, add implementations of these member functions. For the moment, these functions don’t need to do anything other than return the appropriate type, as needed.

Don’t forget that in C++, you usually declare a class in the header file but then implement most of the functions in the cpp file. Also, don’t forget that in the cpp file you need to preface each member name with ClassType::. For example, your implementation of Shutdown would look like the code below.

void Game::Shutdown()
{
    // Code here
}

Using Constants

The style guide says you should avoid magic numbers. This is because if you have a constant like WINDOW_WIDTH in your code that makes a lot more sense than just 800. Also, say you need to calculate something that’s 50 units less than the window width. Is 750 easier to understand or WINDOW_WIDTH - 50?

Some constants might just be local to a function or inside a cpp file.

But other constants may need to be accessed elsewhere in the code, which then makes sense to make accessible in the public section of a class.

So, we’re going to declare two constants for the window width/height inside the Game class declaration. Add the following:

// Window width and height in pixels
static constexpr float WINDOW_WIDTH = 800.0f;
static constexpr float WINDOW_HEIGHT = 600.0f;

We made these two constants static so that other files can use something like Game::WINDOW_WIDTH to access it. Also, we use constexpr because the style guide says to prefer using constexpr for constant values, as it’s more flexible.

Game::Initialize

This function initializes the game. It returns true if the game successfully initializes and false otherwise.

For SDL functions, we will often point you to examples in the documentation. You should look at the example, and adapt it as needed for our usage (this means you won’t necessarily directly copy/paste code from the documentation).

Here are the steps to Initialize:

  1. The first thing you must do in Initialize is request that we want the game to run at 60 FPS (frames per second). To do this, add the following line of code:
    SDL_SetHint("SDL_MAIN_CALLBACK_RATE", "60");
    
  2. Next, Initialize SDL using SDL_Init: https://wiki.libsdl.org/SDL3/SDL_Init. We need the audio and video subsystems, so you will need to use the bitwise OR mentioned in lecture to initialize both. If SDL_Init fails, Initialize should return false.
  3. Create a window using SDL_CreateWindow: https://wiki.libsdl.org/SDL3/SDL_CreateWindow. Make the window have a with of 800 and a height of 600. For the flags, you can pass in 0. This function returns a SDL_Window*, which you will need to save in a private member variable in Game. If SDL_CreateWindow fails, Initialize should return false.
  4. Create a renderer using SDL_CreateRenderer: https://wiki.libsdl.org/SDL3/SDL_CreateRenderer. The first parameter is your window member variable, and the second should be nullptr. This function returns an SDL_Renderer*, which you also should save in a private member variable in Game. If SDL_CreateRenderer fails, Initialize should return false.
  5. If you get here, it means everything worked, so return true

Game::RunIteration

For now, just have RunLoop return true always. This means your game will never stop

Game::Shutdown

This function shuts down SDL systems (and anything else needed).

This function should:

  1. Destroy the renderer – https://wiki.libsdl.org/SDL3/SDL_DestroyRenderer
  2. Destroy the window – https://wiki.libsdl.org/SDL3/SDL_DestroyWindow
  3. Quit SDL – https://wiki.libsdl.org/SDL3/SDL_Quit

Adding a Global Instance of Game

While global instances are generally frowned upon, in this case it’s very convenient to declare a global instance of the game class which every other file can use.

First, in Game.h, after the declaration of the Game class, add the following line:

// Declare a global instance of the Game class
extern Game gGame;

Then in Game.cpp, add the following towards the top of the file (after the includes) to actually create the instance:

Game gGame;

Integrating the SDL Callback functions

Now we can hook up the SDL Callback functions in Main.cpp.

  • SDL_AppInit should call gGame.Initialize() and return one of two things based on what Initialize returns:
    • SDL_APP_CONTINUE if true
    • SDL_APP_FAILURE if false
  • SDL_AppIterate should call gGame.RunIteration() and return one of two things based on what RunIteration returns:
    • SDL_APP_CONTINUE if true
    • SDL_APP_SUCCESS if false
  • SDL_AppEvent should call gGame.HandleEvent(event) and always return SDL_APP_CONTINUE
  • SDL_AppQuit should call gGame.Shutdown()

Run your game using the bug button. When you run, you will see an empty window with a solid color background (probably black), which will look like this: Empty windowYou will notice that there is no way to close the window. The only way you can stop the game right now is by clicking the stop button in the debugger pane towards the bottom of CLion: Stop button in debugger

SDL_EVENT_QUIT

Let’s add support for quitting when you press the close button of the window.

First, add a bool member variable to Game that tracks whether the game should continue running. It should initialize to true.

Next, change the code inside RunIteration so that it returns false if the game should not continue running.

Finally, in HandleEvent, if the event->type is SDL_EVENT_QUIT, set your “continue running” member variable to false.

Now when you run your game, you should be able to close the game by clicking the window’s close button.

Adding the Game Iteration Functions

Remember for every iteration of the game loop, we want to ProcessInput, UpdateGame, and GenerateOutput. Create three private functions in Game for this and call them in the correct order in RunIteration.

Keyboard Input

While we could also use events for keys, it’s often easier to grab the state of the entire keyboard and test the status of individual keys.

At the start of ProcessInput, call SDL_GetKeyboardState: https://wiki.libsdl.org/SDL3/SDL_GetKeyboardState, saving the result in a local variable. You can just pass in nullptr as the parameter.

Once you have this pointer, since its pointing to an array, you can use the array [] syntax to access the bools in the array. In this case, you want to check the index for the ESC key scancode. If that’s true, then you should set your “continue running” member variable to false.

Now when you run the game, it should also quit if you press the ESC key.

Practicing Debugging

For the first few labs, you will be required to practice your debugging because it’s very important to be successful in the course.

You’ll need to take screenshots of certain things and add them to your Lab01/Screenshots folder. If you don’t know how to take a screenshot, there’s this helpful website: https://www.take-a-screenshot.org/

If you do not know how to use the debugger in CLion, or what some of these debugging terms mean, we have a tutorial that explains everything.

Now do the following (you will want to do all the steps in a single debugger run):

  1. Put a breakpoint on the line of code that sets your “continue running” member variable to false if the player presses ESC. Run in debug mode and press ESC. Now take a screenshot that shows the source code view paused on the line of code with the breakpoint. Save this in Lab01/Screenshots/1.png
  2. Now use the “Step Over” button. Take a screenshot of the debug panel at the bottom of the window, showing both the call stack as well as the value of your “continue running” variable. Save this in Lab01/Screenshots/2.png

Now commit and push everything. Verify that you can view your code (and screenshots) on the GitHub website!

Once you’ve pushed, you’re ready to move on to part 2.