In diesem Tutorial bauen wir das Lockpick-System von Bethesda nach bekannt aus spielen wie The Elders Scrolls und Fallout, es ist auch ein sehr gängiges system was auch von vielen anderen spielen verwändet wird. Wir bauen das System als UI only element aber wenn ihr die 3D version haben möchtet geht einfach auf meinen Youtube Kanal.
Um das Lockpick-System nur über die Benutzeroberfläche (UI-only) zu erstellen, müssen wir zunächst ein Main UI Object im Canvas erstellen, am besten ein Image. Dieses wird dann der äußere Ring sein, der sich natürlich nicht dreht.
In unserem Ring-Image-Lockpick-Gameobject erstellen wir nun ein leeres Gameobject, das als Pivot (Drehpunkt) fungieren wird, da wir keine negativen Rotationswerte verwenden möchten darum setzen wir die z-Rotation vom Pivot-Object auf -90. In dem Pivot Gameobject Erstellen wir Zwei neue Images eines für den Innen Ring und eines für die Lockpick-Nadel
Das RectTransform unseres Nadel-Images setzen wir so, dass der Pivot Point (Drehpunkt) an der Spitze unserer Nadel (im Beispiel ein Schraubendreher) sich mittig befindet.
Dadurch, dass wir den Pivot Point auf der Z-Achse auf -90 gesetzt haben, können wir nun das Nadel-Objekt und den Inneren Ring in einem Bereich von 0 bis 180 Grad drehen, was uns die Umrechnung extrem vereinfacht
Nun erstellen wir ein Scrip und nennen es einfach LockSystemUI und packen das auf unser lockpick-Ring parent Object
Wir verwenden das neue Input-System, aber es ist auch möglich, das alte zu verwenden. Das ist jedoch unerheblich.
using UnityEngine.InputSystem;
Als Erstes benötigen wir natürlich eine Anzahl an Lockpicks, die unser Spieler besitzt. Wir integrieren diesen Wert jetzt in unser LockpickSystemUI-Skript. Jedoch solltest du diesen Wert natürlich in dein Player-Skript oder Inventar einbinden.
Zuerst benötigen wir unser Lockpick-Gameobject, um damit das Schloss zu öffnen und zu schließen. Danach müssen wir die RectTransforms unseres Nadel und des Schlosses-Objects hinzufügen. Anschließend fügen wir eine (Integer hinzu, um das Lock-Level festzulegen das entspricht den Schwierigkeitsgrad, um das Schloss zu knacken.
public int lockpicks = 10;
public int LockLevel = 1;
public GameObject LockpickObject;
public RectTransform nadel, lockinnen;
Zuerst benötigen wir drei Floats. Der erste repräsentiert die Unlock-Position, also den Punkt, an dem unser Schloss entriegelt werden kann. Der zweite Wert gibt die minimale Distanz zum Unlock-Point an, um einen Bereich zu definieren, in dem wir das Schloss entsperren können. Durch die Verwendung unserer Lock-Levels können wir diesen Bereich verkleinern, um den Schwierigkeitsgrad zu erhöhen. Der letzte Wert ist das Lockpick-Break-Limit. Dieser Wert bestimmt, ab wann unser Lockpick zerbricht.
public float unlockPosition, minimumDistance = 45f, lockpickBrokeLimit = 5f;
Jetzt fügen wir einige Floats für unsere Bewegungen und Animationen hinzu. Zunächst definieren wir die Geschwindigkeit, mit der sich die Nadel und das Schlossinneres bewegen. Anschließend die Geschwindigkeit, mit der die Nadel zur normalen Position zurückkehrt, wenn der Druck nachlässt, und die Geschwindigkeit, mit der die Nadel auf Null gesetzt wird, wenn ein Lockpick zerbricht.
Des Weiteren benötigen wir drei Booleans. Der wichtigste ist der 'Open'-Bool, der bestimmt, ob das Schloss geöffnet wurde. Dann gibt es einen Boolean 'back', der dazu dient, die Nadel zurück in die Ausgangsposition zu bewegen, wenn der Druck auf das Schloss gelöst wird. Der letzte Bool erfüllt eine ähnliche Funktion, wird jedoch nur aktiviert, wenn ein Lockpick zerbricht. Dann benötigen wir nur noch zwei Float-Werte: Einen als Puffer für die Animation und einen Multiplikator, der sicherstellt, dass bei zu starkem Druck auf das Schloss das Lockpick schnell zerbricht.
public float nedelRotationSpeed = 2f, lockRotationSpeed = 2f, backToNormalPosSpeed = 2f, LerpBackSpeed = 2f;
public bool Open;
bool back, onBack;
float multi = 1f , t;
Jetzt werden wir eine Methode erstellen, die wir aufrufen können, um das Schloss zu öffnen. Der beste Ansatz hierfür wäre, einen Raycast auf das verschlossene Objekt (zum Beispiel eine Tür) abzufeuern und diese Methode dann aufzurufen. Da wir derzeit keinen Spieler haben, erstellen wir einen Test-Boolean als Trigger.
public void openLockPick()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
Open = false;
unlockPosition = Random.Range(5, 175);
minimumDistance = minimumDistance / LockLevel;
LockpickObject.SetActive(true);
//test
test = false;
Debug.Log(unlockPosition);
}
Beim Öffnen der Schloss-UI setzen wir zunächst den Cursor auf Flase.
Dann setzen wir 'Open' auf 'False',
dann lassen eine zufällige Unlock-Position im Bereich von 5 bis 175 setzen.
Wir lassen einen kleinen Abstand zu jeder Kante, damit die Unlock-Position immer erreichbar ist.
Dann wird die minimale Distanz anhand des Lock-Levels minimiert (das minimale Level muss 1 sein).
Anschließend aktivieren wir das Lockpick-Objekt.
Wir setzen den Test bool Trigger wieder auf 'False'.
Zudem geben wir die Unlock-Position in der Konsole aus
Dann erstellen wir eine einfache kleine Methode, die lediglich überprüft, ob wir uns im Entsperren-Bereich befinden.
bool LockArea(float z)
{
return z >= unlockPosition - minimumDistance &&
z <= unlockPosition + minimumDistance;
}
Dann erstellen wir eine Methode, die dafür sorgt, dass sich der sich drehende innere Bereich des Schlosses umso mehr in Richtung 'Unlock' dreht, je näher die Nadel sich dem Unlock-Bereich nähert."
float nedelDis(float z)
{
if (LockArea(z))
{
return 1;
}
else if(z < unlockPosition - minimumDistance)
{
return Mathf.InverseLerp(0, unlockPosition - minimumDistance, z);
}
else if(z > unlockPosition + minimumDistance)
{
return Mathf.InverseLerp(180 + Mathf.Abs(180 - (unlockPosition + minimumDistance)),
unlockPosition + minimumDistance, z);
}
return 0;
}
Jetzt haben wir alles was wir brauchen also gehen wir in die Update Methode.
void Update()
{
if (test) openLockPick();
if(Keyboard.current.escapeKey.wasPressedThisFrame)
LockpickObject.SetActive(false);
if (onBack || lockpicks < 1) return;
Als Erstes fügen wir unseren Test-Trigger ein.
Dann überprüfen wir, ob die Escape-Taste gedrückt wird, um die Schloss-UI wieder zu schließen.
Anschließend überprüfen wir, ob entweder ein Lockpick kaputt ist oder ob wir überhaupt noch Lockpicks besitzen. Falls ja, beenden wir den Vorgang und geben eine Rückmeldung, ansonsten kehren wir zurück."
float achse = Mouse.current.delta.x.value * nedelRotationSpeed * Time.deltaTime;
var r = nadel.localEulerAngles;
r.z += achse;
r.z = Mathf.Clamp(r.z, 0, 180);
nadel.localEulerAngles = r;
Dann erstellen wir einen float der unsere Mause bewegung auf die Nadel-Objekt Rotation übertragt.
Um sicherzustellen dass die Rotation von unseren Nadel-Objekt nicht zuweite geht Clampen wir die veränderte Rotation.
if (Keyboard.current.aKey.isPressed)
{
back = true;
var nr = lockinnen.localEulerAngles;
nr.z = Mathf.Lerp(nr.z, Mathf.Lerp(90, 0, nedelDis(r.z)), Time.deltaTime * lockRotationSpeed);
lockinnen.localEulerAngles = nr;
if (LockArea(r.z))
{
if(nr.z < 1)
{
Open = true;
LockpickObject.SetActive(false);
Debug.Log("Open");
}
}
else
{
multi++;
t += Time.deltaTime * multi;
if (t >= lockpickBrokeLimit) StartCoroutine(lerpBack());
}
}
Jetzt fragen wir ab ob die A taste gedrückt wird und setzen dann als ertses den back boolean auf true da wir ja unser Schloss gedreht haben.
Dann Rotieren wir das Innere des Schlosses mit hilfer der nedelDis Methode.
Dann fragen wir mit hilfer der LockArea Methodeob unser Nadel-Objekt im unlock berreich ist.
Dann fragen noch mal ab das Schloss innere vollständig rum gereht wurde in diesem fall ist das Schloss geknackt darum setzen wir Open auf true deakivieren das Schloss-Objekt.
Sollte die Nadel Position nicht im unlock bereich sein aber weiter gedrückt wird erhören wir den Multiplikator und rechenen den t wert hoch, sobalt der wert größer als das lockpickBrokeLimit dann straten wir die Coroutine lerpBack damit die Nadel und Schloss Ratation wieder normalisiert wird
else
{
multi = 1;
if (back)
{
var nr = lockinnen.localEulerAngles;
nr.z = Mathf.Lerp(nr.z, 90, Time.deltaTime * backToNormalPosSpeed);
lockinnen.localEulerAngles= nr;
if (nr.z == 90) back = false;
}
}
}
Und zum schluss wenn wir keienen druck mehr aufs Schloss ausüben wird erst der Multiplikator wir auf 1 zurück gesetzt und dann Rotieren wir die Nadel und das Schloss innere wieder auf die anfangs Position.
Hier die Ganze Klasse