GamePiece 클래스 만들기
GamePiece 클래스는 Microsoft XNA 게임 피스(game piece) 이미지를 로드하고, 게임 피스와 관련하여 마우스 상태를 추적하고, 마우스를 캡처하고, 조작 및 관성 처리를 제공하고, 게임 피스가 뷰포트의 제한에 도달할 때 바운스 기능을 제공하는 데 필요한 모든 기능을 캡슐화합니다.
전용 멤버
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
Public 속성
이 중 세 개의 전용 멤버는 public 속성을 통해 노출됩니다. Scale 및 PieceColor 속성은 각각 응용 프로그램이 게임 피스의 배율과 색을 지정할 수 있도록 해줍니다. Bounds 속성은 한 게임 피스가 다른 게임 피스와 겹쳐야 할 때 해당 게임 피스가 다른 게임 피스의 경계를 사용하여 자체적으로 렌더링될 수 있도록 노출됩니다. 다음 코드는 public 속성의 선언을 보여 줍니다.
#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() 메서드를 호출하여 관성을 중지합니다. 그런 다음 새 초기 속도 값을 할당하여 현재 속도 값(sponge 동작에 맞춰 조정됨)으로 만들고, 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 메서드 오버로드 중 하나가 호출될 때까지 실제로 렌더링되지 않습니다. 이 메서드의 첫 번째 오버로드는 게임 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 클래스에 의해 사용됩니다.
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