夏休み勉強会 5日目

本日は敵の枠組みを中心に作成していく


敵の挙動を作成する準備を整える

次回の勉強会で敵の挙動を作成するのでその準備をまず整えていこう。

敵の枠組みを作る

まずはお決まりの枠組み作りから始めていこう
Enemy.hに以下のコードを記述してくれ。

#pragma once

//	インクルード達
#include "Libraries/Development/Model3D.h"
#include "Libraries/Development/BoxCollider.h"

//	前方宣言
class CommonResources;

/// <summary>
/// 敵
/// </summary>
class Enemy
{
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 };

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);

public:

	//	コンストラクタ
	Enemy();
	//	デストラクタ
	~Enemy() = default;

	//	開始関数
	void Initialize(CommonResources* common);
	//	更新関数
	void Update(float deltaTime);
	//	描画関数
	void Render(const Develop::CameraObject& camera);
	//	終了関数
	void Finalize();

private:

	//	移動関数
	void Move(const DirectX::SimpleMath::Vector3& moveVelocity, float deltaTime);
	//	射撃関数
	void Shot(const DirectX::SimpleMath::Vector3& direction);
	
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;
};

Enemy.cppに以下のコードを記述してくれ。

#include "pch.h"
#include "Enemy.h"

#include "../CommonResources.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;
}

/// <summary>
/// 更新関数
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::Update(float deltaTime)
{
	//	アクティブ状態かどうか
	if (m_isActive == false) { return; }
}

/// <summary>
///	描画関数
/// </summary>
/// <param name="camera">カメラ</param>
void Enemy::Render(const Develop::CameraObject& camera)
{
	//	アクティブ状態かどうか
	if (m_isActive == false) { return; }

	//	モデルの描画
	m_model.Render(camera);
}

/// <summary>
/// 終了関数
/// </summary>
void Enemy::Finalize()
{
}

/// <summary>
/// 移動関数
/// </summary>
/// <param name="moveVelocity">移動ベクトル</param>
/// <param name="deltaTime">フレーム間秒数</param>
void Enemy::Move(const DirectX::SimpleMath::Vector3& moveVelocity, float deltaTime)
{
}

/// <summary>
/// 射撃関数
/// </summary>
/// <param name="direction">方向</param>
void Enemy::Shot(const DirectX::SimpleMath::Vector3& direction)
{
}

/// <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);
}

それでは実際にPlaySceneで敵を生成して設置してみよう。
出来たらこんな感じになるよ。

敵が見てるね

カメラを凝る

それでは次にカメラを凝っていこう。
プレイヤーの少し後ろからカメラを追従させていって常に敵を眺めているカメラを作成していこう。

プレイヤーのポインタを渡していく

ではまずプレイヤーのポインタを渡していこう

敵のポインタを渡していく

次に敵のポインタを渡していこう

ロックオンカメラを作っていく

まずは自分で作ってみよう。以下の要点を守りつつ作っていこう。

  • eye座標にプレイヤーの座標を入れる

  • target座標に敵の座標を入れる

  • eyeとtargetの高さを揃える

  • eyeからtargetにかけての方向ベクトルを作成する

  • 方向ベクトルをもとに少し後ろにeye座標をずらす

高さを揃える理由としては近づいた時にカメラが安定しなくなるのを防ぐためである。できるとこんな感じになる。

答え合わせ編

それではできた人もできなかった人も答えを確認してみよう。

/// <summary>
/// 更新関数
/// </summary>
void PlaySceneCamera::Update()
{
	SimpleMath::Vector3 eye		= m_pPlayer	->GetPosition();//	レンズ座標
	SimpleMath::Vector3 target	= m_pEnemy	->GetPosition();//	ターゲット座標
	SimpleMath::Vector3 up		= SimpleMath::Vector3::Up;	//	姿勢ベクトル

	//	レンズ座標とターゲット座標の高さを揃える
	eye		.y = HEIGHT_POSITION;
	target	.y = HEIGHT_POSITION;

	//	方向ベクトルを更新
	m_direction = target - eye;
	m_direction.Normalize();

	//	レンズ座標を少し後ろにずらす
	eye -= m_direction * PLAYER_DISTANCE;

	//	ビュー行列の作成
	CreateViewMatrix(eye, target, up);
}

敵が弾を発射できるようにする

敵も弾を発射させたいので前回と同様の手順で作っていく。

弾クラスの枠組みを作成する

それではまず弾のクラスを作成していこう。
EnemyBullet.hに以下のコードを記述してくれ。

#pragma once

//	インクルード達
#include "Libraries/Development/Model3D.h"
#include "Libraries/Development/BoxCollider.h"

//	前方宣言
class CommonResources;

/// <summary>
/// 敵の弾
/// </summary>
class EnemyBullet
{
public:

	//	モデルのディレクトリパスと名前
	static constexpr wchar_t MODEL_DIRECTORY[] = L"Resources/Models/";
	static constexpr wchar_t MODEL_NAME[] = L"EnemyBullet";

	//	モデルスケール
	static constexpr DirectX::SimpleMath::Vector3 MODEL_SCALE{ 0.2f, 0.2f, 0.2f };
	//	衝突判定の大きさ
	static constexpr DirectX::SimpleMath::Vector3 COLLIDER_SIZE{ 0.4f, 0.4f, 0.4f };

	//	生存時間
	static constexpr float ACTIVE_LIMIT = 5.0f;

	//	移動速度
	static constexpr float MOVE_SPEED = 5.0f;

public:

	//	衝突判定を取得
	const Develop::BoxCollider& GetCollider() const { return m_collider; }

	//	アクティブフラグの取得
	bool GetIsActive() const { return m_isActive; }

public:

	//	座標を設定
	void SetPosition(const DirectX::SimpleMath::Vector3& position);

	//	アクティブフラグの設定
	void SetIsActive(bool isActive) { m_isActive = isActive; }

public:

	//	コンストラクタ
	EnemyBullet();
	//	デストラクタ
	~EnemyBullet() = default;

	//	開始関数
	void Initialize(CommonResources* common);
	//	更新関数
	void Update(float deltaTime);
	//	描画関数
	void Render(const Develop::CameraObject& camera);
	//	終了関数
	void Finalize();

	//	起動関数
	void Boot
	(
		const DirectX::SimpleMath::Vector3& position,
		const DirectX::SimpleMath::Vector3& direction
	);

	//	削除関数
	void Erace();
	
private:

	//	モデル
	Develop::Model3D m_model;
	//	衝突判定
	Develop::BoxCollider m_collider;

	//	座標
	DirectX::SimpleMath::Vector3 m_position;
	//	方向
	DirectX::SimpleMath::Vector3 m_direction;

	//	生存時間
	float m_activeTimer;

	//	生存フラグ
	bool m_isActive;

};

では続いてEnemyBullet.cppに以下のコードを記述してくれ。

#include "pch.h"
#include "EnemyBullet.h"

#include "../CommonResources.h"

//	名前空間の使用
using namespace DirectX;

/// <summary>
/// コンストラクタ
/// </summary>
EnemyBullet::EnemyBullet()
	:
	m_activeTimer(0.0f),
	m_isActive(false)
{
}

/// <summary>
/// 開始関数
/// </summary>
/// <param name="common">共通リソース</param>
void EnemyBullet::Initialize(CommonResources* common)
{
}

/// <summary>
/// 更新関数
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void EnemyBullet::Update(float deltaTime)
{
	//	アクティブ状態かどうか
	if (m_isActive == false) { return; }
}

/// <summary>
/// 描画関数
/// </summary>
/// <param name="camera">カメラ</param>
void EnemyBullet::Render(const Develop::CameraObject& camera)
{
	//	アクティブ状態かどうか
	if (m_isActive == false) { return; }
}

/// <summary>
/// 終了関数
/// </summary>
void EnemyBullet::Finalize()
{
}

/// <summary>
/// 起動関数
/// </summary>
/// <param name="position">座標</param>
/// <param name="direction">方向</param>
void EnemyBullet::Boot
(
	const DirectX::SimpleMath::Vector3& position,
	const DirectX::SimpleMath::Vector3& direction
)
{
}

/// <summary>
/// 削除関数
/// </summary>
void EnemyBullet::Erace()
{
	//	アクティブフラグをオフにする
	m_isActive = false;
}

/// <summary>
/// 座標の設定
/// </summary>
/// <param name="position">座標</param>
void EnemyBullet::SetPosition(const DirectX::SimpleMath::Vector3& position)
{
}

これで枠組みの完成だ。

敵に弾を管理させる

それでは前回同様に弾の管理を敵にもさせていこう
以下の手順まずは弾の管理体制を整えよう。

  1. Enemy.hにEnemyBulletをインクルードする

  2. Enemy.hにてEnemyBulletの可変長配列を作成する

  3. EnemyのInitialize関数で配列の初期化を行う

  4. EnemyのUpdate関数で弾の更新処理を行う

  5. EnemyのRender関数で弾の描画処理を行う

  6. EnemyのFinalize関数で弾の終了処理を行う

続いてはShot関数の中身を書いていこう以下の手順で実際に記述してみよう。

  1. 起動する弾のポインタ変数を作成する

  2. 未使用弾がないか配列内を調べる

  3. 未使用弾が無ければ新しく弾を生成する

  4. 弾の発射する座標を求める

  5. 弾を実際に起動する

敵の弾を実装する

ここまで出来たら実際に弾の実装にあたっていく
というところでまた自力で実装してみよう。
仕様は前回のプレイヤーの弾と同じ、で以下のようなものだ。

  • 弾は射出された方向にまっすぐ飛んでいく

  • 弾は発射されてから5秒経つと消える

  • fpsが変わっても速度は一定

関数の役割も一応おさらいしておこう。

  • Initialize関数:弾が生成されたときに呼ばれる関数。モデルの初期化や衝突判定の初期化などを行っていく

  • Update関数:弾の動きを表す関数。指定された方向にまっすぐ進んでいき、指定秒数経つと消えるプログラムもここで書く

  • Render関数:弾の描画を行う関数。モデルの描画を行う

  • Finalize関数:終了処理を記述する関数。今回はやることなし

  • Boot関数:弾が発射されるタイミングで呼ばれる関数。座標の初期化、方向の初期化、秒数タイマーの初期化などを行う。アクティブフラグもここでオンになる

  • SetPosition関数:座標を設定する関数。m_positionを設定するほか、モデルや衝突判定の座標も設定する

それでは実際に書いてみてくれ!
実際に実装してEnemy.cppでUpdate関数にそのままShot関数を呼ぶようにするとこんな感じになる。

弾を吐き出し続ける敵

敵をプレイヤー目掛けて移動させる

プレイヤーのポインタを敵に渡す

まずはプレイヤーの座標を知らないとどこに移動すればいいのかわからんのでポインタでプレイヤーの情報を受け取れるようにしよう。それでは、何度目かのポインタ渡しです。頑張ってみて

敵の移動ベクトルを作成する

それではまずプレイヤーに向かって移動するとのことで、移動ベクトルを作成していこう。以下のコードをEnemy.cppのUpdate関数に記述してくれ。

	//	プレイヤーに向けての方向ベクトルを作成
	SimpleMath::Vector3 moveDirection = m_pPlayer->GetPosition() - m_position;
	moveDirection.y = 0.0f;
	moveDirection.Normalize();
	
	//	移動
	Move(moveDirection * MOVE_SPEED, deltaTime);

これで移動ベクトルをMove関数に送ることができた。

Move関数の中身を書く

それでは実際に書いてみよう。プレイヤーのMove関数とほぼ同じで手順は以下の通りだ。

  1. 外積を用いて、移動したい方向へと旋回する回転の軸を求める

  2. 内積とアークコサイン用いて、移動したい方向へと旋回するのに必要な回転の残りの角度を求める。

  3. 内積とアークコサインで弾き出した残りの角度と、旋回角度を比較して小さい値をこのフレームの旋回角度とする。

  4. 旋回軸と旋回角度を用いてクォータニオンを生成する。

  5. モデルからクォータニオン(回転)を取り出して、生成したクォータニオンと掛け合わせる。

  6. 今向いている方向が変わったので再計算を行い、向ている方向を更新する。

これらをきちんと実装すると以下のようになる。

今回はかなり振り返りがかなり多かったと思う。プログラムは反復練習が基本なので頑張ってね。

この記事が気に入ったらサポートをしてみませんか?