Cum se construiește un sistem Time-Rewind Prince-of-Persia, Partea 2

Ce veți crea

Ultima dată când am creat un simplu joc în care putem reveni la un moment anterior. Acum vom consolida această caracteristică și vom face mult mai distractiv de utilizat.

Tot ceea ce facem aici se va baza pe partea anterioară, deci mergeți! Ca și înainte, veți avea nevoie de Unitate și o înțelegere de bază a acesteia.

Gata? Sa mergem!

Înregistrați mai puține date și interpolați

În acest moment înregistrăm pozițiile și rotațiile jucătorului 50 ori pe secundă. Această cantitate de date va deveni rapid inaccesibilă și acest lucru va deveni deosebit de vizibil cu configurații mai complexe de jocuri și dispozitive mobile cu o putere de procesare mai mică.

Dar ceea ce putem face în schimb este doar record de 4 ori pe secundă și interpolează între aceste cadre cheie. În acest fel, salvăm 92% din lățimea de bandă de procesare și obținem rezultate care nu pot fi diferențiate de înregistrările de 50 de cadre, deoarece acestea se desfășoară în fracțiuni de secundă.

Vom începe doar prin înregistrarea unui cadru cheie la fiecare x cadre. Pentru a face acest lucru, avem mai întâi nevoie de aceste noi variabile:

public keyframe int = 5; private int frameCounter = 0;

Variabila cadru cheie este cadrul din FixedUpdate metodă la care vom înregistra datele jucătorului. În prezent, este setat la 5, ceea ce înseamnă a cincea oară FixedUpdate cicluri de metode, datele vor fi înregistrate. La fel de FixedUpdate rulează de 50 de ori pe secundă, ceea ce înseamnă că 10 cadre vor fi înregistrate pe secundă, comparativ cu 50 înainte. Variabila frameCounter va fi folosit pentru a număra cadrele până la următoarea cheie cheie.

Acum adaptați blocul de înregistrare în FixedUpdate funcția de a arăta astfel:

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

Dacă încercați acum, veți vedea că rebobinarea participă într-un timp mult mai scurt decât înainte. Acest lucru se datorează faptului că am înregistrat mai puține date, dar l-am redat la viteză regulată. Acum trebuie să schimbăm asta.

În primul rând, avem nevoie de altul frameCounter variabilă pentru a urmări nu înregistrarea datelor, ci redarea acestora înapoi.

private int reverseCounter = 0;

Adaptați codul care restabilește poziția jucătorului pentru a utiliza acest lucru în același mod în care înregistram datele. FixedUpdate funcția ar trebui să arate astfel:

void FixedUpdate () if (! isReversing) dacă (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);   else  if(reverseCounter > 0) reversCounter - = 1;  altceva player.transform.position = (Vector3) playerPozitii [playerPositions.Count - 1]; playerPositions.RemoveAt (jucatorPoziții.Count - 1); player.transform.localEulerAngles = (Vector3) jucatorRotații [playerRotations.Count - 1]; playerRotations.RemoveAt (playerRotări.Count - 1); reverseCounter = cheie cheie; 

Când derulați înapoi timpul, jucătorul va reveni la pozițiile anterioare, în timp real!

Dar nu este exact ceea ce vrem. Trebuie să interpolam între acele cadre cheie, care vor fi un pic mai complicate. În primul rând, avem nevoie de aceste patru variabile:

privat Vector3 currentPosition; privat Vector3 previousPosition; privat Vector3 curentRotare; privat Vector3 previousRotation;

Acestea vor salva datele actuale ale jucătorului și cele din cadrele cheie înregistrate înainte de aceasta, astfel încât să putem interpola între cele două.

Atunci avem nevoie de această funcție:

void RestorePositions () int lastIndex = cheie-cadru.Count - 1; int secondToLastIndex = keyframes.Count - 2; dacă (secondToLastIndex> = 0) currentPosition = (Vector3) playerPoziții [lastIndex]; previousPosition = (Vector3) playerPoziții [secondToLastIndex]; playerPositions.RemoveAt (lastIndex); currentRotation = (Vector3) playerRotări [lastIndex]; previousRotation = (Vector3) playerRotări [secondToLastIndex]; playerRotations.RemoveAt (lastIndex); 

Aceasta va aloca informațiile corespunzătoare variabilelor de poziție și de rotație pe care le vom interpola între ele. Avem nevoie de aceasta într-o funcție separată, așa cum o numim în două locuri diferite.

Blocul nostru de restaurare a datelor ar trebui să arate astfel:

dacă (inversăCounter> 0) reverseCounter - = 1;  altfel reverseCounter = cheie cheie; RestorePositions ();  dacă (firstRun) firstRun = false; RestorePositions ();  float interpolare = (float) reversCounter / (float) keyframe; player.transform.position = Vector3.Lerp (precedentPosition, currentPosition, interpolare); player.transform.localEulerAngles = Vector3.Lerp (precedentRotare, curentRotare, interpolare);

Apelam funcția pentru a obține ultimele și a doua până la ultima seturi de informații din matricele noastre ori de câte ori numărul ajunge la intervalul de cadre pe care l-am setat (în cazul nostru 5), dar trebuie să îl numim și în primul ciclu când se întâmplă restaurarea. De aceea avem acest bloc:

dacă (firstRun) firstRun = false; RestorePositions (); 

Pentru ca acest lucru să funcționeze, aveți nevoie de asemenea prima alergare variabil:

bool privat firstRun = adevărat;

Și pentru ao reseta la eliberarea butonului spațiu:

dacă (Input.GetKey (KeyCode.Space)) isReversing = true;  altceva isReversing = false; firstRun = adevărat; 

Iată cum funcționează interpolarea:

În loc să folosim ultima cheie de cadre pe care am salvat-o, acest sistem devine ultimul și cel de-al doilea în ultimul și interpolează între ele. Cantitatea de interpolare se bazează pe cât de mult se află între cadrele din prezent. 

Toate acestea se întâmplă prin intermediul funcției Lerp, unde adăugăm poziția curentă (sau rotația) și cea anterioară. Apoi se calculează fracția interpolării, de la care se poate trece 0 la 1. Apoi, jucătorul este plasat în locul echivalent dintre cele două puncte salvate, de exemplu, 40% pe traseul spre ultimul cadru cheie.

Când îl încetiniți și îl redați cu cadru, puteți să vedeți că personajul jucătorului se mișcă între acele cadre cheie, dar în modul de joc, nu este remarcabil.

Și astfel, am redus mult complexitatea configurației de redirecționare a timpului și l-am făcut mult mai stabilă.

Doar înregistrați un număr fix de cadre cheie

Acum că am redus foarte mult numărul de cadre pe care le salvăm, putem să ne asigurăm că nu salvăm prea multe date.

În momentul de față, vom strânge datele înregistrate în matrice, ceea ce nu se va face pe termen lung. Pe măsură ce matricea crește, va deveni mai greu, accesul va dura mai mult timp, iar întreaga configurație va deveni mai instabilă.

Pentru a rezolva acest lucru, putem institui un cod care verifică dacă matricea a crescut peste o anumită dimensiune. Dacă știm câte cadre pe secundă le salvăm, putem determina câte secunde de timp reîncărcabil ar trebui să salvăm și ce s-ar potrivi jocului nostru și complexității acestuia. Oarecum complexă Printul Persiei permite timp de 15 secunde de rewindable, în timp ce setarea mai simplă a Tresă permite rebobinarea nelimitată.

dacă (playerPositions.Count> 128) playerPositions.RemoveAt (0); playerRotations.RemoveAt (0); 

Ce se întâmplă este că, odată ce matricea crește peste o anumită dimensiune, eliminăm prima intrare a acesteia. Astfel rămâne doar atâta timp cât dorim ca jucătorul să revină înapoi, și nu există pericolul ca acesta să devină prea mare pentru a fi utilizat eficient. Puneți asta în FixedUpdate după codul de înregistrare și de redare.

Utilizați o clasă personalizată pentru a ține datele jucătorului

În acest moment, înregistrăm pozițiile jucătorului și rotațiile în două tablouri separate. În timp ce acest lucru nu funcționează, trebuie să ne amintim întotdeauna să înregistrați și să accesați datele în două locuri în același timp, ceea ce are potențialul pentru probleme viitoare.

Ce putem face, totuși. este să creați o clasă separată pentru a ține atât aceste lucruri, cât și mai multe (dacă acest lucru ar trebui să fie necesar în proiectul dvs.).

Codul pentru o clasă personalizată care să acționeze ca un container pentru date arată astfel:

clasa publică Keyframe public Vector3 poziție; public Vector3 rotație; public Keyframe (pozitia Vector3, rotatie Vector3) this.position = pozitie; this.rotation = rotație; 

Puteți să o adăugați în fișierul TimeController.cs, chiar înainte de începerea declarației de clasă. Ceea ce face este să furnizeze un container pentru a salva atât poziția, cât și rotația jucătorului. Metoda constructorului îi permite să fie creată direct cu informațiile necesare.

Restul algoritmului va trebui adaptat pentru a funcționa cu noul sistem. În metoda Start, matricea trebuie inițializată:

cadrele cheie = noul ArrayList ();

Și în loc să spună:

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

Putem salva direct într-un obiect Keyframe:

keyframes.Add (un nou keyframe (player.transform.position, player.transform.localEulerAngles));

Ceea ce facem aici este să adăugăm poziția și rotația jucătorului în același obiect, care apoi se adaugă într-o singură matrice, ceea ce reduce foarte mult complexitatea acestei configurații.

Adăugați un efect estompător pentru a semnala faptul că reluarea se întâmplă

Avem nevoie drastic de un fel de semnificant care să ne spună că jocul este în curs de reluare. Chiar acum, noi știu asta, dar un jucător ar putea fi confuz. În astfel de situații, este bine să aveți mai multe lucruri care să spună jucătorului că reînfășurarea se întâmplă, cum ar fi vizual (prin întregul ecran estompat un pic) și audio (prin încetinirea și inversarea muzicii).

Să facem ceva similar cu modul în care Printul Persiei face, și adăugați unele estompare.

Revenire din timp de la Printul Persiei: Nisipurile uitate

Unitatea vă permite să adăugați mai multe efecte de cameră pe partea de sus unul de celălalt, iar cu unele experimentări puteți face unul care se potrivește proiectului dvs. perfect.

Înainte de a putea folosi efectele de bază, trebuie să le importăm. Pentru a face acest lucru, du-te la Active> Pachetul de import> Efecte, și să importați tot ce vă este oferit.

Efectele vizuale pot fi adăugate direct la camera principală. Mergi la Componente> Efecte de imagine și adăugați a Estompa și a a inflori efect. Combinația dintre cele două ar trebui să ofere un efect frumos pentru ceea ce mergem.

Acestea sunt setările de bază. Aveți posibilitatea să le reglați pentru a vă potrivi mai bine proiectului.

Când o încerci acum, jocul va avea acest efect tot timpul.

Acum trebuie să o activați și să o dezactivați. Pentru asta, TimeController trebuie să importați efectele imaginii. Adăugați această linie la început:

utilizând UnityStandardAssets.ImageEffects;

Pentru a accesa aparatul foto de la TimeController, adăugați această variabilă:

camera foto aparat de fotografiat privat;

Și alocați-o în start funcţie:

camera = Camera.main;

Apoi, adăugați acest cod pentru a activa efectele în timpul reluării timpului și a le activa în alt mod:

void Actualizare () if (Input.GetKey (KeyCode.Space)) isReversing = true; camera.GetComponent() .enabled = true; camera.GetComponent() .enabled = true;  altceva isReversing = false; firstRun = adevărat; camera.GetComponent() .enabled = false; camera.GetComponent() .enabled = false; 

Când apăsați butonul spațiu, acum nu numai că derulezi scena, dar acționează și efectul de derulare înapoi pe cameră, spunând jucătorului că se întâmplă ceva.

Întregul cod al codului TimeController ar trebui să arate astfel:

utilizând UnityEngine; utilizând System.Collections; utilizând UnityStandardAssets.ImageEffects; clasa publică Keyframe public Vector3 poziție; public Vector3 rotație; public Keyframe (pozitia Vector3, rotatie Vector3) this.position = pozitie; this.rotation = rotație;  clasa publica TimeController: MonoBehavior public GameObject player; cadrele cheie de public ArrayList; boolul public esteReversing = false; public keyframe int = 5; private int frameCounter = 0; private int reverseCounter = 0; privat Vector3 currentPosition; privat Vector3 previousPosition; privat Vector3 curentRotare; privat Vector3 previousRotation; camera foto aparat de fotografiat privat; bool privat firstRun = adevărat; void Start () keyframes = nou ArrayList (); camera = Camera.main;  void Actualizare () if (Input.GetKey (KeyCode.Space)) isReversing = true; camera.GetComponent() .enabled = true; camera.GetComponent() .enabled = true;  altceva isReversing = false; firstRun = adevărat; camera.GetComponent() .enabled = false; camera.GetComponent() .enabled = false;  void FixedUpdate () if (! isReversing) dacă (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; keyframes.Add(new Keyframe(player.transform.position, player.transform.localEulerAngles));   else  if(reverseCounter > 0) reversCounter - = 1;  altfel reverseCounter = cheie cheie; RestorePositions ();  dacă (firstRun) firstRun = false; RestorePositions ();  float interpolare = (float) reversCounter / (float) keyframe; player.transform.position = Vector3.Lerp (precedentPosition, currentPosition, interpolare); player.transform.localEulerAngles = Vector3.Lerp (precedentRotare, curentRotare, interpolare);  dacă (cadre cheie = 128) keyframes.RemoveAt (0);  void RestorePositions () int lastIndex = cheie-cadru.Count - 1; int secondToLastIndex = keyframes.Count - 2; dacă (secondToLastIndex> = 0) currentPosition = (frame-uri cheie [lastIndex] ca Frame-cheie). previousPosition = (cadre cheie [secondToLastIndex] ca Frame-cheie). currentRotation = (cadre cheie [lastIndex] ca Frame-cheie) .rotation; previousRotation = (cadre cheie [secondToLastIndex] ca Frameframe) .rotation; keyframes.RemoveAt (lastIndex); 

Descărcați pachetul construit atașat și încercați!

Concluzie

Jocul nostru de timp înapoi este acum mult mai bun decât înainte. Algoritmul este îmbunătățit considerabil și utilizează cu 90% mai puțină putere de procesare, este mult mai stabil și avem un semnal frumos care ne spune că în prezent redind timpul.

Acum du-te face un joc folosind acest lucru!