第6回:敵vsプレイヤー(2)

プレイヤーの弾と敵の衝突を判定

 まずはプレイヤーが発射した弾が敵に衝突したかを判定するプログラムをもうけましょう。CPlayerBullet::Exec()において、タスクリスト内に格納されているすべての敵クラスを取得し、それぞれに対してCCharactor::HitTest()で衝突したかを調べます。
#include "EnemyBase.h"

void CPlayerBullet::Exec()
{
    /* 弾の移動処理 - 省略 */

    // 優先度から敵クラスを列挙する
    CPrioEnum pe;
    CGameObject *g;
    CEnemyBase *e;
    CreateEnumeration(ENEMY_PRIORITY, ENEMY_PRIORITY + 10000, &pe);

    while(g = pe.GetNext()){
        e = dynamic_cast<CEnemyBase*>(g);
        // CEnemyBaseが基底クラスに無ければ処理はしない
        if(e == NULL) continue;

        if(e->HitTest(this)){
            // 敵にヒットしたら弾を消去
            RemoveObject(this);
            return;
        }
    }

    sprite.Draw(x, y, angle);
}
 次に着弾したときの敵のリアクションをプログラミングしましょう。着弾するごとに、あらかじめ指定しておいた敵の耐久数値を1ずつ引いていき、耐久度が0になれば、爆発処理(爆発クラスをタスクに追加)を行い、自身をタスクから消去するようにします。
 爆発クラスは初期位置と、爆発の大きさ、角度、移動量を初期パラメータに指定できるようにしています。これだけあれば、敵を中心に放射状に爆発するといった簡単な演出がまかなえるでしょう。ちなみに、爆発アニメーションのためのテクスチャ元画像は、オンラインソフトの発色弾で作成しました。

#pragma once
#include "Charactor.h"

#define EXPLOSION_PRIORITY 90000

class CExplosion : public CCharactor
{
public:
    CExplosion(float sx, float sy);
    CExplosion(float sx, float sy, float size, float angle, float speed);
private:
    void Reset(float sx, float sy, float size, float angle, float speed);
    int frame, animframe;
    float mx, my, exsize;
protected:
    virtual void Init();
    virtual void Exec();
};
#include "Explosion.h"

CExplosion::CExplosion(float sx, float sy)
{
    Reset(sx, sy, 1.5f, 0.0f, 0.0f);
}

CExplosion::CExplosion(float sx, float sy, float size, float angle, float speed)
{
    Reset(sx, sy, size, angle, speed);
}

void CExplosion::Reset(float sx, float sy, float size, float angle, float speed)
{
    frame = animframe = 0;
    x = sx;
    y = sy;

    exsize = size;

    mx = cosf(angle) * speed;
    my = sinf(angle) * speed;
}

void CExplosion::Init()
{
    sprite.SetTexture((CTexture*)FindItemBox("explode"));
    sprite.SetSpriteSize(64, 64);
}

void CExplosion::Exec()
{
    frame++;
    if(frame >= 2){
        // 2フレーム単位でアニメーションフレームを進める
        animframe++;
        if(animframe > 15){
            // アニメーションフレームが最後までいったら終了
            RemoveObject(this);
            return;
        }
        
        frame = 0;
        sprite.SetFrame(animframe);
    }

    x += mx;
    y += my;

    sprite.Draw(x, y, exsize, exsize);
}
 敵クラスに耐久度と被弾処理を追加します。被弾処理(CEnemyBase::Damaged())は、敵の規模に合わせた処理を行えるよう、virtual属性にしています。
class CEnemyBase : public CCharactor
{
protected:
    int hardness;
    virtual void Init();
public:
    virtual void Damaged();
};
#include "Explosion.h"

void CEnemyBase::Damaged()
{
    // 残り耐久度を1減らす
    hardness--;
    if(hardness == 0){
        // 爆発処理を追加
        AppendObject(new CExplosion(x, y), EXPLOSION_PRIORITY, true);
        // 自身を削除
        RemoveObject(this);
    }
}
 耐久度の初期化はそれぞれの派生クラス内で行うようにします。
CEnemy1::CEnemy1(float sx)
{
    this->x = sx;
    hardness = 1;
}
 CPlayerBulletクラスに被弾処理を追加すれば完成です。
void CPlayerBullet::Exec()
{
    /* 省略 */

    while(g = pe.GetNext()){
        e = dynamic_cast<CEnemyBase*>(g);
        if(e == NULL) continue;

        if(e->HitTest(this)){
            e->Damaged();   // 敵の被弾処理
            RemoveObject(this);
            return;
        }
    }

    /* 省略 */
}
 では動作確認を行ってみましょう。テクスチャの読み込みやデータの作成を行うCTestクラスを新規に作成し、以下のようにコードを組みます。
#pragma once

#include "GameDef.h"

class CTest : public CGameObject
{
private:
    int frame;
protected:
    virtual void Init();
    virtual void Exec();
};
#include "Test.h"

#include "Player.h"
#include "Enemy.h"

void CTest::Init()
{
    // テクスチャの作成・格納
    CTexture *tex;
    tex = new CTexture(_T("data\\zako.png"));
    AppendItemBox("zakotex", tex);
    AppendObject(tex, 1000, true);

    tex = new CTexture(_T("data\\explode.png"));
    AppendItemBox("explode", tex);
    AppendObject(tex, 1000, true);

    tex = new CTexture(_T("data\\pbullet.png"));
    AppendItemBox("pbullet", tex);
    AppendObject(tex, 1000, true);

    // プレイヤーデータの作成
    CPlayer *player = new CPlayer();
    AppendItemBox("player", player);
    AppendObject(player, PLAYER_PRIORITY, true);

    frame = 0;
}

void CTest::Exec()
{
    frame++;
    if(frame > 120){
        // 120フレーム毎に敵を配置
        CEnemy1 *enemy = new CEnemy1((float)(rand() % 640));
        AppendObject(enemy, ENEMY_PRIORITY, true);

        frame = 0;
    }
}
 これをwinmain.cppにサンプルのように組み込んで実行すると、以下のムービーのような結果が得られます。
    CGameObject game;
    CoInitialize(NULL);
    game.Initialize(mainWnd, hInstance);
    CSound::CreateDirectSound(mainWnd);

    game.AppendObject(new CTaskHead(), 0, true);
    game.AppendObject(new CTaskTail(), INT_MAX, true);

    game.AppendObject(new CTest(), 5000, true);

    ShowWindow(mainWnd, nCmdShow);