C++を使ってシューティングを作った話 #3

# はじめに
この記事は **#3 当たり判定、アニメーションクラス** です。
#0:https://qiita.com/komugikoShimizu/items/9e9a18232d2d5bf7db6a
#1:https://qiita.com/komugikoShimizu/items/5e66093a0a22e57e1e8c
#2:https://qiita.com/komugikoShimizu/items/efd31f4fa683fce4e9c1

# 今回の実装について
当たり判定については、登録されたCollisionObjectすべてをチェックし、当たっている場合はCollisionActionを実行するものが完成形です。

アニメーションについては、クリップを登録してそれをもとに実行できるものを目標とします。

# 当たり判定
当たり判定を統括する **CollisionManager** クラス　を作成しました。

``` C++ CollisionManager.h
/// <summary>
/// 当たり判定管理クラス
/// </summary>
class CollisionManager
{
public:
	/// <summary>
	/// 登録関数
	/// </summary>
	/// <param name="ptr">登録する対象</param>
	void CollisionSubscrive(CollisionObject* ptr);

	/// <summary>
	/// 当たり判定処理更新
	/// </summary>
	void CollisionUpdate();

	/// <summary>
	/// 判定実行処理
	/// </summary>
	/// <param name="a">対象1</param>
	/// <param name="b">対象2</param>
	bool CollisionJudge(CollisionObject* a, CollisionObject* b);
private:
	map<int, CollisionObject*> objectMap;	// 生成したオブジェクトリスト
};
```

登録関数を使ってポインタを登録し、更新処理で判定を行います。

``` C++ CollisionManager.cpp
void CollisionManager::CollisionUpdate()
{
	for (int i = 0; i < InstaceMax; i++)				// インスタンス上限まで実行 
	{
		if (objectMap[i] == nullptr) continue;			// 見た位置がヌルポインタであれば次へ
		// プレイヤーかエネミーでないなら次へ
		if (objectMap[i]->GetType() != CollisionType::PLAYER && objectMap[i]->GetType() != CollisionType::ENEMY) continue;
		for (int j = 0; j < InstaceMax; j++)			// インスタンス上限まで実行
		{
			if (i == j) continue;
			if (objectMap[j] == nullptr) continue;		// 見た位置がヌルポインタであれば次へ
			// 見た位置の接触タイプが未設定なら
			if (objectMap[j]->GetType() == CollisionType::TYPE_NONE)
			{
				objectMap[j] = nullptr;					// 対象の位置にヌルポインタを設定
				continue;								// 次へ
			}
			// 敵対する弾であれば
			if ((objectMap[i]->GetType() == CollisionType::ENEMY && objectMap[j]->GetType() == CollisionType::PLAYER_BULLET) ||
				(objectMap[i]->GetType() == CollisionType::PLAYER && objectMap[j]->GetType() == CollisionType::ENEMY_BULLET))
			{
				// 判定関数を実行し、当たっているのなら
				if (CollisionJudge(objectMap[i], objectMap[j]))
				{
					// 判定を行った双方に接触時関数を実行する
					objectMap[i]->CollisionAction();
					objectMap[j]->CollisionAction();

					objectMap[j] = nullptr;				// 弾のポインタをヌルポインタに設定
				}
			}
		}
	}
}

bool CollisionManager::CollisionJudge(CollisionObject* base, CollisionObject* opponent)
{
	// 2つの座標の距離を計算
	double distance =
		sqrt(pow((double)base->GetCollisionPos().x - (double)opponent->GetCollisionPos().x, 2) +
			pow((double)base->GetCollisionPos().y - (double)opponent->GetCollisionPos().y, 2));

	// 0未満なら整数化するために-1をかける
	if (distance < 0) distance *= -1;

	// 半径をの合計が計算した距離以上であるならtrueを返す
	bool ret = (double)base->GetRadius() + (double)opponent->GetRadius() >= distance;
	return ret;
}
```
このように登録されたオブジェクトすべてに判定をかけ、当たっていたらCollisionActionを双方に実行します。

登録する側も自身を登録するだけでいいので楽にできます。
(例)
```C++ GameScene.cpp
GameScene::GameScene()
{
    PlayerObject player = PlayerObject();
    EnemyObject enemy = EnemyObject();

    collisionManager.CollisionSubscrive(&player);
    collisionManager.CollisionSubscrive(&enemy);
}
```

# アニメーション
アニメーションはUnityにあるような 登録された文字列を実行すれば再生できる ものを目指しました。
```C++ AnimationController.h
/// <summary>
/// アニメーションを制御するクラス
/// </summary>
class AnimationController
{
public:
	/// <summary>
	/// アニメーション登録関数
	/// </summary>
	/// <param name="animationName">アニメーション名</param>
	/// <param name="newClip">登録するクリップ</param>
	void AnimationSubscrive(const char*, AnimationClip);

	/// <summary>
	/// アニメーション変更関数
	/// </summary>
	/// <param name="playAnimationName">実行アニメーション名</param>
	void AnimationPlay(const char*);

	/// <summary>
	/// アニメーション更新関数(常時実行推奨.複数から呼ぶことを非推奨)
	/// </summary>
	/// <param name="position">画像生成座標</param>
	/// <param name="flip">反転生成するかどうか</param>
	void AnimaitonUpdate(Vector2, bool);
private:
	map<const char*, AnimationClip> animationContainer;	// アニメーション名でクリップを保存するmap変数
};
```

登録関数でアニメーション名とクリップをセットで登録することができます。
再生はその際に登録したアニメーション名で再生することができます。
これはmapでアニメーション名をキーにしてクリップを登録しているためです。

(使用例)
```C++ PlayerObject.cpp
void PlayerObject::ClipInstance()
{
	stayAnimation = AnimationClip(
		"Sprites/Player_Stay.png", 4, 2, 2, 400, 400, 0.5f, true);			// 停止アニメーションクリップを作成

	animationController.AnimationSubscrive("Player_Stay", stayAnimation);	// 停止アニメーションを登録
	animationController.AnimationPlay("Player_Stay");						// 停止アニメーションを再生

	shotAnimation = AnimationClip(
		"Sprites/Player_Shot.png", 4, 2, 2, 400, 400, 0.5f, true);			// 弾発射アニメーションクリップを作成

	animationController.AnimationSubscrive("Player_Shot", shotAnimation);	// 弾発射アニメーションを登録	
}

```
(実装内容)
```C++ AnimationController.cpp
void AnimationController::AnimationSubscrive(const char* animationName, AnimationClip newClip)
{
	animationContainer[animationName] = newClip;
}

void AnimationController::AnimationPlay(const char* playAnimationName)
{
	playClip = animationContainer[playAnimationName];									// 再生クリップを保存
	spriteChengeTime = playClip.sliceCountMax / playClip.animationTime;					// 画像変更時間を設定
	timeCount = GetNowCount();															// 時間計測用変数に経過時間を設定
	nextAnimationDiray = (playClip.animationTime / playClip.sliceCountMax) * 1000.0f;	// 画像変更時間を変数に保存(1000.0fをかけると秒数になるらしい)
	nextAnimationTime = timeCount + nextAnimationDiray;									// 画像変更を行う時間を保存
	spriteIndex = 0;																	// 画像を読み込むindexを初期化する
}
```
この二つでアニメーションを管理します。
これで再生されることで、アニメーション再生をする準備ができました。

```C++ AnimationController.cp
void AnimationController::AnimaitonUpdate(Vector2 position, bool flip)
{
	if (playClip.animationTime == FLT_EPSILON) return;		// 再生アニメーション時間が0秒であれば処理しない(アニメーションしない or クリップ未設定のため)

	timeCount = GetNowCount();								// 経過時間更新

	if (timeCount >= nextAnimationTime)						// 画像更新時間を超えれば
	{
		nextAnimationTime += nextAnimationDiray;			// 画像更新時間に経過時間を足して更新

		spriteIndex++;										// 画像表示indexを追加
			
		if (spriteIndex >= playClip.sliceCountMax)			// 画像数が設定された数を上回れば
		{
			if (playClip.isLoop)							// ループ再生するのであれば
			{
				spriteIndex = 0;							// 画像表示indexを再度0に変更
			}
			else
			{
				spriteIndex = playClip.sliceCountMax - 1;	// 要素数限界にindexを調整
			}
		}
	}

	// どちらかにでもサイズ設定がされていれば
	if (spriteSize.x != FLT_EPSILON || spriteSize.y != FLT_EPSILON)	
	{
		Vector2 sizeCalPos = position;						// サイズ反映座標保存用変数
		if (flip)											// 反転するのであれば
		{
			sizeCalPos.x -= spriteSize.x;					// x値のみ減算
			sizeCalPos.y += spriteSize.y;					// y方向は正しく生成してほしいため加算
		}
		else
		{
			sizeCalPos += spriteSize;						// 設定されたサイズを反映した座標値を作成
		}

		// 描画
		DrawExtendGraph(position.x, position.y, sizeCalPos.x, sizeCalPos.y, playClip.handleArray[spriteIndex], true);
	}
	else													// サイズ設定がされていない場合(する必要がない画像)
	{
		if (flip)											// 反転するのであれば
		{
			// 反転描画する
			DrawTurnGraph(position.x, position.y, playClip.handleArray[spriteIndex], true);
		}
		else
		{
			// 通常の描画処理を行う
			DrawGraph(position.x, position.y, playClip.handleArray[spriteIndex], true);
		}
	}
}
```

こうしてクリップの情報をもとにアニメーションを再生していきます。
ループ再生や反転描画にも対応ができるようにしていますし、このプロジェクト全体を通していいものができたと思っています。

# おわりに
いかがでしたでしょうか
基本的な実装が多くあまり参考にはならなったかもしれませんが、この記事が少しでも役に立てば幸いです。
これからも投稿していけたら　と思っていますので、今後もよければご覧いただけると嬉しいです。