Page 1 of 1

C++ Tile Engine from Scratch -- Part 3

#1 RevTorA  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 76
  • View blog
  • Posts: 251
  • Joined: 22-April 11

Posted 07 May 2011 - 05:04 PM

C++ Tile Engine from Scratch -- Part 3
In the last tutorial, we set up the groundwork for our tile engine by implementing the image manager and a Tile class, and then used those to draw a tile to the screen. Well, if drawing one tile was our goal in the last tutorial, our goal this tutorial is going to be drawing a whole bunch of tiles.

I want to make it clear that the way I am setting up this engine is in no way definitive. There are many ways to design a tile engine, and this is just how I've decided to design it this time. I hope that these tutorials get your mind thinking of cool new ways to design a tile engine, ways to make it faster, etc.

Solving an Issue
The most common use for a top-down 2D tile engine is for an rpg, and that is going to be the target "audience" for our tile engine. So, we have an issue that we need to consider. An RPG map can be large, hundreds of tiles wide and hundreds of tiles tall. So how do we draw all of that?

The answer is, we don't. Instead, we draw only a portion of the map on any frame. How do we decide which tiles to draw and where? The solution I like to use actually comes from 3D programming. We're going to implement a Camera.

The Camera will represent a viewport through which the user sees the map. It will have a position somewhere on our map with absolute pixel coordinates (i.e. if the x position is 200, it's 200 pixels to the right of the map origin, which is the upper left corner). It will also have a size, which we can use to determine how many tiles are visible through it. These two properties together will allow us to keep track of which tiles to draw and where.

I'd also like to implement another effect we can accomplish with a camera class, scrolling. We'll keep a target position, and move the camera towards the target slowly over time.

If that didn't make any sense, compile and take a look at the program included at the end of this tutorial.

The Camera Class
Now let's go ahead and create our Camera class in a new header file called "Camera.h":
#ifndef _CAMERA_H
#define _CAMERA_H

#include <SFML\Graphics.hpp>
#include "Tile.h"

class Camera
{
private:
	//Absolute position of camera (number of
	//pixels from origin of level map)
	sf::Vector2f position;

	//Target position camera is moving towards
	sf::Vector2f target;

	//Size of camera
	sf::Vector2i size;

	//Speed of camera, a value between 0.0 and 1.0
	float speed;

public:
	Camera(int w, int h, float speed);
	~Camera();

	//Moves camera immediately to coordinates
	void Move(int x, int y);
	void MoveCenter(int x, int y);

	//Sets camera target
	void GoTo(int x, int y);
	void GoToCenter(int x, int y);

	//Updates camera position
	void Update();

	sf::Vector2i GetPosition() { return sf::Vector2i((int)position.x, (int)position.y); }
	
	//Helper function for retreiving camera's offset from
	//nearest tile
	sf::Vector2i GetTileOffset(int tileSize) { return sf::Vector2i((int)(position.x) % tileSize, (int)(position.y) % tileSize); }

	//Helper function for retreiving a rectangle defining
	//which tiles are visible through camera
	sf::IntRect GetTileBounds(int tileSize);
};

#endif



Hopefully nothing too complicated. We have members for the camera's position and target, as well as it's size and speed. The speed variable will be used in the Update method to allow the user of the class (whoever that may be) to have some control over how quickly the camera scrolls to the target.

There are two methods for changing the camera's position. The two Move methods will set the camera's position, moving it instantly to the specified coordinates. The two GoTo methods however, will set the camera's target, causing the camera to scroll to the specified coordinates over time.

There's a Center version of both Move and GoTo, that will center the camera or target on the specified coordinates rather than set the camera's upper left corner (the camera's origin). This will be useful for centering over the player later on, and encapsulates the camera's size away from the rest of the engine.

We've implemented inline versions of GetPosition and GetTileOffset, since they are fairly trivial functions and this will remove any unnecessary overhead. GetPosition simply returns the camera's position, while GetTileOffset return the camera's position relative to the nearest tile's origin. Both of these will be useful when drawing our tiles and moving the camera around.

Alright, time to implement all this fun stuff, so let's do so in a new code file called "Camera.cpp":
#include <SFML\Graphics.hpp>
#include <math.h>
#include "Camera.h"

Camera::Camera(int w, int h, float speed)
{
	size.x = w;
	size.y = h;

	if(speed < 0.0)
		speed = 0.0;
	if(speed > 1.0)
		speed = 1.0;

	this->speed = speed;
}

Camera::~Camera()
{

}

//Moves camera to coordinates
void Camera::Move(int x, int y)
{
	position.x = (float)x;
	position.y = (float)y;
	target.x = (float)x;
	target.y = (float)y;
}

//Centers camera at coordinates
void Camera::MoveCenter(int x, int y)
{
	x = x - (size.x / 2);
	y = y - (size.y / 2);

	position.x = (float)x;
	position.y = (float)y;
	target.x = (float)x;
	target.y = (float)y;
}

//Sets target to coordinates
void Camera::GoTo(int x, int y)
{
	target.x = (float)x;
	target.y = (float)y;
}

//Centers target at coordinates
void Camera::GoToCenter(int x, int y)
{
	x = x - (size.x / 2);
	y = y - (size.y / 2);

	target.x = (float)x;
	target.y = (float)y;
}

//This function allows us to do a cool camera
//scrolling effect by moving towards a target
//position over time.
void Camera::Update()
{
	//X distance to target, Y distance to target, and Euclidean distance
	float x, y, d;

	//Velocity magnitudes
	float vx, vy, v;

	//Find x and y
	x = (float)(target.x - position.x);
	y = (float)(target.y - position.y);

	//If we're within 1 pixel of the target already, just snap
	//to target and stay there. Otherwise, continue
	if((x*x + y*y) <= 1)
	{
		position.x = target.x;
		position.y = target.y;
	}
	else
	{
		//Distance formula
		d = sqrt((x*x + y*y));

		//We set our velocity to move 1/60th of the distance to
		//the target. 60 is arbitrary, I picked it because I intend
		//to run this function once every 60th of a second. We also
		//allow the user to change the camera speed via the speed member
		v = (d * speed)/60;

		//Keep v above 1 pixel per update, otherwise it may never get to
		//the target. v is an absolute value thanks to the squaring of x
		//and y earlier
		if(v < 1.0f)
			v = 1.0f;
		
		//Similar triangles to get vx and vy
		vx = x * (v/d);
		vy = y * (v/d);

		//Then update camera's position and we're done
		position.x += vx;
		position.y += vy;
	}
}

sf::IntRect Camera::GetTileBounds(int tileSize)
{
	int x = (int)(position.x / tileSize);
	int y = (int)(position.y / tileSize);

	//+1 in case camera size isn't divisible by tileSize
	//And +1 again because these values start at 0, and
	//we want them to start at one
	int w = (int)(size.x / tileSize + 2);
	int h = (int)(size.y / tileSize + 2);

	//And +1 again if we're offset from the tile
	if(x % tileSize != 0)
		w++;
	if(y % tileSize != 0)
		h++;

	return sf::IntRect(x, y, w, h);
}



Most of this code should be self explanatory, except for maybe the Update method. Unfortunately, there is a bit of math involved (oh noes!), but nothing too difficult. I've commented the code enough that anyone with any knowledge of algebra should understand it. If not, then I suggest reading any of the great math primers available here on Dream.In.Code. Hopefully the rest of this code will make sense after we implement a new RenderFrame method.

The Level Class
Alright, we now have a way of determining which tiles to draw and where. Now we need a way of storing each of the tiles. Since we're implementing the framework for an RPG engine, let's call our next class Level. Go ahead and create a new header file, call it "Level.h", and fill it with the following code:
#ifndef _LEVEL_H
#define _LEVEL_H

#include <vector>
#include "Tile.h"

class Level
{
private:
	//A 2D array of Tile pointers
	std::vector<std::vector<Tile*> > map;

	//Width and height of level (in tiles)
	int w;
	int h;

	void SetDimensions(int w, int h);
	
public:
	Level(int w, int h);
	~Level();

	void AddTile(int x, int y, Tile* tile);
	Tile* GetTile(int x, int y);

	void LoadLevel();

	int GetWidth();
	int GetHeight();
};

#endif



We're going to store our map data in a 2D array. While we could have accomplished this with Tile* map[MAXWIDTH][MAXHEIGHT];, or something similar, we want our engine to be as versatile as possible. Therefore, we want a dynamic array. Unfortunately, there isn't a very simple way of doing that, but this is as simple as it gets. What we're using is a vector of a vector of Tile pointers. The cool part about this, is we can actually access individual Tile pointers with the familiar map[x][y] syntax.

In case it's ever requested later on, we go ahead and keep track of the map's width and height. The map's width and height are changed by the SetDimensions method, which is private right now since it's only needed by the Level constructor. We then have an AddTile method for putting a tile into the map, and a GetTile method for retrieving a tile from the map.

The LoadLevel method will be implemented in our next tutorial, and will load level data from a file on the harddrive.

Alright, time to implement this class in a new code file called "Level.cpp":
#include <vector>
#include "Level.h"
#include "Tile.h"

Level::Level(int w, int h)
{
	SetDimensions(w, h);
	this->w = w;
	this->h = h;
}

Level::~Level()
{

}

int Level::GetHeight()
{
	return h;
}

int Level::GetWidth()
{
	return w;
}

void Level::SetDimensions(int w, int h)
{
	//w rows
	map.resize(w);

	//Each row has h columns of null Tile pointers
	for(int i = 0; i < w; i++)
	{
		map.at(i).resize(h, 0);
	}
}

void Level::AddTile(int x, int y, Tile* tile)
{
	map[x][y] = tile;
}

Tile* Level::GetTile(int x, int y)
{
	return map[x][y];
}

void Level::LoadLevel()
{
	//Eventually we'll write code to load level data from a
	//file, but for now we'll just make it all up.
}



This is all very simple actually. The constructor takes in a width and height for defining the dimensions of the Level, which then uses the SetDimensions method to resize the map vector. Notice how we must first resize the map vector, which is a vector of vectors, with the width and then resize each of the vectors in that vector, which are vectors of Tile pointers. We also make sure that we initialize the vector with NULL pointers, so we don't have any nasty access violation exceptions later on.

Using These New Classes in Our Engine
Great, we now have a way to store the level data, and a way to determine how we're going to draw our frames. Now it's time to put this together to create a test demo. First, let's rewrite our RenderFrame method in Engine.cpp:
void Engine::RenderFrame()
{	
	//Camera offsets
	int camOffsetX, camOffsetY;

	Tile* tile;

	window->Clear();
	
	//Get the tile bounds we need to draw and Camera bounds
	sf::IntRect bounds = camera->GetTileBounds();

	//Figure out how much to offset each tile
	camOffsetX = camera->GetOffset().x;
	camOffsetY = camera->GetOffset().y;

	//Loop through and draw each tile
	//We're keeping track of two variables in each loop. How many tiles
	//we've drawn (x and y), and which tile on the map we're drawing (tileX
	//and tileY)
	for(int y = 0, tileY = bounds.Top; y < bounds.Height; y++, tileY++)
	{
		for(int x = 0, tileX = bounds.Left; x < bounds.Width; x++, tileX++)
		{
			//Get the tile we're drawing
			tile = currentLevel->GetTile(tileX, tileY);

			tile->Draw((x * tileSize) - camOffsetX, (y * tileSize) - camOffsetY, window);
		}
	}

	window->Display();
}



This will be the method we'll stick to for most of this tutorial series. What this method does is asks the camera which tiles are currently in view as well as how much the camera is offset from those tiles. Then it loops through each row of tiles and each column, starting from bounds.Top and drawing bounds.Height rows, and starting from bounds.Left and drawing bounds.Width columns. Inside the loop we get the current tile we're drawing, and tell it to draw at the current row/column, taking into account the camera's offset.

If it seems complicated, try working through it manually to see if you can figure out how it works. The following method, which we've added to Engine, is a temporary method that generates a simple level for us to look at:
void Engine::LoadLevel()
{
	//Temporary level for testing
	currentLevel = new Level(40, 40);

	Tile* tile;
	for(int y = 0; y < 40; y++)
	{
		for(int x = 0; x < 40; x++)
		{
			if(y % 4 == 0)
				tile = new Tile(imageManager.GetImage(1));
			else
				tile = new Tile(imageManager.GetImage(0));

			currentLevel->AddTile(x, y, tile);
		}
	}
}



Feel free to modify this if you want. All it does is create a 40x40 level, and fill it with our first sprite (index 0), and put a line of our second sprite every 4th row.

Here are the ProcessInput and Update functions we're using for now. These are to show off the camera "scrolling" effect we implemented with the Camera::Update method.
void Engine::ProcessInput()
{
	sf::Event evt;
	//Loop through all window events
	while(window->PollEvent(evt))
	{
		if(evt.Type == sf::Event::Closed)
			window->Close();
		
		if((evt.Type == sf::Event::MouseButtonPressed) && (mouseDown == false))
		{
			int x = camera->GetPosition().x + window->GetInput().GetMouseX();
			int y = camera->GetPosition().y + window->GetInput().GetMouseY();
			camera->GoToCenter(x, y);
			mouseDown = true;
		}
		if(evt.Type == sf::Event::MouseButtonReleased)
			mouseDown = false;
	}
}

void Engine::Update()
{
	camera->Update();
}



We're letting the user click on a new location to center the camera over, and use GoTo so it scrolls there slowly instead of using Move to do it instantly. Then we remember to call camera->Update(); in the Update function.

Conclusion
Phew! That was a lot of code, but our objective was to draw a lot of tiles, and we have. Unfortunately (and I promise this will never happen again!), I forgot to save a copy of this tutorial's source code before beginning to write the code for the next tutorial, so you'll just have to check out that tutorial! See you soon!

Is This A Good Question/Topic? 2
  • +

Replies To: C++ Tile Engine from Scratch -- Part 3

#2 DarkGlitch  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 25
  • Joined: 24-July 11

Posted 26 July 2011 - 06:45 AM

A few questions:

Is LoadLevel() a member of Engine or Level? You have it declared in Level.h but defined in Engine.cpp.

Also, are you missing something or am I?

sf::Vector2i GetTileOffset(int tileSize) { return sf::Vector2i((int)(position.x) % tileSize, (int)(position.y) % tileSize); }
You have GetTileOffset(int tileSize) but here:

camOffsetX = camera->GetOffset().x;
camOffsetY = camera->GetOffset().y;



You have this function. Also I don't see any place where you defined the tile size so I assumed this would be done in a header file with constants in it or it would be gained from a config file later on. Am I right or did I miss something?

So far, this tutorial is great, but I still can't get my images to load so I'm not sure what I'm doing wrong. I've checked everything about 10 times now.
Was This Post Helpful? 0
  • +
  • -

#3 bluewood  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 28-July 11

Posted 28 July 2011 - 09:23 AM

Your tutorial is nice, but you're missing some potential pitfalls:
  • you create several instances of Tile objects dynamically, but you never delete them anywhere in your code, which could lead to memory leaks;
  • let's say we have a map smaller than the screen we're using (like in Pokemon, for example) - the draw tiles loop in Engine::RenderFrame() doesn't take into account the actual size of the map (which could be really small), only the screen resolution - you'll be accessing invalid memory regions;
  • you don't place restrictions on the Camera target values - if someone tried to click "indefinitely" to reach the edge of the map, eventually target values become invalid and you'll be accessing invalid memory regions.


More than code about tiling engines, I think everyone would appreciate if you put code that avoids problems like memory leaks and accessing invalid memory addresses.

But keep up the good work. Started this tutorial yesterday and I can't wait for the other parts :)

PS: Try replacing the operator[] with function Vector::at() when you're accessing vector positions. Invalid memory access errors aren't masked this way and are easier to detect.
Was This Post Helpful? 0
  • +
  • -

#4 SinisterRainbow  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 29-August 11

Posted 29 August 2011 - 06:49 AM

I'm also very much enjoying this tutorial - as two of my favorite games of all time (Zelda and Shining Force) were written similarly. I think we all are experiencing some confusion however, as some of the functions are not defined, and due to sfml 2.0 being in beta (and constantly changing) some of the code here I've had to change to work with sfml (such as changing most Image class references to Textures)..

This last lesson is throwing me a bit. I had to add a Camera class variable to the Engine, and hope I did it right. But the real kicker seems to be the temporary testing it all in the ProcessInput() from Engine.cpp. So far I've had to change detecting a mouse event (I changed it personally to:
if (sf::Mouse::IsButonPressed(sf::Mouse::Right)) { //camera code
 }


but also am having trouble with the code inside there camera->GetTileBounds is not available, and not sure where gettilebounds is even defined (did I miss something)?
Was This Post Helpful? 0
  • +
  • -

#5 St0n3y  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 15-May 12

Posted 15 May 2012 - 11:23 AM

View PostDarkGlitch, on 26 July 2011 - 06:45 AM, said:

A few questions:

Is LoadLevel() a member of Engine or Level? You have it declared in Level.h but defined in Engine.cpp.

Also, are you missing something or am I?

sf::Vector2i GetTileOffset(int tileSize) { return sf::Vector2i((int)(position.x) % tileSize, (int)(position.y) % tileSize); }
You have GetTileOffset(int tileSize) but here:

camOffsetX = camera->GetOffset().x;
camOffsetY = camera->GetOffset().y;



You have this function. Also I don't see any place where you defined the tile size so I assumed this would be done in a header file with constants in it or it would be gained from a config file later on. Am I right or did I miss something?

So far, this tutorial is great, but I still can't get my images to load so I'm not sure what I'm doing wrong. I've checked everything about 10 times now.


Add the Tile to it and it should work - I think alot has been missed out on this tutorial, because I'm having issues with several things also. I've cleared most stuff up except I seem to be having issues with:

for(int y = 0; tileY = bounds.top; y < bounds.height; y++, tileY++){
		for(int x = 0, tileX = bounds.left; x < bounds.width; x++, tileX++){
			tile = currentLevel->GetTile(tileX, tileY);

			tile->Draw((x*tileSize) - camOffsetX, (y * tileSize) - camOffsetY, window);
		}
	}


It has no idea what tileY is and doesn't like the for loop.. Now I'm fiddling with code bits to try and get it working...
Was This Post Helpful? 0
  • +
  • -

#6 St0n3y  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 15-May 12

Posted 15 May 2012 - 11:28 AM

View PostSt0n3y, on 15 May 2012 - 11:23 AM, said:

View PostDarkGlitch, on 26 July 2011 - 06:45 AM, said:

A few questions:

Is LoadLevel() a member of Engine or Level? You have it declared in Level.h but defined in Engine.cpp.

Also, are you missing something or am I?

sf::Vector2i GetTileOffset(int tileSize) { return sf::Vector2i((int)(position.x) % tileSize, (int)(position.y) % tileSize); }
You have GetTileOffset(int tileSize) but here:

camOffsetX = camera->GetOffset().x;
camOffsetY = camera->GetOffset().y;



You have this function. Also I don't see any place where you defined the tile size so I assumed this would be done in a header file with constants in it or it would be gained from a config file later on. Am I right or did I miss something?

So far, this tutorial is great, but I still can't get my images to load so I'm not sure what I'm doing wrong. I've checked everything about 10 times now.


Add the Tile to it and it should work - I think alot has been missed out on this tutorial, because I'm having issues with several things also. I've cleared most stuff up except I seem to be having issues with:

for(int y = 0; tileY = bounds.top; y < bounds.height; y++, tileY++){
		for(int x = 0, tileX = bounds.left; x < bounds.width; x++, tileX++){
			tile = currentLevel->GetTile(tileX, tileY);

			tile->Draw((x*tileSize) - camOffsetX, (y * tileSize) - camOffsetY, window);
		}
	}


It has no idea what tileY is and doesn't like the for loop.. Now I'm fiddling with code bits to try and get it working...


...Ignore all of that... I've been programming all day... I've been up all night... I'm tired... and I went and put a ; instead of , ... ... ... ... Carry on. :P Great tutorials by the way RevTorA - :)
Was This Post Helpful? 0
  • +
  • -

#7 SergeantBalthazar  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 11
  • Joined: 14-March 12

Posted 20 August 2012 - 03:39 PM

At this part
 Level::Level(int w, int h)
{
    SetDimensions(w, h);
    this->w = w;
    this->h = h;
} 
I'm getting error:
 C:\Users\SergeantBalthazar\Desktop\Coding\C++\RPG Engine\Test\Level.cpp|8|undefined reference to `Level::SetDimensions(int, int)'| 


And here is that function in Level.h:
 int w;
int h;

    void SetDimensions(int w, int h); 

Was This Post Helpful? 0
  • +
  • -

#8 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 998
  • View blog
  • Posts: 4,173
  • Joined: 14-February 08

Posted 21 August 2012 - 05:50 AM

Seems like it could be a circular dependency issue. Make sure that only the needed headers are included. I see that in the tutorial both level.h and level.cpp both include tile.h.
Was This Post Helpful? 0
  • +
  • -

#9 SergeantBalthazar  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 11
  • Joined: 14-March 12

Posted 21 August 2012 - 01:14 PM

Actually, no. The problem was that I hadn't finished Level.cpp completely :P
Was This Post Helpful? 0
  • +
  • -

#10 aratnon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 03-February 13

Posted 29 March 2013 - 10:42 PM

Could anybody explain me about the formula in update method in Camera class ? I can't imagine what's going on in the code.
Was This Post Helpful? 0
  • +
  • -

#11 muramasa  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 12-May 14

Posted 12 May 2014 - 03:30 PM

View Postaratnon, on 29 March 2013 - 10:42 PM, said:

Could anybody explain me about the formula in update method in Camera class ? I can't imagine what's going on in the code.

The algorithm is like this...

(You can imagine that first 2 lines are just variable initializations).

First, it defines the distance between the actual position and the target position (which is calculated with the formula SqrRoot(x^2+y^2),the good-old hypotenuse. x being difference between actual x and target x, and similarly, y being actualY - targetY).
    //Find x and y
    x = (float)(target.x - position.x);
    y = (float)(target.y - position.y);

    //If we're within 1 pixel of target already, just snap to target and stay there, Otherwise, continue
    if((x*x + y*y) <= 1)
    {
        position.x = target.x;
        position.y = target.y;
    }
    else
    {
        //Distance formula
        d = sqrt((x*x+y*y));


Basically, that distance is used to move the camera by 1/60th of the total distance on each frame.
        //We set our velocity to move 1/60th of the distance to the target.
        //60 is arbitrary, I picked it because I intend to run this function once every 60th of a second.
        //We also allow the user to change the camera speed via the speed member.
        v = (d*speed) / 60;

        //Keep v above 1 pixel per update, otherwise it may never get to the target.
        //v is an absolute value thanks to the squaring of x and y earlier.
        if(v < 1.0f)
            v = 1.0f;

        //Similar triangles to get vx and vy
        vx = x * (v/d);
        vy = y * (v/d);

        //The update camera's position and we're done
        position.x += vx;
        position.y += vy;


Though I've observed something...

If we have
v = (d*speed) / 60

And then we do
vx = x* (v/d)

...
vx = x * (d*speed/60)/d
vx = x * speed/60

...So is d necessary? I actually removed all d references (and the check for v being > than 1.0f), and, as long as I can see, it works the same.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1