Spieleditor - Wenn Sie auf den Bildschirm klicken, wie identifizieren Sie, auf welches Objekt geklickt wird?

7

Ich versuche, einen Spieleditor zu erstellen, der derzeit nur verschiedene Arten von Formen und dergleichen platziert. Ich mache das in Windows Forms, während ich 3D mit XNA zeichne. Wenn ich also ein paar Formen auf dem Bildschirm habe und auf den Bildschirm klicke, möchte ich in der Lage sein, "welches" dieser Objekte zu identifizieren, auf die Sie geklickt haben.

Was ist die beste Methode dafür? Da zwei Objekte hintereinander liegen, sollte es in der Lage sein, das vordere und nicht das dahinter liegende zu erkennen, und auch wenn ich die Kamera drehe und auf das dahinterliegende klicke, sollte es es identifizieren und nicht das erste.

Gibt es dafür kluge Wege?

Deukalion
quelle
6
Das Bestimmen, auf welches Objekt geklickt wurde, wird normalerweise als "Kommissionieren" bezeichnet. Einige Google-Suchanfragen nach "xna picking" sollten nützliche Ergebnisse liefern.
Gyan alias Gary Buyn

Antworten:

2

In diesem Video zeige ich, wie es mit Code gemacht wird ... die Projektion / Ansicht ist isometrisch, aber alle Berechnungen werden in 3D durchgeführt ...

http://www.youtube.com/watch?v=axHp1f3RlHM

Wenn ich den Mausstrahl berechne, überprüfe ich, ob eine Ebene kollidiert ... aber das Verfahren ist analog, um eine Liste von Begrenzungskugeln zu überprüfen, die Ihren Objekten zugeordnet sind.

Der Algorithmus sollte folgendermaßen aussehen:

1. Calculate mouse ray
2. for every object
3.     check if ray intersecs with it
4.         if does then save the object and the distance in a sorted list by distance
5. choose from sorted list the nearest object.
Blau
quelle
Ihr Beispiel war so ziemlich das, wonach ich suche, vielleicht nicht wie isometrisch. Wie rendern Sie dieses Raster und die Gliederung eines Würfels? Benötigen Sie so etwas, um zu testen, wo sich die Maus im 3D-Raum befindet.
Deukalion
Ich benutze eine eigene Klasse, um Linien zu zeichnen ... ist einfach durch diesen Satz zu implementieren GraphicsDevice.DrawUserPrimitives <VertexPositionColor> (PrimitiveType.LineList, data.Vertices, 0, data.Index / 2);
Blau
Ja, ich habe etwas Ähnliches ausprobiert, aber ich kann es nicht zum Laufen bringen. Ich benutze dafür eine mathematische Methode, aber mein Verständnis der Methode ist nicht das, was ich denke. Meine Frage dazu: gamedev.stackexchange.com/questions/31424/…
Deukalion
Hier ist der Code zum Zeichnen pastebin.com/YpCKAHcF ist einfach ... nur vertikale und horizontale Linien zeichnen
Blau
Sie rufen DrawLine3D auf, aber es gibt keinen Code dafür. Ist es nur DrawUserPrimitives (LineList, Data.Vertices, 0, data.Index / 2)? data.Index / 2 ist was?
Deukalion
9

Das erste, was Sie verstehen müssen, ist die Umwandlung von 3D-Punkten in 2D-Bildschirmkoordinaten und wieder zurück. Hier sind einige Antworten, die ich zu diesem Thema gegeben habe. Grundlegendes zu 3D zu Bildschirm , 3D zu 2D .

Um von Ihrer Mauskoordinate in 2D zu 3D zu gelangen, müssen Sie eine 3D-zu-2D-Transformation rückwärts durchführen. Das ist ziemlich einfach.

Ein Objekt / eine lokale Koordinate wird in den Bildschirmraum umgewandelt, indem es mit der Weltmatrix multipliziert wird (Weltkoordinate ermitteln), dann mit der Ansichtsmatrix (Weltkoordinate relativ zur Kamera erstellen) und dann mit der Projektionsmatrix (Bildschirmkoordinate ermitteln). .

Dies rückwärts zu tun ist einfach. Invertieren Sie alle Matrizen. Dann multiplizieren Sie mit der invertierten Projektion und dann mit der invertierten Ansichtsmatrix. Jetzt haben Sie keine 3D-Koordinate mehr für Ihre Maus. Der Grund dafür ist, dass Ihnen eine Datendimension fehlte. Was Sie also haben, ist stattdessen ein Strahl. Aber das ist gut. Der Strahl hat eine definierende Koordinate und zeigt in die gleiche Richtung wie der Kamerablick auf den Vektor. Möglicherweise stellen Sie auch fest, dass Sie die Weltmatrix für kein Objekt invertieren müssen. Das liegt nur daran, dass Sie den Strahl nicht wirklich im Objekt- / lokalen Raum haben möchten. Es hat keinen Sinn. Wahrscheinlich haben Sie Ihre Kollisionsvolumina auch im Weltraum definiert.

Jetzt müssen Sie nur noch den Schnittpunkt von Kreisen, Kästchen und Polystrahlen testen, um herauszufinden, ob sich der Strahl mit einem Objekt schneidet.

Suchen Sie alle Objekte, die sich schneiden, und führen Sie eine Liste. Berechnen Sie dann den Abstand jedes Objekts von der Kamera. Diejenige, die der Kamera am nächsten ist, ist diejenige, die der Benutzer auswählen wollte.

Hier ist der Code, den Sie dazu in XNA benötigen würden. Diese Lösung sollte auch für andere Arten von Projekten funktionieren (solange Sie sie zuerst konvertieren). Wenn Sie nur 2D-Sprites rendern, verwenden Sie wahrscheinlich keine Projektionsmatrix. Geben Sie der Funktion einfach die Matrix.Identity.

using System;
using Microsoft.Xna.Framework;

namespace FluxPrototype
{
    /// <summary>
    /// Contains functions useful in working with XNA Vectors.
    /// </summary>
    static class VectorHelper
    {
        /// <summary>
        /// Converts a Vector2 position into a 3D ray.
        /// </summary>
        /// <param name="Position">The 2D position.</param>
        /// <param name="View">The view matrix for the camera.</param>
        /// <param name="Projection">The projection matrix for the camera.</param>
        /// <param name="Point">The returned point defining part of the 3D ray.</param>
        /// <param name="Direction">The direction of the 3D ray.</param>
        public static void Unproject(Vector2 Position, Matrix View, Matrix Projection, out Vector3 Point, out Vector3 Direction)
        {
            if (Position == null)
                Position = Vector2.Zero;

            // Create two 3D points from the position. The first one we will return.
            Point = new Vector3(Position, 0);
            Vector3 Point2 = new Vector3(Position, 1);

            // Transform the points.
            Matrix InvertedProjection = Matrix.Invert(Projection);
            Point = Vector3.Transform(Point, InvertedProjection);
            Point2 = Vector3.Transform(Point2, InvertedProjection);

            Matrix InvertedView = Matrix.Invert(View);
            Point = Vector3.Transform(Point, InvertedView);
            Point2 = Vector3.Transform(Point2, InvertedView);

            // Use the difference between the points to define the ray direction.
            // This will only be the camera direction if the view frustum is orthographic.
            Direction = (Point2 - Point);
            Direction.Normalize();
        }
    }
}
OriginalDaemon
quelle
OK. Ich werde es untersuchen. Möchten Sie erklären, wie Matrizen invertiert werden? Ich habe keine Ahnung wie.
Deukalion
1
Matrix InvertedView = Matrix.Invert (Ansicht);
OriginalDaemon
Funktioniert diese Methode beim Platzieren von Inhalten im Editor? Da ist das "wichtiger" als sie auszuwählen. Wenn ich dem Bildschirm einen Würfel hinzufügen möchte, klicke ich auf die gewünschte Stelle. Unterstützt diese Methode dies auch?
Deukalion
Dies ist soweit ich gekommen bin und ich bin nicht sicher, ob es richtig ist: Vector2 screenPosition = new Vector2 (PointToClient (MousePosition) .X, PointToClient (MousePosition) .Y); Matrix m = screenPosition * matrix.Invert (Camera.MatrixWorld) * Matrix.Invert (Camera.MatrixView) * Matrix.Invert (Camera.MatrixWorld);
Deukalion
... und ich erstelle meine Matrizen (in meiner Kameraklasse) folgendermaßen: matrix_world = Matrix.CreateRotationZ (MathHelper.ToRadians (world_rotation.Z)) * Matrix.CreateRotationY (MathHelper.ToRadians (world_rotation.Y)) * Matrix.CreateRotationX (MathHelper.ToRadians (world_rotation.X)) * Matrix.CreateTranslation (world_location) * Matrix.CreateScale (world_zoom); matrix_view = Matrix.CreateLookAt (view_position, view_lookat, Vector3.Up); matrix_projection = Matrix.CreatePerspectiveFieldOfView (Projektionsfeld der Ansicht, Projektionsaspektratio, Projektionsclipstart, Projektionsstipendium);
Deukalion
2

Normalerweise erstelle ich einfach ein Begrenzungsvolumen mit meiner Maus. Der Ursprung ist dort, wo ich geklickt habe. Führen Sie dann einen Schnittpunkttest durch, indem Sie alle Objekte in einer Schleife durchlaufen. Sortieren Sie dann die Liste, die Sie zurückerhalten, nach den gewünschten Kriterien und wählen Sie Element 0 aus.

Vaughan Hilts
quelle
Ich verstehe das. Aber wie mache ich mit dem Z-Wert? Oder Tiefe, denn wenn ich nur auf den Bildschirm klicke, erhalte ich eine X-, Y-Koordinate, die später einem Punkt in der 3D-Welt entsprechen kann, auf den ich klicke, aber den Tiefenwert, den ich nicht erhalte. Wie "nah" an der Kamera ist es oder wie weit weg?
Deukalion
1

Eine Idee, die ich überlegt, aber noch nicht ausprobiert habe:

Zeichnen Sie ein Offscreen-Bild genauso wie das reale Bild, aber keine Texturen usw. Zeichnen Sie einfach Ihre Objekte, jedes Objekt in einer anderen "Farbe", wobei die "Farbe" eigentlich nur eine Objektnummer ist. Um herauszufinden, was sich unter der Maus befindet, sehen Sie sich einfach die Farbe des Pixels auf dem alternativen Bild an.

Natürlich bedeutet dies viel verschwendete Arbeit, aber es ist die GPU, die dies tut, und nicht die CPU, und es ist sicher, dass die Dinge nach genau den gleichen Regeln gelöst werden.

Loren Pechtel
quelle