夏休み勉強会 6日目
最終日です。
敵の行動パターンを組む
それではまず。敵の行動パターンを組んでいこう。
まずはEnemy.hに必要なものを追加していく。
Enemy.hが以下のようになるようにコードを書き換えてくれ。
#pragma once
// インクルード達
#include "Libraries/Development/Model3D.h"
#include "Libraries/Development/BoxCollider.h"
#include "../EnemyBullet/EnemyBullet.h"
// 前方宣言
class CommonResources;
class Player;
/// <summary>
/// 敵
/// </summary>
class Enemy
{
public:
enum class StateID
{
Move,
Shot,
Tackle
};
public:
// モデルのディレクトリパスと名前
static constexpr wchar_t MODEL_DIRECTORY[] = L"Resources/Models/";
static constexpr wchar_t MODEL_NAME[] = L"Enemy";
// 衝突判定の大きさ
static constexpr DirectX::SimpleMath::Vector3 COLLIDER_SIZE{ 2.0f, 2.0f, 2.0f };
// 衝突判定位置のオフセット
static constexpr DirectX::SimpleMath::Vector3 COLLIDER_OFFSET{ 0, 1.0f, 0 };
// 開始座標
static constexpr DirectX::SimpleMath::Vector3 START_POSITION{ 0.0f, 0.0f, 0.0f };
// 開始回転
static constexpr DirectX::SimpleMath::Vector3 START_ROTATION{ 0.0f, DirectX::XM_PI, 0.0f };
// 旋回速度
static constexpr float TURN_SPEED = 7.5f;
// 移動速度
static constexpr float MOVE_SPEED = 0.5f;
// 銃口オフセット
static constexpr DirectX::SimpleMath::Vector3 MUZZLE_OFFSET{ 0.0f, 0.25f, 0.0f };
// 最大体力
static constexpr int MAX_HP = 50;
// 突撃速度
static constexpr float TACKLE_SPEED = 10.0f;
// 射撃数
static constexpr int SHOT_NUM = 8;
// 移動状態時間
static constexpr float STATE_MOVE_TIME = 3.5f;
// 突撃状態前隙
static constexpr float STATE_TACKLE_BEFORE_TIME = 2.0f;
// 突撃状態攻撃時間
static constexpr float STATE_TACKLE_ATACK_TIME = 0.75f;
// 突撃状態後隙
static constexpr float STATE_TACKLE_AFTER_TIME = 1.0f;
// ダメージ効果時間
static constexpr float DAMAGE_EFFECT_TIMER = 0.1f;
public:
// 座標を取得
const DirectX::SimpleMath::Vector3& GetPosition() const { return m_position; }
// 衝突判定の取得
const Develop::BoxCollider& GetCollider() const { return m_collider; }
// アクティブフラグの取得
bool GetIsActive() const { return m_isActive; }
public:
// 座標を設定
void SetPosition(const DirectX::SimpleMath::Vector3& position);
// プレイヤーのポインタを設定
void SetPlayer(Player* pPlayer) { m_pPlayer = pPlayer; }
public:
// コンストラクタ
Enemy();
// デストラクタ
~Enemy() = default;
// 開始関数
void Initialize(CommonResources* common);
// 更新関数
void Update(float deltaTime);
// 描画関数
void Render(const Develop::CameraObject& camera);
// 終了関数
void Finalize();
// ダメージ関数
void Damage();
private:
// 移動状態
void StateMove(float deltaTime);
// 射撃状態
void StateShot(float deltaTime);
// 突進状態
void StateTackle(float deltaTime);
// 移動関数
void Move(const DirectX::SimpleMath::Vector3& moveVelocity, float deltaTime);
// 射撃関数
void Shot(const DirectX::SimpleMath::Vector3& direction);
// 乱数生成
int Random(int min, int max);
private:
// 共通リソース
CommonResources* m_commonResources;
// モデル
Develop::Model3D m_model;
// 衝突判定
Develop::BoxCollider m_collider;
// 座標
DirectX::SimpleMath::Vector3 m_position;
// 方向
DirectX::SimpleMath::Vector3 m_direction;
// アクティブフラグ
bool m_isActive;
// 弾の配列
std::vector<EnemyBullet> m_bullets;
// プレイヤーのポインタ
Player* m_pPlayer;
// 状態ID
StateID m_stateID;
// 体力
int m_hp;
// 状態時間
float m_stateTimer;
// ダメージ効果時間
float m_damageEffectTimer;
};
それができたら続いてEnemy.cppを書いていこう以下のコードになるように書き換えてくれ。
#include "pch.h"
#include "Enemy.h"
#include <random>
#include "../CommonResources.h"
#include "../Player/Player.h"
// 名前空間の使用
using namespace DirectX;
/// <summary>
/// コンストラクタ
/// </summary>
Enemy::Enemy()
:
m_commonResources(nullptr),
m_isActive(true)
{
}
/// <summary>
/// 開始関数
/// </summary>
/// <param name="common">共通リソース</param>
void Enemy::Initialize(CommonResources* common)
{
// 共通リソースの取得
m_commonResources = common;
// コンテキストの取得
ID3D11DeviceContext* context = common->GetDeviceResources()->GetD3DDeviceContext();
// 共通ステートの取得
CommonStates* states = common->GetCommonStates();
// リソース管理クラスの取得
Develop::ResourceManager* resourceManager = common->GetResourceManager();
// モデルハンドルを取得
Develop::ResourceManager::ModelHandle modelHandle;
modelHandle = resourceManager->LoadModelCMO(MODEL_DIRECTORY, MODEL_NAME);
// モデルの初期化
m_model.Initialize(context, states, modelHandle);
// 衝突判定のサイズを設定
m_collider.SetSize(COLLIDER_SIZE);
// 開始座標を設定
SetPosition(START_POSITION);
// モデルの回転を設定
SimpleMath::Quaternion rotation;
rotation = SimpleMath::Quaternion::CreateFromYawPitchRoll(START_ROTATION);
m_model.SetRotate(rotation);
// 方向の初期化
m_direction = SimpleMath::Vector3::Forward;
m_direction = SimpleMath::Vector3::Transform(m_direction, rotation);
// アクティブフラグをオンに
m_isActive = true;
// 配列の初期化
m_bullets.clear();
// 状態の初期化
m_stateID = StateID::Move;
// 体力の初期化
m_hp = MAX_HP;
// 状態時間の初期化
m_stateTimer = 0.0f;
// ダメージ効果時間の初期化
m_damageEffectTimer = 0.0f;
}
/// <summary>
/// 更新関数
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::Update(float deltaTime)
{
// アクティブ状態かどうか
if (m_isActive == false) { return; }
// 状態ごとの更新をかける
switch (m_stateID)
{
case Enemy::StateID::Move: StateMove(deltaTime); break;
case Enemy::StateID::Shot: StateShot(deltaTime); break;
case Enemy::StateID::Tackle:StateTackle(deltaTime); break;
default: break;
}
// ダメージ効果時間の更新
m_damageEffectTimer -= deltaTime;
m_damageEffectTimer = std::max(m_damageEffectTimer, 0.0f);
// 弾の更新
for (int i = 0; i < m_bullets.size(); i++)
{
m_bullets[i].Update(deltaTime);
}
}
/// <summary>
/// 描画関数
/// </summary>
/// <param name="camera">カメラ</param>
void Enemy::Render(const Develop::CameraObject& camera)
{
// アクティブ状態かどうか
if (m_isActive == false) { return; }
// モデルの描画
m_model.Render(camera);
// 弾の描画
for (int i = 0; i < m_bullets.size(); i++)
{
m_bullets[i].Render(camera);
}
}
/// <summary>
/// 終了関数
/// </summary>
void Enemy::Finalize()
{
// 弾の終了
for (int i = 0; i < m_bullets.size(); i++)
{
m_bullets[i].Finalize();
}
}
/// <summary>
/// ダメージ関数
/// </summary>
void Enemy::Damage()
{
// ダメージを受ける
m_hp--;
// ダメージ効果時間を与える
m_damageEffectTimer = DAMAGE_EFFECT_TIMER;
// ダメージが0以下になったら死ぬ
if (m_hp < 0)
{
m_isActive = false;
}
}
/// <summary>
/// 移動状態
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::StateMove(float deltaTime)
{
// プレイヤー座標を取得
SimpleMath::Vector3 playerPosition = m_pPlayer->GetPosition();
// プレイヤーへの方向ベクトルを求める
SimpleMath::Vector3 toPlayerDireciton = playerPosition - m_position;
toPlayerDireciton.Normalize();
// 歩行速度をかけ合わせて移動させる
Move(toPlayerDireciton * MOVE_SPEED, deltaTime);
// 状態時間を更新
m_stateTimer += deltaTime;
if (m_stateTimer > STATE_MOVE_TIME)
{
// 乱数をもとに次の状態を決める
int randNum = Random(0, 2);
if (randNum == 0) { m_stateID = StateID::Shot; }
else { m_stateID = StateID::Tackle; }
// 状態時間を初期化
m_stateTimer = 0.0f;
}
}
/// <summary>
/// 射撃状態
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::StateShot(float deltaTime)
{
}
/// <summary>
/// 突進状態
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::StateTackle(float deltaTime)
{
}
/// <summary>
/// 移動関数
/// </summary>
/// <param name="moveVelocity">移動ベクトル</param>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::Move(const DirectX::SimpleMath::Vector3& moveVelocity, float deltaTime)
{
// 移動方向を計算
SimpleMath::Vector3 moveDirection = moveVelocity;
moveDirection.Normalize();
// 今向いてる方向と移動方向で軸を算出
SimpleMath::Vector3 moveAxis = m_direction.Cross(moveDirection);
if (moveAxis.y > 0.0f) { moveAxis = SimpleMath::Vector3::Up; }
else { moveAxis = SimpleMath::Vector3::Down; }
// 今向いてる方向と移動方向の角度を算出
float moveAngle = m_direction.Dot(moveDirection);
moveAngle = std::min(1.0f, std::max(-1.0f, moveAngle));
moveAngle = acosf(moveAngle);
// 旋回速度と残りの角度を比較してより小さい方を取得
moveAngle = std::min(moveAngle, TURN_SPEED * deltaTime);
// モデルをそれだけ回転させる
SimpleMath::Quaternion rotate = m_model.GetRotate();
rotate *= SimpleMath::Quaternion::CreateFromAxisAngle(moveAxis, moveAngle);
m_model.SetRotate(rotate);
// 前方ベクトルを更新
m_direction = SimpleMath::Vector3::Transform(SimpleMath::Vector3::Forward, rotate);
m_direction.Normalize();
// 移動させる
SetPosition(m_position + moveVelocity * deltaTime);
}
/// <summary>
/// 射撃関数
/// </summary>
/// <param name="direction">方向</param>
void Enemy::Shot(const DirectX::SimpleMath::Vector3& direction)
{
// 起動する弾のポインタ
EnemyBullet* bootBullet = nullptr;
// 使えそうな弾を検索
for (int i = 0; i < m_bullets.size(); i++)
{
// 使っていない弾を発見
if (m_bullets[i].GetIsActive() == false)
{
bootBullet = &m_bullets[i];
// ループを抜ける
break;
}
}
// 使っていない弾がなければ作成
if (bootBullet == nullptr)
{
// 新しく弾を生成する
m_bullets.push_back(EnemyBullet());
// 一番後ろの要素を取り出す
bootBullet = &m_bullets.back();
// 新しい弾の初期化処理
bootBullet->Initialize(m_commonResources);
}
// 銃口座標
SimpleMath::Vector3 muzzlePosition = MUZZLE_OFFSET;
muzzlePosition = SimpleMath::Vector3::Transform(muzzlePosition, m_model.GetRotate());
muzzlePosition += m_position;
// 弾を発射
bootBullet->Boot(muzzlePosition, direction);
}
/// <summary>
/// 乱数生成関数
/// </summary>
/// <param name="min">最小値</param>
/// <param name="max">最大値</param>
/// <returns>乱数</returns>
int Enemy::Random(int min, int max)
{
//ランダムなシード値を使って乱数生成器を初期化
static std::random_device seed;
static std::mt19937 engine(seed());
// 乱数を範囲内で生成
return engine() % (max - min) + min;
}
/// <summary>
/// 座標を設定
/// </summary>
/// <param name="position">座標</param>
void Enemy::SetPosition(const DirectX::SimpleMath::Vector3& position)
{
m_position = position;
// モデルの座標も設定
m_model.SetPosition(position);
// 衝突判定の座標も設定
m_collider.SetPosition(position + COLLIDER_OFFSET);
}
これで行動を作っていく枠組みができた。今回はenumの状態IDを用いたswitch文での行動パターンを組んでいこうと思う。
突進行動を作成する
それではまず突進行動を作っていく。StateTackle関数に以下のコードを記述してくれ。
// 状態時間を更新
m_stateTimer += deltaTime;
// プレイヤーをサーチ
if (m_stateTimer < STATE_TACKLE_BEFORE_TIME)
{
// プレイヤー座標を取得
SimpleMath::Vector3 playerPosition = m_pPlayer->GetPosition();
// プレイヤーへの方向ベクトルを求める
SimpleMath::Vector3 toPlayerDireciton = playerPosition - m_position;
toPlayerDireciton.Normalize();
// 今向いてる方向と移動方向で軸を算出
SimpleMath::Vector3 moveAxis = m_direction.Cross(toPlayerDireciton);
if (moveAxis.y > 0.0f) { moveAxis = SimpleMath::Vector3::Up; }
else { moveAxis = SimpleMath::Vector3::Down; }
// 今向いてる方向と移動方向の角度を算出
float moveAngle = m_direction.Dot(toPlayerDireciton);
moveAngle = std::min(1.0f, std::max(-1.0f, moveAngle));
moveAngle = acosf(moveAngle);
// 旋回速度と残りの角度を比較してより小さい方を取得
moveAngle = std::min(moveAngle, TURN_SPEED * deltaTime);
// モデルをそれだけ回転させる
SimpleMath::Quaternion rotate = m_model.GetRotate();
rotate *= SimpleMath::Quaternion::CreateFromAxisAngle(moveAxis, moveAngle);
m_model.SetRotate(rotate);
// 前方ベクトルを更新
m_direction = SimpleMath::Vector3::Transform(SimpleMath::Vector3::Forward, rotate);
m_direction.Normalize();
}
// 前に突進
else if (m_stateTimer < STATE_TACKLE_BEFORE_TIME + STATE_TACKLE_ATACK_TIME)
{
// 前方に移動させる
Move(m_direction * TACKLE_SPEED, deltaTime);
}
// 後隙
else
{
// 全体時間を計算
float atackTotalTime = 0.0f;
atackTotalTime += STATE_TACKLE_BEFORE_TIME;
atackTotalTime += STATE_TACKLE_ATACK_TIME;
atackTotalTime += STATE_TACKLE_AFTER_TIME;
// 全体時間を越しているかどうか
if (m_stateTimer > atackTotalTime)
{
// 乱数をもとに次の状態を決める
int randNum = Random(0, 3);
if (randNum == 0) { m_stateID = StateID::Shot; }
else { m_stateID = StateID::Move; }
// 状態時間を初期化
m_stateTimer = 0.0f;
}
}
射撃行動を作っていく。
それでは続いてStateShot関数に以下のコードを記述してくれ。
// 弾を8方向に飛ばす
for (int i = 0; i < SHOT_NUM; i++)
{
// 射撃角を算出
float shotAngle = XM_2PI * (i + 0.5f) / SHOT_NUM;
// 射撃方向としてとりあえず前方ベクトルを取得する
SimpleMath::Vector3 shotDirection = m_direction;
shotDirection.y = 0.0f;
shotDirection.Normalize();
// 射撃方向を回転させる
shotDirection = SimpleMath::Vector3::Transform(
shotDirection,
SimpleMath::Matrix::CreateRotationY(shotAngle)
);
shotDirection.Normalize();
// 射撃
Shot(shotDirection);
}
// 乱数をもとに次の状態を決める
int randNum = Random(0, 3);
if (randNum == 0) { m_stateID = StateID::Tackle; }
else { m_stateID = StateID::Move; }
// 状態時間を初期化
m_stateTimer = 0.0f;
できたらこんな感じになる。
衝突判定を取っていく
それでは続いて衝突判定を取る機構を作っていく。PlayScene.hに以下の関数を追加してほしい。
プレイヤーと敵から弾を取得できるようにする
Player.hに以下の関数を追加してくれ。
// 弾を取得
std::vector<PlayerBullet>* GetBullets() { return &m_bullets; }
Enemy.hにも以下の関数を追加してくれ。
// 弾を取得
std::vector<EnemyBullet>* GetBullets() { return &m_bullets; }
衝突判定をとる関数を作る
PlayScene.hに以下の関数を追加しよう。
private:
// 衝突判定
void JudgementCollision();
続いてPlayScene.cppに以下の関数を作成しよう。
/// <summary>
/// 衝突判定
/// </summary>
void PlayScene::JudgementCollision()
{
// プレイヤーの弾と敵との衝突判定
std::vector<PlayerBullet>* pPlayerBullets = m_player.GetBullets();
for (int i = 0; i < pPlayerBullets->size(); i++)
{
PlayerBullet* pBullet = &(*pPlayerBullets)[i];
if (pBullet->GetIsActive() == false) { continue; }
if (m_enemy.GetIsActive() == false) { continue; }
const Develop::BoxCollider& enemyCollider = m_enemy.GetCollider();
const Develop::BoxCollider& bulletCollider = pBullet->GetCollider();
// 衝突判定を取る
if (enemyCollider.CheckHitAABB(bulletCollider))
{
m_enemy.Damage();
pBullet->Erace();
}
}
// 敵の弾とプレイヤーとの衝突判定
std::vector<EnemyBullet>* pEnemyBullets = m_enemy.GetBullets();
for (int i = 0; i < pEnemyBullets->size(); i++)
{
EnemyBullet* pBullet = &(*pEnemyBullets)[i];
if (pBullet->GetIsActive() == false) { continue; }
if (m_player.GetIsActive() == false) { continue; }
const Develop::BoxCollider& playerCollider = m_player.GetCollider();
const Develop::BoxCollider& bulletCollider = pBullet->GetCollider();
// 衝突判定を取る
if (playerCollider.CheckHitAABB(bulletCollider))
{
m_player.Dead();
pBullet->Erace();
}
}
// プレイヤーと敵との衝突判定
{
if (m_player.GetIsActive() == false) { return; }
if (m_enemy.GetIsActive() == false) { return; }
const Develop::BoxCollider& playerCollider = m_player.GetCollider();
const Develop::BoxCollider& enemyCollider = m_enemy.GetCollider();
// 衝突判定を取る
if (playerCollider.CheckHitAABB(enemyCollider))
{
m_player.Dead();
}
}
}
JudgementCollision関数をUpdate関数で呼んであげると衝突判定が取れるようになる。
ダメージリアクションを作ろう
それでは続いてダメージリアクションを作っていこう。Develop::Model3Dを改造していく。Model3D.hが以下のようになるように改造しよう。
#pragma once
// インクルード達
#include "ResourceManager.h"
namespace Develop
{
// 前方宣言
class CameraObject;
/// <summary>
/// DirectX::Modelのラッパークラス
/// </summary>
class Model3D
{
// ゲッター
public:
// 座標の取得
const DirectX::SimpleMath::Vector3& GetPosition() const { return m_position; }
// スケールの取得
const DirectX::SimpleMath::Vector3& GetScale() const { return m_scale; }
// 回転の取得
const DirectX::SimpleMath::Quaternion& GetRotate() const { return m_rotate; }
// 色
const DirectX::SimpleMath::Color& GetColor() const { return m_color; }
// セッター
public:
// 座標の設定
void SetPosition(const DirectX::SimpleMath::Vector3& position) { m_position = position; }
// スケールの設定
void SetScale(const DirectX::SimpleMath::Vector3& scale) { m_scale = scale; }
// 回転の設定
void SetRotate(const DirectX::SimpleMath::Quaternion& rotate) { m_rotate = rotate; }
// 色
void SetColor(const DirectX::SimpleMath::Color& color) { m_color = color; }
// 公開関数
public:
// コンストラクタ
Model3D();
// デストラクタ
~Model3D() = default;
// 開始関数
void Initialize
(
ID3D11DeviceContext* pDeviceContext ,
DirectX::CommonStates* pCommonStates ,
ResourceManager::ModelHandle modelHandle
);
// 描画関数
void Render(const CameraObject& camera);
// 内部パラメータ
private:
// モデルデータ
ResourceManager::ModelHandle m_pModel;
DirectX::SimpleMath::Vector3 m_position; // 座標
DirectX::SimpleMath::Vector3 m_scale; // スケール
DirectX::SimpleMath::Quaternion m_rotate; // 回転
DirectX::SimpleMath::Color m_color; // 色
ID3D11DeviceContext* m_pDeviceContext; // デバイスコンテキスト
DirectX::CommonStates* m_pCommonStates; // 共通ステート
};
}
続いてModel.cppを以下のようになるようにしよう。
#include "pch.h"
#include "Model3D.h"
#include "DeviceResources.h"
#include "CameraObject.h"
// 名前空間の使用
using namespace DirectX;
/// <summary>
/// コンストラクタ
/// </summary>
Develop::Model3D::Model3D()
:
m_pDeviceContext(nullptr),
m_pCommonStates(nullptr),
m_pModel(nullptr),
m_scale(SimpleMath::Vector3::One),
m_color(Colors::White)
{
}
/// <summary>
/// 開始関数
/// </summary>
/// <param name="pDeviceContext">デバイスコンテキストのポインタ</param>
/// <param name="pCommonStatesm">共通ステートのポインタ</param>
/// <param name="pModel">モデルデータ</param>
void Develop::Model3D::Initialize
(
ID3D11DeviceContext* pDeviceContext,
DirectX::CommonStates* pCommonStates,
ResourceManager::ModelHandle modelHandle
)
{
// デバイスコンテキストと共通ステートの設定
m_pDeviceContext = pDeviceContext;
m_pCommonStates = pCommonStates;
// モデルの登録
m_pModel = modelHandle;
}
/// <summary>
/// 描画関数
/// </summary>
/// <param name="camera">カメラ</param>
void Develop::Model3D::Render(const CameraObject& camera)
{
// ワールド行列の作成
SimpleMath::Matrix world = SimpleMath::Matrix::Identity;
world *= SimpleMath::Matrix::CreateScale(m_scale); // スケール
world *= SimpleMath::Matrix::CreateFromQuaternion(m_rotate); // 回転
world *= SimpleMath::Matrix::CreateTranslation(m_position); // 座標
// エフェクトの更新
(*m_pModel)->UpdateEffects([&](IEffect* pEffect)
{
// ベーシックエフェクトに変換
BasicEffect* pBasicEffect = dynamic_cast<BasicEffect*>(pEffect);
// 変換に成功したかどうか
if (pBasicEffect != nullptr)
{
// 色を設定
pBasicEffect->SetAmbientLightColor(m_color);
}
}
);
// モデルの描画
(*m_pModel)->Draw
(
m_pDeviceContext,
*m_pCommonStates,
world,
camera.GetView(),
camera.GetProj()
);
}
これで、モデルの色を変えることができるようになった。
続いてEnemy.cppのRender関数に以下のコードを記述してくれ。
// ダメージ効果時間中なら赤色にする
if (m_damageEffectTimer > 0.0f)
{
m_model.SetColor(SimpleMath::Color(Colors::Red));
}
else
{
m_model.SetColor(SimpleMath::Color(Colors::White));
}
これらを実装すると完成となる。長い間お疲れ様でした。
この記事が気に入ったらサポートをしてみませんか?