Hur man bygger ett Prince-of-Persia-Style Time-Rewind System, del 2

Vad du ska skapa

Förra gången skapade vi ett enkelt spel där vi kan spola tillbaka tiden till en tidigare punkt. Nu stärker vi denna funktion och gör det mycket roligare att använda.

Allt vi gör här kommer att bygga på föregående del, så gå och kolla! Som tidigare behöver du enighet och en grundläggande förståelse för den.

Redo? Nu går vi!

Spela in mindre data och interpolera

Just nu registrerar vi spelarens positioner och rotationer 50 gånger en sekund. Denna mängd data kommer snabbt att bli ohållbar, och det blir särskilt märkbart med mer komplexa speluppsättningar och mobila enheter med mindre bearbetningskraft.

Men vad vi kan göra i stället är bara ett register 4 gånger i sekundet och interpolerar mellan dessa nyckelbilder. På det sättet sparar vi 92% av bearbetningsbandbredden och får resultat som inte kan skilja sig från 50-bildinspelningarna, eftersom de spelar ut inom en sekunds bråkdel.

Vi börjar med att bara spela in en nyckelram varje x-ram. För att göra detta behöver vi först dessa nya variabler:

allmän int keyframe = 5; privat int ramCounter = 0;

Variabeln nyckelbildruta är ramen i FixedUpdate metod där vi spelar in spelarens data. För närvarande är den inställd på 5, vilket betyder var femte gången FixedUpdate Metoden cyklar igenom, data kommer att spelas in. Som FixedUpdate kör 50 gånger per sekund, det vill säga 10 bilder registreras per sekund, jämfört med 50 tidigare. Variabeln frameCounter kommer att användas för att räkna ramarna fram till nästa nyckelram.

Anpassa nu inspelningsblocket i FixedUpdate funktion att se så här ut:

om (! ärReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);  

Om du försöker ut det nu ser du att återspolningen deltar på mycket kortare tid än tidigare. Detta beror på att vi spelade in mindre data, men spelade tillbaka med jämna mellanrum. Nu behöver vi ändra det.

Först behöver vi en annan frameCounter variabel för att hålla reda på att inte spela in data, men att spela upp den igen.

privat int reverseCounter = 0;

Anpassa koden som återställer spelarens position för att utnyttja detta på samma sätt som vi registrerar data. De FixedUpdate funktionen ska då se ut så här:

void FixedUpdate () if (! isReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);   else  if(reverseCounter > 0) reverseCounter - = 1;  annars 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); reverseCounter = keyframe; 

När du spolar tid nu kommer spelaren att hoppa tillbaka till sina tidigare positioner, i realtid!

Det är dock inte vad vi vill ha. Vi måste interpolera mellan dessa nyckelbilder, vilket blir lite svårare. Först behöver vi dessa fyra variabler:

privat Vector3 currentPosition; privat vektor3 tidigare position privat Vector3 currentRotation; privat Vector3 previousRotation;

De sparar nuvarande spelardata och den från inspelade keyframe före det så att vi kan interpolera mellan de två.

Då behöver vi den här funktionen:

void RestorePositions () int lastIndex = keyframes.Count - 1; int secondToLastIndex = keyframes.Count - 2; om (secondToLastIndex> = 0) currentPosition = (Vector3) playerPositions [lastIndex]; previousPosition = (Vector3) playerPositions [secondToLastIndex]; playerPositions.RemoveAt (lastindex); currentRotation = (Vector3) playerRotations [lastIndex]; previousRotation = (Vector3) playerRotations [secondToLastIndex]; playerRotations.RemoveAt (lastindex); 

Detta kommer att tilldela motsvarande information till position- och rotationsvariablerna som vi ska interpolera mellan. Vi behöver det i en separat funktion, som vi kallar den på två olika ställen.

Vårt dataåterställningsblock ska se ut så här:

om (reverseCounter> 0) reverseCounter - = 1;  annars reverseCounter = keyframe; RestorePositions ();  om (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (tidigarePosition, nuvarandePosition, interpolation); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);

Vi kallar funktionen för att få de sista och näst sista informationssätten från våra arrays när räknaren når det nyckelramsintervall vi har satt (i vårt fall 5), men vi måste också ringa det på första cykeln när återställningen sker. Det är därför vi har det här blocket:

om (firstRun) firstRun = false; RestorePositions (); 

För att detta ska kunna fungera behöver du också första omgången variabel:

privat bool firstRun = true;

Och för att återställa det när rymdknappen lyftes:

om (Input.GetKey (KeyCode.Space)) isReversing = true;  else isReversing = false; firstRun = true; 

Så här fungerar interpoleringen:

Istället för att bara använda den senaste keyframen vi sparade, blir systemet sist och näst sista och interpolerar mellan dem. Mängden interpolering baseras på hur långt mellan de ramar vi för närvarande är. 

Allt detta händer via Lerp-funktionen, där vi lägger till aktuell position (eller rotation) och den föregående. Då beräknas fraktionen av interpolationen, vilken kan gå från 0 till 1. Sedan placeras spelaren på motsvarande plats mellan de två sparade punkterna, till exempel 40% på vägen till den sista keyframen.

När du saktar ner den och spelar den i ram för bild kan du faktiskt se spelarens karaktär flytta mellan dessa nyckelbilder, men i spel är det inte märkbart.

Och sålunda har vi kraftigt minskat komplexiteten i tidspolningsinstallationen och gjort den mycket stabilare.

Ange bara ett fast antal nyckelramar

Nu när vi kraftigt har minskat antalet ramar som vi faktiskt sparar, kan vi se till att vi inte sparar för mycket data.

Just nu hämtar vi bara de inspelade dataen i arrayen, vilket inte kommer att göra på lång sikt. När matrisen växer blir den mer otrygg, åtkomst tar längre tid och hela installationen blir mer instabil.

För att fixa detta kan vi införa kod som kontrollerar om matrisen har ökat över en viss storlek. Om vi ​​vet hur många bilder per sekund vi sparar, kan vi bestämma hur många sekunder återspolningsbar tid vi ska spara, och vad som passar vårt spel och dess komplexitet. Det något komplexa Persiens Prins möjliggör kanske 15 sekunder av återspolningsbar tid, medan den enklare inställningen av Fläta möjliggör obegränsad återspolning.

om (playerPositions.Count> 128) playerPositions.RemoveAt (0); playerRotations.RemoveAt (0); 

Vad händer är att när matrisen växer över en viss storlek tar vi bort den första posten av den. Således är det bara så länge vi vill att spelaren ska spola tillbaka, och det finns ingen fara att det blir för stort att använda effektivt. Sätt detta i FixedUpdate funktion efter inspelning och uppspelningskod.

Använd en anpassad klass för att hålla spelardata

Just nu spelar vi in ​​spelarpositionerna och rotationerna i två separata arrays. Även om detta gör det, måste vi komma ihåg att alltid spela in och komma åt data på två ställen samtidigt, vilket har potential för framtida problem.

Vad vi kan göra, dock. skapar en separat klass för att hålla båda dessa saker och eventuellt ännu mer (om det borde vara nödvändigt i ditt projekt).

Koden för en anpassad klass för att fungera som behållare för data ser så här ut:

public class Keyframe public Vector3 position; allmän vektor3 rotation; allmän nyckelram (vektor3 position, vektor3 rotation) this.position = position; this.rotation = rotation; 

Du kan lägga till den i TimeController.cs-filen, precis innan klassdeklarationen startar. Vad det gör är att ge en behållare för att spara både spelarens läge och rotation. Konstruktormetoden gör att den kan skapas direkt med nödvändig information.

Resten av algoritmen måste anpassas för att fungera med det nya systemet. I startmetoden måste arrayen initieras:

keyframes = new ArrayList ();

Och istället för att säga:

playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);

Vi kan spara det direkt i ett Keyframe-objekt:

keyframes.Add (ny Keyframe (player.transform.position, player.transform.localEulerAngles));

Vad vi gör här är att lägga till läget och rotationen av spelaren i samma objekt, som sedan läggs till i en enda grupp, vilket väsentligt minskar komplexiteten i denna inställning.

Lägg till en suddig effekt för att indikera att återspolning sker

Vi behöver drastiskt en typ av signifierare som berättar att spelet är på väg att återspolas. Just nu, vi vet det här, men en spelare kan vara förvirrad. I sådana situationer är det bra att ha flera saker som berättar för spelaren som återspolning sker, som visuellt (via hela skärmen suddas lite) och ljud (genom att musiken saktar ner och vänder).

Låt oss göra något liknande hur Persiens Prins gör det och lägg till lite suddighet.

Tidspolning från Prince of Persia: The Forgotten Sands

Enhet ger dig möjlighet att lägga till flera kameraflöden ovanpå varandra, och med lite experiment kan du skapa en som passar ditt projekt perfekt.

Innan vi kan använda de grundläggande effekterna måste vi importera dem. För att göra detta, gå till Tillgångar> Importpaket> Effekter, och importera allt som erbjuds dig.

Visuella effekter kan läggas direkt till huvudkamera. Gå till Komponenter> Bildeffekter och lägg till en Fläck och a Blomma effekt. Kombinationen av dessa två borde ge en fin effekt för vad vi ska göra.

Det här är de grundläggande inställningarna. Du kan justera dem för att bättre passa ditt projekt.

När du provar det nu kommer spelet att ha denna effekt hela tiden.

Nu måste vi aktivera det och deaktivera det respektive. För det är TimeController måste importera bildeffekterna. Lägg till den här raden till början:

använder UnityStandardAssets.ImageEffects;

För att komma åt kameran från TimeController, lägg till den här variabeln:

privat kamera kamera;

Och tilldela den i Start fungera:

kamera = Camera.main;

Lägg sedan till den här koden för att aktivera effekterna vid återspolningstid och aktivera dem annars:

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

När du trycker på mellanslagsknappen spolas du inte bara på scenen, utan aktiverar även spolaffekten på kameran, säger spelaren att något händer.

Hela koden för TimeController ska se så här ut:

använder UnityEngine; Använda System.Collections; använder UnityStandardAssets.ImageEffects; public class Keyframe public Vector3 position; allmän vektor3 rotation; allmän nyckelram (vektor3 position, vektor3 rotation) this.position = position; this.rotation = rotation;  public class TimeController: MonoBehaviour public GameObject player; offentliga ArrayList keyframes; allmän bool är Reversing = false; allmän int keyframe = 5; privat int ramCounter = 0; privat int reverseCounter = 0; privat Vector3 currentPosition; privat vektor3 tidigare position privat Vector3 currentRotation; privat Vector3 previousRotation; privat kamera kamera; privat bool firstRun = true; void Start () keyframes = new ArrayList (); kamera = Camera.main;  void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true; camera.GetComponent() .enabled = true; camera.GetComponent() .enabled = true;  else isReversing = false; firstRun = true; camera.GetComponent() .enabled = false; camera.GetComponent() .enabled = false;  void FixedUpdate () if (! isReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; keyframes.Add(new Keyframe(player.transform.position, player.transform.localEulerAngles));   else  if(reverseCounter > 0) reverseCounter - = 1;  annars reverseCounter = keyframe; RestorePositions ();  om (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (tidigarePosition, nuvarandePosition, interpolation); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);  om (keyframes.Count> 128) keyframes.RemoveAt (0);  void RestorePositions () int lastIndex = keyframes.Count - 1; int secondToLastIndex = keyframes.Count - 2; om (secondToLastIndex> = 0) currentPosition = (keyframes [lastIndex] som Keyframe) .position; previousPosition = (keyframes [secondToLastIndex] som Keyframe) .position; currentRotation = (keyframes [lastIndex] som Keyframe) .rotation; previousRotation = (keyframes [secondToLastIndex] som Keyframe) .rotation; keyframes.RemoveAt (lastindex); 

Ladda ner det bifogade byggpaketet och prova det!

Slutsats

Vårt tidspolningsspel är nu mycket bättre än tidigare. Algoritmen förbättras märkbart och använder 90% mindre bearbetningskraft, det är mycket stabilare och vi har en fin signifierare som berättar att vi för närvarande spolar tid.

Nu gör du ett spel med det här!