


今回は前回作った敵のバリエーションを増やしてみましょう。
先ほどのEnemyクラスをAbstractEnemyという抽象クラスに名前変更し、そこから色んな敵に派生させることを考えます。

クラス図的にはこんな感じ。
AbstractEnemyから、SmallEnemy、NormalEnemy、BigEnemyが派生します。
EnemyManagerはAbstractEnemyのポインタを持ちます。
まず実行結果をみてください。
まるっこいのがSmallEnemy、先の章で出てきたのがNormalEnemy、一番でかいのがBigEnemyを示します。
では共通処理はAbstractEnemyクラスが実装し、各々異なる部分を各派生先に実装させるようにします。
AbstractEnemy.h
#pragma once
#include "Task.h"
class AbstractEnemy : public Task
{
public:
AbstractEnemy(float x, float y);
virtual ~AbstractEnemy() = default;
void initialize();
bool update() override;
protected:
virtual void setSize() = 0;
float _x, _y;//座標
float _speed;//速さ
float _angle;//角度
int _counter;//カウンタ
int _width; //幅
int _height;//高さ
};AbstractEnemy.cpp
#include "AbstractEnemy.h"
#include <DxLib.h>
#include "Image.h"
#include "Define.h"
AbstractEnemy::AbstractEnemy(float x, float y) :
_x(x),
_y(y),
_speed(0),
_angle(0),
_counter(0),
_width(0),
_height(0)
{
}
void AbstractEnemy::initialize()
{
setSize();
}
bool AbstractEnemy::update()
{
_counter++;
_x += cos(_angle)*_speed;
_y += sin(_angle)*_speed;
return true;
}
純粋仮装関数であるdraw()はここでは定義していません。
敵の描画の仕方は派生先に依存するのでそちらで定義します。
更に、敵の大きさ_width、_heightを格納したいですが、抽象クラスではセットできません。
そんな時はvirtual void setSize()=0;を純粋仮装関数として定義し、initialize()から派生先の実装関数をコールします。
派生先にはsetSizeを実装しなければコンパイルエラーになります。
update()内の移動処理はどの敵も共通処理なので、親クラスに書きます。
では派生先である、SmallEnemy、NormalEnemy、BigEnemyの実装をみていきます。
SmallEnemy.h
#pragma once
#include "AbstractEnemy.h"
class SmallEnemy final : public AbstractEnemy
{
public:
SmallEnemy(float x, float y);
~SmallEnemy() = default;
void draw() const override;
protected:
void setSize() override;
};
SmallEnemy.cpp
#include "SmallEnemy.h"
#include "Define.h"
#include <DxLib.h>
#include "Image.h"
SmallEnemy::SmallEnemy(float x, float y) : AbstractEnemy(x,y)
{
_speed = 2.0f;
_angle = Define::PI/2;
}
void SmallEnemy::draw() const
{
DrawRotaGraphF(_x, _y, 1.0, Define::PI * 2 / 120 * _counter, Image::getIns()->getEnemySmall()[0], TRUE);
DrawRotaGraphF(_x, _y, 1.0, Define::PI * 2 / 120 * (_counter + 60), Image::getIns()->getEnemySmall()[1], TRUE);
}
void SmallEnemy::setSize()
{
int handle = Image::getIns()->getEnemySmall()[0];
GetGraphSize(handle, &_width, &_height);
}
AbstractEnemyには定義していない処理を実装しました。
スピードと角度は独自のものを設定しました。
SmallEnemyは小さな二つの円い画像が回転して表示されるので、そのようにdrawに実装しました。
回転する時によく利用するのがPI*2/hoge*_counterです。
PI*2は360°を意味しますね。hogeの数を小さくするほど高速回転になります。今回120なので2秒で1回転になります。
_counterは1フレームに1ずつ増えますから1秒間に60ずつ増えます。
setSizeでは使用している画像のサイズをセットしました。
次にNormalEnemyです。
NormalEnemy.h
#pragma once
#include "AbstractEnemy.h"
class NormalEnemy final : public AbstractEnemy
{
public:
NormalEnemy(float x, float y);
~NormalEnemy() = default;
void draw() const override;
protected:
void setSize() override;
};
NormalEnemy.cpp
#include "NormalEnemy.h"
#include <DxLib.h>
#include "Image.h"
#include "Define.h"
NormalEnemy::NormalEnemy(float x, float y) : AbstractEnemy(x,y)
{
_speed = 2.0;
_angle = Define::PI / 2 - Define::PI / 5;
}
void NormalEnemy::draw() const
{
const static int imgID[4] = { 0,1,2,1 };
int add = 3;
if (cos(_angle)>0.1) {//右向き
add = 6;
}
if (cos(_angle)<-0.1) {//左向き
add = 0;
}
const int handle = Image::getIns()->getEnemyNormal()[add + imgID[(_counter / 8) % 4]];
DrawRotaGraphF(_x, _y, 1.0, 0.0, handle, TRUE);
}
void NormalEnemy::setSize()
{
int handle = Image::getIns()->getEnemyNormal()[0];
GetGraphSize(handle, &_width, &_height);
}
SmallEnemyの時と同じくAbstractEnemyに無い部分の実装をしています。
実行結果を見て頂けたら分かる通り、角度は若干下向きより右側にしました。なのでPI/5ほど右方向にしてあります。
drawの中は一件見ても何してるのか分かりにくいですね。
説明していきます。
画像は以下のような画像を使います。

LoadDivGraphでロードすると配列要素としてこの9個に分割される画像は
[0] [1] [2]
[3] [4] [5]
[6] [7] [8]
このように格納されます。
羽がパタパタしている画像は
[0]→[1]→[2]→[1]→ [0]→[1]→[2]→[1]→
の順番でループさせたいです。1フレームに1ずつ増える_counterを使ってこのループを簡単に表現するため、
const static int imgID[4]={0,1,2,1};
を定義しています。この配列要素に左から順にアクセスすることで、0→1→2→1というループが実現できます。
そして、左を向いている時は0,1,2、正面を向いている時は3,4,5、右を向いている時は6,7,8です。
addはその分を加算するものです。
キャラがどっちを向いているかはcosを計算すればすぐわかります。
cosというものは、0°で1、90°で0、180°で-1、270°で0を示します。
※参考
http://www24.atpages.jp/venvenkazuya/math1/trigonometric_ratio6_table1.php
ということでほぼ0の時は正面、
0より少し大きい時は右向き、少し小さい時は左向きだと分かりますので、そのように条件分岐しています。
(_counter / 8) % 4
はどれ位の勢いでパタパタするかを示しています。
%4は配列要素が4つなので、0から3までをループするためにしています。
/8はカウンターの上がる勢いを8分の1に減らしています。もっと早くパタパタさせたい時は8の値を小さくすればよいです。
%4は変えてはいけません。
最後にBitEnemyですが、内容はほぼNormalEnemyと同じです。
BigEnemy.h
#pragma once
#include "AbstractEnemy.h"
class BigEnemy final : public AbstractEnemy
{
public:
BigEnemy(float x, float y);
~BigEnemy() = default;
void draw() const override;
protected:
void setSize() override;
};
BigEnemy.cpp
#include "BigEnemy.h"
#include <DxLib.h>
#include "Image.h"
#include "Define.h"
BigEnemy::BigEnemy(float x, float y) : AbstractEnemy(x, y)
{
_speed = 2.0;
_angle = Define::PI / 2 + Define::PI / 5;
}
void BigEnemy::draw() const
{
const static int imgID[4] = { 0,1,2,1 };
int add = 3;
if (cos(_angle)>0.1) {//右向き
add = 6;
}
if (cos(_angle)<-0.1) {//左向き
add = 0;
}
const int handle = Image::getIns()->getEnemyBig()[add + imgID[(_counter / 8) % 4]];
DrawRotaGraphF(_x, _y, 1.0, 0.0, handle, TRUE);
}
void BigEnemy::setSize()
{
int handle = Image::getIns()->getEnemyBig()[0];
GetGraphSize(handle, &_width, &_height);
}
NormalEnemyと違う点は角度をちょっと左にしている点、
Image::getIns()->getEnemyBit()になっていて、EnemyBitの画像を取得している点、
setSizeの画像のサイズが異なる点です。
後は同じです。
EnemyManagerではこの3つの敵を出現させています。
EnemyManager.cpp
#include "EnemyManager.h"
#include "Define.h"
#include "SmallEnemy.h"
#include "NormalEnemy.h"
#include "BigEnemy.h"
using namespace std;
EnemyManager::EnemyManager()
{
_list.emplace_back(make_shared<SmallEnemy >(Define::CENTER_X, 100));
_list.emplace_back(make_shared<NormalEnemy>(Define::CENTER_X-200, 100));
_list.emplace_back(make_shared<BigEnemy> (Define::CENTER_X+200, 100));
for (auto enemy : _list) {
enemy->initialize();
}
}
bool EnemyManager::update()
{
for (auto enemy : _list) {
enemy->update();
}
return true;
}
void EnemyManager::draw() const
{
for (const auto enemy : _list) {
enemy->draw();
}
}
リストにプッシュする部分だけ追加したら後は自動的に計算・描画してくれます。