Создание космической стрелялки в HGE – часть 2

В этой части, мы создадим объект игрока, который будет управляемым, а также пули и звуковые эффекты. Давайте подумаем, как создать классы для этого. Для этого мы создам переменные для физики, текстуры и спрайты. Так же напишем класс для обнаружения столкновения.

#ifndef C_PLAYER_H
#define C_PLAYER_H
#pragma once
 
#include 
#include 
#include 
 
#define PLAYER_FRICTION 0.95
 
class c_player
{
private:
    static HGE*     hge;
    hgeVector       Position;
    hgeVector       Velocity;
    HTEXTURE        Texture;
    hgeAnimation*   Sprite;
    hgeRect         BoundingBox; //необходимо  для обнаружения столкновений
    float           Speed;

Нам еще нужно несколько методов доступа к этим значениям:

public:
    c_player(hgeVector Position, hgeVector Velocity);
    ~c_player();
 
    bool Update(float delta);
    void Render();
 
    void        SetVelocity(hgeVector velocity) { Velocity = velocity; };
    void        SetPosition(hgeVector position) { Position = position; };
    hgeRect     GetBoundingBox() { return BoundingBox; };
    hgeVector   GetPosition() { return Position; };
};
 
#endif

Давайте начнем с конструктора и деструктора:

#include "c_player.h"
 
HGE* c_player::hge = 0;
 
c_player::c_player(hgeVector position, hgeVector velocity) : Position(position), Velocity(velocity)
{
    hge = hgeCreate(HGE_VERSION); //Get interface to hge
 
    Texture = hge->Texture_Load("images/Spritesheet_64x29.png");
    Sprite  = new hgeAnimation(Texture,4,4,0,0,64,29);
    Sprite->SetHotSpot(32,14.5);
    Sprite->Play();
 
    Speed       = 0.15;
}

Сначала мы получаем интерфейс HGE, чтобы мы могли загрузить текстуры и прочее. Затем мы создаем анимацию (hgeAnimation) с четырьмя кадрами и четыре кадра в секунду. Затем мы устанавливаем хот-спот точку для анимации, которая является центром для масштабирования, поворота и положения. Напишем Sprite->Play();, чтобы запустить анимацию. Speed - это скорость. Деструктор в значительной степени понятен, все, что мы хотим сделать - это здесь очистить наши ресурсы, которые мы использовали и удалить интерфейс HGE:

c_player::~c_player()
{
    hge->Texture_Free(Texture);
    delete Sprite;
 
    hge->Release();
}

Давайте объявим функцию для обновления расчетов:
(Velocity * Friction && Position * Velocity)

bool c_player::Update(float delta)
{
    Velocity.x *= PLAYER_FRICTION;
    Velocity.y *= PLAYER_FRICTION;
 
    Position.x += Velocity.x;
    Position.y += Velocity.y;

Далее мы хотим сделать его подвижным для пользователя, мы будем использовать AWSD и клавиши со стрелками для передвижения. Мы проверяем ввод с помощью Input_GetKeyState функции, она возвращает истину, если указанная кнопка нажата, иначе возвращает ложь. Мы также хотим сделать так, чтобы игрок не мог улететь за пределы экрана. Если кнопка нажата, мы будем увеличивать или уменьшать скорость: (Примечание: M_PI является предопределенным значением в hge.h)

if(hge->Input_GetKeyState(HGEK_A) || hge->Input_GetKeyState(HGEK_LEFT)     && Position.x > 32) Velocity.x -= (Speed * M_PI) /2;
if(hge->Input_GetKeyState(HGEK_D) || hge->Input_GetKeyState(HGEK_RIGHT)  && Position.x < 800) Velocity.x += (Speed * M_PI) /2;
if(hge->Input_GetKeyState(HGEK_W) || hge->Input_GetKeyState(HGEK_UP)   && Position.y > 0) Velocity.y -= (Speed * M_PI) /2;
if(hge->Input_GetKeyState(HGEK_S) || hge->Input_GetKeyState(HGEK_DOWN)   && Position.y < 600) Velocity.y += (Speed * M_PI) /2;

На следующем шаге мы хотим держать игрока внутри экрана, если он покидает экран - отражаем перемещение в обратную сторону:

if(Position.x > 800) { Position.x -= 1; Velocity.x = -Velocity.x; };
if(Position.x < 0)   { Position.x += 1; Velocity.x = -Velocity.x; };
if(Position.y < 0)    { Position.y += 1; Velocity.y = -Velocity.y; };
if(Position.y > 600) { Position.y -= 1; Velocity.y = -Velocity.y; };

Теперь мы хотим обновить нашу анимацию и BoundingBox. Функция должна возвращать ложь, иначе игра остановится.

	Sprite->Update(delta);
 
    Sprite->GetBoundingBox(Position.x, Position.y, &BoundingBox);
    return false;
}

Рисуем игрока на экране:

void c_player::Render()
{
    Sprite->Render(Position.x, Position.y);
}

Теперь реалиузем наш класс игрока в main.cpp:
Сначала добавим объявление нашего класса и переменную указатель:

#include "c_player.h"
 
//..
 
c_player*   Player1             = NULL;

В функции кадра добавьте эту строку в самый верх:
(Timer_GetDelta() возвращает время, прошедшее с последнего вызова функции кадра)

float delta = hge->Timer_GetDelta();

а это в нижней части, чтобы обновить логику игрока:

Player1->Update(delta);

Рисуем игрока внутри функции Render:

Player1->Render();

Внутри System_Initiate мы создаем и удаляем наш объект игрока:

Player1 = new c_player(hgeVector(10, 268), hgeVector(5,0));
//...
delete Player1;

Фото
Вот теперь у нас получился управляемый космический корабль.

Пули

Мы снова начанем с создания нового класса для пули:

#ifndef C_BULLET_H
#define C_BULLET_H
#pragma once
 
#include 
#include 
#include 
#include 
 
#define BULLET_FRICTION 0.99
 
class c_bullet
{
private:
    static HGE*     hge;
    hgeVector       Position;
    hgeVector       Velocity;
    hgeSprite*      Sprite;
    hgeRect         BoundingBox;
    float           Speed;
    short           Damage;

почти те же переменные, как указано в класса игрока, в дополнение к ним, мы добавим несколько переменных

float           CenterY;
float           Radius;
float           Angle;
bool            bCenterYSet;
bool            bOscillate;
bool            bOscillateReverse;

Определим координаты центра пули. Радиус и угол должен быть известен. Давайте добавим несколько методов класса:

public:
    c_bullet(hgeVector Position, hgeVector Velocity, HTEXTURE &Texture, short Damage);
    ~c_bullet();
 
    void Update();
    void Render();
 
    void        SetDamage(short damage) { Damage = damage; };
    short       GetDamage() { return Damage; };
    void        SetOscillateReverse(bool Value) { bOscillateReverse = Value; };
    void        SetOscillate(bool Value) { bOscillate = Value; };
    hgeRect     GetBoundingBox() { return BoundingBox; };
    hgeVector   GetPosition() { return Position; };
};
 
#endif

Так как мы будем использовать много пуль мы не будем создавать текстуру каждый раз, мы ее подгрузим раньше в main.cpp.

c_bullet::c_bullet(hgeVector position, hgeVector velocity,HTEXTURE &Texture, short damage) : Position(position), Velocity(velocity), Damage(damage)
{
    hge = hgeCreate(HGE_VERSION);
 
    Sprite  = new hgeSprite(Texture,0,0,6,3);
    Sprite->SetHotSpot(3,1.5);
 
    Speed   = 0.15;
 
    CenterY             = 0;
    Radius              = hge->Random_Float(50.0f,80.0f);
    Angle               = 0.0f;
    bCenterYSet         = false;
    bOscillate          = false;
    bOscillateReverse   = true;
}

В деструктора удалим используемые ресурсы.

c_bullet::~c_bullet()
{
    delete Sprite;
 
    hge->Release();
}

Создадим фуннкцию обновления логики пули:

void c_bullet::Update()
{
    Velocity.x *= BULLET_FRICTION;
    Velocity.y *= BULLET_FRICTION;
 
    Position.x += Velocity.x;
    if(!bOscillate) Position.y += Velocity.y;
    else
    {
        if(!bCenterYSet)
        {
            CenterY = Position.y;
            bCenterYSet = true;
        }
 
        if(!bOscillateReverse) Position.y = CenterY - sin(Angle) * Radius;
        else Position.y = CenterY + sin(Angle) * Radius;
        Angle += 5 * hge->Timer_GetDelta();
    }
 
    Sprite->GetBoundingBox(Position.x, Position.y, &BoundingBox);
}

И функции рисования пули:

void c_bullet::Render()
{
    Sprite->Render(Position.x, Position.y);
}

Добавим заголовки класса пули и создадим список указателей на пули.

#include 
//...
#include "c_bullet.h"
//...
 
std::list Bullets;
HTEXTURE    g_tBullet           = 0;

В функции обновления добавим создание выстрела при нажатом пробеле и удаление пуль, если они улетели за пределы экрана.

if(hge->Input_KeyDown(HGEK_SPACE))
{
    //Single shot
    c_bullet* Bullet = new c_bullet(Player1->GetPosition() + hgeVector(16,0), hgeVector(15,0), g_tBullet, 10);
 
    Bullets.push_back(Bullet);
}
for(auto i = Bullets.begin(); i != Bullets.end(); /**/)
{
    (*i)->Update();
 
    if((*i)->GetPosition().x > 800 || (*i)->GetPosition().x < 0 || (*i)->GetPosition().y < 0 || (*i)->GetPosition().y > 600) i = Bullets.erase(i);
    else i++;
}

В функции рисования проходимся циклом по списку пуль и русуем их на экране:

for(auto i = Bullets.begin(); i!= Bullets.end(); i++)
{
    (*i)->Render();
}

далее подгружаем текстуры пули и добавим их удалиние внутри System_Initiate:

g_tBullet       = hge->Texture_Load("images/bullet.png");
//...
hge->Texture_Free(g_tBullet);
 
for(auto i = Bullets.begin(); i != Bullets.end(); /**/)
{
    delete (*i);
    i = Bullets.erase(i);
}

Фото
Результат стрельбы

Звук

Таким образом, чтобы добавить звук первое, что вам нужно, сделать, это скопировать bass.dll из 

\hge181\ to \. Кроме того, необходимо изменить состояние системы из HGE, установить это состояние:

hge->System_SetState(HGE_USESOUND, true);

Для каждого звукака мы будем использовать три отдельных звуковых файла:

внутрь main.cpp добавим:

//Sounds
HEFFECT     g_eBGMusic          = 0;
HEFFECT     g_eBGMusic2         = 0;
HEFFECT     g_eBGMusic3         = 0;

Внутри System_Initiate добавим подгрузку и удаление звуков:

//Sounds
g_eBGMusic      = hge->Effect_Load("sounds/134707__atanasiu__deepspace-sonification.ogg");
g_eBGMusic2     = hge->Effect_Load("sounds/158733__panzertank15__spaceambient.ogg");
g_eBGMusic3     = hge->Effect_Load("sounds/108929__soulman-90__ambience-space.ogg");
 
hge->Effect_PlayEx(g_eBGMusic, 40, 0, 0, true);
hge->Effect_PlayEx(g_eBGMusic2, 100, 0, 0, true);
hge->Effect_PlayEx(g_eBGMusic3, 10, 0, 0, true);

Запустим звуки с разной громкостью для создания окружения и зациклим их воспроизведение.
Удаление звуков:

hge->Effect_Free(g_eBGMusic);
hge->Effect_Free(g_eBGMusic2);
hge->Effect_Free(g_eBGMusic3);

Звук стрельбы попробуйте добавить сами.

Результат части 2:
http://www.youtube.com/