Hallo Leute,
Auch in dieser Woche ist eine Menge passiert.
Gefahr für den Spieler
Zuerst habe ich Schießen auf den Spieler implementiert. Dafür habe ich eine Funktion geschrieben, die einen Ray auf den Spieler schießt. Wenn der Ray den Spieler trifft stirbt der Spieler. Diese Funktion wird durch invokeRepeating alle zwei Sekunden aufgerufen.
Dann habe ich einen mit SFXR erstellten Schuss-Sound hinzugefügt und mit einem Partikelsystem einen Muzzle Flash erstellt.
Am darauf folgenden Tag habe ich eine Todes-Animation für die Kamera erstellt und das Script so erweitert, dass die Animation bei einem Treffer des Gegners abgespielt wird und das Movement-Script des Spielers deaktiviert wird. Nun funktionierte das Schießen nur noch sehr merkwürdig. Der Gegner schoss überhaupt nicht mehr, nur wenn man ihn berührte würde plötzlich die TodesAnimation abgespielt.
Später fand ich heraus, dass invoke in der Update() Funktion nur sehr eingeschränkt funktioniert. Also erstellte ich mir mein eigenes Invoke. Dafür habe ich eine Variable erstellt, die wenn man sie auf 1 setzt jeden Frame um 1/120 Sekunde verringert wird. Wenn diese Variable 0 erreicht, wird die SchussFunktion ausgeführt. Damit schoss der Gegner schon mal wieder zuverlässig alle zwei Sekunden. Nur das Geräusch und der Muzzle Flash fehlten. Dieses Problem konnte ich auch schnell lösen.
Dann gab es noch das Problem, dass der Gegner perfekt zielen konnte man somit immer vom ersten Schuss des Gegners erfasst wurde. Zuerst dachte ich, dass ich das so lassen würde, denn in Unnoticed geht es ja schließlich darum unbemerkt zu bleiben und nicht darum dem Gegner zu entkommen. Schließlich habe ich mit meinem Vater darüber gesprochen und er sagte, dass der Gegner immer auf ein Objekt Taregt zielen sollte das dem Spieler zeitverzögert folgt. Somit ist es für den Gegner schwieriger den Spieler zu treffen wenn der Spieler sich viel bewegt. Diese Idee ließ sich dank Lerp sehr leicht umsetzten. Lerp ist eine Funktion, die einen Zwischenwert von zwei Floats oder Vektoren errechnet. Wen das interessiert, dem empfehle ich dieses YoutubeVideo. Mit dem Folgenden Script lasse ich das Target verzögert dem Spieler folgen.
public transform Player;
public float value = 0.2;
void Update(){
transform.postion = Vector3.Lerp(transform.position, Player.position, value) ;
}
So setzte ich die position des Targets in jedem Frame auf 20% zwischen der eigenen und der Spieler position. Jetzt wo ich so darüber nachdenke sollte ich das doch besser nicht jeden Frame, sondern jeden PhysikFrame tun, denn PhysikFrames werden immer nach einer einheitlichen Zeit ausgeführt. Also ersetzten wir Update einfach durch FixedUpdate. Denn sonst hätte der Gegner es schwerer zu zielen, wenn man auf besserer Hardware spielt. Ich denke allerdings, dass man dabei auch nicht so genau sein muss, schließlich sieht der Spieler ja von alldem garnichts.
Pause Menü
Ein paar Tage später habe ich ein PauseMenü erstellt. Das Problem ist nur, dass die Buttons aus irgendeinem Grund nicht funktionieren. Ich recherchierte und bemerkte, dass daran der FPS-CharacterController schuld ist. Der FPS-Controller hat in seinen Einstellungen eine BooleanVariable names Lock Cursor. Ich habe gelesen, dass ich diese wenn pausiert wird deaktivieren sollte. Bis jetzt schaffte ich dass allerdings noch nicht.
Builden von Version0.2
Nun dachte ich mir, ich sollte mal wieder eine Standalone-Version meines Spiels builden und diese hier hochladen. Als ich dies versuchte, erhielt ich einen Compilererror, der mich auf falsch gesetzte geschweifte Klammern in dem nicht von mir erstellten IK-Skript hinwies. Zu erst war ich verwundert, denn im Test-Modus entstand dieser Fehler ja auch nicht. Egal. Ich öffnete das IK-Script und sah mir die Klammern an. Mir fiel nichts falsches auf. Da der Compiler ja sagte etwas Stimme mit den (äußersten) Klammern nicht, probierte ich einfach aus noch eine Klammer hinzuzufügen oder wegzunehmen. Doch nichts half.
IK ohne IK
Zuerst war ich für ein paar Tage frustriert und arbeitete nicht weiter. Irgendwann recherchierte ich mal wieder ein bisschen zum Thema IK und fand etwas interessantes. Jemand benutzte statt Animation-Rigging oder anderem einfach ein transform.LookAt in der LateUpdate-Funktion. Dadurch wird in jedem Frame einfach nachdem der Animator seine Änderungen vorgenommen hat der transform eines Knochens so eingestellt, dass er direkt auf eine bestimmte Position zeigt. In meinem Fall auf den Spieler.
Am nächsten Tag probierte ich dies aus. Ich dachte ich müsste nur die Rotation des Oberarms verändern und die untereren Knochen auf 0 setzten, weil die Transformationsveränderung durch den Parent sich nicht auf die Werte der Transformation auswirkt. Damit lag ich falsch. Leider merkte ich das zu spät. Stattdessen probierte ich dieses ‘IK-System’ mithilfe von Offsets richtig einzustellen. Nach einer Menge Arbeit hatte ich den Oberarm so eingestellt, dass er immer in die Richtung des Spieler zielt. Nun versuchte ich den Unterarm und die Hand richtig rotieren zu lassen. Nach einer Weile gelang mir auch das. Doch als ich dies im Spiel ausprobieren wollte, fiel mir auf, dass der Unterarm nur aus einer bestimmten Perspektive funktionierte.
Gerade denke ich darüber nach, ob ich nicht auch Unterarm und Hand mit LookAt auf den Spieler zeigen lassen könnte. Das sollte ich vielleicht das nächste mal, wenn ich meinen Rechner an habe ausprobieren. Sollte das nicht klappen mache ich erstmal eine kleine Pause mit der Entwicklung von Unnoticed.
Am nächsten Tag
Ich habe das Problem jetzt lösen können. Mir fiel auf, dass ich bereits beim letzten mal versucht hatte alle Knochen mit LookAt auf den Spieler zeigen zu lassen.
UpperArm.transform.LookAt(target.transform);
UpperArm.transform.rotation= UpperArm.transform.rotation * Quaternion.Euler(offset);
LowerArm.transform.LookAt(target.transform);
LowerArm.transform.rotation = Quaternion.Euler(offset);
Hand.transform.LookAt(target.transform);
Hand.transform.rotation = Quaternion.Euler(offset);
Und? Habt ihr den Fehler gesehen? Richtig ich setzte fehlerhafterweise Hand und Unterarm zuerst auf die Rotation in Richtung Spieler und dann setzte ich die Rotation auf den Offset. Ich sollte mich echt schämen, dass ich an soetwas verzweifle.
Naja egal, Hauptsache ist, dass ich dieses Problem nun lösen konnte. So sieht dieser CodeAbschnitt jetzt aus :
UpperArm.transform.LookAt(target.transform);
UpperArm.transform.rotation erArm.transform.rotation= UpperArm.transform.rotation * Quaternion.Euler(offset);
LowerArm.transform.LookAt(target.transform);
LowerArm.transform.rotation = LowerArm.transform.rotation * Quaternion.Euler(offset);
Hand.transform.LookAt(target.transform);
Hand.transform.rotation = Hand.transform.rotation * Quaternion.Euler(offset);
Was Quaternion.Euler macht? Nun ja, in Unity werden Rotationen in Quaternions gerechnet. Deshalb muss ich mit Quaternion.Euler einen EulerWinkel in einen Quaternion umwandeln. Ein EulerWinkel besteht einfach aus x, y und z Achse einer Rotation. Ein Quaternion ist … ich habe keine Ahnung. Quaternions werden benutzt, weil EulerWinkel scheinbar sich scheinbar irgendwie sebst blockieren können. Wie das funktioniert wüsste ich auch gern.
Nun habe ich noch ein Boot in Blender modelliert, von dem aus der Spieler das Spiel startet und ein paar Textnachrichten, die dem Spieler helfen sollen. Nach alldem ist es Zeit für die Version 0.2! Übrigens habe ich diesmal auch einen WebGL build erstellt, damit ihr mein Spiel nicht downloaden müsst.