Condividi tramite


Creazione della classe GamePiece

La classe GamePiece include tutta la funzionalità richiesta per caricare un'immagine di parte del gioco (Game Piece) di Microsoft XNA, di tenere traccia dello stato del mouse in relazione alla parte del gioco, di acquisire il mouse, di fornire l'elaborazione della modifica e dell'inerzia e di fornire funzionalità di rimbalzo quando la parte del gioco raggiunge i limiti del viewport.

Membri privati

All'inizio della classe GamePiece vengono dichiarati molti membri privati.

#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

Proprietà pubbliche

Tre di questi membri privati vengono esposti tramite le proprietà pubbliche. Le proprietà Scale e PieceColor consentono all'applicazione di specificare, rispettivamente, le proporzioni e il colore della parte. La proprietà Bounds viene esposta per consentire a una parte di utilizzare i limiti di un'altra parte per eseguire il proprio rendering, come nel caso in cui una parte deve essere sovrapposta a un'altra. Nel codice riportato di seguito viene illustrata la dichiarazione delle proprietà pubbliche.

#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

Costruttore di classe

Il costruttore per la classe GamePiece accetta i seguenti parametri:

  • Un tipo SpriteBatch (la pagina potrebbe essere in inglese). Il riferimento passato in questo caso viene assegnato al membro privato spriteBatch e viene utilizzato per accedere al metodo SpriteBatch.Draw (la pagina potrebbe essere in inglese) quando viene eseguito il rendering della parte del gioco. Inoltre, la proprietà GraphicsDevice (la pagina potrebbe essere in inglese) viene utilizzata per creare l'oggetto Texture (la pagina potrebbe essere in inglese) associato alla parte del gioco e per ottenere la dimensione del viewport per individuare il momento in cui la parte del gioco rileva un limite della finestra per fare in modo che la parte possa rimbalzare.

  • Stringa che specifica il nome del file dell'immagine da utilizzare per la parte del gioco.

Il costruttore consente anche di creare un oggetto ManipulationProcessor2D e un oggetto InertiaProcessor2D e stabilisce i gestori dell'evento per gli eventi corrispondenti.

Nell'esempio di codice riportato di seguito viene illustrato il costruttore per la classe 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

Acquisizione dell'input del mouse

Il metodo UpdateFromMouse è responsabile del rilevamento del momento in cui un pulsante del mouse viene premuto mentre il mouse è all'interno dei limiti della parte del gioco e del rilevamento del momento in cui il pulsante del mouse viene rilasciato.

Quando viene premuto il pulsante sinistro del mouse (mentre il mouse è all'interno dei limiti della parte), questo metodo imposta un flag per indicare che questa parte del gioco ha acquisito il mouse e che l'elaborazione della modifica ha inizio.

L'elaborazione della modifica viene iniziata creando una matrice di oggetti Manipulator2D e passandoli all'oggetto ManipulationProcessor2D. Questo fa in modo che il processore della modifica valuti i manipolatori (in questo caso un solo manipolatore) e generi eventi di modifica.

Inoltre, viene salvato il punto al quale si verifica il trascinamento. Questo elemento viene utilizzato in un secondo momento durante l'evento Delta per regolare i valori della conversione delta in modo che la parte del gioco si allinei dietro al punto del trascinamento.

Questo metodo, infine, restituisce lo stato mouse capture. Ciò consente all'oggetto GamePieceCollection di gestire l'acquisizione quando sono presenti più parti del gioco.

Nel codice riportato di seguito viene illustrato il metodo 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

Elaborazione delle modifiche

Quando inizia la modifica, viene generato l'evento Started. Il gestore per questo evento interrompe l'elaborazione dell'inerzia, se in corso, e imposta il flag processInertia su false.

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

Quando i valori associati alla modifica cambiano, viene generato l'evento Delta. Il gestore per questo evento utilizza i valori delta passati negli argomenti dell'evento per apportare modifiche ai valori della posizione e della rotazione della parte del gioco.

#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

Quando tutti i manipolatori (in questo caso un solo manipolatore) associati a una modifica vengono rimossi, il processore della modifica genera l'evento Completed. Il gestore per questo evento inizia l'elaborazione dell'inerzia impostando le velocità iniziali del processore a inerzia su quelle indicate dagli argomenti dell'evento e imposta il flag processInertia su 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

Elaborazione dell'inerzia

Mentre l'elaborazione dell'inerzia estrapola i nuovi valori per le velocità angolari e lineari, le coordinate della posizione (conversione) e la rotazione, viene generato l'evento Delta. Il gestore per questo evento utilizza i valori delta passati negli argomenti dell'evento per modificare la posizione e la rotazione della parte del gioco.

Se le nuove coordinate comportano lo spostamento della parte del gioco oltre i limiti del viewport viene invertita la velocità dell'elaborazione dell'inerzia. Ciò fa in modo che la parte del gioco rimbalzi in corrispondenza del limite del viewport che ha incontrato.

Non è possibile modificare le proprietà di un oggetto InertiaProcessor2D mentre è in corso l'estrapolazione. Pertanto, durante l'inversione della velocità X o Y, il gestore dell'evento interrompe innanzi tutto l'inerzia chiamando il metodo Complete(). Assegna quindi i nuovi valori della velocità iniziale perché siano i valori di velocità correnti (regolati per il comportamento di sponge) e imposta il flag processInertia su true.

Il codice riportato di seguito illustra il gestore dell'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

Al termine dell'elaborazione dell'inerzia il processore a inerzia genera l'evento Completed. Il gestore per questo evento imposta il flag processInertia su false.

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

Nessun elemento della logica presentato finora consente l'estrapolazione dell'inerzia vera e propria. Ciò viene portato a termine nel metodo ProcessInertia. Questo metodo, che viene chiamato ripetutamente dal ciclo di aggiornamento del gioco (il metodo Game.Update, la pagina potrebbe essere in inglese) verifica se il flag processInertia è impostato su true e, in tal caso, chiama il metodo Process(). La chiamata di questo metodo fa in modo che si verifichi l'estrapolazione e genera l'evento Delta.

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

Della parte del gioco non si esegue il rendering effettivo fino a che non è stato chiamato uno degli overload del metodo Draw. Il primo overload di questo metodo viene chiamato ripetutamente dal ciclo di disegno del gioco (il metodo Game.Draw, la pagina potrebbe essere in inglese). Ciò consente di eseguire il rendering della parte del gioco con la posizione corrente, la rotazione e i fattori di scala.

#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

Ulteriori proprietà

Tre proprietà private vengono utilizzate dalla classe GamePiece.

  1. Timestamp. Ottiene un valore del timestamp che deve essere utilizzato dai processori della modifica e a inerzia.

  2. X. Ottiene o imposta la coordinata X della parte del gioco. Durante l'impostazione, regolare i limiti utilizzati per l'hit test e il percorso di rotazione del processore della modifica.

  3. Y. Ottiene o imposta la coordinata Y della parte del gioco. Durante l'impostazione, regolare i limiti utilizzati per l'hit test e il percorso di rotazione del processore della modifica.

#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

Vedere anche

Concetti

Utilizzo delle modifiche e dell'inerzia in un'applicazione XNA

Creazione della classe GamePieceCollection

Creazione della classe Game1

Altre risorse

Modifiche e inerzia