オブジェクト指向を活用したシューティングゲーム - C/C++言語とDXライブラリでゲーム作成入門
目次
- C/C++言語とDXライブラリでゲーム作成入門
- シューティングゲームの作成(チュートリアル)
- ドットイートゲームの作成(チュートリアル)
- 15パズルの作成
- 神経衰弱の作成
- オセロの作成
- オブジェクト指向を活用したシューティングゲーム
今回の目的
- オブジェクト指向を活用したゲームの作り方を知る
- ゲームのキャラクター管理を通してSTLのlistの使い方を知る
ソースファイル
今回の講座のソースを全て含んだプロジェクトファイル(Visual C++ 2010)を以下に置いておきます。shooting2.zip
ゲームの説明
カーソルキーで自機を動かしてスペースキーで弾を発射します。弾が敵に当たると得点が入ります。キャラクター画像の準備
以下の4つの画像が必要です。大きさは適当で構いません。自機画像(player.png)
敵画像(enemy.png)
弾画像(shot.png)
背景用 雲画像(cloud.png)
プログラム
普通はクラスごとにファイルを分けることが多いのですが、今回はひとつのソースで全てを記述しています。main.cpp
#include <string>
#include <list>
#include <map>
#include <algorithm>
#include "DxLib.h"
enum ChrType {TYPE_NONE, TYPE_SHOT, TYPE_PLAYER, TYPE_ENEMY, TYPE_CLOUD, TYPE_NUM};
class Chr;
typedef std::shared_ptr<Chr> ChrRef;
// キャラクタ基本クラス
class Chr {
static int loaded_images[TYPE_NUM];
bool remove_flag;
protected:
int image;
int x, y;
int width, height;
int hit_x, hit_y;
int hit_width, hit_height;
void remove() {remove_flag = true;}
public:
Chr();
void setImage(char*);
void setPosition(int, int);
void setHitArea(int, int, int, int);
bool isRemove() {return remove_flag;}
virtual ChrType getType() = 0;
virtual ChrType hitType() {return TYPE_NONE;}
virtual void move() = 0;
virtual void draw();
bool hitTest(ChrRef&);
virtual void hit() {};
};
int Chr::loaded_images[] = {};
// 弾クラス
class Shot : public Chr {
public:
Shot();
ChrType getType() {return TYPE_SHOT;}
ChrType hitType() {return TYPE_ENEMY;}
void move();
void hit();
};
// プレイヤークラス
class Player : public Chr {
bool shot_flag;
int dead_time;
public:
Player();
ChrType getType() {return TYPE_PLAYER;}
void move();
void draw();
void hit();
};
// 敵クラス
class Enemy : public Chr {
public:
Enemy();
ChrType getType() {return TYPE_ENEMY;}
ChrType hitType() {return TYPE_PLAYER;}
void move();
void hit();
};
// 雲クラス
class Cloud : public Chr {
int speed;
void resetPosition(bool);
public:
static const int NUM = 25;
Cloud();
ChrType getType() {return TYPE_CLOUD;}
void move();
};
// ゲームクラス
class Game {
std::list<ChrRef> chr_list;
int score;
public:
static const int WIDTH = 640;
static const int HEIGHT = 480;
static Game* me;
void addList(ChrRef&);
void addScore(int s) {score += s;}
void init();
void main();
void end();
};
Game* Game::me;
// キャラクタクラス メンバ関数
Chr::Chr() {
remove_flag = false;
}
void Chr::setImage(char* file_name) {
if (loaded_images[getType()] == 0) {
loaded_images[getType()] = LoadGraph(file_name);
}
image = loaded_images[getType()];
GetGraphSize(image, &width, &height);
hit_width = width;
hit_height = height;
hit_x = hit_y = 0;
}
void Chr::setPosition(int px, int py) {
x = px; y = py;
}
void Chr::setHitArea(int hx, int hy, int hw, int hh) {
hit_x = hx; hit_y = hy;
hit_width = hw; hit_height = hh;
}
void Chr::draw() {
DrawGraph(x, y, image, TRUE);
}
bool Chr::hitTest(ChrRef& dst) {
int x1 = x + hit_x;
int y1 = y + hit_y;
int w1 = hit_width;
int h1 = hit_height;
int x2 = dst->x + dst->hit_x;
int y2 = dst->y + dst->hit_y;
int w2 = dst->hit_width;
int h2 = dst->hit_height;
if (x1 + w1 > x2 && x1 < x2 + w2 && y1 + h1 > y2 && y1 < y2 + h2) return true;
return false;
}
// 弾クラス メンバ関数
Shot::Shot() {
setImage("shot.png");
}
void Shot::move() {
x += 12;
if (x > Game::WIDTH) remove();
}
void Shot::hit() {
remove();
}
// プレイヤークラス メンバ関数
Player::Player() {
setImage("player.png");
setPosition(30, 200);
setHitArea(14, 12, 36, 16);
shot_flag = false;
dead_time = 0;
}
void Player::move() {
if (dead_time > 0) {
dead_time--;
return;
}
if (CheckHitKey(KEY_INPUT_LEFT)) x -= 6;
if (CheckHitKey(KEY_INPUT_RIGHT)) x += 6;
if (CheckHitKey(KEY_INPUT_UP)) y -= 6;
if (CheckHitKey(KEY_INPUT_DOWN)) y += 6;
if (x < -hit_x) x = -hit_x;
if (x > Game::WIDTH - hit_x - hit_width) x = Game::WIDTH - hit_x - hit_width;
if (y < -hit_y) y = -hit_y;
if (y > Game::HEIGHT - hit_y - hit_height) y = Game::HEIGHT - hit_y - hit_height;
if (CheckHitKey(KEY_INPUT_SPACE)) {
if (!shot_flag) {
ChrRef shot(new Shot());
shot->setPosition(x + width - 16, y + 16);
Game::me->addList(shot);
shot_flag = true;
}
} else {
shot_flag = false;
}
}
void Player::draw() {
if (dead_time % 5 == 0) Chr::draw();
}
void Player::hit() {
dead_time = 50;
}
// 敵クラス メンバ関数
Enemy::Enemy() {
setImage("enemy.png");
setPosition(Game::WIDTH, GetRand(Game::HEIGHT - height));
}
void Enemy::move() {
x -= 4;
if (x < -width) remove();
}
void Enemy::hit() {
Game::me->addScore(100);
remove();
}
// 雲クラス メンバ関数
Cloud::Cloud() {
setImage("cloud.png");
resetPosition(true);
}
void Cloud::resetPosition(bool init) {
int cx = GetRand(Game::WIDTH + width) - width;
int cy = GetRand(Game::HEIGHT + height) - height;
if (init) setPosition(cx, cy);
else setPosition(Game::WIDTH, cy);
speed = GetRand(4) + 2;
}
void Cloud::move() {
x -= speed;
if (x < -width) resetPosition(false);
}
// ゲームクラス メンバ関数
void Game::addList(ChrRef& c) {
chr_list.push_back(c);
}
void Game::init() {
me = this;
score = 0;
ChangeWindowMode(TRUE);
SetGraphMode(Game::WIDTH, Game::HEIGHT, 32);
DxLib_Init();
SetDrawScreen(DX_SCREEN_BACK);
SetBackgroundColor(124, 210, 255);
// 雲生成
for (int i = 0; i < Cloud::NUM; i++) {
ChrRef cloud(new Cloud());
addList(cloud);
}
// プレイヤー生成
ChrRef player(new Player());
addList(player);
}
void Game::main() {
while (!ProcessMessage()) {
ClearDrawScreen();
// 敵生成
if (GetRand(49) == 0) {
ChrRef enemy(new Enemy());
addList(enemy);
}
// 移動
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->move();});
// 描画
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->draw();});
// 当たり判定
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& src) {
if (src->hitType() == TYPE_NONE) return;
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& dst) {
if (src->hitType() == dst->getType()) {
if (src->hitTest(dst)) {
src->hit();
dst->hit();
}
}
});
});
// スコア表示
DrawFormatString(Game::WIDTH - 120, 5, GetColor(0, 0, 0), "SCORE %d", score);
ScreenFlip();
// 消すべきキャラクタの消去
auto end = std::remove_if(chr_list.begin(), chr_list.end(), [](ChrRef& c) {return c->isRemove();});
chr_list.erase(end, chr_list.end());
}
}
void Game::end() {
DxLib_End();
}
// WinMain
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
Game game;
game.init();
game.main();
game.end();
return 0;
}
プログラムの構成
クラス構成
クラス階層図は以下のようになります。Gameクラスがゲーム全体を管理するクラスです。Chrクラスは画面上に表示されるキャラクタの基本クラスとなります。さらにChrクラスを継承したShotクラス(弾)、Playerクラス(プレイヤー)、Enemyクラス(敵)、Cloudクラス(背景の雲)があります。
キャラクタ管理リスト
Gameクラスのデータメンバchr_listは、Chrクラスのオブジェクトへのポインタを格納したリストになります。
std::list<ChrRef> chr_list;
画面上に表示するキャラクタをここに登録し、画面から消す場合はここから削除します。リストはSTLのlistを使用しています。スマートポインタ
chr_listの要素の型はChrRefですが、これはソースの上の部分でtypedefされています。
typedef std::shared_ptr<Chr> ChrRef;
shared_ptrは「スマートポインタ」と呼ばれています。通常のポインタと同じように使用できますが、newで確保されたメモリ領域をdeleteしなくても自動的に解放してくれるという大きなメリットがあります。それ以外は、ChrRefはChr*と同じように使うことができます。継承によるメンバ関数のオーバーライド
chr_listには、実際にはChrクラスのオブジェクトではなく、Chrクラスを継承したクラスのオブジェクトが入ります。派生クラス側で「キャラクタの移動(move)」「キャラクタの描画(draw)」「他のキャラクタにぶつかったときのアクション(hit)」などのメンバ関数をオーバーライドすることによって、派生クラス独自の動作を統一的に呼び出すことができます。キャラクタタイプ
enum ChrType {TYPE_NONE, TYPE_SHOT, TYPE_PLAYER, TYPE_ENEMY, TYPE_CLOUD, TYPE_NUM};
列挙型を用いてそれぞれのキャラクタのタイプを設定します。TYPE_NONE | なし |
---|---|
TYPE_SHOT | 弾 |
TYPE_PLAYER | プレイヤー |
TYPE_ENEMY | 敵 |
TYPE_CLOUD | 雲 |
プログラムの解説
キャラクタ基本クラス
class Chr {
static int loaded_images[TYPE_NUM];
bool remove_flag;
protected:
int image;
int x, y;
int width, height;
int hit_x, hit_y;
int hit_width, hit_height;
void remove() {remove_flag = true;}
public:
Chr();
void setImage(char*);
void setPosition(int, int);
void setHitArea(int, int, int, int);
bool isRemove() {return remove_flag;}
virtual ChrType getType() = 0;
virtual ChrType hitType() {return TYPE_NONE;}
virtual void move() = 0;
virtual void draw();
bool hitTest(ChrRef&);
virtual void hit() {};
};
int Chr::loaded_images[] = {};
画面上に表示する全てのキャラクタの基本クラスです。抽象クラスなので、必ず派生クラスを作成しなければなりません。データメンバ
loaded_images | 画像がメモリに読み込み済かを管理する配列 |
---|---|
remove_flag | キャラクタ消去フラグ。キャラクタを画面から消す場合にtrueになる |
image | グラフィックハンドル |
x | x座標 |
y | y座標 |
width | 幅 |
height | 高さ |
hit_x | 当たり判定の左上x座標 |
hit_y | 当たり判定の左上y座標 |
hit_width | 当たり判定の幅 |
hit_height | 当たり判定高さ |
キャラクタ基本クラス setImage関数
void Chr::setImage(char* file_name) {
if (loaded_images[getType()] == 0) {
loaded_images[getType()] = LoadGraph(file_name);
}
image = loaded_images[getType()];
GetGraphSize(image, &width, &height);
hit_width = width;
hit_height = height;
hit_x = hit_y = 0;
}
画像ファイル名を指定すると、メモリに読み込む関数です。一度読み込まれた画像はグラフィックハンドルをloaded_images配列に保存しておき、重複して読み込まないようにしています。画像を読み込んだ後、画像の幅と高さを取得し、当たり判定の初期値を画像の大きさと同じにしています。キャラクタ基本クラス setPosition関数
void Chr::setPosition(int px, int py) {
x = px; y = py;
}
キャラクタの位置を設定します。キャラクタ基本クラス setHitArea関数
void Chr::setHitArea(int hx, int hy, int hw, int hh) {
hit_x = hx; hit_y = hy;
hit_width = hw; hit_height = hh;
}
当たり判定の領域を設定します。画像の大きさと同じ当たり判定領域では大きすぎる場合などに使います。キャラクタ基本クラス isRemove関数
bool isRemove() {return remove_flag;}
キャラクタ消去フラグの状態を返します。キャラクタ基本クラス getType関数
virtual ChrType getType() = 0;
自分自身のキャラクタタイプを返します。純粋仮想関数なので必ず派生クラスでオーバーライドする必要があります。キャラクタ基本クラス hitType関数
virtual ChrType hitType() {return TYPE_NONE;}
自分自身と当たり判定のあるキャラクタのタイプを返します。デフォルトはTYPE_NONEで、誰とも当たり判定を行いません。キャラクタ基本クラス move関数
virtual void move() = 0;
キャラクタの移動を行う関数です。純粋仮想関数なので必ず派生クラスでオーバーライドする必要があります。キャラクタ基本クラス draw関数
void Chr::draw() {
DrawGraph(x, y, image, TRUE);
}
キャラクタの描画を行います。仮想関数なので派生クラスで独自の描画を行うこともできます。キャラクタ基本クラス hitTest関数
bool Chr::hitTest(ChrRef& dst) {
int x1 = x + hit_x;
int y1 = y + hit_y;
int w1 = hit_width;
int h1 = hit_height;
int x2 = dst->x + dst->hit_x;
int y2 = dst->y + dst->hit_y;
int w2 = dst->hit_width;
int h2 = dst->hit_height;
if (x1 + w1 > x2 && x1 < x2 + w2 && y1 + h1 > y2 && y1 < y2 + h2) return true;
return false;
}
当たり判定を行う関数です。当たり判定のイメージは以下のようになります。黒い四角がキャラクタの当たり判定となります。この四角同士が重なっていればtrueを返します。
弾クラス
class Shot : public Chr {
public:
Shot();
ChrType getType() {return TYPE_SHOT;}
ChrType hitType() {return TYPE_ENEMY;}
void move();
void hit();
};
弾クラス コンストラクタ
Shot::Shot() {
setImage("shot.png");
}
基本クラスのsetImage関数を呼び出して弾の画像をセットしています。弾クラス hitType関数
ChrType hitType() {return TYPE_ENEMY;}
弾は敵と当たり判定を行うようにしています。弾クラス move関数
void Shot::move() {
x += 12;
if (x > Game::WIDTH) remove();
}
右にまっすぐ進みます。画面の右端より右に進んだら、基本クラスのremove関数を呼び出して弾を消去します。弾クラス hit関数
void Shot::hit() {
remove();
}
弾が敵に当たったときに呼ばれます。弾を消去しているだけです。プレイヤークラス
class Player : public Chr {
bool shot_flag;
int dead_time;
public:
Player();
ChrType getType() {return TYPE_PLAYER;}
void move();
void draw();
void hit();
};
データメンバ
shot_flag | スペースキーが押されている間trueとなる。連射を防ぐためのフラグ |
---|---|
dead_time | 敵に当たって点滅している時間 |
プレイヤークラス コンストラクタ
Player::Player() {
setImage("player.png");
setPosition(30, 200);
setHitArea(14, 12, 36, 16);
shot_flag = false;
dead_time = 0;
}
まずプレイヤーの画像をセット(setImage)し、次に初期位置を設定(setPosition)し、次に当たり判定を自機より少し小さく(setHitArea)しています。プレイヤークラス move関数
void Player::move() {
if (dead_time > 0) {
dead_time--;
return;
}
if (CheckHitKey(KEY_INPUT_LEFT)) x -= 6;
if (CheckHitKey(KEY_INPUT_RIGHT)) x += 6;
if (CheckHitKey(KEY_INPUT_UP)) y -= 6;
if (CheckHitKey(KEY_INPUT_DOWN)) y += 6;
if (x < -hit_x) x = -hit_x;
if (x > Game::WIDTH - hit_x - hit_width) x = Game::WIDTH - hit_x - hit_width;
if (y < -hit_y) y = -hit_y;
if (y > Game::HEIGHT - hit_y - hit_height) y = Game::HEIGHT - hit_y - hit_height;
if (CheckHitKey(KEY_INPUT_SPACE)) {
if (!shot_flag) {
ChrRef shot(new Shot());
shot->setPosition(x + width - 16, y + 16);
Game::me->addList(shot);
shot_flag = true;
}
} else {
shot_flag = false;
}
}
dead_timeが正の値、つまり敵に当たって点滅している間は操作不能なのでそのままreturnしています。それ以外の場合は、まずカーソルキーをチェックし、上下左右に移動します。その際、自機の当たり判定の矩形が画面の外に出ないように調整しています。ここで当たり判定の座標を使う必要性はないのですが、丁度よい大きさだったので使っています。その次にスペースキーをチェックし、押されていれば弾を発射します。弾の生成部分は以下のようになります。
ChrRef shot(new Shot());
スマートポインタにnewしたオブジェクトを渡しています。
Game::me->addList(shot);
GameクラスのaddList関数によって、作成したオブジェクトのポインタをキャラクタ管理リストに登録しています。プレイヤークラス draw関数
void Player::draw() {
if (dead_time % 5 == 0) Chr::draw();
}
基本クラスのdraw関数をオーバーライドし、敵に当たったときに点滅する表現を追加しています。プレイヤークラス hit関数
void Player::hit() {
dead_time = 50;
}
敵に当たったときに、点滅時間を設定しています。敵クラス
class Enemy : public Chr {
public:
Enemy();
ChrType getType() {return TYPE_ENEMY;}
ChrType hitType() {return TYPE_PLAYER;}
void move();
void hit();
};
敵クラス コンストラクタ
Enemy::Enemy() {
setImage("enemy.png");
setPosition(Game::WIDTH, GetRand(Game::HEIGHT - height));
}
敵の画像をセットし、位置を画面右端の上下ランダムな位置に配置しています。敵クラス hitType関数
ChrType hitType() {return TYPE_PLAYER;}
敵はプレイヤーと当たり判定を行うようにしています。敵クラス move関数
void Enemy::move() {
x -= 4;
if (x < -width) remove();
}
左にまっすぐ進みます。画面左に消えた場合は消去しています。敵クラス hit関数
void Enemy::hit() {
Game::me->addScore(100);
remove();
}
敵が弾に当たったときに呼ばれます。得点を追加し敵を消去します。雲クラス
class Cloud : public Chr {
int speed;
void resetPosition(bool);
public:
static const int NUM = 25;
Cloud();
ChrType getType() {return TYPE_CLOUD;}
void move();
};
データメンバ
speed | 雲の速度 |
---|---|
NUM | 画面上に表示する雲の数 |
雲クラス コンストラクタ
Cloud::Cloud() {
setImage("cloud.png");
resetPosition(true);
}
雲画像をセットし、画面上にランダムに配置します。雲クラス resetPosition関数
void Cloud::resetPosition(bool init) {
int cx = GetRand(Game::WIDTH + width) - width;
int cy = GetRand(Game::HEIGHT + height) - height;
if (init) setPosition(cx, cy);
else setPosition(Game::WIDTH, cy);
speed = GetRand(4) + 2;
}
雲をランダムな場所に配置し、ランダムな速度を設定する内部関数です。雲クラス move関数
void Cloud::move() {
x -= speed;
if (x < -width) resetPosition(false);
}
雲を左に移動し、画面から消えたら右端のランダムな位置に配置し直します。ゲームクラス
class Game {
std::list<ChrRef> chr_list;
int score;
public:
static const int WIDTH = 640;
static const int HEIGHT = 480;
static Game* me;
void addList(ChrRef&);
void addScore(int s) {score += s;}
void init();
void main();
void end();
};
Game* Game::me;
データメンバ
chr_list | キャラクタ管理リスト |
---|---|
score | 得点 |
WIDTH | 画面の幅 |
HEIGHT | 画面の高さ |
me | 自分自身のインスタンスを表すポインタ |
ゲームクラス addList関数
void Game::addList(ChrRef& c) {
chr_list.push_back(c);
}
キャラクタ管理リストにキャラクタを追加する関数です。listのpush_back関数はリストの末尾にオブジェクトを追加します。ゲームクラス init関数
void Game::init() {
me = this;
score = 0;
ChangeWindowMode(TRUE);
SetGraphMode(Game::WIDTH, Game::HEIGHT, 32);
DxLib_Init();
SetDrawScreen(DX_SCREEN_BACK);
SetBackgroundColor(124, 210, 255);
// 雲生成
for (int i = 0; i < Cloud::NUM; i++) {
ChrRef cloud(new Cloud());
addList(cloud);
}
// プレイヤー生成
ChrRef player(new Player());
addList(player);
}
プログラムの初期化部分です。以下に内部の説明をします。
me = this;
Gameクラスのインスタンスはひとつしかないので、静的メンバmeで外部からアクセスできるようにしています。
for (int i = 0; i < Cloud::NUM; i++) {
ChrRef cloud(new Cloud());
addList(cloud);
}
雲を生成しキャラクタ管理リストに追加しています。
ChrRef player(new Player());
addList(player);
プレイヤーを生成しキャラクタ管理リストに追加しています。ゲームクラス main関数
void Game::main() {
while (!ProcessMessage()) {
ClearDrawScreen();
// 敵生成
if (GetRand(49) == 0) {
ChrRef enemy(new Enemy());
addList(enemy);
}
// 移動
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->move();});
// 描画
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->draw();});
// 当たり判定
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& src) {
if (src->hitType() == TYPE_NONE) return;
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& dst) {
if (src->hitType() == dst->getType()) {
if (src->hitTest(dst)) {
src->hit();
dst->hit();
}
}
});
});
// スコア表示
DrawFormatString(Game::WIDTH - 120, 5, GetColor(0, 0, 0), "SCORE %d", score);
ScreenFlip();
// 消すべきキャラクタの消去
auto end = std::remove_if(chr_list.begin(), chr_list.end(), [](ChrRef& c) {return c->isRemove();});
chr_list.erase(end, chr_list.end());
}
}
プログラムのメイン部分で、ゲーム中はこの中でループします。以下に内部の説明をします。
if (GetRand(49) == 0) {
ChrRef enemy(new Enemy());
addList(enemy);
}
毎フレームごとに1/50の確率で敵を生成しています。
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->move();});
キャラクタ管理リストに登録されているキャラクタ全てのmove関数を呼び出している部分です。move関数は実際にはChrクラスの派生クラスのmove関数が呼ばれ、それぞれのキャラクタ独自の動きを実現しています。for_eachはコンテナ(vectorやlistなど)に対する繰り返しを行うテンプレート関数です(範囲for文という新しい文法を使うとさらにシンプルに記述できますが、Visual Studio 2010ではサポートされていません)。for_each関数は以下のような書式になります。
for_each(begin, end, function);
beginには開始位置のイテレータ、endには終了位置のイテレータ、functionには要素に適用する関数を記述します。ここではC++11の新機能であるラムダ関数(Visual C++ 2010では実装されています)を渡しています。
[](ChrRef& c) {c->move();}
このように関数を直接書くことができます。ここではそれぞれの要素についてmove関数を呼び出しています。
std::for_each(chr_list.begin(), chr_list.end(), [](ChrRef& c) {c->draw();});
キャラクタ管理リストに登録されているキャラクタ全てのdraw関数を呼び出して描画する部分です。
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& src) {
if (src->hitType() == TYPE_NONE) return;
std::for_each(chr_list.begin(), chr_list.end(), [&](ChrRef& dst) {
if (src->hitType() == dst->getType()) {
if (src->hitTest(dst)) {
src->hit();
dst->hit();
}
}
});
});
当たり判定を行います。外側のループにあるsrcには当たり判定の元になるキャラクタのポインタが入ります。内側のループになるdstには当たり判定の先になるキャラクタのポインタが入ります。当たり判定を行うかどうかをhitTypeにより判別し、hitTest関数により当たり判定を行い、当たっていれば両方のhit関数を呼び出して当たったときの処理を行います。
auto end = std::remove_if(chr_list.begin(), chr_list.end(), [](ChrRef& c) {return c->isRemove();});
chr_list.erase(end, chr_list.end());
remove_flagの立ったキャラクタを実際に消している部分です。remove_if関数によってキャラクタ消去フラグが立っているキャラクタを消去し、erase関数でリストを詰めます。