Questa serie illustrerà come creare un sistema di fisica semplice e robusto per un gioco platform. In questa parte, esamineremo i dati di collisione dei personaggi.
Bene, quindi la premessa è la seguente: vogliamo creare un platform 2D con una fisica semplice, robusta, reattiva, accurata e prevedibile. In questo caso, non vogliamo utilizzare un motore fisico 2D di grandi dimensioni, e ci sono alcuni motivi per questo:
Naturalmente, ci sono anche molti professionisti nell'usare un motore fisico standard, come la possibilità di impostare interazioni fisiche complesse abbastanza facilmente, ma questo non è ciò di cui abbiamo bisogno per il nostro gioco.
Un motore fisico personalizzato aiuta il gioco ad avere un tocco personalizzato, e questo è davvero importante! Anche se inizierai con una configurazione relativamente semplice, il modo in cui le cose si muoveranno e interagiranno tra loro sarà sempre influenzato solo dalle tue stesse regole, piuttosto che da quelle di qualcun altro. Andiamo ad esso!
Iniziamo definendo il tipo di forme che useremo nella nostra fisica. Una delle forme più elementari che possiamo usare per rappresentare un oggetto fisico in un gioco è un riquadro di allineamento allineato sull'asse (AABB). AABB è fondamentalmente un rettangolo non ruotato.
In molti giochi platform, gli AABB sono sufficienti per approssimare il corpo di ogni oggetto nel gioco. Sono estremamente efficaci, perché è molto facile calcolare una sovrapposizione tra gli AABB e richiede pochissimi dati: per descrivere un AABB, è sufficiente conoscere il suo centro e le sue dimensioni.
Senza ulteriori indugi, creiamo una struttura per il nostro AABB.
struttura pubblica AABB
Come accennato in precedenza, tutto ciò di cui abbiamo bisogno qui per quanto riguarda i dati sono due vettori; il primo sarà il centro di AABB, e il secondo la metà delle dimensioni. Perchè metà taglia? Il più delle volte per i calcoli avremo comunque bisogno della mezza dimensione, quindi invece di calcolarlo ogni volta lo memorizzeremo semplicemente anziché la dimensione completa.
struttura pubblica AABB public Vector2 center; halfSize pubblico Vector2;
Iniziamo aggiungendo un costruttore, quindi è possibile creare la struct con parametri personalizzati.
AABB pubblico (Vector2 center, Vector2 halfSize) this.center = center; this.halfSize = halfSize;
Con questo possiamo creare le funzioni di controllo delle collisioni. Innanzitutto, facciamo un semplice controllo se due AABB si scontrano tra loro. Questo è molto semplice - abbiamo solo bisogno di vedere se la distanza tra i centri su ciascun asse è inferiore alla somma delle mezze misure.
public bool Overlaps (altro AABB) if (Mathf.Abs (center.x - other.center.x)> halfSize.x + other.halfSize.x) restituisce false; if (Mathf.Abs (center.y - other.center.y)> halfSize.y + other.halfSize.y) restituisce false; ritorna vero;
Ecco un'immagine che mostra questo controllo sull'asse x; l'asse y viene controllato allo stesso modo.
Come puoi vedere, se la somma delle mezze taglie dovesse essere inferiore alla distanza tra i centri, non sarebbe possibile alcuna sovrapposizione. Si noti che nel codice qui sopra, possiamo evitare il controllo collisione in anticipo se scopriamo che gli oggetti non si sovrappongono sul primo asse. La sovrapposizione deve esistere su entrambi gli assi, se gli AABB si scontrano nello spazio 2D.
Iniziamo creando un corso per un oggetto che è influenzato dalla fisica del gioco. In seguito, useremo questo come base per un oggetto giocatore reale. Chiamiamo questa classe MovingObject.
classe pubblica MovingObject
Ora riempiamo questa classe con i dati. Avremo bisogno di molte informazioni per questo oggetto:
Posizione, velocità e scala sono vettori 2D.
public class MovingObject public Vector2 mOldPosition; pubblico Vector2 mPosition; public Vector2 mOldSpeed; pubblico Vector2 mSpeed; pubblico Vector2 mScale;
Ora aggiungiamo AABB e l'offset. L'offset è necessario per poter abbinare liberamente l'AABB allo sprite dell'oggetto.
mAABB AABB pubblico; pubblico Vector2 mAABBOffset;
E infine, dichiariamo le variabili che indicano lo stato di posizione dell'oggetto, sia che si trovi sul terreno, vicino a un muro o al soffitto. Questi sono molto importanti perché ci faranno sapere se possiamo saltare o, per esempio, aver bisogno di suonare un suono dopo aver sbattuto contro un muro.
public bool mPushedRightWall; public bool mPushesRightWall; public bool mPushedLeftWall; public bool mPushesLeftWall; public bool mWasOnGround; public bool mOnGround; public bool mWasAtCeiling; bool pubblico mAtCeiling;
Queste sono le basi. Ora, creiamo una funzione che aggiornerà l'oggetto. Per ora non imposteremo tutto, ma abbastanza per iniziare a creare controlli di base per i caratteri.
pubblico vuoto UpdatePhysics ()
La prima cosa che vorremmo fare qui è salvare i dati del frame precedente sulle variabili appropriate.
pubblico vuoto UpdatePhysics () mOldPosition = mPosition; mOldSpeed = mSpeed; mWasOnGround = mOnGround; mPushedRightWall = mPushesRightWall; mPushedLeftWall = mPushesLeftWall; mWasAtCeiling = mAtCeiling;
Ora aggiorniamo la posizione usando la velocità attuale.
mPosition + = mSpeed * Time.deltaTime;
E solo per ora, facciamo in modo che se la posizione verticale è inferiore a zero, assumiamo che il personaggio sia a terra. Questo è solo per ora, quindi possiamo impostare i controlli del personaggio. Più tardi, faremo una collisione con una tilemap.
se (mPosition.y < 0.0f) mPosition.y = 0.0f; mOnGround = true; else mOnGround = false;
Dopo questo, dobbiamo anche aggiornare il centro di AABB, in modo che corrisponda effettivamente alla nuova posizione.
mAABB.center = mPosition + mAABBOffset;
Per il progetto demo, sto usando Unity e per aggiornare la posizione dell'oggetto che deve essere applicato al componente di trasformazione, quindi facciamolo pure. Lo stesso deve essere fatto per la scala.
mTransform.position = new Vector3 (Mathf.Round (mPosition.x), Mathf.Round (mPosition.y), - 1.0f); mTransform.localScale = new Vector3 (mScale.x, mScale.y, 1.0f);
Come puoi vedere, la posizione di rendering viene arrotondata per eccesso. Questo per assicurarsi che il personaggio renderizzato sia sempre catturato su un pixel.
Ora che abbiamo fatto la nostra lezione base di MovingObject, possiamo iniziare giocando con il movimento del personaggio. È una parte molto importante del gioco, dopotutto, e può essere fatto praticamente subito - non c'è bisogno di approfondire troppo nei sistemi di gioco, e sarà pronto quando avremo bisogno di testare il nostro personaggio- collisioni di mappe.
Per prima cosa, creiamo una classe Character e la ricaviamo dalla classe MovingObject.
carattere di classe pubblica: MovingObject
Avremo bisogno di gestire alcune cose qui. Prima di tutto, gli input: facciamo un enum che coprirà tutti i controlli per il personaggio. Creiamolo in un altro file e chiamiamolo KeyInput.
public enum KeyInput GoLeft = 0, GoRight, GoDown, Jump, Count
Come puoi vedere, il nostro personaggio può muoversi a sinistra, a destra, in basso e saltare in alto. Lo spostamento verso il basso funzionerà solo su piattaforme a senso unico, quando vogliamo eliminarle.
Ora dichiariamo due array nella classe Character, uno per gli input del frame corrente e un altro per i frame precedenti. A seconda del gioco, questa configurazione può avere più o meno senso. Di solito, invece di salvare lo stato della chiave in una matrice, viene verificato su richiesta utilizzando le funzioni specifiche di un motore o di una struttura. Tuttavia, avere un array che non è strettamente legato all'input reale può essere utile, se ad esempio vogliamo simulare la pressione di un tasto.
bool protetti [] mInput; bool protetto [] mPrevInputs;
Questi array verranno indicizzati dall'enumerazione KeyInput. Per utilizzare facilmente quegli array, creiamo alcune funzioni che ci aiuteranno a verificare la presenza di una chiave specifica.
protected bool Rilasciato (KeyInput key) return (! mInputs [(int) key] && mPrevInputs [(int) key]); protected bool KeyState (KeyInput key) return (mInputs [(int) key]); protected bool Premuto (KeyInput key) return (mInputs [(int) key] &&! mPrevInputs [(int) key]);
Niente di speciale qui - vogliamo essere in grado di vedere se un tasto è stato appena premuto, appena rilasciato, o se è acceso o spento.
Ora creiamo un'altra enumerazione che manterrà tutti gli stati possibili del personaggio.
public enum CharacterState Stand, Walk, Jump, GrabLedge,;
Come puoi vedere, il nostro personaggio può stare fermo, camminare, saltare o afferrare una sporgenza. Ora che ciò è fatto, dobbiamo aggiungere variabili come velocità di salto, velocità di marcia e stato attuale.
public CharacterState mCurrentState = CharacterState.Stand; public float mJumpSpeed; public float mWalkSpeed;
Ovviamente ci sono altri dati necessari qui come lo sprite del personaggio, ma il suo aspetto dipende molto dal tipo di motore che si intende utilizzare. Dato che sto usando Unity, userò un riferimento ad un Animatore per assicurarmi che lo sprite riproduca l'animazione per uno stato appropriato.
Bene, ora possiamo iniziare il lavoro sul ciclo di aggiornamento. Quello che faremo qui dipenderà dallo stato attuale del personaggio.
public void CharacterUpdate () switch (mCurrentState) case CharacterState.Stand: break; caso CharacterState.Walk: break; caso CharacterState.Jump: break; caso CharacterState.GrabLedge: break;
Iniziamo col riempire ciò che dovrebbe essere fatto quando il personaggio non è in movimento, nello stato di stand. Prima di tutto, la velocità dovrebbe essere impostata su zero.
case CharacterState.Stand: mSpeed = Vector2.zero; rompere;
Vogliamo anche mostrare lo sprite appropriato per lo stato.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); rompere;
Ora, se il personaggio non è a terra, non può più resistere, quindi dobbiamo cambiare lo stato per saltare.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); if (! mOnGround) mCurrentState = CharacterState.Jump; rompere; rompere;
Se viene premuto il tasto GoLeft o GoRight, sarà necessario modificare il nostro stato per camminare.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); if (! mOnGround) mCurrentState = CharacterState.Jump; rompere; if (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; pausa pausa;
Nel caso in cui venga premuto il tasto Salta, vogliamo impostare la velocità verticale alla velocità di salto e cambiare lo stato in cui saltare.
if (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; rompere; else if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mCurrentState = CharacterState.Jump; rompere;
Sarà per questo stato, almeno per ora.
Ora creiamo una logica per spostarci a terra e subito inizi a riprodurre l'animazione a piedi.
caso CharacterState.Walk: mAnimator.Play ("Walk"); rompere;
Qui, se non premiamo il tasto sinistro o destro o entrambi questi sono premuti, vogliamo tornare allo stato immobile fermo.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; rompere;
Se viene premuto il tasto GoRight, è necessario impostare la velocità orizzontale su mWalkSpeed e assicurarsi che lo sprite sia ridimensionato in modo appropriato: la scala orizzontale deve essere modificata se si desidera capovolgere lo sprite orizzontalmente.
Dovremmo anche muoverci solo se in realtà non ci sono ostacoli avanti, quindi se mPushesRightWall è impostato su true, la velocità orizzontale dovrebbe essere impostata su zero se ci stiamo muovendo correttamente.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; rompere; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; else mSpeed.x = mWalkSpeed; mScale.x = Mathf.Abs (mScale.x); else if (KeyState (KeyInput.GoLeft)) if (mPushesLeftWall) mSpeed.x = 0.0f; else mSpeed.x = -mWalkSpeed; mScale.x = -Mathf.Abs (mScale.x);
Dobbiamo anche gestire il lato sinistro nello stesso modo.
Come abbiamo fatto per lo stato in piedi, abbiamo bisogno di vedere se viene premuto un pulsante di salto, e impostare la velocità verticale, se è così.
if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; rompere;
Altrimenti, se il personaggio non è a terra, allora ha bisogno di cambiare lo stato per saltare pure, ma senza un'aggiunta di velocità verticale, quindi semplicemente cade giù.
if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; rompere; else if (! mOnGround) mCurrentState = CharacterState.Jump; rompere;
Questo è tutto per il camminare. Passiamo allo stato di salto.
Iniziamo impostando un'animazione appropriata per lo sprite.
mAnimator.Play ( "Jump");
Nello stato di salto, dobbiamo aggiungere gravità alla velocità del personaggio, quindi va sempre più veloce verso il terreno.
mSpeed.y + = Constants.cGravity * Time.deltaTime;
Ma sarebbe sensato aggiungere un limite, quindi il personaggio non può cadere troppo velocemente.
mSpeed.y = Mathf.Max (mSpeed.y, Constants.cMaxFallingSpeed);
In molti giochi, quando il personaggio è nell'aria, la manovrabilità diminuisce, ma ci concentreremo su alcuni controlli molto semplici e precisi che consentono la massima flessibilità quando si è in volo. Quindi, se premiamo il tasto GoLeft o GoRight, il personaggio si muove nella direzione mentre salta velocemente come sarebbe se fosse a terra. In questo caso possiamo semplicemente copiare la logica del movimento dallo stato di deambulazione.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mSpeed.x = 0.0f; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; else mSpeed.x = mWalkSpeed; mScale.x = Mathf.Abs (mScale.x); else if (KeyState (KeyInput.GoLeft)) if (mPushesLeftWall) mSpeed.x = 0.0f; else mSpeed.x = -mWalkSpeed; mScale.x = -Mathf.Abs (mScale.x);
Infine, faremo il salto più in alto se il pulsante di salto viene premuto più a lungo. Per fare ciò, ciò che faremo in realtà è abbassare il salto se non si preme il pulsante di salto.
if (! KeyState (KeyInput.Jump) && mSpeed.y> 0.0f) mSpeed.y = Mathf.Min (mSpeed.y, Constants.cMinJumpSpeed);
Come puoi vedere, se il tasto di salto non viene premuto e la velocità verticale è positiva, la velocità viene ridotta al valore massimo di cMinJumpSpeed
(200 pixel al secondo). Ciò significa che se dovessimo semplicemente toccare il pulsante di salto, la velocità del salto, invece di essere uguale a mJumpSpeed
(410 di default), verrà abbassato a 200, e quindi il salto sarà più breve.
Poiché non abbiamo ancora una geometria di livello, per il momento dobbiamo saltare l'implementazione GrabLedge.
Una volta che il frame è finito, possiamo aggiornare gli input precedenti. Creiamo una nuova funzione per questo. Tutto quello che dovremo fare qui è spostare i valori dello stato chiave da mInputs
matrice al mPrevInputs
schieramento.
public void UpdatePrevInputs () var count = (byte) KeyInput.Count; per (byte i = 0; i < count; ++i) mPrevInputs[i] = mInputs[i];
Alla fine della funzione CharacterUpdate, abbiamo ancora bisogno di fare un paio di cose. Il primo è aggiornare la fisica.
UpdatePhysics ();
Ora che la fisica è aggiornata, possiamo vedere se dovremmo suonare qualsiasi suono. Vogliamo suonare un suono quando il personaggio colpisce qualsiasi superficie, ma al momento può solo toccare terra perché la collisione con la tilemap non è ancora stata implementata.
Controlliamo se il personaggio è appena caduto a terra. È molto facile farlo con l'attuale configurazione: dobbiamo solo cercare se il personaggio è a terra in questo momento, ma non era nel frame precedente.
if (mOnGround &&! mWasOnGround) mAudioSource.PlayOneShot (mHitWallSfx, 0.5f);
Infine, aggiorniamo gli input precedenti.
UpdatePrevInputs ();
Tutto sommato, questo è il modo in cui la funzione CharacterUpdate dovrebbe apparire ora, con piccole differenze a seconda del tipo di motore o struttura che stai usando.
public void CharacterUpdate () switch (mCurrentState) case CharacterState.Stand: mWalkSfxTimer = cWalkSfxTime; mAnimator.Play ( "Stand"); mSpeed = Vector2.zero; if (! mOnGround) mCurrentState = CharacterState.Jump; rompere; // se viene premuto il tasto sinistro o destro, ma non entrambi se (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; rompere; else if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx); mCurrentState = CharacterState.Jump; rompere; rompere; caso CharacterState.Walk: mAnimator.Play ("Walk"); mWalkSfxTimer + = Time.deltaTime; if (mWalkSfxTimer> cWalkSfxTime) mWalkSfxTimer = 0.0f; mAudioSource.PlayOneShot (mWalkSfx); // se vengono premuti entrambi o nessuno dei tasti sinistra o destra, smettere di camminare e attendere se (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; rompere; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; else mSpeed.x = mWalkSpeed; mScale.x = -Mathf.Abs (mScale.x); else if (KeyState (KeyInput.GoLeft)) if (mPushesLeftWall) mSpeed.x = 0.0f; else mSpeed.x = -mWalkSpeed; mScale.x = Mathf.Abs (mScale.x); // se non ci sono tessere su cui camminare, fall if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; rompere; else if (! mOnGround) mCurrentState = CharacterState.Jump; rompere; rompere; case CharacterState.Jump: mWalkSfxTimer = cWalkSfxTime; mAnimator.Play ( "Jump"); mSpeed.y + = Constants.cGravity * Time.deltaTime; mSpeed.y = Mathf.Max (mSpeed.y, Constants.cMaxFallingSpeed); if (! KeyState (KeyInput.Jump) && mSpeed.y> 0.0f) mSpeed.y = Mathf.Min (mSpeed.y, 200.0f); if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mSpeed.x = 0.0f; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; else mSpeed.x = mWalkSpeed; mScale.x = -Mathf.Abs (mScale.x); else if (KeyState (KeyInput.GoLeft)) if (mPushesLeftWall) mSpeed.x = 0.0f; else mSpeed.x = -mWalkSpeed; mScale.x = Mathf.Abs (mScale.x); // se colpiamo il suolo se (mOnGround) // se non c'è alcun movimento cambia stato in piedi se (mInputs [(int) KeyInput.GoRight] == mInputs [(int) KeyInput.GoLeft]) mCurrentState = CharacterState .Stand; mSpeed = Vector2.zero; mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); else // vengono premuti a destra o a sinistra, quindi cambiamo lo stato per camminare mCurrentState = CharacterState.Walk; mSpeed.y = 0.0f; mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); rompere; caso CharacterState.GrabLedge: break; UpdatePhysics (); if ((! mWasOnGround && mOnGround) || (! mWasAtCeiling && mAtCeiling) || (! mPushedLeftWall && mPushesLeftWall) || (! mPushedRightWall && mPushesRightWall)) mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); UpdatePrevInputs ();
Scriviamo una funzione Init per il personaggio. Questa funzione prenderà gli array di input come parametri. Forniremo questi dalla classe manager in seguito. Oltre a questo, abbiamo bisogno di fare cose come:
public void CharacterInit (bool [] input, bool [] prevInputs)
Useremo alcune delle costanti definite qui.
public const float cWalkSpeed = 160.0f; public const float cJumpSpeed = 410.0f; public const float cMinJumpSpeed = 200.0f; public const float cHalfSizeY = 20.0f; public const float cHalfSizeX = 6.0f;
Nel caso della demo, possiamo impostare la posizione iniziale nella posizione nell'editor.
public void CharacterInit (bool [] input, bool [] prevInputs) mPosition = transform.position;
Per AABB, abbiamo bisogno di impostare l'offset e la mezza dimensione. L'offset nel caso dello sprite della demo deve essere solo la metà della dimensione.
public void CharacterInit (bool [] input, bool [] prevInputs) mPosition = transform.position; mAABB.halfSize = new Vector2 (Constants.cHalfSizeX, Constants.cHalfSizeY); mAABBOffset.y = mAABB.halfSize.y;
Ora possiamo occuparci del resto delle variabili.
public void CharacterInit (bool [] input, bool [] prevInputs) mPosition = transform.position; mAABB.halfSize = new Vector2 (Constants.cHalfSizeX, Constants.cHalfSizeY); mAABBOffset.y = mAABB.halfSize.y; mInput = input; mPrevInputs = prevInputs; mJumpSpeed = Constants.cJumpSpeed; mWalkSpeed = Constants.cWalkSpeed; mScale = Vector2.one;
Dobbiamo chiamare questa funzione dal manager di gioco. Il gestore può essere configurato in molti modi, il tutto a seconda degli strumenti che stai utilizzando, ma in generale l'idea è la stessa. In init del manager, dobbiamo creare gli array di input, creare un player e avviarlo.
Gioco di classe pubblica personaggio pubblico mPlayer; bool [] mInputs; bool [] mPrevInputs; void Start () inputs = new bool [(int) KeyInput.Count]; prevInputs = new bool [(int) KeyInput.Count]; player.CharacterInit (input, prevInputs);
Inoltre, nell'aggiornamento del manager, è necessario aggiornare gli input del giocatore e del giocatore.
void Update () inputs [(int) KeyInput.GoRight] = Input.GetKey (goRightKey); input [(int) KeyInput.GoLeft] = Input.GetKey (goLeftKey); input [(int) KeyInput.GoDown] = Input.GetKey (goDownKey); input [(int) KeyInput.Jump] = Input.GetKey (goJumpKey); void FixedUpdate () player.CharacterUpdate ();
Si noti che aggiorniamo la fisica del personaggio nell'aggiornamento fisso. Ciò assicurerà che i salti abbiano sempre la stessa altezza, indipendentemente dal frame rate con il quale il gioco funziona. C'è un eccellente articolo di Glenn Fiedler su come sistemare il timestep, nel caso in cui non si stia utilizzando Unity.
A questo punto possiamo testare il movimento del personaggio e vedere come ci si sente. Se non ci piace, possiamo sempre modificare i parametri o il modo in cui la velocità viene cambiata alla pressione dei tasti.
I controlli del personaggio possono sembrare molto leggeri e non così piacevoli come un movimento basato sul momento per alcuni, ma si tratta solo del tipo di controlli che si adatta meglio al tuo gioco. Fortunatamente, cambiare semplicemente il modo in cui il personaggio si muove è abbastanza facile; è sufficiente modificare il modo in cui il valore della velocità cambia negli stati di camminata e salto.
Questo è tutto per la prima parte della serie. Abbiamo finito con un semplice schema di movimento dei personaggi, ma non molto di più. La cosa più importante è che abbiamo preparato la strada per la parte successiva, in cui faremo interagire il personaggio con una tilemap.