...

воскресенье, 13 октября 2013 г.

Пишем игры на 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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий