LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-05-16

はじめに

この記事は #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);
		}
	}
}

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

おわりに

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0