XNA GSE チュートリアル2 解説と課題攻略


さて、前回の続きです。前回追加したコードは重要なようなので、チュートリアルの記述を元に、何がどうなってるのかを解明していきましょう。

//ゲームパッドの状態を取得する
GamePadState currentState = GamePad.GetState( PlayerIndex.One );

ここではGamePadStateと言うオブジェクトとしてcurrentStateを作成して、そこにコントローラー1のパッド情報をGetStateで取得しています。
今回の場合はスティックトリガーに使うとのこと。

//左スティックを使ったモデルの回転
modelRotation -= currentState.ThumbSticks.Left.X * 0.10f;

GetStatecurrentStateに代入されたパッド状態のうち、ThumbSticks.Left.Xにより、左スティックのX軸(つまり左右)の動きに0.1かけたものをmodelRotationから引くというもの。
1/10にしているのはそのままだと回転が速すぎるからだそうです。
と言うわけで、* 0.10fを削ったり、0.01fにしたりして、その効果を確認してみてください。
また、何故加算じゃなく除算をしているかというと、そのまま加算すると入力した方向に回転するからです。
まぁ、分かるでしょうが分からないなら試しに+にして動かしてみてください。

//トリガーを引いた時の移動変化量に関する関数
Vector3 modelVelocityAdd = Vector3.Zero;
//回転をさせる時、どの方向に押し込んだのかを検出
modelVelocityAdd.X = -( float ) Math.Sin( modelRotation );modelVelocityAdd.Z = -( float ) Math.Cos(modelRotation);
//トリガーを引いたらどれだけ移動するか
modelVelocityAdd *= currentState.Triggers.Right;

ここではmodelVelocityAddのXとZにそれぞれmodelRotationのサインとコサインをマイナスしたものを代入しています。
これにより、回転の変化量を移動量に変換しているそうです。
一般的にXは左右、Zは前後の動きを指します。
マイナスで代入しているのはたぶん余弦定理やらなにやらだと思います。
もう忘れちゃったよ、そんな昔の話!
また、Triggers.Rightで左トリガーの押した量を数値に掛けています。

//最後に、移動変化量にこの移動量を加えるmodelVelocity += modelVelocityAdd;

最後に、パッドから受け取った移動量に関するデータを実際のモデルの移動変化量に加算して適用させ、動かします。つまり、この処理をしないと何を押そうが動かない、と。

//移動時に振動させる
GamePad.SetVibration(
PlayerIndex.One,
currentState.Triggers.Right,
currentState.Triggers.Right);

文字通り、振動させます。SetVibrationではまず振動させるデバイス、右を振動させる時の量、左を振動させる時の量を設定します。
ここでは、コントローラー1の右モーターと左モーターを右トリガーの押した分だけ振動させるという風になります。
試しにLeftとかやってみてください。
片方だけLeftとかRightにするのも面白いですよ。
左右のモーターの違いも確かめることができます。

//画面外にはみ出た場合、Aボタンを押すことにより、中心に戻す
if (currentState.Buttons.A == ButtonState.Pressed){
modelPosition = Vector3.Zero;
modelVelocity = Vector3.Zero;
modelRotation = 0.0f;}

これは見ての通りです。modelPositionmodelVelocityVector3.Zeroで初期化し、modelRotationを0.0fという
フロート値で初期化しています。これにより、画面中央に移動するはずです。

いかがだったでしょうか?チュートリアル2はこんな感じです。
さてさて、「アイデアの拡張」と題された、以下のような課題が書かれていました。

・見下ろし型のアーケードゲームを彷彿とさせる感じで、モデルを上から見下ろすように変更してみよう。
(ヒント:cameraPositionベクターを使おう。
注意事項は上か下かを正確にセットできないこと。
なぜなら、カメラのベクターは”上”のベクターが同じとは限らないから。)
・見ている方向に近づいたら、振動の大きさをもっと激しくしてみよう。
(ヒント:modelPositioncameraPosition間の距離を使おう。)
Keybordクラスを参照して、キーボードを使って宇宙船を操作してみよう。
(ヒント:Xbox 360にUSBキーボードを繋げることができるぞ。)

と書いてあります。
面白そうですね。
チュートリアル1では飛ばしたんですけど、やってみましょう。

まず、見下ろし型に視点を変更しましょう。
まず、そのままのコード。

//空間内におけるカメラ位置を設定
Vector3 cameraPosition = new Vector3(
0.0f,	//X軸
50.0f,	//Y軸
5000.0f	//Z軸
);
//カメラの上方を設定
Vector3 UpPosition = new Vector3(
0,	//X
1,	//Y
0	//Z
);

この2つのメソッドを変更するだけでできそうです。
UpPositionUpで定義しなかったのは、ある種正解ですね。
まず、上から見下ろすと言うことは、UpPositionY軸に垂直な方向から見る必要があります。
と言うことは、カメラの上に当たる方向はは、Z軸である必要が出てきます。
つまり、Yを0にして、Zを1にすることで、見下ろし型に変更できます。

さらに、cameraPositionを変更する必要があります。Z軸に原点から5000も離れていたら、そもそも宇宙船が表示されないので、50.0fに修正。Y軸に原点から50しか離
れていないとドアップになるので、9000.0f
らいに修正しましょう。コードは以下のようになり、実行した感じは下の写真のようになります。

//空間内におけるカメラ位置を設定
Vector3 cameraPosition = new Vector3(
0.0f,		//X軸
9000.0f,	//Y軸
50.0f		//Z軸
);
//カメラの上方を設定
Vector3 UpPosition = new Vector3(
0,	//X
0,	//Y
1	//Z
);

xna_20

なんだかゴキブリが這ってるような感じですね(笑。

また、ちょっと余談ですが、現状だと下向きに表示されると思いますが、UpPositionのZを-1にするとそのまま上向きになります。
が、ここで気になるのが同時に2つの値をいじった場合、それが加味されるのか?と言うこと。
じゃあやってみましょう。
Zは-1のままにして、Yを1にしてみましょう。
どうですか?変わらないですよね?
X、Y、Z順番に評価していって、一番最後に0以外の値があったポジションに対して、カメラの上方を決めるという感じかと思ったんですが、どうやらそうでもないようです。
まぁ、このあたりはそのうち解明されるでしょう・・・。
あと、何故Y軸のカメラ位置を9000でとったかというと、視界が10000までだからです。
キリがいいので9000にしました。10000でもギリギリ表示されると思うんですけど、カメラから10000を超えた時点でその部分は描画されません。
それは、Drawメソッドの一番最後の方で下記のように記述してあるからです。

effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),	//視野角45度
spectRatio,						//アスペクト比
1.0f,							//描画する一番近い距離
10000.0f						//描画する一番遠い距離
);

たぶん、カメラ位置を9500.0fにしても大丈夫だと思います。
また、カメラの最大描画距離を11000.0fに修正してみるのも解決法の一つです。
お次は画面との距離に応じて振動の強弱を付けるというもの。
これは良くありがちな演出ですね。
覚えておいて、損はないと思います。
ヒントにあるとおり、modelPositioncameraPositionを使ってなんとか乗り切ってみましょう。
これが以外とプログラム経験がほとんど無い自分にとっては難題でして、結構コードも長くなりました。
正解はもっと短いコードなんですよねぇ、きっと・・・。

//カメラ-モデル間の距離を求める
Vector3 distance = new Vector3(
Math.Abs(cameraPosition.Z - modelPosition.Z),
Math.Abs(cameraPosition.X - modelPosition.X),
0);
//バイブレーションの強度を設定する
float vibration_power = 0.0;
//Zの距離が5000以内かつXの距離が3000以内の場合、実行
if (distance.Z <= 5000 && distance.X <= 3000){
float distance_temp =
currentState.Triggers.Right *
(-distance.X / 3000.0f + 1.0f) *
(-distance.Z / 5000.0f + 1.0f);
vibration_power = distance_temp;
}
//移動時に振動させる
GamePad.SetVibration(
PlayerIndex.One,
vibration_power.Z,
vibration_power.X);

まず、距離を求めたいところなんですが、お恥ずかしい話、斜辺の求め方がどうしても分からず、結局Z軸とX軸上の差異しか使えませんでした。
角度さえ分かれば・・・。
で、この距離をまんまdistanceVector3で定義します。
また、Math.Absと言う絶対値を求めるクラスを使って距離の絶対値を求め、そこに突っ込みます。

振動を設定するために、vibration_powerfloatで定義して、ゼロで初期化します。
何故ここで代入しないかというと、distance3000、ないし5000で割っているからです。
例えばdistance.Zが0の時に振動が最大になるような式を組んでいるわけですが、5001でも最大に近い振動が出てしまいます。
そこで、XとZのおのおのの振動範囲内にある場合のみ振動を設定すればいいわけで、そこためにif文を挿入しました。

こうすることで、Xの-3000~3000とZの-5000~5000の範囲外では振動しません。
また、何故最大値が1.0fなのかというと、XNAのリファレンスによれば、バイブレーションの範囲は0.0~1.0だからです。

さらに、、GamePad.SetVibration内にあるcurrentState.Trigers.Rightvibration_powerと書き換えてください。
そうすると近づいた時にだけ振動し、離れていくと振動しなくなります。
もっと細かく振動させたい場合は10000とかそういう値で試してみてください。
その場合、if文の範囲の変更もお忘れ無く。

最後はキーボードによる操作を追加しましょう。
これを付け加えることにより、ゲームパッドを接続しなくても操作できるようになります。
パッドを持っていない方も多いかと思うので、結構有用なのではないでしょうか?
UpdateInputに以下のコードを追加します。

//キーボードの状態を取得
KeyboardState currentKeyState = Keyboard.GetState();
Vector3 model_vel_by_key = Vector3.Zero;
if (currentKeyState.IsKeyDown(Keys.Left))
modelRotation += 0.10f;
if (currentKeyState.IsKeyDown(Keys.Right))
modelRotation -= 0.10f;
model_vel_by_key.X = -(float)Math.Sin(modelRotation);
model_vel_by_key.Z = -(float)Math.Cos(modelRotation);
if (currentKeyState.IsKeyDown(Keys.Up)){
model_vel_key.Z *= 5.0f;
model_vel_key.X *= 5.0f;
modelVelocity += model_vel_by_key;
}
if (currentKeyState.IsKeyDown(Keys.Down)){
model_vel_key.Z *= -5.0f;
model_vel_key.X *= -5.0f;
modelVelocity += model_vel_by_key;
}

やっていることはパッドの時とあまり変わりません。
モデルの角度の-サインをかけることによって、X座標上における向きの変化を取得し、-コサインではZ軸上における向きの変化を取得していると言うことが、今さらながら分かりました。
これは便利ですね。

特定のキーを押したかを検出するために、if文にKeybord.Getstate.IsKeyDown(Keys.○○)を挿入して、必要な動作を文の中に書き込みます。
キーボードは見ての通りデジタルデバイスなので、ある程度動作を補間した数を加算なり乗算します。
例えば今回は5.0fをかけることにしました。
あと、宇宙船がバックできるはずがないのですが、目標はあくまでF1的なゲームを作ることなので、普通にバックできるようにしてます。
方向キーの下を押せば普通にバックできます。

最後の課題、実はん・ぱか工房というサイトのXNA Game Studioメモというページからキーイベントを処理するというページを参考にさせていただきました。
リファレンスだけではどうしても理解に苦しんだので・・・。
例えばIsKeyDownとか。
カッコ内にKey.を入れてからキーを指定しないと動作しないと言うのが、このサイトを見るまで分からなかったと思います。
このブログより遙かに有益な情報が多いサイトなので、皆さんもご覧あれ。

お疲れさまです。
今回はここまでです。
ちょっと冗長的になっちゃいましたかね?
次はチュートリアル3に移ります。
XACT(エグザクト)と言う本物のゲーム開発者も使っている、音声ファイル編集ソフトを使ったプログラミングになるそうです。
更新はあくまでマッタリなので、そこんとこヨロシク。
あ、そうそう。
現時点でのGame1.csのソースコード内容と、実行ファイルを置いておきます。
XNA GSEを導入されている方なら動くと思いますが、それ以外の人で必要なソフトは.Net Framework 2.0、XNA GSE 1.0ランタイムです。
あと、動作条件がSharder Model 1.0(2.0以上が望ましい)とのことなので、古いGPUとかGPUが無いようなマシンだと動かないかもしれません。
まぁ、最近の3Dゲームが動くならハードウェア的に問題ないと思います。

アディオス。

実行ファイル:xna_test_program_2007_07_06

region Using Statementsusing System;
using System.Collections.Generic;using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace yu++_XNA_Game_01{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
}
//3Dモデルを描画
Model myModel; //モデルの宣言
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent) {
myModel = content.Load<Model>("Content\\Models\\p1_wedge");
//p1_wedgeをmyModelとして代入
}
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true) {
content.Unload();
}
}
//モデルの移動変化量を各フレーム上のモデル位置に反映させる
Vector3 modelVelocity = Vector3.Zero;
protected override void Update(GameTime gameTime)
{
//パッドのBackボタンを押すと終了
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//入力の受け付け
UpdateInput();
//現在のポジションに移動変化量を加算する
modelPosition += modelVelocity;
//操作後の移動変化量の減少度合いを記述
modelVelocity *= 0.95f;
base.Update(gameTime);
}
protected void UpdateInput()
{
//ゲームパッドの状態を取得する
GamePadState currentState = GamePad.GetState(PlayerIndex.One);
if (currentState.IsConnected) {
//左スティックを使ったモデルの回転
modelRotation -= currentState.ThumbSticks.Left.X * 0.10f;
//トリガーを引いた時の移動変化量に関する関数
Vector3 modelVelocityAdd = Vector3.Zero;
//回転をさせる時、どの方向に押し込んだのかを検出
modelVelocityAdd.X = -(float)Math.Sin(modelRotation);
modelVelocityAdd.Z = -(float)Math.Cos(modelRotation);
//トリガーを引いたらどれだけ移動するか
modelVelocityAdd *= currentState.Triggers.Right;
//最後に、移動変化量にこの移動量を加える
modelVelocity += modelVelocityAdd;
//カメラ-モデル間の距離を求める
Vector3 distance = new Vector3(
Math.Abs(cameraPosition.Z - modelPosition.Z),
Math.Abs(cameraPosition.X - modelPosition.X),
0);
//バイブレーションの強度を設定する
float vibration_power = 0.0f;
//Zの距離が5000以内、かつXの距離が3000以内の場合、実行
if (distance.Z <= 5000 && distance.X <= 3000) {
float distance_temp =
currentState.Triggers.Right *
(-distance.X / 3000.0f + 1.0f) *
(-distance.Z / 5000.0f + 1.0f);
vibration_power = distance_temp;
}
//移動時に振動させる
GamePad.SetVibration(
PlayerIndex.One,
vibration_power,
vibration_power);
//画面外にはみ出た場合、Aボタンを押すことにより、中心に戻す
if (currentState.Buttons.A == ButtonState.Pressed) {
modelPosition = Vector3.Zero;
modelVelocity = Vector3.Zero;
modelRotation = 0.0f;
}
}
//キーボードの状態を取得
KeyboardState currentKeyState = Keyboard.GetState();
Vector3 model_vel_key = Vector3.Zero;
if (currentKeyState.IsKeyDown(Keys.Left))
modelRotation += 0.10f;
if (currentKeyState.IsKeyDown(Keys.Right))
modelRotation -= 0.10f;
model_vel_key.X = -(float)Math.Sin(modelRotation);
model_vel_key.Z = -(float)Math.Cos(modelRotation);
if (currentKeyState.IsKeyDown(Keys.Up)) {
model_vel_key.Z *= 5.0f;
model_vel_key.X *= 5.0f;
modelVelocity += model_vel_key;
}
if (currentKeyState.IsKeyDown(Keys.Down)) {
model_vel_key.Z *= -5.0f;
model_vel_key.X *= -5.0f;
modelVelocity += model_vel_key;
}
}
//空間内におけるモデルの位置と角度を設定
Vector3 modelPosition = Vector3.Zero; //モデルの位置を初期化
float modelRotation = 0.0f;
//モデルの角度を初期化(フロート)
//空間内におけるカメラ位置を設定
Vector3 cameraPosition = new Vector3(
0.0f,		//X軸
50.0f,		//Y軸
-5000.0f	//Z軸
);
//カメラの上方を設定
Vector3 UpPosition = new Vector3(
0,	//X
1,	//Y
0	//Z
);
//投影されるアスペクト比を設定
float aspectRatio = 640.0f / 480.0f; //画面サイズ640x480を設定
protected override void Draw(GameTime gameTime) {
graphics.GraphicsDevice.Clear(Color.Green);
//変形させるための元をコピー
Matrix[] transforms = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(transforms);
//複数のメッシュを持ったモデルを描画し、それを繰り返す
foreach (ModelMesh mesh in myModel.Meshes) {
//myModelにBasicEffectを付与
foreach (BasicEffect effect in mesh.Effects) {
//BasicEffectのデフォルトライトを設定
effect.EnableDefaultLighting();
//BasicEffectによる位置座標変換の設定
effect.World =
transforms[mesh.ParentBone.Index] *
Matrix.CreateRotationY(modelRotation) *
Matrix.CreateTranslation(modelPosition);
//BasicEffectによるカメラの設定
effect.View = Matrix.CreateLookAt(
cameraPosition,	//カメラの位置座標
Vector3.Zero,	//カメラの向いている方向
UpPosition);	//カメラの上方向を指定
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),	//視野角45度
aspectRatio,					//アスペクト比
1.0f,							//描画する一番近い距離
10000.0f);						//描画する一番遠い距離
}
//エフェクト設定を避けてメッシュを描画
mesh.Draw();
}
}
}
}

Post to Twitter

, ,

  1. No comments yet.
(will not be published)