Writing C ++ Games, Part 1/3 - Writing a Mini-Framework

  • Tutorial
We write games in C ++, Part 2/3 - State-based programming
We write games in C ++, Part 3/3 - Classics of the genre

Hello, Habrahabr!

There are not many lessons on creating games on the hub, why not support domestic developers?
I present to you my lessons that teach you how to create games in C ++ using SDL!

What you need to know


  • At least a basic knowledge of C ++ (we will use Visual Studio)
  • Patience


What is this part about?


  • We will create a framework for all games, we will use SDL as a renderer. This is a library for graphics.


In the next posts there will be more action, this is just preparation :)



Why SDL?


I chose this library as the easiest and fastest to learn. Indeed, a lot of time will pass from the first read article on OpenGL or DirectX to the 100,000 reprint of the snake.

Now you can start.

1.1. The beginning of time


Download SDL from the official site.
We create the Win32 project in Visual Studio, connect the SDL libs and includs (if you don’t know how to do this, then Google will help you!)

You also need to use multi-byte character encoding. To do this, go to Project-> Properties-> Configuration Properties-> Character Set-> Use Multi-Byte Encoding.

Create the main.cpp file
#include 
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
	return 0;
}

He is not doing anything yet.

King and God of the
Carcass - Game Game.h class
#ifndef _GAME_H_
#define _GAME_H_
class Game
{
private:
	bool run;
public:
	Game();
	int Execute();
	void Exit();
};
#endif

Game.cpp
#include "Game.h"
Game::Game()
{
	run = true;
}
int Game::Execute()
{
	while(run);
	return 0;
}
void Game::Exit()
{
	run = false;
}


We create the file Project.h, it will be very useful to us in the future
#ifndef _PROJECT_H_
#define _PROJECT_H_
#include 
#include "Game.h"
#endif


Change main.cpp
#include "Project.h"
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
	Game game;
	return game.Execute();
}


Already a little better, but still somehow not a lot.

1.2. Graphics


We create already 2 classes - Graphics for rendering graphics and Image for rendering images.

Graphics.h
#ifndef _GRAPHICS_H_
#define _GRAPHICS_H_
#include "Project.h"
#include "Image.h"
class Image;
class Graphics
{
private:
	SDL_Surface* Screen;
public:
	Graphics(int width, int height);
	Image* NewImage(char* file);
	Image* NewImage(char* file, int r, int g, int b);
	bool DrawImage(Image* img, int x, int y);
	bool DrawImage(Image* img, int x, int y, int startX, int startY, int endX, int endY);
	void Flip();
};
#endif


Image.h
#ifndef _IMAGE_H
#define _IMAGE_H
#include "Project.h"
class Image
{
private:
	SDL_Surface* surf;
public:
	friend class Graphics;
	int GetWidth();
	int GetHeight();
};
#endif


Modify Project.h
#ifndef _PROJECT_H_
#define _PROJECT_H_
#pragma comment(lib,"SDL.lib")
#include 
#include 
#include "Game.h"
#include "Graphics.h"
#include "Image.h"
#endif


SDL_Surface - a class from SDL for storing image information.
Consider Graphics
NewImage - there are 2 options for loading a picture. The first option just loads the picture, and the second after that also gives transparency to the picture. If we have a red background in the picture, then enter r = 255, g = 0, b = 0
DrawImage - also 2 options for rendering the picture. The first one draws the whole picture, the second only part of the picture. startX, startY - coordinates of the beginning of the part of the picture. endX, endY - end coordinates of the image part. This drawing method is used if image atlases are used. Here is an example of an atlas:

image
(image taken from the interesnoe.info web resource)

Consider Image
It just holds its surface and gives access to its private members to the Graphics class, and it changes the surface.
This is essentially a wrapper over SDL_Surface. It also gives the size of the

Graphics.cpp picture.
#include "Graphics.h"
Graphics::Graphics(int width, int height)
{
	SDL_Init(SDL_INIT_EVERYTHING);
	Screen = SDL_SetVideoMode(width,height,32,SDL_HWSURFACE|SDL_DOUBLEBUF);
}
Image* Graphics::NewImage(char* file)
{
	Image* image = new Image();
	image->surf = SDL_DisplayFormat(SDL_LoadBMP(file));
	return image;
}
Image* Graphics::NewImage(char* file, int r, int g, int b)
{
	Image* image = new Image();
	image->surf = SDL_DisplayFormat(SDL_LoadBMP(file));
	SDL_SetColorKey(image->surf, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		SDL_MapRGB(image->surf->format, r, g, b));
	return image;
}
bool Graphics::DrawImage(Image* img, int x, int y)
{
	if(Screen == NULL || img->surf == NULL)
        return false;
    SDL_Rect Area;
    Area.x = x;
    Area.y = y;
    SDL_BlitSurface(img->surf, NULL, Screen, &Area);
	return true;
}
bool Graphics::DrawImage(Image* img, int x, int y, int startX, int startY, int endX, int endY)
{
	if(Screen == NULL || img->surf == NULL)
        return false;
    SDL_Rect Area;
    Area.x = x;
    Area.y = y;
    SDL_Rect SrcArea;
	SrcArea.x = startX;
	SrcArea.y = startY;
	SrcArea.w = endX;
	SrcArea.h = endY;
	SDL_BlitSurface(img->surf, &SrcArea, Screen, &Area);
	return true;
}
void Graphics::Flip()
{
	SDL_Flip(Screen);
	SDL_FillRect(Screen,NULL, 0x000000);
}

In the constructor, SDL is initialized and a screen is created.
The Flip function must be called every time after drawing the pictures, it represents the resultant on the screen and cleans the screen black for further rendering.
The rest of the functions are of little interest, I recommend that

Image.cpp himself figure it out
#include "Image.h"
int Image::GetWidth()
{
	return surf->w;
}
int Image::GetHeight()
{
	return surf->h;
}

No, you are doing everything right, this file should be like that :)

You need to change Game.h, Game.cpp and main.cpp
Game.h
#ifndef _GAME_H_
#define _GAME_H_
#include "Project.h"
class Graphics;
class Game
{
private:
	bool run;
	Graphics* graphics;
public:
	Game();
	int Execute(int width, int height);
	void Exit();
};
#endif

Here we add a pointer to Graphics and in Execute add the screen size of

Game.cpp
#include "Game.h"
Game::Game()
{
	run = true;
}
int Game::Execute(int width, int height)
{
	graphics = new Graphics(width,height);
	while(run);
	SDL_Quit();
	return 0;
}
void Game::Exit()
{
	run = false;
}


Nothing special, except skip the SDL_Quit function to clear SDL

main.cpp
#include "Project.h"
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
	Game game;
	return game.Execute(500,350);
}

Here we create a screen measuring 500 by 350.

1.3. Enter


We need to work with keyboard input.

Create Input.h
#ifndef _INPUT_H_
#define _INPUT_H_
#include "Project.h"
class Input
{
private:
	SDL_Event evt;
public:
    void Update();
	bool IsMouseButtonDown(byte key);
	bool IsMouseButtonUp(byte key);
	POINT GetButtonDownCoords();
	bool IsKeyDown(byte key);
	bool IsKeyUp(byte key);
	byte GetPressedKey();
	bool IsExit();
};
#endif

SDL_Event is a class of some event, we hold it in Input so as not to create an object of this class every cycle
Below are methods that are not of particular interest. Note: methods ending with Down are called when the key has been pressed, and with the ending Up when called.

Input.cpp
#include "Input.h"
void Input::Update()
{
	while(SDL_PollEvent(&evt));
}
bool Input::IsMouseButtonDown(byte key)
{
	if(evt.type == SDL_MOUSEBUTTONDOWN)
		if(evt.button.button == key)
			return true;
	return false;
}
bool Input::IsMouseButtonUp(byte key)
{
	if(evt.type == SDL_MOUSEBUTTONUP)
		if(evt.button.button == key)
			return true;
	return false;
}
POINT Input::GetButtonDownCoords()
{
	POINT point;
	point.x = evt.button.x;
	point.y = evt.button.y;
	return point;
}
bool Input::IsKeyDown(byte key)
{
	return (evt.type == SDL_KEYDOWN && evt.key.keysym.sym == key);
}
bool Input::IsKeyUp(byte key)
{
	return (evt.type == SDL_KEYUP && evt.key.keysym.sym == key);
}
byte Input::GetPressedKey()
{
	return evt.key.keysym.sym;
}
bool Input::IsExit()
{
	return (evt.type == SDL_QUIT);
}

Here we process our event object in the Update function, and the rest of the functions simply check the type of event and its values.

We change now Game.h and Game.cpp
#ifndef _GAME_H_
#define _GAME_H_
#include "Project.h"
#include "Graphics.h"
class Graphics;
#include "Input.h"
class Input;
class Game
{
private:
	bool run;
	Graphics* graphics;
	Input* input;
public:
	Game();
	int Execute(int width, int height);
	Graphics* GetGraphics();
	Input* GetInput();
	void Exit();
};
#endif

As you can see, we added a pointer to Input and created Graphics and Input

Game.cpp return methods
#include "Game.h"
Game::Game()
{
	run = true;
}
int Game::Execute(int width, int height)
{
	graphics = new Graphics(width,height);
	input = new Input();
	while(run)
	{
		input->Update();
	}
	delete graphics;
	delete input;
	SDL_Quit();
	return 0;
}
Graphics* Game::GetGraphics()
{
	return graphics;
}
Input* Game::GetInput()
{
	return input;
}
void Game::Exit()
{
	run = false;
}


1.4. Summary


This was the first lesson. If you get to this place, I congratulate you! You have the will inherent in the programmer :) See the links at the beginning of the article for subsequent lessons in order to learn a lot more!

For all questions, contact the PM, and if you are not lucky to be registered on the hub, write to the email izarizar@mail.ru


Also popular now: