In diesem Beitrag schauen wir uns den Bau einer Schleuder in Unreal Engine an, als Alternative zu typischen Waffen-Mechaniken, um ein Projektil abzufeuern. Eine Schleuder ist ein 2-händiges Werkzeug: Eine Hand hält und zielt, die andere greift und zieht das Projektil zurück, um die finale Zielrichtung und Abschussgeschwindigkeit zu bestimmen, und lässt es dann los, um es abzufeuern.

Tech Stack
Für diese Erkundung verwenden wir den folgenden Tech Stack:
Schleuder-Aufbau
Wir unterteilen die Schleuder-Implementierung in folgende Teile:
- Slingshot mesh - ein Y-förmiges Mesh, das gegriffen werden kann
- Projectile mesh - ein Kugel-Mesh, das gegriffen werden kann, wenn die Schleuder mit der anderen Hand gehalten wird
- Actor Blueprint - kombiniert die verschiedenen Komponenten und fügt Logik hinzu, z.B. zum Abfeuern eines Projektils
- Projectile grab/release handling - die Logik, um das Projektil zu bewegen, wenn es gegriffen wird, und es abzufeuern, wenn es losgelassen wird
- Sling Niagara system - zeigt das dynamische Gummiband zwischen dem Schleuder-Mesh und dem Projektil an, bevor das Projektil abgefeuert wird
- Aim Niagara system - zeigt einen Laserstrahl in die Richtung an, in die das Projektil abgefeuert wird, um beim Zielen zu helfen
- Physics Constraint - simuliert das Projektil, das im Gummiband hängt, um ihm etwas mehr Dynamik zu geben, während man sich bewegt
Slingshot Mesh
Jedes Y-förmige Mesh kann verwendet werden, um die Schleuder darzustellen. Falls kein solches Mesh verfügbar ist, können wir es einfach mit Unreal Engines Modeling Mode erstellen, indem wir folgende Schritte befolgen:
- Erstelle einen dünnen Zylinder für den I-Teil
- Erstelle einen größeren Zylinder und subtrahiere einen kleineren Kreis aus seiner Mitte, um eine O-Form zu erstellen
- Plane-cut die O-Form, um eine U-Form zu erstellen
- Platziere die U-Form auf dem I-Teil und merge sie, um die finale Y-Form zu erstellen
- Verschiebe den Pivot zu center-min-Z, damit er unten ist

Im Static Mesh Editor können wir einige Sockets hinzufügen, um die Position und Ausrichtung wichtiger Punkte zu markieren, die wir später im Blueprint verwenden werden:
Grip- hier wird die Hand einrasten, wenn die Schleuder gegriffen wird, damit sie immer in derselben Position/Ausrichtung gehalten wirdAim- hier platzieren wir das Aim Niagara SystemProjectile- hier platzieren wir das Projektil, wenn es nicht von der anderen Hand gehalten wirdSlingLeft- hier befestigen wir das Sling Niagara System auf der linken SeiteSlingRight- hier befestigen wir das Sling Niagara System auf der rechten Seite

Die Verwendung von Sockets macht es später einfacher, verschiedene Schleuder-Meshes mit demselben Blueprint zu verwenden.
Projectile Mesh
Für das Projektil-Mesh verwenden wir eine einfache Kugel mit dem Pivot in der Mitte.

Actor Blueprint
Wir erstellen ein neues Blueprint mit GrippableStaticMeshActor aus dem VR Expansion Plugin als Parent-Klasse, das bereits die VRGrip Interface implementiert und eine ausgezeichnete Basis für unsere Schleuder bietet:
- Wir konfigurieren die Actor-Eigenschaften
- Verwende das Schleuder-Mesh als Static Mesh in der Root-Komponente
Slot Default Grip Typeist aufAttachment Gripgesetzt, damit die Schleuder an der Hand befestigt wird, während sie gegriffen wirdGameplay Tagssind aufDropType.OnPrimaryGripReleaseundGrabType.OnPrimaryGripgesetzt, um Greifen/Loslassen mit Standard-Steuerungen zu ermöglichen- Aktiviere
Simulate Physics, damit die Schleuder nicht in der Luft schwebt, wenn sie nicht gehalten wird - Collision Presets sind auf
PhysicsActorgesetzt

Blueprint mit dem Schleuder-Mesh - Wir fügen eine
Hand Socket BaseKomponentePrimaryGripfür die primäre Griff-Position/Ausrichtung hinzu und passen die Hand-Pose an, damit sie die Schleuder richtig umschließt- Aktiviere
Flip For Off Hand, damit ein Linkshand-Griff die gespiegelte Pose verwendet - Aktiviere
Use Custom Pose Deltas, damit die konfigurierte Hand-Pose tatsächlich verwendet wird

PrimaryGrip Komponente mit Hand-Pose - Aktiviere
- Wir fügen eine
SceneKomponenteProjectileOriginhinzu und befestigen sie amProjectileSocket
Projectile origin Komponente - Wir fügen eine
GrippableStaticMeshComponentProjectilehinzu und befestigen sie amProjectileOrigin, und konfigurieren sie richtig:Slot Default Grip TypeundFree Default Grip Typesind aufCustom Gripgesetzt, da wir angepasstes Verhalten haben werden, während das Projektil gehalten wirdGameplay Tagssind aufDropTypeundGrabTypegesetzt, um Greifen/Loslassen standardmäßig zu deaktivieren (wir aktivieren es nur, wenn die Schleuder selbst gehalten wird)

Projectile Komponente - Wir fügen eine
Hand Socket BaseKomponenteProjectileGripfür die Projektil-Griff-Position/Ausrichtung hinzu und passen die Hand-Pose an, damit sie das Projektil richtig umschließt- Aktiviere
Flip For Off Hand, damit ein Linkshand-Griff die gespiegelte Pose verwendet - Setze
Mirror AxisaufY, damit die Hand-Pose korrekt ist, wenn die linke Hand verwendet wird, um das Projektil zu greifen - Aktiviere
Use Custom Pose Deltas, damit die konfigurierte Hand-Pose tatsächlich verwendet wird

ProjectileGrip Komponente mit Hand-Pose - Aktiviere
Hinweis: Normalerweise würden wir die PrimaryGrip Komponente am Grip Socket des Schleuder-Meshes befestigen, jedoch funktioniert diese Komponente nicht gut mit Sockets. Stattdessen setzen wir im Begin Play Event ihre Position/Ausrichtung auf die des Sockets.
Projectile Grab/Release Handling
Aktuell können wir die Schleuder mit beiden Händen greifen, aber wir können noch nicht mit dem Projektil interagieren, da wir die Standardeinstellungen so gesetzt haben, dass alle Greif-/Loslass-Aktionen ignoriert werden.
Daher fügen wir etwas Logik hinzu, wenn die Schleuder gegriffen/losgelassen wird, um das Projektil-Greifen zu aktivieren/deaktivieren, indem wir die entsprechenden VR Grip Interface Methoden am Actor implementieren:
OnGrip- aktiviere das Projektil-Greifen/Loslassen, indem wir die Gameplay TagsGrabType.OnPrimaryGrip/DropType.OnPrimaryGripReleasezur Projektil-Komponente hinzufügenOnGripRelease- deaktiviere das Projektil-Greifen/Loslassen, indem wir die Gameplay TagsGrabType.OnPrimaryGrip/DropType.OnPrimaryGripReleasevon der Projektil-Komponente entfernen

Jetzt müssen wir behandeln, wenn das Projektil selbst gegriffen wird, indem wir die entsprechenden Events an der Projectile Komponente implementieren:
OnGripped- speichere die Griff-Informationen und beginne, die Projektil-Komponenten-Position/Ausrichtung basierend auf der Hand-Position/Ausrichtung im ActorTickEvent zu aktualisierenOnDropped- feuere ein Projektil ab und bringe die Projektil-Komponente zurück zu ihrer ursprünglichen Position/AusrichtungEvent Tick- wenn das Projektil gehalten wird, aktualisiere seine Position/Ausrichtung auf die der Hand, die es hält

Um ein Projektil abzufeuern, wenn die Projektil-Komponente losgelassen wird, implementieren wir unsere LaunchProjectile Methode:
- Wir stellen sicher, dass die Projektil-Komponente in einem gültigen Bereich ist (z.B. mindestens 20 cm hinter ihrer ursprünglichen Position), sonst feuern wir kein Projektil ab, damit das Projektil zurückgelegt werden kann, ohne abgefeuert zu werden
- Wir bestimmen die Abschussrichtung, indem wir die
Look At Rotationvon der Projektil-Komponente zu ihrer aktuellen ursprünglichen Position finden - Wir spawnen ein neues
BP_Projectileund setzen seine Ausrichtung entsprechend der Abschussrichtung - Wir spielen einen 'whoosh' Sound ab, um einen erfolgreichen Abschuss anzuzeigen

Zwischenergebnis 1
Das folgende Video zeigt, dass wir jetzt eine funktionierende Schleuder in unserer Mixed Reality Test-Map haben.

Wenn du auf den Play-Button klickst, wird der YouTube-Player eingebettet und Cookies von Drittanbietern gesetzt und Daten von YouTube gesammelt.
Natürlich können wir viele Verbesserungen hinzufügen, einige davon werden wir in den folgenden Abschnitten behandeln.
Sling Niagara System
Aktuell scheint das Projektil in der Schleuder zu schweben, also erstellen wir ein Gummiband zwischen der Schleuder und dem Projektil.
Zuerst erstellen wir ein Material für das Gummiband, wie im Bild unten gezeigt. Da wir Niagara Beams für das Gummiband verwenden werden und das Standard-Beam-Material additiv ist und einen Gradienten hat (was bedeutet, dass es nicht vollständig opak ist), erstellen wir unser eigenes Beam-Material, das vollständig opak sein kann und sogar ein wenig Fade entlang der Beam-Kante hinzufügt, um den Eindruck eines nicht-flachen Beams zu vermitteln.

Dann erstellen wir ein Niagara System mit 2 Emittern, einen für jede Seite des Gummibands.


Die Emitter sind auf Local Space gesetzt, damit das Gummiband sich mit der Schleuder bewegt, sonst könnten die Gummibänder hinterherhinken, wenn man die Schleuder bewegt. Als Bonus müssen wir Start- und Endpunkt nicht jeden Frame aktualisieren, da sie relativ zur Schleuder konstant bleiben.
Wir definieren 5 Parameter, um das Gummiband vom Blueprint aus zu steuern:
SlingStart- der Start der ersten Hälfte des Gummibands, relativ zur Niagara System KomponenteSlingMiddle- das Ende beider Hälften des Gummibands, relativ zur Niagara System KomponenteSlingEnd- der Start der zweiten Hälfte des Gummibands, relativ zur Niagara System KomponenteSlingColor- die Farbe der GummibänderSlingWidth- die Breite der Gummibänder
Hinweis: Die Beam End Position ist relativ zu Beam Start, also müssen wir im Emitter den Beam Start Wert vom SlingMiddle Wert subtrahieren, um eine korrekte Position für Beam End sicherzustellen.
Jetzt, da das Niagara System bereit ist, fügen wir es zum Schleuder Blueprint hinzu, befestigen es am SlingLeft Socket und geben einige sinnvolle Standardwerte für die Parameter an:
SlingStartbleibt bei(0, 0, 0), was der Position desSlingLeftSockets entsprichtSlingMiddleist auf(9, 0, -1)gesetzt, was der Mitte des Projektils entsprichtSlingEndist auf(18, 0, 0)gesetzt, was der Position desSlingRightSockets entspricht- Wir könnten etwas Logik zu
Begin Playhinzufügen, um tatsächlich den richtigen Wert vom Socket zu nehmen und zu setzen, wir überlassen dies dem Leser als Übung
- Wir könnten etwas Logik zu
SlingWidthist auf1.0gesetzt, was dick genug für das Gummiband ist

Jetzt müssen wir sicherstellen, dass die Mitte des Gummibands immer der Position des Projektils entspricht, also fügen wir eine neue Funktion UpdateSlingPosition zum Blueprint hinzu und rufen sie an 2 Stellen im EventGraph auf:
- Nachdem das Projektil nach dem Loslassen zurück zu seiner ursprünglichen Position bewegt wurde
- Nachdem das Projektil zur Position des greifenden Controllers bewegt wurde, wenn es gehalten wird

Beim Aktualisieren der Mitte des Gummibands müssen wir sicherstellen, dass die Position, die wir setzen, relativ zur Niagara System Komponente ist, da dies das ist, was unser Niagara System erwartet.

Jetzt haben wir ein funktionierendes Gummiband, das sich mit dem Projektil bewegt, was die Schleuder bereits deutlich verbessert. In zukünftigen Iterationen könnten wir es noch besser machen, zum Beispiel durch:
- Anpassen der Farbe und Breite des Gummibands, wenn es länger wird (durch Greifen des Projektils und Zurückziehen), um zunehmende Spannung am Gummiband anzuzeigen
- Hinzufügen eines 'Bucket'-Meshes, das das Projektil hält, und Befestigen des Gummibands daran (anstatt das Gummiband mit der Mitte des Projektils zu verbinden)
Aim Niagara System
Um dem Benutzer beim Zielen der Schleuder zu helfen, fügen wir ein weiteres Niagara System für einen Laser hinzu, der in die Richtung zeigt, in die das Projektil abgefeuert wird. Es zeigt auch an, wann das Projektil beim Loslassen abgefeuert wird, da wir den Laser nur aktivieren, wenn das Projektil mehr als 15 cm von seinem ursprünglichen Ursprung entfernt ist.
Zuerst erstellen wir ein Material für das Zielen, wie im Bild unten gezeigt, damit wir etwas mehr Transparenz zu den Kanten des Beams haben. Anstatt Additive verwenden wir ein Translucent Material, damit es auch in Bereichen sichtbar ist, wo Passthrough aktiviert ist.

Dann erstellen wir ein Niagara System mit 1 Emitter.

Der Emitter ist auf Local Space gesetzt, damit das Zielen sich mit der Schleuder bewegt, sonst könnte das Zielen hinterherhinken, wenn man die Schleuder bewegt. Hier nehmen wir an, dass der Start des Aim Beams immer an der Position der Niagara System Komponente ist.
Wir definieren 3 Parameter, um das Zielen vom Blueprint aus zu steuern:
AimEnd- das Ende des Aim Beams, relativ zur Niagara System KomponenteAimColor- die Farbe des Aim BeamsAimWidth- die Breite des Aim Beams
Jetzt, da das Niagara System bereit ist, fügen wir es zum Schleuder Blueprint hinzu, befestigen es am Aim Socket und geben einige sinnvolle Standardwerte für die Parameter an:
AimEndist auf(100, 0, 0)gesetzt, damit wir es im Blueprint Viewport sehen können und überprüfen können, dass es an der richtigen Position ist und die richtige Ausrichtung hatAimWidthist auf1.0gesetzt, was dick genug für das Zielen ist

Jetzt müssen wir sicherstellen, dass:
- der Aim Beam nur sichtbar ist, wenn das Projektil mindestens 15 cm von seinem ursprünglichen Ursprung entfernt ist (wo das Loslassen ein Projektil abfeuern würde), und
- die Richtung des Aim Beams der Richtung entspricht, in die das Projektil gerade abgefeuert werden würde, und
- der Aim Beam am ersten sichtbaren Objekt/Oberfläche stoppt, die er trifft (er sollte nicht durch feste Objekte/Oberflächen durchgehen)
Dafür fügen wir eine neue Funktion UpdateAimPosition zum Blueprint hinzu und rufen sie an 3 Stellen im EventGraph auf:
BeginPlayEvent - dies stellt sicher, dass der Aim Beam am Anfang des Levels versteckt ist, unter der Annahme, dass das Projektil an seinem Ursprung ist- Nachdem das Projektil nach dem Loslassen zurück zu seiner ursprünglichen Position bewegt wurde
- Nachdem das Projektil zur Position des greifenden Controllers bewegt wurde, wenn es gehalten wird

Die Implementierung macht im Wesentlichen folgende Schritte:
- Zeige oder verstecke das Zielen basierend auf der aktuellen Entfernung zwischen dem Projektil und seinem ursprünglichen Ursprung
- Setze die Richtung des Aim Beams auf die Richtung des Projektils, wenn es gerade abgefeuert werden würde
- Führe einen Visibility LineTrace vom Aim Start Standort in die Aim Richtung durch und passe die Aim Beam Länge entsprechend an (damit es nicht durch feste Objekte/Oberflächen geht)


Jetzt ist das Zielen viel einfacher, auch wenn die Aim Start Position leicht unter der tatsächlichen Projektil-Abschuss-Position liegt. Dies ist kein großes Problem, angesichts der relativ hohen Abschussgeschwindigkeit und der Tatsache, dass das Projektil aktuell von der Schwerkraft beeinflusst wird, während das Zielen eine gerade Linie ist.
Zwischenergebnis 2
Das folgende Video zeigt, dass wir jetzt eine Schleuder in unserer Mixed Reality Test-Map mit einem funktionierenden Gummiband und Zielen haben, die dem Benutzer auch anzeigt, wann ein Projektil beim Loslassen des Projektils abgefeuert werden würde oder wann es einfach zu seinem ursprünglichen Ursprung zurückkehren würde.

Wenn du auf den Play-Button klickst, wird der YouTube-Player eingebettet und Cookies von Drittanbietern gesetzt und Daten von YouTube gesammelt.
Natürlich können wir immer noch einige Verbesserungen hinzufügen, insbesondere eine Möglichkeit, die Projektil-Position weniger starr zu machen, wenn sie nicht gehalten wird, damit es aussieht, als würde das Projektil im Gummiband hängen.
Physics Constraint
Aktuell hängt das Projektil sehr starr in der Schleuder, was nicht sehr schön ist. Um das Projektil ein wenig basierend auf der Schleuder-Bewegung bewegen zu lassen, können wir einen PhysicsConstraint zur Schleuder hinzufügen.
Wir haben uns entschieden, einen unsichtbaren Proxy für das Projektil (PhysicsProjectile) zu simulieren und die sichtbare Projektil-Position auf die Proxy-Position zu aktualisieren, wann immer es nicht gehalten wird. Auf diese Weise müssen wir den PhysicsConstraint nicht brechen/zurücksetzen, wann immer der Benutzer das Projektil greift oder loslässt.
Für dieses Setup benötigen wir 3 zusätzliche Komponenten:
- Eine
PhysicsOriginKomponente, die der feste Punkt der Constraint ist, die keine Physik simuliert und nicht von der Schwerkraft beeinflusst wird - Eine
PhysicsProjectileKomponente, die der unsichtbare Proxy für das Projektil ist, die Physik simuliert und von der Schwerkraft beeinflusst wird - Eine
PhysicsConstraintKomponente, die beide Komponenten verbindet und das PhysicsProjectile an der richtigen Stelle hält, auch wenn wir die Schleuder bewegen oder drehen
Die PhysicsOrigin hat folgende Eigenschaften:
- Sie ist leicht über dem
ProjectileOriginplatziert, in diesem Fall 1 cm Collisionist aufNo CollisiongesetztHidden in Gameist aktiviert, da wir dies nur als Referenzpunkt für den PhysicsConstraint verwenden

Das PhysicsProjectile hat folgende Eigenschaften:
Simulate Physicsist aktiviertCollisionist aufPhysics Onlygesetzt, und alle Collision Responses sind aufIgnoregesetztHidden in Gameist aktiviert, da wir dies nur für die Simulation verwenden, während das Projektil nicht gehalten wird

Der PhysicsConstraint hat folgende Eigenschaften:
- Er ist am
ProjectileSocket der Schleuder platziert - Die Constraint
Component Namessind aufPhysicsOriginundPhysicsProjectilegesetzt (dies muss genau mit den Komponentennamen übereinstimmen) Disable Collisionist aktiviert, da wir das PhysicsProjectile nur sehr begrenzt (2 cm) innerhalb der Schleuder bewegen lassenParent Dominatesist aktiviert, da wir nur das PhysicsProjectile bewegen wollen und den PhysicsOrigin als festen Referenzpunkt innerhalb der Schleuder betrachten- Wir setzen
X / Y / Z MotionaufLimitedund setzen dasLimitauf 2 cm - Wir setzen die
Angular LimitsaufLocked, da wir uns momentan nicht für Rotationen interessieren - Wir setzen das Linear Motor
Position Targetauf0, 0, 0mit 25 Stärke - Wir setzen das Linear Motor
Velocity Targetauf0, 0, 0mit 1 Stärke


Damit dies richtig funktioniert, wenn wir die Schleuder bewegen, müssen wir das PhysicsProjectile wecken und den Reference Frame jeden Tick anpassen. Wir tun dies, indem wir eine neue Funktion UpdatePhysicsConstraint hinzufügen und diese am Anfang des Event Tick im EventGraph aufrufen.


Das Ergebnis kann im folgenden Video gesehen werden, das jetzt viel besser aussieht mit dem hängenden Projektil.

Wenn du auf den Play-Button klickst, wird der YouTube-Player eingebettet und Cookies von Drittanbietern gesetzt und Daten von YouTube gesammelt.
Weitere Verbesserungen
Die Schleuder, die wir bisher gebaut haben, funktioniert bereits ziemlich gut, aber natürlich gibt es weitere Verbesserungen, die gemacht werden können:
- Aktiviere die Tick-Verarbeitung nur, wenn die Schleuder gehalten wird
- Verwende eine
GrippableSphereComponentfür den Projektil-Griff (anstatt einerGrippableStaticMeshComponent), damit wir sie etwas größer als das Projektil selbst machen können, um den Projektil-Griff für den Benutzer einfacher zu machen (aktuell könnte der Benutzer versehentlich die Schleuder selbst greifen, wenn die Hand zu weit links oder rechts vom Projektil ist) - Trenne das Projektil von der Schleuder (damit der Benutzer ein Projektil von woanders aufheben muss und es dann mit der Schleuder verwenden kann)
- Begrenze den Bereich, in dem ein Projektil abgefeuert wird, aktuell ist jede Richtung erlaubt, auch wenn es keinen Sinn macht (z.B. das Projektil gerade nach unten durch die Schleuder ziehen)
- Passe die Abschussgeschwindigkeit des Projektils basierend darauf an, wie weit das Projektil von der Schleuder zurückgezogen wurde
Fazit
Eine Schleuder ist eine schöne Variation der traditionellen Waffen-Mechaniken und kann zusätzliche Immersion bieten, indem sie eine unterhaltsame Möglichkeit bietet, Projektile in einer VR- oder MR-Umgebung zu zielen und abzufeuern.
Wir bei Immerstory sind begeistert, unsere Kunden bei ihren immersiven Erfahrungen zu unterstützen, und freuen uns darauf, bald weitere Erkundungsfortschritte zu teilen.
Welche immersieve Erfahrung würdest du gerne sehen? Lass es uns wissen!
Zusätzliche Ressourcen
Die Dokumentation für das MetaXR Plugin, speziell das Mixed Reality Utility Kit, das für diese Demo verwendet wurde, kann hier gefunden werden
Die Soundeffekte in der Demo-App wurden dankenswerterweise von Sonniss GameAudioGDC bezogen

