Hvordan bygge et Prince-of-Persia-Style Time-Rewind System, del 2

Hva du skal skape

Sist gang vi opprettet et enkelt spill hvor vi kan spole tiden tilbake til et tidligere punkt. Nå vil vi størkne denne funksjonen og gjøre det mye morsommere å bruke.

Alt vi gjør her vil bygge på forrige del, så sjekk det! Som før, trenger du Enhet og en grunnleggende forståelse av det.

Klar? La oss gå!

Legg inn færre data og interpolere

Akkurat nå registrerer vi spillernes posisjoner og rotasjoner 50 ganger et sekund. Denne mengden data blir raskt uholdbar, og dette vil bli spesielt merkbar med mer komplekse spilloppsett og mobile enheter med mindre prosessorkraft.

Men det vi kan gjøre i stedet, er bare plate 4 ganger i sekundet og interpolere mellom de keyframes. På den måten lagrer vi 92% av prosessbåndbredden, og får resultater som ikke skiller seg fra 50-bildene, da de spiller ut i brøkdeler på en sekund.

Vi starter med å bare ta opp en keyframe hver x-ramme. For å gjøre dette trenger vi først disse nye variablene:

offentlig int keyframe = 5; privat int rammeCounter = 0;

Variabelen keyframe er rammen i FixedUpdate Metode hvor vi vil registrere spillerdataene. For tiden er den satt til 5, som betyr hver femte gang den FixedUpdate Metoden sykler gjennom, dataene blir registrert. Som FixedUpdate kjører 50 ganger per sekund, dette betyr at 10 rammer blir registrert per sekund, sammenlignet med 50 før. Variabelen frameCounter vil bli brukt til å telle rammene til neste keyframe.

Nå tilpass innspillingsblokken i FixedUpdate funksjon for å se slik ut:

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

Hvis du prøver det nå, vil du se at tilbakespolingen tar del på en mye kortere tid enn før. Dette skyldes at vi registrerte mindre data, men spilte den tilbake med vanlig hastighet. Nå må vi endre det.

Først trenger vi en annen frameCounter variabel for å holde styr på ikke å registrere dataene, men å spille den tilbake.

privat int reverseCounter = 0;

Tilpass koden som gjenoppretter spillerens posisjon for å utnytte dette på samme måte som vi registrerer dataene. De FixedUpdate funksjonen skal da se slik ut:

void FixedUpdate () hvis (! erReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);   else  if(reverseCounter > 0) reverseCounter - = 1;  ellers 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 spole tid nå, hopper spilleren tilbake til sine tidligere posisjoner, i sanntid!

Det er ikke helt det vi vil ha, skjønt. Vi må interpolere mellom de keyframes, som vil være litt vanskeligere. Først trenger vi disse fire variablene:

privat Vector3 currentPosition; privat Vector3 previousPosition; privat Vector3 currentRotation; privat Vector3 previousRotation;

De vil lagre gjeldende spillerdata og den fra den innspilte keyframe før det, slik at vi kan interpolere mellom de to.

Da trenger vi denne funksjonen:

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

Dette vil tilordne tilsvarende informasjon til posisjons- og rotasjonsvariablene som vi skal interpolere mellom. Vi trenger dette i en egen funksjon, som vi kaller den på to forskjellige steder.

Vår datarestaureringsblokk skal se slik ut:

hvis (reverseCounter> 0) reverseCounter - = 1;  ellers reverseCounter = keyframe; RestorePositions ();  hvis (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (forrige posisjon, nåværende posisjon, interpolering); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);

Vi kaller funksjonen for å få de siste og andre til siste informasjonssettene fra våre arrays når telleren når det keyframe-intervallet vi har satt (i vårt tilfelle 5), men vi må også ringe det på første syklus når gjenopprettingen skjer. Det er derfor vi har denne blokken:

hvis (firstRun) firstRun = false; RestorePositions (); 

For at dette skal fungere, trenger du også første forsøk variabel:

privat bool firstRun = true;

Og for å nullstille det når mellomromstasten er løftet:

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

Slik fungerer interpoleringen:

I stedet for bare å bruke den siste keyframe vi lagret, blir systemet sist og nest til siste og interpolerer mellom dem. Mengden interpolering er basert på hvor langt mellom rammene vi er for øyeblikket. 

Alt dette skjer via Lerp-funksjonen, hvor vi legger til gjeldende posisjon (eller rotasjon) og den forrige. Deretter beregnes brøkdel av interpoleringen, som kan gå fra 0 til 1. Deretter plasseres spilleren på det tilsvarende stedet mellom de to lagrede punktene, for eksempel 40% på ruten til den siste keyframe.

Når du setter den ned og spiller den i ramme for ramme, kan du faktisk se spilleren-tegnet flytte mellom de keyframes, men i spill er det ikke merkbart.

Og dermed har vi sterkt redusert kompleksiteten til tidspolingsoppsettet og gjort det mye mer stabilt.

Bare ta opp et fast antall nøkkelrammer

Nå som vi har redusert antall bilder vi faktisk lagrer, kan vi sørge for at vi ikke lagrer for mye data.

Akkurat nå hoper vi bare innspillte dataene inn i arrayet, som ikke vil gjøre langsiktige. Etter hvert som arrayet vokser, blir det mer uhåndterlig, tilgangen vil ta lengre tid, og hele oppsettet blir mer ustabilt.

For å fikse dette, kan vi sette inn kode som kontrollerer om arrayet har vokst over en viss størrelse. Hvis vi vet hvor mange rammer per sekund vi lagrer, kan vi avgjøre hvor mange sekunder med tilbakespolingstid vi skal lagre, og hva som passer vårt spill og dets kompleksitet. Den noe komplekse Prins av Persia muliggjør kanskje 15 sekunder med tilbaketrukket tid, mens det enklere oppsettet av Flette muliggjør ubegrenset tilbakespoling.

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

Hva som skjer er at når matrisen vokser over en viss størrelse, fjerner vi den første oppføringen av den. Derved forblir det bare så lenge vi vil at spilleren skal spole tilbake, og det er ingen fare for at den blir for stor til å bruke effektivt. Sett dette i FixedUpdate funksjon etter opptak og replaying kode.

Bruk en egendefinert klasse for å holde spillerdata

Akkurat nå registrerer vi spillernes posisjoner og rotasjoner i to separate arrays. Selv om dette gjør det, må vi huske å alltid registrere og få tilgang til dataene på to steder samtidig, noe som har potensial for fremtidige problemer.

Hva vi kan gjøre, imidlertid. er å lage en egen klasse for å holde begge disse tingene, og muligens enda mer (hvis det skulle være nødvendig i prosjektet ditt).

Koden for en egendefinert klasse for å fungere som container for dataene ser slik ut:

offentlig klasse Keyframe offentlig Vector3 posisjon; offentlig Vector3-rotasjon; offentlig Keyframe (Vector3-posisjon, Vector3-rotasjon) this.position = position; this.rotation = rotation; 

Du kan legge den til TimeController.cs-filen, like før klassedeklarasjonen starter. Hva det gjør er å gi en beholder for å lagre både posisjon og rotasjon av spilleren. Konstruksjonsmetoden tillater det å bli direkte opprettet med nødvendig informasjon.

Resten av algoritmen må tilpasses for å arbeide med det nye systemet. I Start-metoden må arrayet initialiseres:

keyframes = new ArrayList ();

Og i stedet for å si:

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

Vi kan lagre det direkte i et Keyframe-objekt:

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

Det vi gjør her er å legge til posisjon og rotasjon av spilleren i samme objekt, som deretter blir lagt til i et enkelt array, noe som i stor grad reduserer kompleksiteten til dette oppsettet.

Legg til et uskarpt effekt for å signifikere at tilbakevending skjer

Vi trenger drastisk en slags signifier som forteller oss at spillet for øyeblikket er rewound. Akkurat nå, vi vet dette, men en spiller kan være forvirret. I slike situasjoner er det godt å ha flere ting som forteller spilleren som spoling skjer, som visuelt (via hele skjermen slør litt) og lyd (ved å senke og reversere musikken).

La oss gjøre noe som ligner på hvordan Prins av Persia gjør det, og legg til litt uskarphet.

Tidspoling fra Prince of Persia: The Forgotten Sands

Enhet gir deg mulighet til å legge til flere kameraeffekter på toppen av hverandre, og med noen eksperimenter kan du lage en som passer perfekt til prosjektet ditt..

Før vi kan bruke de grunnleggende effektene, må vi importere dem. For å gjøre dette, gå til Eiendeler> Importpakke> Effekter, og importere alt som tilbys til deg.

Visuelle effekter kan legges direkte til hovedkameraet. Gå til Komponenter> Bildeffekter og legg til en Uklarhet og a Bloom effekt. Kombinasjonen av disse to skal gi en fin effekt for det vi skal til.

Dette er de grunnleggende innstillingene. Du kan justere dem for å bedre passe ditt prosjekt.

Når du prøver det nå, vil spillet ha denne effekten hele tiden.

Nå må vi aktivere den og deaktivere den henholdsvis. For det, den TimeController må importere bildeeffekter. Legg til denne linjen helt til begynnelsen:

bruker UnityStandardAssets.ImageEffects;

For å få tilgang til kameraet fra TimeController, legg til denne variabelen:

privat kamera kamera;

Og tilordne det i Start funksjon:

kamera = Camera.main;

Legg deretter til denne koden for å aktivere effektene under tilbakespolingstid, og få dem aktivert ellers:

ugyldig oppdatering () 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 trykker på mellomromstasten, spoler du ikke bare scenen, men du aktiverer også tilbakespolingseffekten på kameraet, fortelle spilleren at noe skjer.

Hele koden til TimeController skal se slik ut:

bruker UnityEngine; bruker System.Collections; bruker UnityStandardAssets.ImageEffects; offentlig klasse Keyframe offentlig Vector3 posisjon; offentlig Vector3-rotasjon; offentlig Keyframe (Vector3-posisjon, Vector3-rotasjon) this.position = position; this.rotation = rotation;  offentlig klasse TimeController: MonoBehaviour offentlig GameObject player; offentlige ArrayList keyframes; offentlig bool erReversing = false; offentlig int keyframe = 5; privat int rammeCounter = 0; privat int reverseCounter = 0; privat Vector3 currentPosition; privat Vector3 previousPosition; privat Vector3 currentRotation; privat Vector3 previousRotation; privat kamera kamera; privat bool firstRun = true; void Start () keyframes = new ArrayList (); kamera = Camera.main;  ugyldig oppdatering () hvis (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 () hvis (! erReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; keyframes.Add(new Keyframe(player.transform.position, player.transform.localEulerAngles));   else  if(reverseCounter > 0) reverseCounter - = 1;  ellers reverseCounter = keyframe; RestorePositions ();  hvis (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (forrige posisjon, nåværende posisjon, interpolering); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);  hvis (keyframes.Count> 128) keyframes.RemoveAt (0);  void RestorePositions () int lastIndex = keyframes.Count - 1; int secondToLastIndex = keyframes.Count - 2; hvis (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); 

Last ned den vedlagte byggepakken og prøv den ut!

Konklusjon

Vårt tidspinspill er nå mye bedre enn før. Algoritmen er merkbart forbedret og bruker 90% mindre prosessorkraft, den er mye mer stabil, og vi har en fin signifier som forteller oss at vi for tiden spoler tid.

Nå gjør du et spill med dette!