GamePiece クラスは、Microsoft XNA ゲーム ピース イメージの読み込み、ゲーム ピースに関連するマウスの状態の追跡、マウスのキャプチャ、操作処理と慣性処理の提供、ゲーム ピースがビューポートに達したときのバウンス機能の提供を行うために必要なすべての機能をカプセル化します。
プライベート メンバー
プライベート メンバーは、GamePiece クラスの先頭で宣言します。
#region PrivateMembers
// The sprite batch used for drawing the game piece.
private SpriteBatch spriteBatch;
// The position of the game piece.
private Vector2 position;
// The origin used for rendering the game piece.
// Gets set to be the center of the piece.
private Vector2 origin;
// The texture for the piece.
private Texture2D texture;
// The bounds of the game piece. Used for hit testing.
private Rectangle bounds;
// The rotation of the game piece, in radians.
private float rotation;
// The scale, in percentage of the actual image size. 1.0 = 100%.
private float scale;
// The view port, used to detect when to bounce.
private Viewport viewport;
// The manipulation processor for this game piece.
private ManipulationProcessor2D manipulationProcessor;
// The inertia processor for this game piece.
private InertiaProcessor2D inertiaProcessor;
// Flag to indicate that inertia processing should start or continue.
private bool processInertia;
// Flag to indicate whether this piece has captured the mouse.
private bool isMouseCaptured;
// Used during manipulation to indicate where the drag is occurring.
private System.Windows.Point dragPoint;
// The color of the game piece.
private Color pieceColor;
// Represents how spongy the walls act when the piece bounces.
// Must be <= 1.0 (if greater than 1.0, the piece will accelerate on bounce)
// 1.0 = no slowdown during a bounce.
// 0.0 (or less) = won't bounce.
private float spongeFactor = 0.925f;
#endregion
パブリック プロパティ
これらの 3 つのプライベート メンバーは、パブリック プロパティを介して公開されます。 Scale および PieceColor プロパティを使用して、アプリケーションはピースのスケールと色を指定できます。 Bounds プロパティは、あるピースを別のピースの境界を使用できるようにするものです (あるピースが別のピースにオーバーレイする場合など)。 次のコードでは、パブリック プロパティを宣言しています。
#region PublicProperties
public float Scale
{
get { return scale; }
set
{
scale = value;
bounds.Width = (int)(texture.Width * value);
bounds.Height = (int)(texture.Height * value);
// Setting X and Y (private properties) causes
// bounds.X and bounds.Y to adjust to the scale factor.
X = X;
Y = Y;
}
}
public Color PieceColor
{
get { return pieceColor; }
set { pieceColor = value; }
}
public Rectangle Bounds
{
get { return bounds; }
}
#endregion
クラス コンストラクター
GamePiece クラスのコンストラクターは、次のパラメーターを受け入れます。
SpriteBatch 型。 ここで渡される参照は、プライベート メンバーである spriteBatch に割り当てられ、ゲーム ピースが自身を描画するときに SpriteBatch.Draw メソッドにアクセスするために使用されます。 さらに、GraphicsDevice プロパティを使用して、ゲーム ピースに関連付ける Texture オブジェクトを作成し、ビューポートのサイズを取得して、ゲーム ピースがウィンドウの境界に到達したときはそれを検出してバウンスできるようにします。
ゲーム ピースに使用するイメージのファイル名を指定する文字列。
コンストラクターは ManipulationProcessor2D オブジェクトと InertiaProcessor2D オブジェクトも作成し、イベント用のイベント ハンドラーを構築します。
次のコードに、GamePiece クラスのコンストラクターを示します。
#region Constructor
public GamePiece(SpriteBatch spriteBatch, string fileName)
{
// For brevity, omitting checking of null parameters.
this.spriteBatch = spriteBatch;
// Get the texture from the specified file.
texture = Texture2D.FromFile(spriteBatch.GraphicsDevice, fileName);
// Initial position set to 0,0.
position = new Vector2(0);
// Set the origin to be the center of the texture.
origin = new Vector2(texture.Width / 2.0f, texture.Height / 2.0f);
// Set bounds. bounds.X and bounds.Y are set as the position or scale changes.
bounds = new Rectangle(0, 0, texture.Width, texture.Height);
// Create manipulation processor.
Manipulations2D enabledManipulations =
Manipulations2D.Translate | Manipulations2D.Rotate;
manipulationProcessor = new ManipulationProcessor2D(enabledManipulations);
manipulationProcessor.Pivot = new ManipulationPivot2D();
manipulationProcessor.Pivot.Radius = texture.Width / 2;
manipulationProcessor.MinimumScaleRotateRadius = 10.0f;
manipulationProcessor.Started += OnManipulationStarted;
manipulationProcessor.Delta += OnManipulationDelta;
manipulationProcessor.Completed += OnManipulationCompleted;
// Create inertia processor.
inertiaProcessor = new InertiaProcessor2D();
inertiaProcessor.Delta += OnInertiaDelta;
inertiaProcessor.Completed += OnInertiaCompleted;
inertiaProcessor.TranslationBehavior.DesiredDeceleration = 0.0001F;
inertiaProcessor.RotationBehavior.DesiredDeceleration = 1e-6F;
inertiaProcessor.ExpansionBehavior.DesiredDeceleration = 0.0001F;
// Save the view port. Used to detect when the piece needs to bounce.
viewport = spriteBatch.GraphicsDevice.Viewport;
// Set the piece in a random location.
Random random = new Random((int)Timestamp);
X = random.Next(viewport.Width);
Y = random.Next(viewport.Height);
// Set a random orientation.
rotation = (float)(random.NextDouble() * Math.PI * 2.0);
dragPoint = new System.Windows.Point(double.NaN, double.NaN);
pieceColor = Color.White;
// Set scale to normal (100%)
Scale = 1.0f;
}
#endregion
マウス入力のキャプチャ
マウスがゲーム ピースの境界内にあるときにマウス ボタンが押されたときと、マウス ボタンが離されたときの検出は、UpdateFromMouse メソッドによって行われます。
マウスの左ボタンが押されると (マウスがピースの境界内にある場合)、このメソッドはフラグを設定し、このゲーム ピースはマウスをキャプチャし、操作処理を開始したことを示します。
Manipulator2D オブジェクトの配列を作成し、それを ManipulationProcessor2D オブジェクトに渡すことで、操作処理が始まります。 これにより、操作プロセッサがマニピュレーターの評価を行い (この場合は単一マニピュレーター)、操作イベントを発生させます。
さらに、ドラッグが発生した位置が保存されます。 これは、後で Delta イベントで使用され、ゲーム ピースがドラッグ ポイントで境界内に収まるようにデルタ (平行移動) が調整されます。
最後に、このメソッドは、マウス キャプチャの状態を返します。 これにより、ゲーム ピースが複数存在する場合に、GamePieceCollection オブジェクトがキャプチャを管理できるようになります。
次のコードに、UpdateFromMouse メソッドを示します。
#region UpdateFromMouse
public bool UpdateFromMouse(MouseState mouseState)
{
if (mouseState.LeftButton == ButtonState.Released)
{
if (isMouseCaptured)
{
manipulationProcessor.CompleteManipulation(Timestamp);
}
isMouseCaptured = false;
}
if (isMouseCaptured ||
(mouseState.LeftButton == ButtonState.Pressed &&
bounds.Contains(mouseState.X, mouseState.Y)))
{
isMouseCaptured = true;
Manipulator2D[] manipulators = new Manipulator2D[]
{
new Manipulator2D(0, mouseState.X, mouseState.Y)
};
dragPoint.X = mouseState.X;
dragPoint.Y = mouseState.Y;
manipulationProcessor.ProcessManipulators(Timestamp, manipulators);
}
// If the right button is pressed, stop the piece and move it to the center.
if (mouseState.RightButton == ButtonState.Pressed)
{
processInertia = false;
X = viewport.Width / 2;
Y = viewport.Height / 2;
rotation = 0;
}
return isMouseCaptured;
}
#endregion
操作の処理
操作が始まると、Started イベントが発生します。 このイベントのハンドラーは、慣性処理が開始するとそれを停止し、processInertia フラグを false に設定します。
#region OnManipulationStarted
private void OnManipulationStarted(object sender, Manipulation2DStartedEventArgs e)
{
if (inertiaProcessor.IsRunning)
{
inertiaProcessor.Complete(Timestamp);
}
processInertia = false;
}
#endregion
操作に関連する値に変更が生じると、Delta イベントが発生します。 このイベントのハンドラーは、イベント引数で渡されたデルタ値を使用して、ゲーム ピースの位置の値と回転の値に変更を加えます。
#region OnManipulationDelta
private void OnManipulationDelta(object sender, Manipulation2DDeltaEventArgs e)
{
//// Adjust the position and rotation of the game piece.
float deltaX = e.Delta.TranslationX;
float deltaY = e.Delta.TranslationY;
if (dragPoint.X != double.NaN || dragPoint.Y != double.NaN)
{
// Single-manipulator-drag-rotate mode. Adjust for drag / rotation
System.Windows.Point center = new System.Windows.Point(position.X, position.Y);
System.Windows.Vector toCenter = center - dragPoint;
double sin = Math.Sin(e.Delta.Rotation);
double cos = Math.Cos(e.Delta.Rotation);
System.Windows.Vector rotatedToCenter =
new System.Windows.Vector(
toCenter.X * cos - toCenter.Y * sin,
toCenter.X * sin + toCenter.Y * cos);
System.Windows.Vector shift = rotatedToCenter - toCenter;
deltaX += (float)shift.X;
deltaY += (float)shift.Y;
}
X += deltaX;
Y += deltaY;
rotation += e.Delta.Rotation;
}
#endregion
操作に関連付けられたマニピュレーターのすべてが削除されると (この場合は単一マニピュレーター)、操作プロセッサは Completed イベントを発生させます。 このイベントのハンドラーは、慣性プロセッサの初期速度をイベント引数で指定された値に設定し、processInertia フラグを true に設定することで、慣性処理を開始します。
#region OnManipulationCompleted
private void OnManipulationCompleted(object sender, Manipulation2DCompletedEventArgs e)
{
inertiaProcessor.TranslationBehavior.InitialVelocityX = e.Velocities.LinearVelocityX;
inertiaProcessor.TranslationBehavior.InitialVelocityY = e.Velocities.LinearVelocityY;
inertiaProcessor.RotationBehavior.InitialVelocity = e.Velocities.AngularVelocity;
processInertia = true;
}
#endregion
慣性の処理
慣性処理は、角度および線形の速度、位置 (平行移動) 座標、回転の新しい値を推定するときに、Delta イベントを発生させます。 このイベントのハンドラーは、イベント引数で渡されたデルタ値を使用して、ゲーム ピースの位置の値と回転の値を変更します。
新しい座標を適用するとゲーム ピースがビューポートの境界を超えて移動することになる場合は、慣性処理の速度は反転します。 これにより、ゲーム ピースはビューポートの境界でバウンスされます。
外挿の実行中、InertiaProcessor2D オブジェクトのプロパティを変更することはできません。 そのため、X 方向または Y 方向の速度を反転する場合、イベント ハンドラーは、まず Complete() メソッドを呼び出して慣性を停止します。 そして、新たに初期の速度値を割り当てて現在の速度値とし (スポンジ動作用に調整した値)、processInertia フラグを true に設定します。
次のコードに、Delta イベントのイベント ハンドラーを示します。
#region OnInertiaDelta
private void OnInertiaDelta(object sender, Manipulation2DDeltaEventArgs e)
{
// Adjust the position of the game piece.
X += e.Delta.TranslationX;
Y += e.Delta.TranslationY;
rotation += e.Delta.Rotation;
// Check to see if the piece has hit the edge of the view port.
bool reverseX = false;
bool reverseY = false;
if (X > viewport.Width)
{
reverseX = true;
X = viewport.Width;
}
else if (X < viewport.X)
{
reverseX = true;
X = viewport.X;
}
if (Y > viewport.Height)
{
reverseY = true;
Y = viewport.Height;
}
else if (Y < viewport.Y)
{
reverseY = true;
Y = viewport.Y;
}
if (reverseX || reverseY)
{
// Get the current velocities, reversing as needed.
// If reversing, apply sponge factor to slow the piece slightly.
float velocityX = e.Velocities.LinearVelocityX * ((reverseX) ? -spongeFactor : 1.0f);
float velocityY = e.Velocities.LinearVelocityY * ((reverseY) ? -spongeFactor : 1.0f);
// Must stop inertia processing before changing parameters.
if (inertiaProcessor.IsRunning)
{
inertiaProcessor.Complete(Timestamp);
}
// Assign the new velocities.
inertiaProcessor.TranslationBehavior.InitialVelocityX = velocityX;
inertiaProcessor.TranslationBehavior.InitialVelocityY = velocityY;
// Set flag so that inertia processing will continue.
processInertia = true;
}
}
#endregion
慣性処理が完了すると、慣性プロセッサは Completed イベントを発生させます。 このイベントのイベント ハンドラーは、processInertia フラグを false に設定します。
#region OnInertiaCompleted
private void OnInertiaCompleted(object sender, Manipulation2DCompletedEventArgs e)
{
processInertia = false;
}
#endregion
慣性の外挿を実際に生じさせるようなロジックはありません。 これは、ProcessInertia メソッドによって実行されます。 このメソッドは、ゲームの更新ループ (Game.Update メソッド) で繰り返し呼び出されるもので、processInertia フラグが true に設定されているかどうかを確認し、これに該当する場合は Process() メソッドを呼び出します。 このメソッドを呼び出すと、外挿が行われ、Delta イベントが発生します。
#region ProcessInertia
public void ProcessInertia()
{
if (processInertia)
{
inertiaProcessor.Process(Timestamp);
}
}
#endregion
Draw メソッド オーバーロードが呼び出されるまで、ゲーム ピースは実際には表示されません。 このメソッドの最初のオーバーロードは、ゲームの描画ループ (Game.Draw メソッド) で繰り返し呼び出されます。 この場合、ゲーム ピースは現在の位置、回転、およびスケールのファクターを使用して描画されます。
#region Draw
public void Draw()
{
spriteBatch.Draw(
texture, position,
null, pieceColor, rotation,
origin, scale,
SpriteEffects.None, 1.0f);
}
public void Draw(Rectangle bounds)
{
spriteBatch.Draw(texture, bounds, pieceColor);
}
#endregion
その他のプロパティ
GamePiece クラスでは、3 つのプライベート プロパティが使用されます。
Timestamp – 操作および慣性のプロセッサで使用されるタイムスタンプ値を取得します。
X – ゲーム ピースの X 座標を取得または設定します。 設定するとき、ヒット テストで使用する境界と、操作プロセッサのピボット位置を調整します。
Y – ゲーム ピースの Y 座標を取得または設定します。 設定するとき、ヒット テストで使用する境界と、操作プロセッサのピボット位置を調整します。
#region PrivateProperties
private long Timestamp
{
get
{
// Get timestamp in 100-nanosecond units.
double nanosecondsPerTick = 1000000000.0 / System.Diagnostics.Stopwatch.Frequency;
return (long)(System.Diagnostics.Stopwatch.GetTimestamp() / nanosecondsPerTick / 100.0);
}
}
private float X
{
get { return position.X; }
set
{
position.X = value;
manipulationProcessor.Pivot.X = value;
bounds.X = (int)(position.X - (origin.X * scale));
}
}
private float Y
{
get { return position.Y; }
set
{
position.Y = value;
manipulationProcessor.Pivot.Y = value;
bounds.Y = (int)(position.Y - (origin.Y * scale));
}
}
#endregion