Поделиться через


Создание класса GamePiece

Класс 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

Открытые свойства

Три из этих закрытых членов предоставляются с помощью открытых свойств. Свойства 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 использует три закрытых свойства.

  1. Timestamp — получает значение отметки времени, которая будет использоваться процессорами манипуляции и инерции.

  2. X — возвращает или задает координату X элемента игры. Если значение задано, настраивает границы, используемые для проверки попадания и места поворота для процессора манипуляции.

  3. 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

См. также

Основные понятия

Использование манипуляций и инерции в приложении XNA

Создание класса GamePieceCollection

Создание класса Game1

Другие ресурсы

Манипуляции и инерция