Freigeben über


Erstellen der GamePiece-Klasse

Die GamePiece-Klasse kapselt die gesamte Funktionalität, die für folgende Schritte erforderlich ist: Laden eines Microsoft XNA-Spielsteinbilds, Nachverfolgen des Mauszustands im Verhältnis zum Spielstein, Erfassen der Maus, Bereitstellen der Manipulations- und Trägheitsverarbeitung und Bereitstellen der Funktion zum Abprallen, wenn der Spielstein die Grenzen des Viewports erreicht.

Private Member

Im oberen Bereich der GamePiece-Klasse werden mehrere private Member deklariert.

#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

Öffentliche Eigenschaften

Drei dieser privaten Member werden über öffentliche Eigenschaften verfügbar gemacht. Die Eigenschaften Scale und PieceColor ermöglichen der Anwendung das Angeben der Skalierung und der Farbe des Spielsteins. Die Bounds-Eigenschaft wird verfügbar gemacht, damit ein Spielstein die Grenzen eines anderen Spielsteins zum Rendern verwenden kann, z. B. wenn ein Spielstein einen anderen überlagern soll. Der folgende Code zeigt die Deklaration der öffentlichen Eigenschaften.

#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

Klassenkonstruktor

Der Konstruktor für die GamePiece-Klasse akzeptiert die folgenden Parameter:

  • Einen SpriteBatch-Typ. Der hier übergebene Verweis wird dem privaten spriteBatch-Member zugewiesen und zum Zugreifen auf die SpriteBatch.Draw-Methode verwendet, wenn sich der Spielstein rendert. Außerdem wird mithilfe der GraphicsDevice-Eigenschaft das Texture-Objekt erstellt, das dem Spielstein zugeordnet ist, und die Größe des Viewports abgerufen. Auf diese Weise kann ermittelt werden, wann der Spielstein auf eine Fensterbegrenzung trifft und abprallt.

  • Eine Zeichenfolge, die den Dateinamen des Bilds angibt, das für den Spielstein verwendet werden soll.

Der Konstruktor erstellt auch ein ManipulationProcessor2D-Objekt und ein InertiaProcessor2D-Objekt und richtet für deren Ereignisse Ereignishandler ein.

Der folgende Code zeigt den Konstruktor für die GamePiece-Klasse.

#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

Erfassen der Mauseingabe

Die UpdateFromMouse-Methode erkennt, wenn eine Maustaste gedrückt wird, während sich die Maus innerhalb der Grenzen des Spielsteins befindet, und wenn die Maustaste losgelassen wird.

Wenn die linke Maustaste gedrückt gehalten wird (während sich die Maus innerhalb der Grenzen des Spielsteins befindet), gibt diese Methode mithilfe eines Flags an, dass dieser Spielstein die Maus erfasst hat, und startet die Manipulationsverarbeitung.

Die Manipulationsverarbeitung wird gestartet, indem ein Array mit Manipulator2D-Objekten erstellt und an das ManipulationProcessor2D-Objekt übergeben wird. Dies bewirkt, dass der Manipulationsprozessor die Manipulatoren (in diesem Fall ein einzelner Manipulator) auswertet und Manipulationsereignisse auslöst.

Außerdem wird der Punkt gespeichert, an dem das Ziehen auftritt. Dies wird später während des Delta-Ereignisses verwendet, um die Deltaübersetzungswerte anzupassen, damit der Spielstein hinter dem Ziehpunkt ausgerichtet wird.

Als Letztes gibt diese Methode den Zustand der Mausauswahl zurück. Das GamePieceCollection-Objekt kann den Auswahlvorgang auch dann verwalten, wenn mehrere Spielsteine vorhanden sind.

Der folgende Code zeigt die UpdateFromMouse-Methode.

#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

Verarbeiten von Manipulationen

Zu Beginn der Manipulation wird das Started-Ereignis ausgelöst. Der Handler für dieses Ereignis hält die Trägheitsverarbeitung an, falls diese aktiv ist, und legt das processInertia-Flag auf false fest.

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

Wenn sich die Werte ändern, die der Manipulationsänderung zugeordnet sind, wird das Delta-Ereignis ausgelöst. Der Handler für dieses Ereignis verwendet die Deltawerte, die in den Ereignisargumenten übergeben werden, um Änderungen an den Positions- und Drehungswerten des Spielsteins vorzunehmen.

#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

Wenn alle Manipulatoren (in diesem Fall nur ein Manipulator) entfernt werden, die einer Manipulation zugeordnet sind, löst der Manipulationsprozessor das Completed-Ereignis aus. Der Handler für dieses Ereignis startet die Trägheitsverarbeitung, indem er die ursprünglichen Geschwindigkeiten des Trägheitsprozessors auf die Geschwindigkeiten festlegt, die von den Ereignisargumenten gemeldet werden. Außerdem legt er das processInertia-Flag auf true fest.

#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

Verarbeiten der Trägheit

Während die Trägheitsverarbeitung neue Werte für Winkelfrequenzen und lineare Geschwindigkeiten, Positionskoordinaten (Übersetzung) und Drehungen extrapoliert, wird das Delta-Ereignis ausgelöst. Der Handler für dieses Ereignis verwendet die Deltawerte, die in den Ereignisargumenten übergeben werden, um Änderungen an der Position und Drehung des Spielsteins vorzunehmen.

Falls die neuen Koordinaten dazu führen, dass der Spielstein über die Grenzen des Viewports hinaus verschoben wird, wird die Geschwindigkeit der Trägheitsverarbeitung umgekehrt. Dies bewirkt, dass der Spielstein von der Viewportgrenze zurückspringt, auf die er gestoßen ist.

Sie können die Eigenschaften eines InertiaProcessor2D-Objekts nicht ändern, während es die Extrapolierung durchführt. Aus diesem Grund stoppt der Ereignishandler beim Umkehren der X- oder Y-Geschwindigkeit zuerst die Trägheit, indem er die Complete()-Methode aufruft. Anschließend weist er die neuen Anfangsgeschwindigkeitswerte als aktuelle Geschwindigkeitswerte zu (angepasst für "Schwammverhalten") und legt das processInertia-Flag auf true fest.

Der folgende Code zeigt den Ereignishandler für das Delta-Ereignis.

#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

Nachdem die Trägheitsverarbeitung abgeschlossen ist, löst der Trägheitsprozessor das Completed-Ereignis aus. Der Handler für dieses Ereignis legt das processInertia-Flag auf false fest.

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

Keine der bisher beschriebenen Logiken bewirkt das Auftreten der Trägheitsextrapolierung. Dies wird mithilfe der ProcessInertia-Methode erreicht. Diese Methode, die von der Aktualisierungsschleife des Spiels (Game.Update-Methode) wiederholt aufgerufen wird, überprüft, ob das processInertia-Flag auf true festgelegt ist. Wenn ja, wird die Process()-Methode aufgerufen. Das Aufrufen dieser Methode bewirkt, dass die Extrapolierung durchgeführt und das Delta-Ereignis ausgelöst wird.

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

Der Spielstein wird erst gerendert, wenn eine der Überladungen der Draw-Methode aufgerufen wird. Die erste Überladung dieser Methode wird wiederholt von der Ziehschleife des Spiels (Game.Draw-Methode) aufgerufen. Der Spielstein wird dann mit den aktuellen Positions-, Drehungs- und Skalierungsfaktoren gerendert.

#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

Zusätzliche Eigenschaften

Drei private Eigenschaften werden von der GamePiece-Klasse verwendet.

  1. Timestamp – Ruft einen Timestampwert ab, der von den Manipulations- und Trägheitsprozessoren verwendet werden soll.

  2. X – Ruft die X-Koordinate des Spielsteins ab bzw. legt diese fest. Beim Festlegen werden die Grenzen für Treffertests und die Pivotposition des Manipulationsprozessors angepasst.

  3. Y – Ruft die Y-Koordinate des Spielsteins ab bzw. legt diese fest. Beim Festlegen werden die Grenzen für Treffertests und die Pivotposition des Manipulationsprozessors angepasst.

#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

Siehe auch

Konzepte

Verwenden von Manipulationen und Trägheit in einer XNA-Anwendung

Erstellen der GamePieceCollection-Klasse

Erstellen der Game1-Klasse

Weitere Ressourcen

Manipulationen und Trägheit