Hoe een Prince-of-Persia-stijl Time-Rewind-systeem te bouwen, deel 1

Wat je gaat creëren

In deze tutorial bouwen we een eenvoudig spel waarin de speler kan spelen rewind vooruitgang in Unity (het kan ook worden aangepast om in andere systemen te werken). Dit eerste deel zal ingaan op de basis van het systeem, en het volgende deel zal het uitwerken en het veel veelzijdiger maken.

Eerst zullen we echter kijken naar welke games dit gebruiken. Vervolgens bekijken we de andere toepassingen voor deze technische setup, voordat we uiteindelijk een klein spel maken dat we kunnen terugspoelen, wat u een basis voor uw eigen spel zou moeten geven..

Een demonstratie van de basisfunctionaliteit

Hiervoor hebt u de nieuwste versie van Unity nodig en er moet enige ervaring mee zijn. De broncode is ook beschikbaar om te downloaden als je je eigen voortgang ertegen wilt controleren.

Klaar? Laten we gaan!

Hoe is dit eerder gebruikt?

Prince of Persia: The Sands of Time is een van de eerste games die echt een tijd-opwindende monteur in zijn gameplay heeft geïntegreerd. Als je sterft, hoef je niet alleen opnieuw te laden, maar kun je het spel een paar seconden terugspoelen tot waar je weer in leven was, en onmiddellijk opnieuw proberen.

Prince of Persia: The Forgotten Sands. De Sands Of Time Trilogy integreert de tijdomwikkeling prachtig in de gameplay en vermijdt onderdompeling snel herladen.

Deze monteur is niet alleen geïntegreerd in de gameplay, maar ook in het verhaal en het universum en wordt in het hele verhaal genoemd.

Andere games die deze systemen gebruiken zijn Vlecht, bijvoorbeeld, die ook gecentreerd is rond het opwinden van de tijd. De held Tracer in Overwatch heeft een kracht die haar terugbrengt tot een positie een paar seconden geleden, in wezen terugspoelen haar tijd, zelfs in een multiplayer-spel. De GRID-een reeks racegames heeft ook een snapshot-monteur, waar je tijdens een race een kleine pool van rewinds hebt, die je kunt gebruiken als je een kritieke crash hebt. Dit voorkomt frustratie veroorzaakt door crashes aan het einde van de race, wat bijzonder irritant kan zijn.


Wanneer je een fatale crash hebt GRID je krijgt de kans om het spel terug te spoelen tot een punt voor de crash.

Andere gebruiken

Maar dit systeem kan niet alleen worden gebruikt om quick-saving te vervangen. Een andere manier om dit te gebruiken is ghosting in racegames en asynchrone multiplayer.

herhalingen

Herhalingen zijn een andere leuke manier om deze functie te gebruiken. Dit is te zien in games zoals SUPER HEET, de wormen series, en vrijwel de meeste sportgames.

Herhalingen voor sport werken op dezelfde manier als ze worden weergegeven op tv, waar een actie opnieuw wordt getoond, mogelijk vanuit een andere hoek. Hiervoor wordt geen video opgenomen, maar eerder de acties van de gebruiker, waardoor de herhaling verschillende camerahoeken en -opnamen mogelijk maakt. De Worms-spellen gebruiken dit op een humoristische manier, waar zeer komische of effectieve moorden worden getoond in een Instant Replay.

SUPERHOT registreert ook uw beweging. Wanneer je klaar bent met spelen, wordt je hele voortgang vervolgens opnieuw gespeeld, waarbij de paar seconden worden getoond van de daadwerkelijke beweging die is gebeurd.

Super Meat Boy gebruikt dit op een leuke manier. Wanneer je een level hebt voltooid, zie je een replay van al je eerdere pogingen die bovenop elkaar gelegd zijn, met als hoogtepunt dat je finishrun de laatste links is.

De eind-van-niveau replay in Super Meat Boy. Elk van uw eerdere pogingen wordt opgenomen en vervolgens tegelijkertijd afgespeeld.

Time-Trial Ghosts

Race-Ghosting is een techniek waarbij je racet voor de beste tijd op een lege baan. Maar tegelijkertijd race je tegen een geest, dat is een spookachtige, transparante auto, die exact dezelfde manier bestuurt waarop je eerder hebt gereden tijdens je beste poging. Je kunt er niet mee botsen, wat betekent dat je je nog steeds kunt concentreren op het krijgen van de beste tijd.

In plaats van alleen te rijden krijg je concurreren tegen jezelf, wat tijdritten veel leuker maakt. Deze functie verschijnt in de meeste racegames, van de Need for Speed serie naar Diddy Kong Racing.

Een geest binnen rennen Trackmania Nations. Deze heeft de zilveren moeilijkheidsgraad, wat betekent dat ik de zilveren medaille krijg als ik ze versla. Let op de overlap in automodellen, waaruit blijkt dat de geest niet lichamelijk is en kan worden doorgereden.

Multiplayer-Ghosts

Asynchrone Multiplayer-Ghosting is een andere manier om deze setup te gebruiken. In deze zelden gebruikte functie worden multiplayer-matches bereikt door de gegevens van één speler te registreren, die vervolgens hun run naar een andere speler stuurt, die vervolgens kan vechten tegen de eerste speler. De gegevens worden op dezelfde manier toegepast als een time-trial-geest zou zijn, alleen dat je tegen een andere speler racet.

Een vorm hiervan komt naar voren in de Trackmania-spellen, waar het mogelijk is om tegen bepaalde moeilijkheden te racen. Deze opgenomen racers geven je een te kloppen tegenstander voor een bepaalde beloning.

Filmbewerkingsopties

Weinig games bieden dit vanaf het begin, maar als het wordt gebruikt, kan het een leuk hulpmiddel zijn.Team Fortress 2 biedt een ingebouwde replay-editor, waarmee je je eigen clips kunt maken.

De replay-editor van Team Fortress 2. Eenmaal opgenomen kan een wedstrijd vanuit elk perspectief worden bekeken, niet alleen van de speler.

Nadat de functie is geactiveerd, kunt u eerdere wedstrijden opnemen en bekijken. Het vitale element is dat alles wordt opgenomen, niet alleen uw mening. Dit betekent dat je door de opgenomen spelwereld kunt bewegen en zien waar iedereen zich bevindt, en controle hebben over de tijd.

Hoe het te bouwen

Om dit systeem te testen, hebben we een eenvoudig spel nodig waar we het kunnen testen. Laten we er een maken!

De speler

Maak een kubus in je scene, dit wordt ons speler-personage. Maak vervolgens een nieuwe C # -script-oproepen Player.cs en pas het aan Bijwerken()-functie om er zo uit te zien:

void Update () transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical")); transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal")); 

Hiermee kunt u eenvoudig bewegen met de pijltjestoetsen. Bevestig dit script aan de speler-kubus. Als je nu op play klikt, zou je al in staat moeten zijn om te bewegen.

Richt de camera vervolgens zo dat deze de kubus van bovenaf bekijkt, met een kamer op zijn kant waar we hem kunnen verplaatsen. Maak ten slotte een vliegtuig om op te treden als verdieping en wijs een aantal verschillende materialen toe aan elk object, zodat we het niet binnen een leegte verplaatsen. Het zou er zo uit moeten zien:

Probeer het en je zou je kubus kunnen verplaatsen met behulp van de WSAD en de pijltjestoetsen.

De TimeController

Maak nu een nieuw C # -script genaamd TimeController.cs en voeg het toe aan een nieuw leeg GameObject. Dit zal de feitelijke opname en daaropvolgende terugspoelen van het spel afhandelen.

Om dit te laten werken, registreren we de beweging van het personage van de speler. Als we vervolgens op de terugspoelknop drukken, passen we de coördinaten van de speler aan. Om dit te doen, begin met het maken van een variabele om de speler vast te houden, zoals deze:

openbare GameObject-speler;

En wijs het speler-object toe aan de resulterende slot op de TimeController, zodat deze toegang heeft tot de speler en zijn gegevens.

Dan moeten we een array maken om de spelersgegevens te bewaren:

public ArrayList playerPositions; void Start () playerPositions = new ArrayList (); 

Wat we hierna zullen doen is continu de positie van de speler vastleggen. We hebben de positie opgeslagen van waar de speler was in het laatste frame, de positie waar de speler 6 frames geleden was en de positie waar de speler 8 seconden geleden was (of hoe lang je hem ook op wilt nemen). Wanneer we later op een knop drukken, gaan we achteruit door onze reeks posities en bepalen deze frame voor frame, wat resulteert in een tijdherwikkelfunctie.

Laten we eerst de gegevens opslaan:

void FixedUpdate () playerPositions.Add (player.transform.position); 

In de FixedUpdate ()-functie registreren we de gegevens. FixedUpdate () wordt gebruikt omdat het draait met een constante 50 cycli per seconde (of waar je het ook op zet), wat een vast interval toestaat om de gegevens op te slaan en in te stellen. De Bijwerken()-De functie wordt ondertussen uitgevoerd afhankelijk van het aantal frames dat de CPU beheert, waardoor dingen moeilijker worden.

Deze code slaat de spelerpositie van elk frame in de array op. Nu moeten we het toepassen!

We voegen een vinkje toe om te zien of de knop Terugspoelen is ingedrukt. Hiervoor hebben we een Booleaanse variabele nodig:

public bool isReversing = false;

En een cheque in de Bijwerken()-functie om het in te stellen op basis van het feit of we de gameplay willen terugspoelen:

void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true;  else isReversing = false; 

Om het spel te laten lopen achterwaarts, we zullen de gegevens toepassen in plaats van opnemenDe nieuwe code voor het opnemen en toepassen van de positie van de speler moet er als volgt uitzien:

void FixedUpdate () if (! isReversing) playerPositions.Add (player.transform.position);  else player.transform.position = (Vector3) playerPositions [playerPositions.Count - 1]; playerPositions.RemoveAt (playerPositions.Count - 1); 

En het geheel TimeController-script zoals dit:

gebruikmakend van UnityEngine; met behulp van System.Collections; public class TimeController: MonoBehaviour public GameObject player; public ArrayList playerPositions; public bool isReversing = false; void Start () playerPositions = new ArrayList ();  void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true;  else isReversing = false;  void FixedUpdate () if (! isReversing) playerPositions.Add (player.transform.position);  else player.transform.position = (Vector3) playerPositions [playerPositions.Count - 1]; playerPositions.RemoveAt (playerPositions.Count - 1); 

Vergeet ook niet om een ​​vinkje te zetten bij de speler-klasse om te zien of het TimeController is op dit moment aan het terugspoelen of niet, en beweegt alleen als het niet omkeert. Anders zou het buggy-gedrag kunnen veroorzaken:

gebruikmakend van UnityEngine; met behulp van System.Collections; openbare klasse Player: MonoBehaviour private TimeController timeController; void Start () timeController = FindObjectOfType (typeof (TimeController)) als TimeController;  void Update () if (! timeController.isReversing) transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical")); transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal"));  

Deze nieuwe regels vinden automatisch de TimeController-object in de scène tijdens het opstarten en controleer het tijdens runtime om te zien of we het spel momenteel spelen of terugspoelen. We kunnen het personage alleen besturen als we op dit moment geen tijd terugdraaien.

Nu zou je in staat moeten zijn om de wereld rond te gaan en je beweging terug te spoelen door op spatie te drukken. Als u het buildpakket dat aan dit artikel is gekoppeld downloadt en opent TimeRewindingFunctionality01 je kunt het uitproberen!

Maar wacht, waarom blijft onze eenvoudige speler-kubus in de laatste richting kijken waarin we ze hebben achtergelaten? Omdat we niet in de buurt zijn om ook de rotatie ervan vast te leggen!

Daarvoor hebt u een andere array nodig om de rotatiewaarden te behouden, om deze aan het begin te instantiëren en om de gegevens op te slaan en toe te passen op dezelfde manier als waarop we positiegegevens hebben verwerkt.

gebruikmakend van UnityEngine; met behulp van System.Collections; public class TimeController: MonoBehaviour public GameObject player; public ArrayList playerPositions; openbare ArrayList-spelersRotaties; public bool isReversing = false; void Start () playerPositions = new ArrayList (); playerRotations = new ArrayList ();  void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true;  else isReversing = false;  void FixedUpdate () if (! isReversing) playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);  else player.transform.position = (Vector3) playerPositions [playerPositions.Count - 1]; playerPositions.RemoveAt (playerPositions.Count - 1); player.transform.localEulerAngles = (Vector3) playerRotations [playerRotations.Count - 1]; playerRotations.RemoveAt (playerRotations.Count - 1); 

Probeer het! TimeRewindingFunctionality02 is de verbeterde versie. Nu kan onze speler-kubus in de tijd achteruitgaan en er op dezelfde manier uitzien als op dat moment.

Conclusie

We hebben een eenvoudig prototypegame gebouwd met een al bruikbaar tijd-opwikkelsysteem, maar het is nog lang niet klaar. In het volgende deel van deze serie zullen we het veel stabieler en veelzijdiger maken en enkele nette effecten toevoegen. 

Dit is wat we nog moeten doen:

  • Neem alleen elk ~ 12e frame op en interpoleer tussen de opgenomen beelden om te besparen op de enorme hoeveelheid gegevens
  • Noteer alleen de laatste ~ 75 posities van de speler en rotaties om te zorgen dat de array niet te log wordt en de game niet crasht

We zullen ook kijken hoe dit systeem verder te gaan dan alleen het speler-personage:

  • Neem meer op dan alleen de speler
  • Voeg een effect toe om aan te geven dat terugspoelen plaatsvindt (zoals VHS-vervaging)
  • Gebruik een aangepaste klasse om de positie en rotatie van de speler te behouden in plaats van arrays