PikaRun – Hand Made Jump’n’Run

Jump’n’Run-Games sind Klassiker, jeder kennt Super Mario und seine Genre-Kollegen. Wir wollten uns selbst an einem versuchen und haben es deutlich unterschätzt. Von Programmieren über Modelling bis zur Animation alles selber zu machen war eine Herausforderung, doch sieh dir das Resultat selbst an.

Hilf dem Pika, sich durch die Berglandschaft zu hoppeln und den Weg zurück in den Schatten seines Baus zu finden.

Hier kommst du zum Spiel (nur auf dem Computer spielbar).

(spu)

Kritik
von Gabriel Baschung und Nevio Heimberg

Idee

Jeder der selbst Videospiele spielt, will doch einmal sein eigenes Spiel produzieren. Doch man merkt schnell, dass dies nicht so einfach gemacht, wie es gesagt ist. Wir überlegten uns zuerst, was für ein Spiel wir machen wollten. Ein Jump'n'Run, ein Shooter oder ein Aufbauspiel. Soll das Spiel 2D oder 3D sein, aus der Egoperspektive spielbar oder aus der sogenannten 3rd Person Perspektive. Dies sind Fragen, die wir zuerst beantworten mussten. Wir haben uns für folgendes entschieden:

  • 2.5D - Man kann nur nach links oder rechts laufen, die Umgebung ist jedoch 3D.
  • Jump 'n' Run - Ein einfaches, aber zeitloses Spielprinzip.

Wir machten uns also an die Konzeption:

Konzeption

Zu Beginn des Projektes haben wir uns überlegt, wie wir das Spiel aufbauen möchten. Schnell kamen wir auf die Idee der fliegenden Inseln. Wir haben also in Photoshop ein Level gezeichnet, wonach wir dann die Einzelteile auch gemodelt haben. Darin waren folgende Levelelemente:

  • Gehen (nach links und rechts)
    • Gemacht, dies funktioniert gut
  • Springen (DoubleJump -> man kann einmal springen, dann noch einmal verstärken)
    • Gemacht, dies funktioniert ebenfalls gut
  • Liane (Man kann sich von einer Insel auf die nächste schwingen)
    • Verworfen, da wir es zwar hingekriegt haben, die Liane hat jedoch das Movement (Gehen und Springen) durcheinander gebracht, wodurch sich das Spielerlebnis nicht mehr so smooth angefühlt hat.
  • Fahrende Plattformen (man kann auf die Plattform und man wird mittransportiert)
    • Verworfen, da wir es zwar hingekriegt haben, die Plattform hat aber zu viele Bugs verursacht, was das Spielen frustriert hätte.

Scripting

Movement

Der Grundstein eines Spiels ist das Movement, also die Bewegung der Spielfigur. Dies hat sehr schnell funktioniert. Wir wollten, dass man nur nach links und rechts gehen kann. Die meisten Tutorials für Unity3D zeigen jedoch auch ein Movement nach vorne und hinten. Dafür haben wir die Bewegung der Z-Achse auf 0 gestellt. Wir haben sowohl einen MoveSpeed als auch einen JumpSpeed hinzugefügt, die sich unterschiedlich (je nach eingegebenem Wert) auf den Spieler auswirken. Damit man DoubleJumpen kann, haben wir einen Jumpcounter eingebaut und diesen mit einer if-Bedingung verknüpft. Wenn der Spieler am Boden ist, kann man immer springen. Wenn man ein zweites Mal springt, erhöht sich der JumpCounter um eins. Wenn sich der Spieler nicht am Boden befindet und der JumpCounter < 1 ist, kann man noch einmal springen. Wenn der JumpCounter >= 1 ist, kann man erst wieder springen, wenn man einmal den Boden berührt hat. So sind DoubleJumps möglich und man kann somit grössere Hürden überwinden. Man muss sich jedoch mit dem richtigen Timing den zweiten Sprung ansetzen. Durch dieses Feature ist die Springmechanik interessanter und vielfältiger.

Doch das Pika (unser Hauptcharakter) muss auch in die Richtung schauen, in die es läuft. Dies war mit Abstand der Schwierigste Punkt. Zu Beginn hat es gut geklappt, wir haben eine if-Bedingung an die A-Taste (Links gehen) und an die D-Taste (Rechts gehen) gebunden. Mit einer einfachen Schaltervariable wird geprüft, ob man schon nach links oder rechts geht (damit sich das Modell nicht noch mehr dreht). Dann haben wir den transform.rotation Befehl in die if-Bedingung hinzugefügt. Beim ersten Test klappte dies anfänglich sehr gut, doch wenn man ein bisschen rumgelaufen ist, stürzte man plötzlich durch die Insel und "starb". Dies war uns zuerst unerklärlich, nach einigem googlen haben wir herausgefunden, dass dies am transform liegt, da der Rigidbody (Ein Rigidbody ist eine Eigenschaft, die, wenn man sie einem beliebigen Objekt hinzufügt, es ihm erlaubt, mit einer Menge fundamentalen physikalischen Verhaltensweisen, wie Kräfte und Beschleunigung, zu interagieren.) nicht mit rotiert wird. Mit dem Befehl rigidbody.MoveRotation fiel man nicht mehr durch den Boden. Nun kam jedoch ein neues Problem auf: Das Modell ist auf dem Kopf und dreht sich komisch beim Gehen. Man kann jedoch die genaue Gradzahl der Rotation einstellen. Ein wenig Rumprobieren und auch dies ist geschafft.

Screens
Je nachdem was geschieht, müssen verschiedene Screens eingeblendet werden:

  • Hauptmenü - Wenn man das Spiel startet, oder das Spiel verlässt
    • Dabei haben wir die Szene aus dem ersten Level kopiert und ein Canvas hinzugefügt. Auf den Canvas haben wir zwei Buttons gepackt (Play/Quit). Anschliessend haben wir ein kleines Skript (Spiel starten/Spiel beenden) hinzugefügt. Die einzigen Probleme dabei waren, dass zuerst der Sky nicht angezeigt wurde, obwohl er zu sehen sein sollte. Dies konnte gelöst werden, indem die Kamera verschoben wurde und die einzelnen Objekte (Insel + Assets/Himmel) auf den Nullpunkt gesetzt wurden.
  • Pausenmenü - Wenn man die Escape-Taste drückt
    • Mit einer einfachen if-Bedingung wird das Pausenmenü angezeigt, wenn man die Escape-Taste drückt. Dies kennt man aus praktisch allen Spielen, weswegen auch wir diese Taste gewählt haben. Vorgegangen sind wir genau gleich, wie beim Hauptmenü. Wir haben ein Canvas hinzugefügt, die beiden Buttons darauf gepackt und ein kleines Skript hinzugefügt. Danach standardmässig das Canvas ausgeblendet und eine if-Bedingung an die Escape-Taste gebunden. Wenn man die Escape-Taste drückt, wird das Pausenmenü eingeblendet und die Zeit auf 0 gestellt (so wird das Spiel pausiert/es geht nicht mehr weiter). Beim Pausenmenü hatten wir keine Schwierigkeiten. Es gibt viele Hilfreiche Tutorials/Forumthreads zu diesem Thema.
  • Todesscreen - Wenn man "stirbt"
    • Auch hier wurde ein Canvas erstellt und die Schriftzüge darauf gepackt. Nun sollte Der Todesscreen nur dann eingeblendet werden, wenn man stirbt. Also wurde dieser Befehl in das Respawnskript mit eingebunden (mehr dazu unter dem Punkt "Respawn). Der Todesscreen funktionierte dann gut.
  • Winningscreen - Wenn man das Level erfolgreich beendet hat
    • Am Ende des Levels haben wir einen Collider hinzugefügt, wenn der Spieler diesen berührt, wird der Winningscreen angezeigt und man hat das Level gewonnen. Das Design des Winningscreens ist dasselbe wie beim Todesscreen, nur mit einem anderen Text und in grün. Der Code ist ebenfalls derselbe (Man kann neu starten oder zurück ins Hauptmenü).
  • Tutorial
    • Um dem Spieler die Steuerung näher zu bringen, haben wir uns für ein ingame Tutorial entschieden. Wenn man das Spiel startet wird angezeigt, wie man nach links und rechts geht und wie man springt. Wenn man die Tasten drückt, wird entsprechend das Tutorial grün angezeigt. So wird dem Spieler gezeigt, dass er die richtige Taste gedrückt hat. Wenn man alle einmal gedrückt hat, wird das Tutorial ausgeblendet. Dies wird mit der Hilfe von if-Bedingungen und Schaltervariablen gemacht. Dies hat sehr schnell und gut funktioniert.

Animation der Assets
Sowohl der Wolkenparkour, als auch die Hammerpendel zum Ende des Levels mussten sich bewegen. Dies würde mit Code gehen, doch das war uns jedoch zu kompliziert und zu umständlich. Also haben wir den eingebauten “Animator” benutzt. Dies funktioniert ähnlich wie im After Effects. Man geht in den Animationsmodus, verschiebt das Objekt so wie gewünscht und fügt die Keyframes ein. Dabei ist viel Ausdauer und Testen gefragt. Dabei haben wir alle Wolken (Wolkenparkour + Hintergrund) und alle Pendel einzeln animiert. Dies funktionierte gut, dauerte aber lange.

Respawn
Wenn der Spieler von den Inseln fällt, soll er "sterben" und die Möglichkeit kriegen, das Level noch einmal von vorne zu beginnen. Wir haben uns gegen Checkpoints entschieden, da wir der Meinung sind, dass das Level einfach genug ist, um es in einem Durchgang zu schaffen. In zukünftigen Levels könnten solche Checkpoints jedoch sicher nützlich sein. Ein Respawn-Trigger unterhalb der Inseln (Box Collider mit einer Triggerfunktion) erkennt, wenn der Spieler diesen berührt. Sobald der Spieler "stirbt", wird die Zeit verlangsamt und der Todesscreen angezeigt. Wenn man auf "Respawn" drückt wird der Spieler an den Respawn-Point teleportiert. Dieser Punkt ist ein EmptyGameObject, welches am Beginn des Levels platziert wurde.

Probleme mit dem Respawn kamen auf, nachdem wir den Todesscreen eingebaut haben. Nachdem man "stirbt" erscheint dieser, man drückt auf "respawn" und der Screen erscheint noch einmal. Dies sollte natürlich nicht so sein. Wir versuchten dies mit Schaltervariablen und if-Bedingungen zu lösen, doch das Problem ging nicht weg. Mithilfe des Debug.Log Befehls (es wird etwas in die Konsole geschrieben), haben wir herausgefunden, dass der Respawn zweimal getriggert wird, einmal beim "sterben" und einmal beim zurückteleportieren. Deswegen wurde auch der Todesscreen zweimal angezeigt. Wir haben vieles ausprobiert und die Schaltervariablen/if-Bedingungen wieder entfernt. Wir haben also keine direkte Lösung gefunden, wir vermuten jedoch, dass beim ersten Versuch irgendein Fehler im Code das Problem ausgelöst hat. Wir sind jedoch froh, dass das Problem nun nicht mehr auftaucht, auch wenn wir den genauen Grund dafür nicht kennen.

Bugs
Es kommt teilweise leider immer noch zu Bugs. Der Grösste dabei ist, dass man "stirbt" obwohl man nichts falsch gemacht hat. Wenn man das Spiel neu lädt, funktioniert es wieder, wie es sollte. Der Bug könnte daran liegen, dass der Box Collider, der den Spieler erkennt, wenn er von den Inseln fällt, irgendwie ausgelöst wird. Wo dies genau geschieht, haben wir bis zur Abgabe leider nicht in jedem Fall gefunden. Wir haben folgende Lösungsvarianten versucht:

  • Box Collider des Respawns nach weiter unten verschoben
  • Mesh Collider des Spielers ersetzt, und den nicht aktivierten Collider entfernt
  • Eine if-Bedingung im Respawnskript eingefügt, um nur den "Todesscreen" anzuzeigen, wenn der Spieler in den Box Collider des Respawn “fällt”.
  • In den Testläufen hat es geholfen, wenn man noch einmal zurück ins Menü (Escape -> Menu) ging und noch einmal von vorne gestartet hat. Der Bug ist jedoch nur aufgetreten, wenn man schon mind. einmal gestorben ist und im letzten Testdurchgang ist alles so verlaufen, wie es sollte.

Modeling

Für das Modeling der einzelnen Meshes wurde viel Zeit beansprucht. Vorerst galt es herauszufinden, in welchem Look sich das Game genau befindet, und wie wir diesen Look erreichen. Als Programm verwendeten wir Blender aufgrund der relativ benutzerfreundlichen Anknüpfung zu Unity.

Inseln
Die Basis des Games wird durch das Level selbst gebildet – das Terrain. Durch die vorgängig angefertigten Skizzen des Aufbaus war die generelle Form der jeweiligen Inseln klar. Doch der Weg dorthin führte über etliche Versuche, bis ein speditiver, flexibler und verlässlicher Prozess für das Modeling der jeweiligen Meshes feststand.

Die Grundform der Insel begann meist mit einer 2D-Fläche, welche den ungefähren Grundriss (meist nur von oben, manchmal auch seitlich) festlegte. Danach wurde entsprechend den Skizzen die gewünschten Flächen grob extrudiert und so das Terrain «geblockt». Anschliessend wurde die noch sehr rudimentäre Mesh im Sculpting-Modus von Blender in Richtung finale Form gebracht. Stundenlanges Experimentieren mit verschiedenen Pinselstärken, Smoothing und der tatsächlich nötigen Anzahl an Geometrie

war nötig, um den richtigen Weg zu finden, der sich auf jede Insel schlussendlich anwenden lässt. Insgesamt sollten die Inseln einen angemessenen Low-Poly-Faktor aufweisen, ohne zu detailliert/hochaufgelöst oder zu zerschossen auszusehen.

Sobald die Mesh die angedachte Form erreicht hatte (was unzähligen Stunden an Tweaking bedingte), verwendeten wir den in Blender eingebetteten Decimate-Modifier, der die Geometrie weiter verringert und so das Resultat lieferte, das wir suchten.

Assets
Blosse Inseln machen aber noch keine stimmige Landschaft. Um den Look des Games zu bereichern und eine immersive Atmosphäre zu schaffen erstellten wir in Blender einige Deko-Assets. Diese sollten dem Design des Levels treu bleiben und die Geometrie auch ihrer jeweiligen Grösse entsprechen. Wir modelten sieben Arten von Steinen, zwei Arten Gras, Wolken, drei Arten Nadelbäume und zwei Laubbäume und dazu einen abgestorbenen, liegenden Baumstamm. Für jedes Objekt wurde ein eigenes Material mit entsprechender Beschaffenheit, Farbe und Textur erstellt. Die Assets verteilten wir anschliessend im Unity-Projekt, bis die Landschaft lebendig und dynamisch wirkte. Auch hier wurden die verschiedenen Objekte einige Male neu angepasst und anschliessend wieder importiert, bis das Aussehen unseren Vorstellungen entsprach.

Charakter – Pika
Ohne einen Hauptcharakter im Spiel fühlt sich das Game-Erlebnis jedoch sehr flach an. Der  vorerst als Kapsel-Platzhalter dagewesene Player-Dummy musste mit einem «echten» Player ersetzt werden.

Wir recherchierten und suchten uns Inspiration für unseren Hauptcharakter. Von Eule über Pinguin hatten wir alles auf der Liste, bis wir das Pika fanden. Pikas sind vom Aussterben bedrohte Nagetiere. Ihr Lebensraum beläuft sich auf Gebirge in Nordamerika und Osten Asiens, wo sie sehr selten aufzufinden sind. Die knuffigen Mini-Hasen sind vor allem wegen der rapide voranschreitenden Klimaerwärmung bedroht  – da ihr Fell zu warm und dicht ist  und sich nicht genügend schnell der Veränderung unserer Erdtemperatur anpassen kann, sterben viele Pikas an der wachsenden Hitze. Daher kam die Idee, das Pika in den Schatten seines Baus zu bringen, wo es vor dem heissen Klima etwas geschützter ist.

Das Modeln des Pikas war eine Geschichte für sich. Nach langer Recherche für passende Referenzbilder war es eine Herausforderung, den Grat zwischen Low-Poly und anatomischer Korrektheit zu wandern. Auch war es unser Ziel, mit dem Pika einen Sympathieträger als Charakter zu vermitteln, was dem Spiel Spass, Aktion und eine gewisse Süsse verleiht.

Das Pika sollte also herzig und «echt» aussehen zugleich. Aus einem einfachen Cube versuchten wir dann anhand Box-Modeling die Form des Pikas zu treffen. Mit einigen Loopcuts und dem Bewegen von Kanten und Punkten konnte anschliessend Plastizität erreicht werden – mit der Skala des Low-Poly-Faktors im Hinterkopf, natürlich. Dank des Mirror-Modifiers brauchte nur eine Hälfte der Mesh angefertigt zu werden – durch Anwenden der Spiegelung ergänzte Blender die andere Hälfte des Pikas anschliessend automatisch und symmetrisch. Hier kam die Erfahrung im 3D-Modeling, welche durch Neugier und viel Zeitinvestition in den Programmen entstanden ist, sehr zur Geltung. Das Pika wurde anschliessend simpel mit Hilfe von verschiedenfarbenen Materials texturiert.

Animation

Ein Charakter braucht aber auch Bewegung. Dazu war es nötig, das Pika vorgängig in Blender zu riggen. Hierfür half eine vertiefte Studie der Anatomie des Nagers und dessen Bewegungsabläufen, welche wir recherchierten.

Hauptankerpunkt des Rigs ist ein sogenannter Root-Bone, der die Verschiebung des ganzen Rigs, welches als Parent der Mesh fungiert, verschieben kann. Die Beinknochen riggten wir mit Inverse Kinematics-Constraints und selbst erstellten Rotation-Controllern für Füsse und Knie, um eine realistische Bewegung der Vorder- und Hinterläufe zu erhalten. Auch hier musste die Knochenstruktur noch nach der Anwendung des Rigs auf die Mesh angepasst werden, damit die Bewegungen die Mesh nicht zu unnatürlich verziehen oder verkorksen.

Nachdem das Rig funktionstüchtig war ging es um die Animation. Um auf die verschiedenen Bewegungen, die im Spiel für das Movement nötig sind, eingehen zu können, brauchten wir drei Grundanimationen:  Idle (Leerlauf), Walkcycle (Geh-/Laufzyklus) und Jump (Springen). DIe Animation dieser Aktionen erfolgte ebenfalls in Blender und beanspruchte viel Zeit, um eine geschmeidige und realistische Bewegung imitieren zu können. Die Animationen wurden über mehr Frames erstellt, als wir sie anschliessend im Spiel benutzten, damit  die Bewegung auch wirklich gut stimmte. Das Timing konnte dank genügend Abstand zwischen den Frames dann auch in doppelter Geschwindigkeit in Unity problemlos bestehen. Probleme machte vor allem die Wirbelsäule, die sich nicht ganz so realistisch einsetzen lässt wie gehofft, da die Mesh kein perfektes Weight-Painting aufweist. Trotz Anpassungen des Weight-Paintings ist es schwierig, ganz ohne unnatürliche Verzerrungen der Geometrie auszukommen. Nach einigen Tweaks konnte aber die Animation gut mit sich selbst leben. Mithilfe von einem leichten Offset zwischen den linken und rechten Läufen konnten wir auch den etwas synthetischen Charakter der Bewegung beseitigen: Das Hoppeln sieht hoppelig aus, der Jump erfüllt seinen Zweck (und ist dem WalkCycle ähnlich, weil, nun ja, Pikas halt fast immer hoppeln, ob sie nun gehen oder Sprünge machen), und in der Idle-Animation liessen wir uns von den geschauten Pika-Clips inspirieren und liessen das Tier um sich schauen, die Beine anziehen, und anschliessend absitzen.

Dank Blenders Action Editor ist es möglich, mehrere Animationen in einem Stash zu stacken, so dass die einzelnen Animationen anschliessend in Unity den jeweiligen Aktionen zugewiesen werden können. Nach einigem Coding und unzähligen Anpassungen der Überblendungen zwischen den Animationen erreichten wir anschliessend das Resultat, wie es nun zu betrachten ist: Ein lebendig wirkendes Pika das hoppelt, hüpft und chillt.

Import Unity

Der Import der Animation in Unity war schwieriger als gedacht. Zuerst wurden die Animationen im Blender file nicht angezeigt, als fbx. file ging dies jedoch. Schnell haben wir die Animationen per Animator Controller auf den Charakter gespielt, doch plötzlich schwebte der Charakter einen Meter über dem Boden. Dies konnten wir beheben, indem wir den Center des Character Controllers von (0, 0, 0) auf (0, 1, 0) verschoben. Wir denken, dies liegt daran, dass wir den Nullpunkt vom Mesh mit der Animation und Rig im Blender nach unten verschoben haben. Anschliessend hat auch die Rotation (in welche Richtung der Charakter schaut) nicht mehr richtig funktioniert, auch dort mussten wir an den Zahlen drehen. Das Animationsskript ist mithilfe einiger Tutorials schnell geschrieben (wenn man die Taste “a” drückt, dann spiele diese Animation ab). Dies hat auch gut funktioniert. Probleme hatten wir beim Loop der Animation. Dieser funktionierte zuerst nicht, da Unity die importierten Animationen nur als “read” abspeichert, diese kann man also nicht bearbeiten. Eine Lösung dazu ist, dass man die importierte Animation einfach kopiert und dann gehts. Super - merci an diesen super Workflow :).

Als der Loop funktioniert hat, stockte er ein wenig. Wir haben also an den Keyframes herumgespielt und viele Einstellungen angepasst (transition time, exit time etc.). Wir haben an den Reglern gedreht bis wir mit der Animation zufrieden waren.

Level Design

Wie bereits erwähnt, haben wir zu Beginn des Projektes das Level gezeichnet. Da wir leider nicht alle geplanten Aspekte einbauen konnten, mussten wir einige Dinge abändern. Wir haben uns gemeinsam im Onlinecall getroffen und das Level aufgebaut. Zuerst alle Inseln platziert, dann getestet. Dann den Wolkenparkour platziert und immer wieder getestet, ob die Sprünge möglich sind. Dann die Pendel zum Schluss des Levels platziert und getestet ob es möglich ist, diese zu umgehen. Nach dem ganzen Testen haben wir nach und nach die Assets (Gebüsche, Steine, Bäume, Wasser etc.) platziert.  Dies war reine Fleissarbeit, da man auch bei diesem Schritt immer wieder testen und schauen musste, dass alles gut aussieht und passt.

Der Hintergrund besteht aus einem Gradient (von Weiss zu blau) und einem Wolkenmodell, das verschieden skaliert ist. Jede Wolke hat eine eigene, leichte Animation, um dem Hintergrund eine gewisse dynamik zu verleihen. Zum Schluss haben wir dem mit einem Depth of Field Effekt und einem Color Grading noch den Feinschliff verpasst.

Export

Für den Export in Unity muss man eine Erweiterung (WebGL) installieren. Danach ist es eine einfache Sache. Man klickt auf "build" und kriegt einen Ordner, den man auf den gewünschten Webserver laden kann. Dies ist wirklich sehr Benutzerfreundlich und einfach gestaltet. Es gibt auch andere "buildoptions", z.B. als .exe für Windows oder als Mobilegame. Wir haben uns jedoch explizit für eine Online-Veröffentlichung entschieden.

Equipment

  • Unity
  • Blender
  • Atom

Fazit

Wir sind zufrieden mit dem Endprodukt, hätten aber gerne noch einige Levelelemente hinzugefügt. Dies war aber sehr schwierig, aufgrund der (für uns) neuen Programmiersprache C#. Wir mussten sehr viel googeln, Videos schauen, manuals und Forumthreads lesen und am Schluss hat es dann doch nicht wie gewünscht geklappt. Zum Schluss können wir aber sagen, dass wir froh sind, soweit gekommen zu sein. Für zukünftige Projekte in dieser Art sind wir jedoch gerüstet. Eine Schwierigkeit war auch unser “Spielformat”, 2.5D. Viele Tutorials sind entweder für 2D oder für 3D vorgesehen, so mussten wir immer umdenken und konnten leider auch nicht alles lösen.

Aufgrund von Visualisieren 3 und 4 hatten wir schon Erfahrungen mit 3D-Modeling. Wir sind aber sehr zufrieden, wie die Models herausgekommen sind und wie das Level aussieht. Wir werden in Zukunft sicher noch mehr mit 3D-Programmen arbeiten, jedoch wahrscheinlich lieber mit Cinema4D, als mit Blender, da der Workflow unserer Meinung nach besser ist.

Keine Kommentare

Schreibe einen Kommentar