夏休み勉強会 2日目

2日目ではかけっこゲームの完成を目指していく。


プレイヤーを追従するカメラを作ろう

プレイヤーの座標を外部からでも取得できるようにしよう!

ということでまずはプレイヤーに座標を記憶する変数を作るところから始めよう。
Player.hに以下の変数を追加してね。

	//	座標
	DirectX::SimpleMath::Vector3 m_position;

そこからさらに座標に対するアクセサも作ろう
(アクセサは1行であれば.hに書いても許されることが多いよ。)

public:

	//	座標を取得
	const DirectX::SimpleMath::Vector3& GetPosition() const { return m_position; }

public:

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

それではPlayer.cppに移ってSetPosition関数の実装を行おう!

/// <summary>
///	座標を設定
/// </summary>
/// <param name="position">座標</param>
void Player::SetPosition(const DirectX::SimpleMath::Vector3& position)
{
	m_position = position;

	//	モデルの座標を設定
	m_model.SetPosition(position);
}

SetPositionではついでにモデルの座標も設定するようにしている。
こうすることにより、座標の連携を取りやすくなりバグが減るのである。

人によっては嫌な場合もあると思うがそういう人はDevelop::Model3Dの改造を行い自分に合ったものにしてほしい。

1つ捕捉を、「モデルにも座標変数があるならそれを使えばいいのでは?」という疑問が浮かんだのではないだろうか?なぜわざわざ新しく座標変数を作るのか、だが答えはモデルの座標が必ずしもプレイヤーの座標とは限らないからである。どういうことかというと、例えば今回のプレイヤーの座標はモデルの足元に設定されている。ここでもし、モデルの中心座標がモデルの丁度ど真ん中だとしたらどうだろうか、設定したい座標とモデルの中心座標が違うため、場合によっては座標を設定するとモデルが地面に埋まるなんてこともある。その事故を防ぐために別々の座標にしているのだ。

ではここで実際にプログラムを実行してみよう。
ここでは前回と変わらない画面になれば成功だ。

前回と何も変わらない画面

次にプレイヤーの開始座標とモデルのスケールを設定してほしい。そのままPlayer.cppのInitialize関数に以下のコードを記述してね。

	//	モデルのサイズ変更
	m_model.SetScale(MODEL_SCALE);

	//	開始地点を設定
	SetPosition(START_POSITION);

ここでまた実行してみよう。プレイヤーが映っていなければ成功

プレイヤーが消えた画面

まぁそらそうだ、プレイヤーの座標を(0, 0, 60)に設置しているのでそうなるのは必然である。では実際にプレイヤーが映るようにカメラを追従させるコードを書いていこう。

カメラクラスの仮組

まずはPlaySceneCamera.hに行って以下のコードを記述してね。

#pragma once

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

/// <summary>
/// プレイシーンのカメラ
/// </summary>
class PlaySceneCamera : public Develop::CameraObject
{
public:

	//	カメラのレンズ座標
	static constexpr DirectX::SimpleMath::Vector3 CAMERA_EYE	= { 2.0f, 2.0f, -5.0f };
	//	カメラのターゲット座標
	static constexpr DirectX::SimpleMath::Vector3 CAMERA_TARGET = { 0.0f, 0.0f,  0.0f };
	//	カメラの姿勢ベクトル
	static constexpr DirectX::SimpleMath::Vector3 CAMERA_UP		= { 0.0f, 1.0f,  0.0f };
	
	//	座標制限
	static constexpr float POSITION_Z_MIN = -60.0f;

public:

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

	//	開始関数
	void Initialize();
	//	更新関数
	void Update();
};

では続いてPlaySceneCamera.cppも記述していこう。

#include "pch.h"
#include "PlaySceneCamera.h"

#include "../Screen.h"

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

/// <summary>
/// コンストラクタ
/// </summary>
PlaySceneCamera::PlaySceneCamera()
{
}

/// <summary>
/// 開始関数
/// </summary>
void PlaySceneCamera::Initialize()
{
}

/// <summary>
/// 更新関数
/// </summary>
void PlaySceneCamera::Update()
{
}

なぜ、Develop::CameraObjectを継承しているのか気になった人は多いのではないのだろうか。その理由だが、一言で言えば「そっちの方が楽だから」に落ち着く。Develop::CameraObjectを基底クラスにすることで、PlayerなどのRender関数で要求されているカメラをこの派生クラスでも代用できるようになり、複数のカメラを作ることがしやすくなるのだ。

実際に使用してみればわかるのでとりあえず先に進もう。
ではPlaySceneCamera.cppのInitialize関数に以下のコードを記述していこう。

	//	射影行列の作成
	CreateProjMatrix(Screen::SIZE_RECT);

では続いてUpdate関数にコードを書いていこう。

	SimpleMath::Vector3 eye		= CAMERA_EYE;	//	レンズ座標
	SimpleMath::Vector3 target	= CAMERA_TARGET;//	ターゲット座標
	SimpleMath::Vector3 up		= CAMERA_UP;	//	姿勢ベクトル

	//	追加座標
	SimpleMath::Vector3 addPosition = SimpleMath::Vector3::Zero;

	//	Z座標の制限
	addPosition.z = std::max(addPosition.z, POSITION_Z_MIN);

	//	各座標に加算
	eye		+= addPosition;
	target	+= addPosition;

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

addPosition変数はのちにプレイヤーの座標が入るの頭に入れておいてね。
では、実際にインスタンスを生成していこう。
PlayScene.hでDevelop::CameraObjectをPlaySceneCameraに変えてみよう。

	//	カメラ
	PlaySceneCamera m_camera;

続いてPlayScene.cppのInitialize関数でビュー行列と射影行列の作成を行っていたが、削除してカメラの開始関数を呼ぼう。

	//	カメラの初期化
	m_camera.Initialize();

では続いてカメラの更新関数をUpdate関数で呼ぼう。

	//	カメラの更新
	m_camera.Update();

では実際に起動してみよう。

奥にプレイヤーが見える成功画像

プレイヤーと連携を取る

では実際にプレイヤーと連携を取っていこう。
プレイヤーとの連携ではポインタを用いていく、そのため循環参照を防ぐため前方宣言を行おう。PlaySceneCamera.hで以下のコードを記述していく。

//	前方宣言
class Player;

そして、プレイヤーのポインタを格納する変数

private:

	//	プレイヤーのポインタ
	Player* m_pPlayer;

プレイヤーのポインタを設定できるアクセサを作ろう

public:

	//	プレイヤーのポインタを設定
	void SetPlayer(Player* pPlayer) { m_pPlayer = pPlayer; }

では、実際にこのポインタを使ってプレイヤーの座標を取得していこう。
前方宣言はあくまでそういうクラスがあるらしいと知らせてるだけなので実際にどんなクラスか知らせるためにインクルードを加える必要がある。
そのため、まずはPlaySceneCamera.cppにインクルードの追加。

#include "../Player/Player.h"

そして、Update関数に行き、addPositionに代入する座標をプレイヤーの座標に変更だ!

	//	追加座標
	SimpleMath::Vector3 addPosition = m_pPlayer->GetPosition();

ここで「できた―!」と言って実行するとエラーになるので気を抜くのはまだ早いぞ!
PlayScene.cppのInitialize関数でプレイヤーのポインタを設定してあげよう。

	//	プレイヤーのポインタを設定
	m_camera.SetPlayer(&m_player);

これが終わってようやく実装完了だ!実際に実行してみよう。

プレイヤーがばっちり写ってる画像

プレイヤーの本実装!

プレイヤーがカメラで映るようになったら本格的に実装を行おう!

プレイヤーの更新関数が呼ばれるようにする

プレイヤーの更新処理を書いていくので、そもそも呼ばれていなかったら意味がないという事でまずはそこから書いていく。
PlayScene.cppのUpdate関数に以下のコードを記述。

	//	フレーム間秒数の取得
	float deltaTime = static_cast<float>(
		m_commonResources->GetStepTimer()->GetElapsedSeconds()
	);

	//	プレイヤーの更新
	m_player.Update(deltaTime);

GetElapsedSecondはフレーム間の秒数を返してくれる関数だ。

プレイヤーの仕様

気を取り直して、まずはプレイヤーの仕様を確認していこう。

  • ZキーかXキーを押したら加速する

  • ZキーかXキーが押されていなかったら摩擦により減速する

  • プレイヤーの加速度には上限がある

  • プレイヤーの加速度はマイナスにはならない。

  • fpsが変わっても速度は一定にしてほしい。(deltaTimeを使えってこと)

この条件を満たした実装を行っていく。

プレイヤーの実装

ではまず、Player.hにて以下の定数を定義しよう。

	//	加速度
	static constexpr float MOVE_ACCELERATION = 50.0f;
	//	最大加速度
	static constexpr float MOVE_ACCELERATION_MAX = 50.0f;
	//	摩擦
	static constexpr float MOVE_FRICTION = 2.0f;

そして、加速度を保持する変数も追加しよう。

	//	加速度
	float m_acceleration;

次に、Player.cppのInitialize関数で変数の初期化も行おう。

	//	加速度の初期化
	m_acceleration = 0.0f;

それでは、キーの取得の部分の関数を記述していく、以下のコードをUpdate関数に記述してほしい。

	//	キーボードトラッカーを取得
	mylib::InputManager* pInputManager = m_commonResources->GetInputManager();
	const std::unique_ptr<Keyboard::KeyboardStateTracker>& kbTracker = pInputManager->GetKeyboardTracker();

	//	ZキーもしくはXキーが押されたら
	if (kbTracker->IsKeyPressed(Keyboard::Z) ||
		kbTracker->IsKeyPressed(Keyboard::X))
	{

	}
	//	押されなかったら
	else
	{

	}

では残りの部分は自分で考えて実装してみよう。(SetPositionで座標を変更するのを忘れずに!)
実装が終わるとこのようになる。

これでプレイヤーの挙動は完成だ!
それでは仕上げの項目へGO!

ゴールの設置と演出の追加

ゴールの設置を設置する

それではまずゴールを設置していこう。
Goal.hに移動し、以下のコードを記述してくれ!

#pragma once

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

//	前方宣言
class CommonResources;

/// <summary>
/// ゴール
/// </summary>
class Goal
{
private:

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

	//	画像のファイルパス
	static constexpr wchar_t TEXTURE_PATH[] = L"Resources/Textures/GoalText.dds";

	//	モデルのスケール
	static constexpr DirectX::SimpleMath::Vector3 MODEL_SCALE{ 0.9f, 0.5f, 0.9f };

	//	ゴール座標
	static constexpr DirectX::SimpleMath::Vector3 POSITION{ 0.0f, 0.0f, -60.0f };

	//	ゴールのテキスト画像の位置
	static constexpr DirectX::SimpleMath::Vector2 TEXTURE_POSITION{ Screen::CENTER_X, Screen::CENTER_Y };


public:

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

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

private:

	//	共通リソース
	CommonResources* m_commonResources;

	//	モデル
	Develop::Model3D m_model;
};

では続いてGoal.cppに移動し、以下のコードを記述していこう。

#include "pch.h"
#include "Goal.h"

#include "../CommonResources.h"

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

/// <summary>
/// コンストラクタ
/// </summary>
Goal::Goal()
	:
	m_commonResources(nullptr)
{
}

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

/// <summary>
/// 更新関数
/// </summary>
/// <param name="deltaTime">フレーム間秒数</param>
void Goal::Update(float deltaTime)
{
}

/// <summary>
/// 描画関数
/// </summary>
/// <param name="camera">カメラ</param>
void Goal::Render(const Develop::CameraObject& camera)
{
}

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

それでは実際にモデルを描画してみよう!
ということで実際にモデルの描画までをやってみてくれ!(プレイシーンでインスタンスを生成するのを忘れずに!)
実装が終わるときちんとゴールにモデルが立つはずだ!

ゴールが立ってる画像

ゴールの演出を作る

今のままではゴールしたのに少し寂しいのでゴールしたらテキストの画像か何かが表示されるようにしよう。
という事で、Goal.hに以下の変数を追加だ!

	//	ゴール画像のシェーダーリソースビュー
	Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_textureSRV;

	//	スプライトバッチ
	std::unique_ptr<DirectX::SpriteBatch> m_spriteBatch;

	//	画像の基準点
	DirectX::SimpleMath::Vector2 m_textureAnchor;

続いてGoal.cppのInitialize関数に以下の処理を記述しよう。

	//	デバイスの取得
	ID3D11Device* device = common->GetDeviceResources()->GetD3DDevice();

	//	テクスチャのリソース情報
	ID3D11Resource* pResource = nullptr;

	//	例外処理
	DX::ThrowIfFailed
	(
		//	シェーダーリソースビューを読み込む
		DirectX::CreateDDSTextureFromFile
		(
			device		,
			TEXTURE_PATH,
			&pResource	,
			m_textureSRV.ReleaseAndGetAddressOf()
		)
	);

	//	リソースをテクスチャ2D型に型変換
	ID3D11Texture2D* pTexture = static_cast<ID3D11Texture2D*>(pResource);

	//	テクスチャのフォーマット情報を取得
	D3D11_TEXTURE2D_DESC texDesc;
	pTexture->GetDesc(&texDesc);

	//	テクスチャの幅と高さを取得
	m_textureAnchor.x = texDesc.Width	/ 2.0f;
	m_textureAnchor.y = texDesc.Height	/ 2.0f;

	//	テクスチャのリソース情報を解放する
	pResource->Release();

	//	スプライトバッチの作成
	m_spriteBatch = std::make_unique<SpriteBatch>(context);

それでは実際に描画してみよう。
Goal.cppのRender関数に以下のコードを記述してみよう。

	//	スプライトバッチの開始
	m_spriteBatch->Begin();
	//	画像の登録
	m_spriteBatch->Draw(
		m_textureSRV.Get()	,
		TEXTURE_POSITION	,
		nullptr				,
		Colors::White		,
		0.0f				,
		m_textureAnchor     ,
        1.0f 
	);
	//	スプライトバッチの終了(描画)
	m_spriteBatch->End();

これで実際に実行してみると、ゴールしてないけどゴール表示がなされるはずだ。

開幕から祝福を受けるプレイヤー

ということで、実際に画像を画面に描画することができた。
スプライトバッチの使い方などがわかってくれると嬉しい。ちなみに今回は使用しなかったが、Develop::Sprite2DクラスやDevelop::ResourceManagerのLoadDDS関数なども参考になるので是非見て欲しいものだ。

ID3D11ShaderResourceView : 2D画像を表現するものと、勘違いしがちだが厳密には違う。あくまでもViewと名の付くようにシェーダーリソース(画像データ)を指し示すものである。ただ実際のところはほとんど画像データのような扱いを受けるので、その認識でもゲームは作れる。
Microsoft::WRL::ComPtr : マイクロソフトが提供しているスマートポインタの1種。役割的にはstd::shared_ptrと似たような性質を持つが、明確に違う点がある。それはDirectXに特化したスマートポインタであるという点だ。基本的にID3D11~とついているものはオブジェクト破棄時に開放処理(Release関数を呼ぶ)を行う必要がある、しかしこのスマートポインタではその開放処理も担ってくれているのだ。便利だね~。
DirectX::SpriteBatch : 2Dスプライトの描画を行ってくれるクラス。Begin関数によりスプライト画像の受付を開始し、Draw関数で描画を行う画像の登録を行う。そして、End関数で一気に描画を行うという仕組みを取っている。本来であれば画像描画専用の管理クラスを作るとよいが、今回は動きや使い方を教えたいため、あえてゴールに記述している。

ゴールの判定を取る

それでは最後に、実際にゴールしたら表示する!みたいな流れをやっていこう。まずはGoal.hに以下の定数を追加してくれ。

	//	画像のスケール速度
	static constexpr float TEXTUER_SCALE_SPEED = 5.0f;
	//	画像の最大スケール
	static constexpr float TEXTUER_SCALE_MAX = 1.0f;

続いて変数の追加だ。

	//	画像の大きさ
	float m_scale;

ではGoal.cppに移動し、コンストラクタで初期化を行った後さらにInitialize関数で初期化を行おう

	//	スケールの初期化
	m_scale = 0.0f;

それではRender関数の画像の登録の部分を以下のように変更してくれ。

	//	画像の登録
	m_spriteBatch->Draw(
		m_textureSRV.Get()	,
		TEXTURE_POSITION	,
		nullptr				,
		Colors::White		,
		0.0f				,
		m_textureAnchor		,
		m_scale
	);

次にUpdate関数に以下のコードを記述してくれ。

	//	プレイヤーの座標を取得
	SimpleMath::Vector3 playerPosition = SimpleMath::Vector3::Zero;

	//	プレイヤーがゴール位置を超えているかどうか
	if (playerPosition.z < POSITION.z)
	{
		//	スケールの更新
		m_scale += TEXTUER_SCALE_SPEED * deltaTime;

		//	スケールが上限を越さないようにする
		m_scale = std::min(m_scale, TEXTUER_SCALE_MAX);
	}

ではここでまた、やって見て欲しいことがある。
実際にプレイヤーのポインタを受け取りきちんとゴールが作動するのか確かめてみてくれ。playrePosition変数にプレイヤーの座標を代入してあげることでこのプログラムは完成する。ここまでできたならできるはずだ!(ゴールのUpdate関数がプレイシーンで呼ばれてるかの確認を忘れずに!)

実際にこのように動いていればOK!

これにて「連打かけっこゲームを作ろう」制作終了!
おつかれさま!

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