Пишем игры на C++, Часть 1/3 — Написание мини-фреймворка

от автора

Пишем игры на C++, Часть 2/3 — State-based программирование
Пишем игры на C++, Часть 3/3 — Классика жанра

Здравствуй, Хабрахабр!

На хабре не очень много уроков по созданию игр, почему бы не поддержать отечественных девелоперов?
Представляю вам свои уроки, которые учат создавать игры на C++ с использованием SDL!

Что нужно знать

  • Хотя бы начальные знания C++ (использовать будем Visual Studio)
  • Терпение

О чем эта часть?

  • Мы создадим каркас для всех игр, в качестве отрисовщика будем использовать SDL. Это библиотека для графики.

В следующих постах будет больше экшена, это лишь подготовка 🙂

Почему SDL?

Я выбрал эту библиотеку как наиболее легкую и быструю в освоении. Действительно, от первой прочитанной статьи по OpenGL или DirectX до стотысячного переиздания змейки пройдет немало времени.

Теперь можно стартовать.

1.1. Начало начал

Скачиваем SDL с официального сайта.
Создаем проект Win32 в Visual Studio, подключаем lib’ы и includ’ы SDL (если вы не умеете этого делать, то гугл вам в помощь!)

Также необходимо использовать многобайтную кодировку символов. Для этого идем в Проект->Свойства->Свойства конфигурации->Набор символов->Использовать многобайтную кодировку.

Создаем файл main.cpp

#include <Windows.h>  int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { 	return 0; } 

Пока что он ничего не делает.

Царь и бог каркаса — класс Game
Game.h

#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; } 

Создаем файл Project.h, он нам очень пригодится в будущем

#ifndef _PROJECT_H_ #define _PROJECT_H_  #include <Windows.h>  #include "Game.h"  #endif 

Изменяем main.cpp

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

Уже чуточку получше, но все равно как-то не густо.

1.2. Графика

Создаем аж 2 класса — Graphics для отрисовки графики и Image для отрисовки картинок

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 

Изменяем Project.h

#ifndef _PROJECT_H_ #define _PROJECT_H_  #pragma comment(lib,"SDL.lib")  #include <Windows.h> #include <SDL.h>  #include "Game.h" #include "Graphics.h" #include "Image.h"  #endif 

SDL_Surface — класс из SDL для хранения информации об картинке
Рассмотрим Graphics
NewImage — есть 2 варианта загрузки картинки. Первый вариант просто грузит картинку, а второй после этого еще и дает прозрачность картинке. Если у нас красный фон в картинке, то вводим r=255,g=0,b=0
DrawImage — тоже 2 варианта отрисовки картинки. Первый рисует всю картинку целиком, второй только часть картинки. startX, startY — координаты начала части картинки. endX, endY — конечные координаты части картинки. Этот метод рисования применяется, если используются атласы картинок. Вот пример атласа:

image
(изображение взято из веб-ресурса interesnoe.info)

Рассмотрим Image
Он просто держит свой сурфейс и дает право доступа к своим закрытым членам классу Graphics, а он изменяет сурфейс.
По сути, это обертка над SDL_Surface. Также он дает размер картинки

Graphics.cpp

#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); } 

В конструкторе инициализируется SDL и создается экран.
Функция Flip должна вызываться каждый раз после отрисовки картинок, она представляет получившееся на экран и чистит экран в черный цвет для дальнешней отрисовки.
Остальные функции малоинтересны, рекомендую разобраться в них самому

Image.cpp

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

Нет, вы все правильно делаете, этот файл и должен быть таким 🙂

Надо изменить Game.h, Game.cpp и 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 

Тут мы добавляем указатель на Graphics и в Execute добавляем размер экрана

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; } 

Ничего особенного, разве что не пропустите функцию SDL_Quit для очистки SDL

main.cpp

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

Тут мы создаем экран размером 500 на 350.

1.3. Ввод

Надо поработать со вводом с клавиатуры

Создаем 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 — класс какого-нибудь события, его мы держим в Input’е для того, чтобы не создавать объект этого класса каждый цикл
Ниже расположены методы, не представляющие особого интереса. Примечание: методы с окончанием Down вызываются, когда клавиша была нажата, а с окончанием Up — когда опущена.

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); } 

Здесь мы обрабатываем наш объект событий в функции Update, а остальные функции просто проверяют тип события и его значения.

Изменяем теперь Game.h и 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 

Как видно, мы добавили указатель на Input и создали методы-возвращатели Graphics и Input

Game.cpp

#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. Итоги

Это был первый урок. Если вы дошли до этого места, я вас поздравляю! У вас есть воля, присущая программисту 🙂 Смотрите ссылки в начале статьи на последующие уроки для того, чтобы узнать еще много нового!

По всем вопросам обращайтесь в ЛС, а если вам не повезло быть зарегистрированным на хабре, пишите на мейл izarizar@mail.ru

ссылка на оригинал статьи http://habrahabr.ru/post/197278/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *