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

Добро пожаловать в третью часть!

В этой части мы создадим врагов, сделаем обнаружение столкновений и эффекты. 
Начнем с создания класса врага, он похож на класс игрока:

#ifndef C_ENEMY_H
#define C_ENEMY_H
#pragma once
 
#include 
#include 
#include 
 
#define ENEMY_FRICTION 0.95
 
class c_enemy
{
private:
    static HGE*     hge;
    hgeVector       Position;
    hgeVector       Velocity;
    hgeAnimation*   Sprite;
    hgeRect         BoundingBox;
    float           Speed;
 
    float           CenterY;
    float           Radius;
    float           Angle;
    bool            bCenterYSet;
    bool            bOscillate;
    short           Health;
public:
    c_enemy(hgeVector Position, hgeVector Velocity, short Health, HTEXTURE Texture);
    ~c_enemy();
 
    bool Update(float delta);
    void Render();
 
    short       GetHealth() { return Health; };
    void        SetHealth(short health) { Health = health; };
    void        SetOscillate(bool Value) { bOscillate = Value; };
    float       GetSpeed() { return Speed; };
    void        SetSpeed(float speed) { Speed = speed; };
    hgeRect     GetBoundingBox() { return BoundingBox; };
    hgeVector   GetPosition() { return Position; };
};
 
#endif

Как вы могли заметить, мы заставим врага колебаться позже (менять направление движения), это сделает игру немного сложнее.  Перед тем, как создать врага, я хочу отметить, что у нас есть несколько различных цветных текстур врага.

Каждый раз, когда объект врага создается, выберем случайным образом один из этих цветов. Но так как мы собираемся создавать довольно много врагов, мы предварительно загрузим эти текстуры в main.cpp.
Фото

Добавим объявление для текстур:

//Enemies
HTEXTURE    g_tEColors[5]       = { 0 };

Загружаем их в память и удаляем их в конце:

//Enemies
g_tEColors[0]   = hge->Texture_Load("images/eSpritesheet_40x30.png");
g_tEColors[1]   = hge->Texture_Load("images/eSpritesheet_40x30_hue1.png");
g_tEColors[2]   = hge->Texture_Load("images/eSpritesheet_40x30_hue2.png");
g_tEColors[3]   = hge->Texture_Load("images/eSpritesheet_40x30_hue3.png");
g_tEColors[4]   = hge->Texture_Load("images/eSpritesheet_40x30_hue4.png");
 
//...
 
for(short i = 0; i < 5; i++) hge->Texture_Free(g_tEColors[i]);

Хорошо, теперь мы можем приступить к написанию класса врага, мы начнем с конструктора и деструктора:

#include "c_enemy.h"
 
HGE* c_enemy::hge = 0;
 
c_enemy::c_enemy(hgeVector position, hgeVector velocity, short health, HTEXTURE Texture) : Position(position), Velocity(velocity), Health(health)
{
    hge = hgeCreate(HGE_VERSION);
 
    Sprite  = new hgeAnimation(Texture,6,6,0,0,40,30);
    Sprite->SetHotSpot(20,15);
    Sprite->Play();
 
    Speed       = 0.030;
 
    CenterY     = 0;
    Radius      = hge->Random_Float(50.0f,80.0f);
    Angle       = 0.0f;
    bCenterYSet = false;
    bOscillate  = false;
}
 
c_enemy::~c_enemy()
{
    delete Sprite;
 
    hge->Release();
}

Теперь создадим функцию кадра, где будет обновляться логика врага:

bool c_enemy::Update(float delta)
{
    Velocity.x *= ENEMY_FRICTION;
    Velocity.y *= ENEMY_FRICTION;
 
    Position.x += Velocity.x;
    if(!bOscillate) Position.y += Velocity.y;
    else
    {
        if(!bCenterYSet)
        {
            if(Velocity.y > -0.000001f && Velocity.y < 0.000001f)
            {
                CenterY = Position.y;
                bCenterYSet = true;
            }
        }
 
        Position.y = CenterY + sin(Angle) * Radius;
        Angle += 2 * delta;
    }
 
    Sprite->Update(delta);
 
    Velocity.x -= Speed;
 
    Sprite->GetBoundingBox(Position.x, Position.y, &BoundingBox);
    return false;
}

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

Теперь функция рисования на экране:

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

Фото

Давайте добавим то что у нас получилось в наш main.cpp, мы начинаем включая класс и создавая список:

#include "c_enemy.h"
//..
std::list Enemies;

И удаляем врагов в фунции System_Initiate:

for(auto i = Enemies.begin(); i != Enemies.end(); /**/)
{
    delete (*i);
    i = Enemies.erase(i);
}

Внутри функции кадра добавил создание врагов. Сделаем так, чтобы враги создавались, когда их количество меньше 5.

//Enemies
if(Enemies.size() < 5)
{
    short Health = hge->Random_Int(50, 100);
 
    c_enemy* Enemy = new c_enemy( hgeVector(830, hge->Random_Int(50,550)), hgeVector(-hge->Random_Int(2,8), hge->Random_Int(-4,4)), Health, g_tEColors[hge->Random_Int(0,4)]);
 
    Enemies.push_back(Enemy);
}

Мы задаем случайным образом значение здоровья, возиции, скорости и текстуры, что придает игре более разнообразный геймплей. Так же, если враг улетел за пределы экрана, удаляем его:

for(auto i = Enemies.begin(); i != Enemies.end(); /**/)
    {
        if((*i)->GetPosition().x < 0 || (*i)->GetPosition().y > 580 || (*i)->GetPosition().y < 20)
        {
            delete (*i);
            i = Enemies.erase(i);
        }
        else
        {
            (*i)->Update(delta);
            i++;
        }
    }
[.code]

И  в конце игры удаляем всех врагов.
[code]//Enemies
for(auto i = Enemies.begin(); i != Enemies.end(); i++)
{
    (*i)->Render();
}

На следующем этапе добавим определение столкновений игрока, врагов и пуль. Используем для этого встроенные в HGE класс:

//Collision Bullet vs Enemy
if(!Bullets.empty() && !Enemies.empty())
{
    for(auto i = Bullets.begin(); i != Bullets.end(); /**/)
    {
        bool bHit = false;
 
        for(auto x = Enemies.begin(); x != Enemies.end(); x++)
        {
            if((*x)->GetBoundingBox().Intersect(&(*i)->GetBoundingBox()))
            {
                (*x)->SetHealth((*x)->GetHealth() - (*i)->GetDamage());
 
                delete (*i);
                i    = Bullets.erase(i);
                bHit = true;
 
                break;
            }
        }
        if(!bHit) i++;
    }
}

Тут в цикле мы удаляем врагов, чье здоровье стало меньше 0:

//Check Enemy's health
for(auto i = Enemies.begin(); i != Enemies.end(); /**/)
{
    if((*i)->GetHealth() <= 0)
    {
        delete (*i);
        i = Enemies.erase(i);
    }
    else i++;
}

Здесь проверям столкновение игрока с врагом. Если это произошло, то уничтожаем их с анимацией взрыва и пересоздаем игрока.

//Enemy vs Player
for(auto i = Enemies.begin(); i != Enemies.end(); /**/)
{
    if((*i)->GetBoundingBox().Intersect(&Player1->GetBoundingBox()))
    {
        delete (*i);
        i = Enemies.erase(i);
 
        Player1->SetPosition(hgeVector(10,268));
        Player1->SetVelocity(hgeVector(0,0));
 
    }
    else i++;
}

Теперь создадим эффект взрыва.
Добавим объявления в main.cpp:

HTEXTURE    g_tExplosion        = 0;
HEFFECT     g_eExplosion        = 0;

Фото

Создадим для хранения эфффекта структуру:

struct explosion
{
    hgeAnimation*   Animation;
    hgeVector       Position;
};
std::list Explosions;

Очевидно, каждый взрыв нуждается в анимации и позиции. Мы сохраняя их в STD :: list. Также подготовим функции, которые мы будем описывать позже: CreateExplosion (hgeVector позиция);
Загрузим текстуры в память и освободим их впоследствии: (Внутри system_initiate)

//Explosion
g_tExplosion    = hge->Texture_Load("images/Explosion-Sprite-Sheet.png");
g_eExplosion    = hge->Effect_Load("sounds/21410__heigh-hoo__blow.aif");
 
//...
 
hge->Texture_Free(g_tExplosion);
hge->Effect_Free(g_eExplosion);
 
for(auto i = Explosions.begin(); i != Explosions.end(); /**/)
{
    delete (*i).Animation;
    i = Explosions.erase(i);
}

Пока все хорошо, давайте продолжим с создания функции взрыва:

void CreateExplosion(hgeVector Position)
{
    explosion exp;
    exp.Animation = new hgeAnimation(g_tExplosion,5,10,0,0,118,118);
    exp.Animation->SetHotSpot(59,59);
    exp.Animation->Play();
    exp.Position = Position;
    Explosions.push_back(exp);
 
    hge->Effect_PlayEx(g_eExplosion,100,0,hge->Random_Float(1,3));
}

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

for(auto i = Explosions.begin(); i != Explosions.end(); i++)
{
    (*i).Animation->Render((*i).Position.x, (*i).Position.y);
}
 
//...
 
for(auto i = Explosions.begin(); i != Explosions.end(); /**/)
{
    if((*i).Animation->GetFrame() == 4)
    {
        delete (*i).Animation;
        i = Explosions.erase(i);
    }
    else
    {
        (*i).Animation->Update(delta);
        i++;
    }
}

Вы можете добавить взрыв, куда угодно, как и к игрок против врага или когда здоровье противника равна нулю. Ну вот и все.

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