Tick()
functions to update game objects independently of eventsIn our previous projects, we’ve updated the state of our objects based on events detected in our application loop:
// Application Loop
while (shouldContinue) {
// Handle Events
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) {
shouldContinue = false;
}
GameObject.HandleEvent(Event);
}
// Render Objects
GameWindow.Render();
GameObject.Render(GameWindow.GetSurface());
// Update Frame
GameWindow.Update();
}
However, in more complex projects, most objects may need to update their state consistently, even when they’re not receiving any events. For example, characters not controlled by our player may need to continue to act and move, and animation, and visual effects should continue to update.
In this lesson, we’ll introduce how to write data from our program to external sources. We’ll focus on writing to files for now, but the techniques we cover lay the foundations for working with other data streams, such as network traffic.
As we might expect, SDL’s mechanism for writing data shares much in common with their API for reading data. We’ll rely on SDL_RWops
objects, and the functions we use will be familiar to what we learned in the previous lesson.
Like before, we’ll start with a basic main
function that initializes SDL, and calls Write()
and Read()
functions within an included File
 namespace.
// main.cpp
#include <SDL.h>
#include "File.h"
int main(int argc, char** argv) {
SDL_Init(0);
File::Write("output.txt");
File::Read("output.txt");
return 0;
}
Our Read()
function logs out the file’s contents, using techniques we covered in the previous lesson. In this lesson, we’ll work on the Write()
function, which is currently empty:
// File.h
#pragma once
#include <iostream>
#include <SDL.h>
namespace File {
void Read(const std::string& Path) {
char* Content{static_cast<char*>(
SDL_LoadFile(Path.c_str(), nullptr)
)};
if (Content) {
std::cout << "Content: " << Content;
} else {
std::cout << "Error loading file: "
<< SDL_GetError();
}
SDL_free(Content);
}
void Write(const std::string& Path) {
// ...
}
}
Running our program, we should see an error output from our Read()
function, as it’s trying to read a file that we haven’t created yet:
Error loading file: Parameter 'src' is invalid
Throughout this chapter, we’ve started to take more proactive control over the memory used in our programs, and controlling the lifecycle of objects (when they’re created, and when they’re destroyed)
We’ve used standard library utilities like std::unique_ptr
and std::vector
to help with this. In this lesson, we’ll learn how to take full control by manually managing our object lifecycles.
We’ll also see how this can be difficult, dangerous, and something to avoid where possible.
However, avoiding it is not always an option. In larger projects, we’ll interact with libraries or other systems that implement manual memory management, and understanding the techniques involved and problems to avoid will be extremely useful.
In this lesson, we’ll explore how objects are copied in more depth. There are two scenarios where our objects get copied. The first is when a new object is created by passing an existing object of the same type to the constructor:
struct Weapon{/*...*/};
int main() {
Weapon SwordA;
// Create SwordB by copying SwordA
Weapon SwordB{SwordA};
}
This copying process also happens when we pass an argument by value to a function. The function parameter is created by copying the object provided as the corresponding argument:
struct Weapon{/*...*/};
void SomeFunction(Weapon W) {/*...*/}
int main() {
Weapon Sword;
// Create the W parameter by copying Sword
SomeFunction(Sword);
}
The second scenario is when an existing object is provided as the right operand to the =
operator. In the following example, we’re expecting an existing PlayerTwo
to be updated by copying values from PlayerOne
:
struct Weapon{/*...*/};
int main() {
Weapon SwordA;
Weapon SwordB;
// Update SwordA by copying values from SwordB
SwordA = SwordB;
}
As we’ve likely noticed, C++ supports these behaviors by default, even when the objects we’re copying use our custom types. In this lesson, we’ll explore what that default behavior does, and learn how to override it when our classes and structs have more complex requirements.
std::vector
std::vector
Inevitably, we will want to store a group of related objects. That might be a collection of characters in a party, a collection of buttons on a UI, or countless other use cases.
Let's imagine we have these objects, which we want to store and manage as a single collection:
class Character {};
Character Frodo;
Character Gandalf;
Character Gimli;
Character Legolas;
Character Aragorn;
Programmers have invented a huge range of options for storing collections of objects. These containers are broadly called data structures, and which data structure we should use depends on our specific requirements. We’ll cover a much wider range of data structures in the next course. Here, we’ll introduce the most common choice - the dynamic array.
Under the hood, arrays are contiguous blocks of memory, big enough to store multiple objects in sequence.
SDL_RWops
In this chapter, we’ll introduce read/write operations, specifically using SDL_RWops
. This is the API that SDL provides for reading from and writing to files on our players’ hard drives.
Many of the topics covered here additionally lay the foundation for more advanced capabilities. Streaming data to and from files has much in common with streaming data over a network, for example, which is fundamental for creating multiplayer games.
In this lesson, we’ll create a basic SDL application that calls Write()
and then Read()
within a File
namespace we’ll create. The file I/O system is initialized by default, so we can simply pass 0
as the initialization flag to SDL_Init()
:
#include <SDL.h>
#include "File.h"
int main(int argc, char** argv) {
SDL_Init(0);
File::Write("data.txt");
File::Read("data.txt");
return 0;
}
The following code example shows the content of our File
 namespace.
We’ll focus on the Read()
function in this lesson and will cover writing in the next lesson. For now, we can just note that Write()
creates a file on our hard drive containing the text "Hello World", so we have something to test our Read()
function with:
// File.h
#pragma once
#include <iostream>
#include <SDL.h>
namespace File{
void Read(const std::string& Path) {}
void Write(const std::string& Path) {
SDL_RWops* Context{
SDL_RWFromFile(Path.c_str(), "wb")};
const char* Content{"Hello World"};
SDL_RWwrite(Context, Content, sizeof(char),
strlen(Content));
SDL_RWclose(Context);
}
}
In this final part of our Minesweeper series, we'll add the ability for players to place flags on suspected mine locations.
We'll update our game logic to handle flag placement and removal, create a visual representation for flags, and implement a counter to track the number of flags placed.
By the end of this lesson, you'll have a fully functional Minesweeper game with all the classic features players expect!
In this series, we'll build a fully functional Minesweeper game from scratch, giving you hands-on experience with game development concepts and C++Â programming.
We'll separate our project into two main parts:
For example, we'll create a general Button
class in our engine that can be used across various projects. The cells of our Minesweeper grid will then inherit from this Button
class, expanding it with Minesweeper-specific logic such as whether the cell contains a bomb and counting the number of bombs in adjacent cells.
In this lesson, we’ll combine the techniques we’ve covered so far in this chapter into a cohesive Image
class. We’ll focus on creating a friendly API so external code can easily control how images are rendered onto their surfaces We’ll cover:
As a starting point, we’ll be building upon a basic Window
and application loop, built using topics we covered earlier in the course:
Image
APIIn this lesson, we’ll discuss some ways we can improve upon the Image
class created in the previous lesson. We’ll cover three topics:
The examples we use here are based on the Image
class we created previously:
In this lesson, we’ll update our game to detect and react to the player winning or losing.
Let’s get this working!
SDL_Image
SDL_Image
.In this lesson, we’ll start using the SDL_Image
extension we installed earlier. We’ll cover 3 main topics:
SDL_Image
IMG_Load()
function to load and render a wide variety of image types, rather than being restricted to the basic bitmap (.bmp
) format.IMG_SaveJPG()
and IMG_SavePNG()
We’ll be building upon the basic application loop and surface-blitting concepts we covered earlier in the course:
SDL_ttf
SDL_ttf
extensionIn this lesson, we’ll see how we can render text within our programs. We’ll use the official SDL_ttf extension we installed earlier in the course.
We’ll build upon the concepts we introduced in the previous chapters. Our main.cpp
looks like below.
The key thing to note is that we have created a Text
class, and instantiated an object from it called TextExample
. This object is being asked to Render()
onto the window surface every frame:
SDL_ttf
In this lesson, we’ll build on our text rendering capabilities with some new tools:
Our main.cpp
is below. Compared to the last lesson, we’ve added a GetWidth()
method to the Window
 class.