はじめに
この記事は #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 クラス を作成しました。
/// <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; // 生成したオブジェクトリスト
};
登録関数を使ってポインタを登録し、更新処理で判定を行います。
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を双方に実行します。
登録する側も自身を登録するだけでいいので楽にできます。
(例)
GameScene::GameScene()
{
PlayerObject player = PlayerObject();
EnemyObject enemy = EnemyObject();
collisionManager.CollisionSubscrive(&player);
collisionManager.CollisionSubscrive(&enemy);
}
アニメーション
アニメーションはUnityにあるような 登録された文字列を実行すれば再生できる ものを目指しました。
/// <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でアニメーション名をキーにしてクリップを登録しているためです。
(使用例)
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); // 弾発射アニメーションを登録
}
(実装内容)
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を初期化する
}
この二つでアニメーションを管理します。
これで再生されることで、アニメーション再生をする準備ができました。
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);
}
}
}
こうしてクリップの情報をもとにアニメーションを再生していきます。
ループ再生や反転描画にも対応ができるようにしていますし、このプロジェクト全体を通していいものができたと思っています。
おわりに
いかがでしたでしょうか
基本的な実装が多くあまり参考にはならなったかもしれませんが、この記事が少しでも役に立てば幸いです。
これからも投稿していけたら と思っていますので、今後もよければご覧いただけると嬉しいです。