Share via


Crear la clase GamePiece

La clase GamePiece encapsula toda la funcionalidad necesaria para cargar la imagen de una pieza de juego de Microsoft XNA, realizar el seguimiento del estado del mouse en relación con la pieza de juego, capturar el mouse, proporcionar el procesamiento de manipulación e inercia y proporcionar capacidad de rebote cuando la pieza de juego alcanza los límites del área de vista.

Miembros privados

En el comienzo de la clase GamePiece se declaran varios miembros privados.

#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

Propiedades públicas

Tres de estos miembros privados se exponen a través de propiedades públicas. Las propiedades Scale y PieceColor permiten a la aplicación especificar la escala y el color de la pieza, respectivamente. La propiedad Bounds se expone para que una pieza pueda utilizar los límites de otra para su representación, como cuando una pieza se debe superponer a otra. En el código siguiente se muestra la declaración de las propiedades públicas.

#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

Constructor de la clase

El constructor para la clase GamePiece acepta los siguientes parámetros:

  • Un tipo de SpriteBatch. La referencia pasada aquí se asigna al miembro privado spriteBatch y se utiliza para tener acceso al método SpriteBatch.Draw cuando la pieza de juego se representa. Además, la propiedad GraphicsDevice se utiliza para crear el objeto Texture asociado a la pieza de juego y para obtener el tamaño del área de vista con el fin de detectar cuándo la pieza de juego encuentra el límite de la ventana para poder rebotar.

  • Una cadena que especifica el nombre de archivo de la imagen que se utiliza para la pieza de juego.

El constructor también crea un objeto ManipulationProcessor2D y un objeto InertiaProcessor2D, y establece controladores de eventos para sus eventos.

En el código siguiente se muestra el constructor para la clase 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

Capturar la entrada del mouse

El método UpdateFromMouse se encarga de detectar cuándo se presiona un botón del mouse mientras el mouse está dentro de los límites de la pieza de juego, y de detectar cuándo se libera el botón del mouse.

Cuando se presiona el botón primario (mientras el mouse está dentro de los límites de la pieza), este método establece una marca para indicar que esta pieza de juego ha capturado el mouse, y comienza el procesamiento de manipulación.

El procesamiento de manipulación se inicia creando una matriz de objetos Manipulator2D y pasándolos al objeto ManipulationProcessor2D. Esto hace que el procesador de manipulación evalúe los manipuladores (en este caso un solo manipulador) y genere eventos de manipulación.

Además, se guarda el punto en el que se produce la operación de arrastrar. Esto se utiliza posteriormente durante el evento Delta para ajustar los valores de delta de traslación de modo que la pieza de juego gire en la línea detrás del punto de arrastre.

Finalmente, este método devuelve el estado de la captura del mouse. Esto permite al objeto GamePieceCollection administrar la captura cuando hay varias piezas de juego.

En el código siguiente se muestra el método 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

Procesar manipulaciones

Cuando empieza la manipulación, se genera el evento Started. El controlador de este evento detiene el procesamiento de inercia si se está produciendo, y establece la marca processInertia en false.

#region OnManipulationStarted
private void OnManipulationStarted(object sender, Manipulation2DStartedEventArgs e)
{
    if (inertiaProcessor.IsRunning)
    {
        inertiaProcessor.Complete(Timestamp);
    }
    processInertia = false;
}
#endregion

Cuando cambian los valores asociados a la manipulación, se genera el evento Delta. El controlador de este evento utiliza los valores de delta pasados en los argumentos del evento para realizar cambios en los valores de posición y giro de la pieza de juego.

#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

Cuando se quitan todos los manipuladores (en este caso, un solo manipulador) asociados a una manipulación, el procesador de manipulación genera el evento Completed. El controlador de este evento comienza el procesamiento de inercia estableciendo las velocidades iniciales del procesador de inercia en las notificadas por los argumentos del evento, y establece la marca processInertia en 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

Procesar la inercia

Cuando el procesamiento de inercia extrapola nuevos valores para las velocidades lineal y angular, las coordenadas de posición (traslación) y el giro, se genera el evento Delta. El controlador de este evento utiliza los valores de delta pasados en los argumentos del evento para modificar la posición y el giro de la pieza de juego.

Si las nuevas coordenadas hacen que la pieza de juego se mueva más allá de los límites del área de vista, la velocidad del procesamiento de inercia se invierte. Esto hace que la pieza de juego rebote con el límite del área de vista que ha encontrado.

No se pueden cambiar las propiedades de un objeto InertiaProcessor2D mientras se está ejecutando la extrapolación. Por consiguiente, cuando se invierte la velocidad X o la velocidad Y, el controlador de eventos detiene la inercia en primer lugar llamando al método Complete(). A continuación, asigna a los nuevos valores de velocidad inicial los valores de velocidad actual (ajustados para el comportamiento de esponja) y establece la marca processInertia en true.

En el código siguiente se muestra el controlador de eventos para el evento 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

Cuando el procesamiento de inercia se completa, el procesador de inercia genera el evento Completed. El controlador de este evento establece la marca processInertia en false.

#region OnInertiaCompleted
private void OnInertiaCompleted(object sender, Manipulation2DCompletedEventArgs e)
{
    processInertia = false;
}
#endregion

Ninguna parte de la lógica presentada hasta el momento hace que se produzca realmente la extrapolación de inercia. Esto se logra en el método ProcessInertia. Este método, al que se llama repetidamente desde el bucle de actualización del juego (el método Game.Update) comprueba si la marca processInertia está establecida en true, y en ese caso, llama al método Process(). Cuando se llama a este método se produce la extrapolación y se genera el evento Delta.

#region ProcessInertia
public void ProcessInertia()
{
    if (processInertia)
    {
        inertiaProcessor.Process(Timestamp);
    }
}
#endregion

La pieza de juego no se representa realmente hasta que se llama a una de las sobrecargas del método Draw. La primera sobrecarga de este método se invoca repetidamente desde el bucle de dibujo del juego (el método Game.Draw). De este modo la pieza de juego se representa con la posición, giro y factores de la escala actuales.

#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

Propiedades adicionales

La clase GamePiece utiliza tres propiedades privadas.

  1. Timestamp: Obtiene un valor de marca de tiempo para su utilización por los procesadores de manipulación e inercia.

  2. X: Obtiene o establece la coordenada X de la pieza de juego. Cuando se establece, ajusta los límites utilizados para la prueba de posicionamiento y la ubicación de pivote del procesador de manipulación.

  3. Y: Obtiene o establece la coordenada Y de la pieza de juego. Cuando se establece, ajusta los límites utilizados para la prueba de posicionamiento y la ubicación de pivote del procesador de manipulación.

#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

Vea también

Conceptos

Usar manipulaciones e inercia en una aplicación XNA

Crear la clase GamePieceCollection

Crear la clase Game1

Otros recursos

Manipulaciones e inercia