Cafe capybara TECH-BLOG

PHPな会社でゴリゴリしてるあかいいぬの技術ブログです

FPSゲームの作り方.1 一人称視点の実装

ここ数年、ずっとFPSゲームを作ったり作ってなかったりします。全然完成しないのは、色々機能をつけたがりな人だからです。

で、技術的なものはほぼ全部インターネットから取得しているのですが、なぜかFPSゲームの作り方については全くといっていいほど書かれていません。

そこで、自分が獲得したFPSゲームの作り方を他の方に向けて紹介していきます。

C#+XNA4.0による解説ですが、読み替えれば他の環境にも対応出来ると思います。

一人称視点を実装する

ともあれ、まずは3D空間を動き回れるようになるのが一番最初になります。

3D空間を形成するには、空間内の三次元座標をウィンドウの二次元座標に変換する必要があります。それを担うのが「Projection(射影)」行列です。

行列というのは、まあなんか数学的に超便利な数字の集まりです。ワシも数学はてんでダメなので、雰囲気だけ覚えていればなんとかなります。

それともう一つ、空間内のどこからどこを見ているか、つまり視点を表す「View(ビュー)」行列というものも必要です。

絶対座標で示されたポリゴンの頂点座標を、視点からの相対座標に変換するのがこの行列の役目です。

この行列がないと、いくら操作してもカメラは{X:0, Y:0, Z:0}の場所から-Z側を見ているだけになってしまいます。

この2つの行列を、入力に従って取得出来れば一人称視点で動きまわることが出来ます。

今回は、WASDで視点位置の移動、マウス操作で視点の回転、Escapeキーでゲームの終了が出来ます。

では、実際のコードを見てみましょう。

(Game1.cs)
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace WindowsGame1
{
	public class Game1 : Microsoft.Xna.Framework.Game
	{
		GraphicsDeviceManager graphics;
		float _yaw;
		float _pitch;
		Vector3 _position;
		Matrix _view;
		Matrix _projection;

		public Game1()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
		}

		protected override void Update(GameTime gameTime)
		{
			// ■■ここから重要■■

			// マウスが画面中央からどれだけ移動したかを取得し、値を変化させる
			// YawはY軸を中心とした横回転
			_yaw -= (Mouse.GetState().X - GraphicsDevice.Viewport.Width / 2) / 1000f;
			// PitchはX軸を中心とした縦回転
			_pitch -= (Mouse.GetState().Y - GraphicsDevice.Viewport.Height / 2) / 1000f;

			// YawとPitchから前方の座標と左側、上側の座標を取得
			Vector3 forward =
				Vector3.TransformNormal(Vector3.Forward,
				Matrix.CreateFromYawPitchRoll(_yaw, _pitch, 0));
			Vector3 left =
				Vector3.TransformNormal(Vector3.Left,
				Matrix.CreateFromYawPitchRoll(_yaw, _pitch, 0));
			Vector3 up =
				Vector3.TransformNormal(Vector3.Up,
				Matrix.CreateFromYawPitchRoll(_yaw, _pitch, 0));

			// マウスを画面の中心に置く
			Mouse.SetPosition(
				GraphicsDevice.Viewport.Width / 2,
				GraphicsDevice.Viewport.Height / 2);

			// キー入力情報を取得
			KeyboardState key = Keyboard.GetState();
			if (key.IsKeyDown(Keys.W))
				_position += forward;
			if (key.IsKeyDown(Keys.S))
				_position -= forward;
			if (key.IsKeyDown(Keys.A))
				_position += left;
			if (key.IsKeyDown(Keys.D))
				_position -= left;

			// ビュー行列を作成
			_view = Matrix.CreateLookAt(_position, _position + forward, up);

			// プロジェクション行列は事前に設定してもよい
			_projection = Matrix.CreatePerspectiveFieldOfView(
				MathHelper.ToRadians(80f),
				GraphicsDevice.Viewport.AspectRatio,
				1f, 10000f);

			// Escapeキーでゲームを終了する
			if(key.IsKeyDown(Keys.Escape))
				this.Exit();

			base.Update(gameTime);
		}

		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.CornflowerBlue);
			
			base.Draw(gameTime);
		}
	}
}

forward, left, upの取得がなんか最適化出来そうなんですけど、よくわかりません。数学はわからぬ。

ちなみにこれだけだとDrawメソッド内に何も書いていないので、もちろん何も表示されません。

それじゃあつまらないし、ちゃんと生成できているのかわからないので、地面を簡単に作りましょう。

(Game1.cs)
		// 変数宣言に追加
		BasicEffect _basicEffect;
		VertexPositionColor[] _ground;

// ~中略~


		protected override void LoadContent()
		{
			// エフェクトの作成
			_basicEffect = new BasicEffect(GraphicsDevice);
			_basicEffect.VertexColorEnabled = true;
			_basicEffect.TextureEnabled = false;
			_basicEffect.LightingEnabled = false;

			// 地面のポリゴン作成
			_ground = new VertexPositionColor[] {
				new VertexPositionColor(new Vector3(-100, -10, -100), Color.White),
				new VertexPositionColor(new Vector3( 100, -10, -100), Color.Red),
				new VertexPositionColor(new Vector3(-100, -10,  100), Color.Green),
				new VertexPositionColor(new Vector3( 100, -10,  100), Color.Blue),
			};
		}

// ~中略~

		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.CornflowerBlue);

			_basicEffect.View = _view;
			_basicEffect.Projection = _projection;
			_basicEffect.CurrentTechnique.Passes[0].Apply();

			GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
				PrimitiveType.TriangleStrip,
				_ground, 0, 2);
			
			base.Draw(gameTime);
		}

変数を追加して、LoadContentメソッド、Drawメソッドにちょっと追加しました。

f:id:expert88:20120320180807j:plain
実行したらこんな感じ。

ね、簡単でしょ?終了する時はEscapeキーです。

プログラミングが初めての方は、コピペでやるのではなく、一文字ずつ打って確かめましょう。この行の処理は何をしているのか、確認しつつ。

なので、完成したコードや実行データは配布しない予定です。


ちなみに、Pitch制限をかけていないので下方向や上方向にずっと向けていくと、世界がひっくりかえります。


次は「銃を撃つ」という処理について書く予定です。