Roguelike tutorial - Part 1

Intro

This will be a roguelike tutorial based on /r/rouglikedev/ Does The Complete Roguelike Tutorial. Insted of Python and Tcod, I will be using C++ and the olcPixelGameEngine. For those of you who don't know it, the PixelGameEngine is a project from javidx9 a.k.a. OneLoneCoder. It is described as:

PGE Provides a fast, richly featured, cross platform pixel drawing and user interface framework

javidx9 uses the PGE as a tool to teach programming in C++ on his youtube channel that I highly recommend you check out. His explinations of complex topics are great and very clear.

Now for a small disclaimer. I am not a C++ programmer, I work with Python and Javascript and am a DevOps engineer by trade. Having said that, this will be as much a learning experience for me to learn some C++ and build a little game. I will try to explain the code as best as I can, but don't get upset if I get it wrong :). If you read it and think I should do something different, leave a comment or make a pull request.

For more info about the pixelGameEngine, Check out the link to the repo above, or checkout javidx9's Youtube channel.

Setup

Lets get started! Getting started with the PixelGameEngine is easy, go to the github page and download a copy of olcPixelGameEngine.h. Create a folder and put a the copy of olcPixelGameEngine.h in it. Then create a file called rogue.cpp and a file called Makefile. I like to use a Makefile so I don't have to remember the command to build a project, but that is not mandetory. Put the following code in the make file (if you don't want to use a Makefile, us the g++ command to compile your code):

all:
	g++ -o rogue rogue.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17

The minimal code

This will make it possible for us to just type make and it will build our game. Now we need to add the basic setup code for the PGE to work:

#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"

class RogueLike : public olc::PixelGameEngine
{
  public:
    RogueLike()
    {
      sAppName = "Roguelike Tutorial";
    }
  public:
    bool OnUserCreate() override
    {
      // Called once at the start, so create things here
      return true;
    }

    bool OnUserUpdate(float fElapsedTime) override
    {
      // called once pre frame
      DrawString(10, 10, "@", olc::WHITE);
      return true;
    }
};

int main()
{
  RogueLike rogue;
  if (rogue.Construct(256, 240, 4, 4))
    rogue.Start();
  return 0;
}

Compile and run it, and you should see our 'player' on the screen!

The player

How does this work?

Lets go through the code quickly. Please keep in mind that I don't do C++ on a daily basis, so what I say might be complete rubish!

#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"

These are prepocessor directives, the define creates a sort of constant and is needed to make sure we only start one instance. The include statement loads the code for the PGE so we can use  it.

class RogueLike : public olc::PixelGameEngine
{
  public:
    RogueLike()
    {
      sAppName = "Roguelike Tutorial";
    }

Here we create a class that inherits from the PGE, followed by the class constructor. In the constructor we set the name of our app

public:
    bool OnUserCreate() override
    {
      // Called once at the start, so create things here
      return true;
    }

This function (as the comments states) is run on engine start, to setup things we need.

    bool OnUserUpdate(float fElapsedTime) override
    {
      // called once pre frame
      DrawString(10, 10, "@", olc::WHITE);
      return true;
    }
};

The OnUserUpdate is the main game loop. In the loop we have a call to DrawString that puts text on the screen.

int main()
{
  RogueLike rogue;
  if (rogue.Construct(256, 240, 4, 4))
    rogue.Start();
  return 0;
}

The main function is the entry point to our game. We first create an instance of our game class. Then, in the if statemen, we call the constructor with a with, hight, and pixel scaling parameters. If this returns true, we start our actual game. Not mutch to it, right?

Add some Movement

Just some text on the screen is not a real game, so it is time to start moving him around. First we will add
some constants:

const int OFFSET = 8;
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

We will use the Width and Hight later in te constructor to avoid magic numbers in our code. The OFFSET is needed becaus we want our game to work on grid, and after some experimenting it turned our that 8 pixels was what we needed for that. Now update the if statement in the main function to use the constants:

  if (rogue.Construct(SCREEN_WIDTH, SCREEN_HEIGHT, 4, 4))

Now we add 2 variables to keep track of the player's position:

int player_x = 2;
int player_y = 2;

And then update the DrawString call to use these:

DrawString(player_x*OFFSET, player_y*OFFSET, "@", olc::WHITE);

With the setup out out of the way it's time to get some actual movement. Add the following code to the OnUserUpdate function to handle the user input:

if (GetKey(olc::Key::RIGHT).bReleased) player_x += 1;

if you compile the game now, you can move the player. Except, its leaving a trail. That is no good! Add the following function before the DrawString function:

Clear(olc::BLACK);

This will clear the screen on every update. Now we need to add the keys to move the player in the other
directions:

if (GetKey(olc::Key::LEFT).bReleased) player_x -= 1;
if (GetKey(olc::Key::UP).bReleased) player_y -= 1;
if (GetKey(olc::Key::DOWN).bReleased) player_y += 1;

And that is it, we have a moving player! Great, but it feels a bit slaped together. We can do better.

Refactor

After you get something working, it is always a good idea to look it over and see if you can improve it. We are going to refactor our code into multiple files, to make it simpeler to maintain. Create a file called olcPixelGameEngine.cpp and add these lines to the top:

#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"        

Remove the #define line from rogue.cpp and create a new file called Actor.h and Actor.cpp. Add the following code to Actor.h:

#include "olcPixelGameEngine.h"

class Actor
{
public:
    Actor()
    {
    }

    void DrawSelf(olc::PixelGameEngine* pge, int offset);
    void Move(std::string direction);

public:
    olc::vf2d position;
};

In a header file you define the functions and variabales, and in the cpp file you implement them. We will call the DrawSelf function in the main loop, aswell as the move function to handle the input. Note the draw function has a pointer to our game engine. This is needed to be able to actually use the draw functions. Now and add the following to Actor.cpp:

#include "olcPixelGameEngine.h"
#include "Actor.h"

olc::vf2d position = {1, 1};

void Actor::DrawSelf(olc::PixelGameEngine* pge, int offset)
{
  pge->DrawString(position.x*offset, position.y*offset, "@", olc::WHITE);
}

void Actor::Move(std::string direction) {
  if (direction == "right" ) position.x += 1;
  if (direction == "left" ) position.x -= 1;
  if (direction == "down" ) position.y += 1;
  if (direction == "up" ) position.y -= 1;
}

Fist we set the position, then we define the draw function, and add a new way to handle the movement of our player. As a final step we need to update rouge.cpp, first move the OFFSET declaration into the RogueLike class and create a player of the type Actor:

  public:
    int OFFSET = 8;
  protected:
    Actor player;

Then we need to update the OnUserUpdate function to reference our player class and move function:

if (GetKey(olc::Key::RIGHT).bReleased) player.Move("right");
if (GetKey(olc::Key::LEFT).bReleased) player.Move("left");
if (GetKey(olc::Key::UP).bReleased) player.Move("up");
if (GetKey(olc::Key::DOWN).bReleased) player.Move("down");

Now remove the call to DrawString and replace it with the player.DrawSelf function. Note the this that is a reference to the PGE.

Clear(olc::BLACK);

// Draw the  player
player.DrawSelf(this, OFFSET);

and offcourse don't forget to update our include section:

#include "Actor.h"

Finally we need to update the Makefile:

all:
	g++ -o rogue rogue.cpp olcPixelGameEngine.cpp Actor.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17

If you run make now, and start the game, we still have a moving @ but the code looks better!

Conclusion

That was a lot longer than I thougt it would be when I started writing this. I hope every thing was clear, and you learned something and had some fun. I got a bit ahead of the RoguelikeDev tutorial by already creating a generic actor class, but I feel it fits in this part. In the next part we will start working on a game map. For now check out the finished code for this part on github and see you in part 2!

Show Comments