Deze serie gaat over hoe je een eenvoudig en robuust physics-systeem kunt maken voor een platformgame. In dit deel bekijken we botsingsgegevens van personages.
Oké, dus het uitgangspunt ziet er zo uit: we willen een 2D-platformer maken met eenvoudige, robuuste, responsieve, nauwkeurige en voorspelbare fysica. We willen in dit geval geen grote 2D-physics-engine gebruiken, en daar zijn een paar redenen voor:
Natuurlijk zijn er ook veel voordelen aan het gebruik van een standaard physics-engine, zoals het gemakkelijk kunnen opzetten van complexe fysica-interacties, maar dat is niet wat we nodig hebben voor onze game..
Een aangepaste physics-engine helpt de game om op maat te voelen, en dat is echt belangrijk! Zelfs als je begint met een relatief eenvoudige opstelling, zal de manier waarop dingen zich verplaatsen en met elkaar omgaan altijd worden beïnvloed door alleen je eigen regels, in plaats van die van iemand anders. Laten we ernaar toe gaan!
Laten we beginnen met te definiëren welke vormen we in onze fysica zullen gebruiken. Een van de meest basale vormen die we kunnen gebruiken om een fysiek object in een spel te vertegenwoordigen, is een Axis Aligned Bounding Box (AABB). AABB is in feite een niet-geroteerde rechthoek.
In veel platformgames zijn AABB's voldoende om het lichaam van elk object in het spel te benaderen. Ze zijn buitengewoon effectief, omdat het heel eenvoudig is om een overlap tussen AABB's te berekenen en heel weinig gegevens nodig hebben - om een AABB te beschrijven, het is genoeg om het centrum en de grootte te kennen.
Laten we zonder meer een struct maken voor onze AABB.
public struct AABB
Zoals eerder vermeld, alles wat we hier nodig hebben wat betreft data betreft twee vectoren; de eerste is het centrum van de AABB en de tweede helft is de helft. Waarom de halve maat? Meestal hebben we voor berekeningen hoe dan ook de halve grootte nodig, dus in plaats van het elke keer te berekenen, onthouden we het eenvoudig in plaats van het volledige formaat.
public struct AABB public Vector2 center; openbare Vector2 halfSize;
Laten we beginnen met het toevoegen van een constructor, dus het is mogelijk om de struct te maken met aangepaste parameters.
public AABB (Vector2 center, Vector2 halfSize) this.center = center; this.halfSize = halfSize;
Hiermee kunnen we de botscontrole-functies creëren. Laten we eerst een eenvoudige controle uitvoeren of twee AABB's met elkaar botsen. Dit is heel eenvoudig: we hoeven alleen maar te kijken of de afstand tussen de centers op elke as kleiner is dan de som van de halve maten.
public bool Overlaps (AABB anders) if (Mathf.Abs (center.x - other.center.x)> halfSize.x + other.halfSize.x) return false; if (Mathf.Abs (center.y - other.center.y)> halfSize.y + other.halfSize.y) geeft false terug; geef waar terug;
Hier is een afbeelding die deze controle op de x-as demonstreert; de y-as wordt op dezelfde manier gecontroleerd.
Zoals je kunt zien, zou de som van de halve maten kleiner zijn dan de afstand tussen de centra, zodat er geen overlapping mogelijk is. Merk op dat we in de bovenstaande code vroegtijdig aan de botsingcontrole kunnen ontsnappen als we vaststellen dat de objecten elkaar niet overlappen op de eerste as. De overlapping moet op beide assen voorkomen als de AABB's in 2D-ruimte moeten botsen.
Laten we beginnen met het maken van een klasse voor een object dat wordt beïnvloed door de fysica van het spel. Later gebruiken we dit als basis voor een echt spelersobject. Laten we deze klasse MovingObject noemen.
openbare klasse MovingObject
Laten we nu deze klasse vullen met de gegevens. We hebben behoorlijk wat informatie nodig voor dit object:
Positie, snelheid en schaal zijn 2D-vectoren.
public class MovingObject public Vector2 mOldPosition; openbare Vector2 mPosition; openbare Vector2 mOldSpeed; openbare Vector2 mSpeed; openbare Vector2 mScale;
Laten we nu de AABB en de offset toevoegen. De offset is nodig, zodat we de AABB vrij kunnen koppelen aan de sprite van het object.
openbaar AABB mAABB; openbare Vector2 mAABBOffset;
En ten slotte, laten we de variabelen verklaren die de positietoestand van het object aangeven, of het nu op de grond is, naast een muur of aan het plafond. Deze zijn erg belangrijk omdat ze ons laten weten of we kunnen springen of bijvoorbeeld een geluid moeten spelen nadat we tegen een muur zijn gestoten.
public bool mPushedRightWall; public bool mPushesRightWall; public bool mPushedLeftWall; public bool mPushesLeftWall; public bool mWasOnGround; public bool mOnGround; openbare bool mWasAtCeiling; openbare bool mAtCeiling;
Dit zijn de basis. Laten we nu een functie maken die het object zal bijwerken. We zullen voorlopig niet alles instellen, maar net genoeg zodat we basiskarakterbesturingselementen kunnen maken.
public void UpdatePhysics ()
Het eerste dat we hier willen doen is de gegevens van het vorige frame opslaan in de juiste variabelen.
public void UpdatePhysics () mOldPosition = mPosition; mOldSpeed = mSpeed; mWasOnGround = mOnGround; mPushedRightWall = mPushesRightWall; mPushedLeftWall = mPushesLeftWall; mWasAtCeiling = mAtCeiling;
Laten we nu de positie bijwerken met de huidige snelheid.
mPosition + = mSpeed * Time.deltaTime;
Laten we ervoor zorgen dat als de verticale positie kleiner is dan nul, we aannemen dat het personage op de grond ligt. Dit is net voor nu, dus we kunnen de besturing van het personage instellen. Later doen we een botsing met een tilemap.
if (mPosition.y < 0.0f) mPosition.y = 0.0f; mOnGround = true; else mOnGround = false;
Hierna moeten we ook het centrum van AABB updaten, zodat het overeenkomt met de nieuwe positie.
mAABB.center = mPosition + mAABBOffset;
Voor het demoproject gebruik ik Unity en om de positie van het object dat moet worden toegepast op de transformatiecomponent bij te werken, dus laten we dat ook doen. Hetzelfde moet worden gedaan voor de schaal.
mTransform.position = new Vector3 (Mathf.Round (mPosition.x), Mathf.Round (mPosition.y), - 1.0f); mTransform.localScale = new Vector3 (mScale.x, mScale.y, 1.0f);
Zoals u kunt zien, is de weergegeven positie afgerond. Dit is om ervoor te zorgen dat het gerenderde teken altijd naar een pixel wordt geklikt.
Nu we onze basisklasse MovingObject hebben voltooid, kunnen we beginnen met spelen met de personagebeweging. Het is tenslotte een heel belangrijk onderdeel van het spel en kan vrijwel meteen worden gedaan - het is niet nodig om nog te diep in de gamesystemen te duiken en het is klaar wanneer we ons karakter moeten testen. kaart botsingen.
Laten we eerst een Character-klasse maken en deze afleiden uit de klasse MovingObject.
public class Character: MovingObject
We moeten hier een paar dingen regelen. Allereerst, de inputs - laten we een enum maken dat alle knoppen voor het personage zal omvatten. Laten we het in een ander bestand maken en het KeyInput noemen.
public enum KeyInput GoLeft = 0, GoRight, GoDown, Jump, Count
Zoals je ziet, kan ons karakter naar links, rechts, omlaag en omhoog springen. Naar beneden gaan werkt alleen op eenrichtingsplatforms, als we er doorheen willen vallen.
Laten we nu twee arrays in de tekenklasse declareren, één voor de invoer van het huidige frame en een andere voor de vorige frames. Afhankelijk van een game, kan deze instelling meer of minder logisch lijken. Gewoonlijk wordt in plaats van de sleutelstatus in een array op te slaan, deze op aanvraag gecontroleerd met behulp van de specifieke functies van een engine of framework. Het kan echter nuttig zijn om een array te hebben die niet strikt gebonden is aan echte invoer, als we bijvoorbeeld toetsaanslagen willen simuleren.
protected bool [] m Inputs; protected bool [] mPrevInputs;
Deze arrays worden geïndexeerd door het KeyInput-opsommingsteken. Als u deze arrays gemakkelijk wilt gebruiken, maken we een paar functies waarmee we kunnen controleren op een specifieke sleutel.
protected bool Released (KeyInput-sleutel) return (! mInvoegtoetsen [(int) -toets] && mPrevInvoer [(int) -toets]); protected bool KeyState (KeyInput-sleutel) return (mInvoegtoets [(int)]); protected bool Pressed (KeyInput-toets) return (mInvoegtoets [(int) -toets] &&! mPrevInvoer [(int) -toets]);
Hier is niets speciaals - we willen kunnen zien of een toets net is ingedrukt, zojuist is losgelaten of dat deze is in- of uitgeschakeld.
Laten we nu een andere opsomming maken die alle mogelijke toestanden van het personage bevat.
public enum CharacterState Stand, Walk, Jump, GrabLedge,;
Zoals je ziet, kan ons karakter stilstaan, lopen, springen of een richel grijpen. Nu dit is gebeurd, moeten we variabelen toevoegen zoals springsnelheid, loopsnelheid en huidige status.
public CharacterState mCurrentState = CharacterState.Stand; openbare float mJumpSpeed; openbare float mWalkSpeed;
Natuurlijk zijn hier wat meer gegevens nodig, zoals personagesprite, maar hoe dit eruit zal zien, hangt sterk af van wat voor soort engine je gaat gebruiken. Omdat ik Unity gebruik, zal ik een verwijzing naar een animator gebruiken om ervoor te zorgen dat de sprite animatie afspeelt voor een geschikte staat.
Oké, nu kunnen we beginnen aan de update-lus. Wat we hier zullen doen, hangt af van de huidige status van het personage.
public void CharacterUpdate () switch (mCurrentState) case CharacterState.Stand: break; case CharacterState.Walk: break; case CharacterState.Jump: pauze; case CharacterState.GrabLedge: pauze;
Laten we beginnen met het vullen van wat er moet gebeuren als het personage niet beweegt in de staat van de stand. Allereerst moet de snelheid op nul worden ingesteld.
case CharacterState.Stand: mSpeed = Vector2.zero; breken;
We willen ook de juiste sprite laten zien voor de staat.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); breken;
Nu, als het personage niet op de grond staat, kan het niet langer staan, dus we moeten de staat veranderen om te springen.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); if (! mOnGround) mCurrentState = CharacterState.Jump; breken; pauze;
Als op de GoLeft- of GoRight-toets wordt gedrukt, moeten we onze staat wijzigen om te lopen.
case CharacterState.Stand: mSpeed = Vector2.zero; mAnimator.Play ( "Stand"); if (! mOnGround) mCurrentState = CharacterState.Jump; breken; if (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; pauze pauze;
Als de Jump-toets wordt ingedrukt, willen we de verticale snelheid instellen op de springsnelheid en de staat wijzigen om te springen.
if (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; breken; else if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mCurrentState = CharacterState.Jump; breken;
Dat zal het voor deze staat zijn, althans voor nu.
Laten we nu een logica maken om op de grond te bewegen en meteen de loopanimatie spelen.
case CharacterState.Walk: mAnimator.Play ("Walk"); breken;
Hier, als we niet op de linker- of rechterknop drukken of beide ingedrukt houden, willen we teruggaan naar de stilstaande staat.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; breken;
Als de GoRight-toets wordt ingedrukt, moeten we de horizontale snelheid instellen op mWalkSpeed en controleren of de sprite op de juiste manier is geschaald - de horizontale schaal moet worden gewijzigd als we de sprite horizontaal willen spiegelen.
We moeten ook alleen verplaatsen als er echt geen obstakel voor is, dus als mPushesRightWall op true is ingesteld, moet de horizontale snelheid op nul worden gezet als we naar rechts gaan.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; breken; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; anders 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);
We moeten ook op dezelfde manier met de linkerkant omgaan.
Zoals we deden voor de staande toestand, moeten we kijken of er op een springknop wordt gedrukt en de verticale snelheid instellen als dat zo is.
if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; breken;
Anders, als het personage niet op de grond staat, moet het de staat veranderen om ook te springen, maar zonder een toevoeging van verticale snelheid, dus valt het gewoon naar beneden.
if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; breken; else if (! mOnGround) mCurrentState = CharacterState.Jump; breken;
Dat is het voor het wandelen. Laten we naar de sprongstatus gaan.
Laten we beginnen met het instellen van een geschikte animatie voor de sprite.
mAnimator.Play ( "Ga");
In de staat Jump moeten we zwaartekracht toevoegen aan de snelheid van het personage, zodat het sneller en sneller naar de grond gaat.
mSpeed.y + = Constants.cGravity * Time.deltaTime;
Maar het zou verstandig zijn om een limiet toe te voegen, zodat het personage niet te snel kan vallen.
mSpeed.y = Mathf.Max (mSpeed.y, Constants.cMaxFallingSpeed);
In veel games, wanneer het personage in de lucht is, neemt de manoeuvreerbaarheid af, maar we gaan voor een aantal zeer eenvoudige en nauwkeurige bedieningselementen die volledige flexibiliteit in de lucht mogelijk maken. Dus als we op de GoLeft- of GoRight-toets drukken, beweegt het personage in de richting terwijl hij zo snel springt als wanneer hij op de grond zou zijn. In dit geval kunnen we eenvoudig de bewegingslogica kopiëren van de looptoestand.
if (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mSpeed.x = 0.0f; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; anders 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);
Ten slotte gaan we de sprong hoger maken als de springknop langer wordt ingedrukt. Om dit te doen, wat we eigenlijk zullen doen is de sprong lager maken als de springknop niet wordt ingedrukt.
if (! KeyState (KeyInput.Jump) && mSpeed.y> 0.0f) mSpeed.y = Mathf.Min (mSpeed.y, Constants.cMinJumpSpeed);
Zoals je kunt zien, als de spring-toets niet wordt ingedrukt en de verticale snelheid positief is, dan klemmen we de snelheid tot de maximale waarde van cMinJumpSpeed
(200 pixels per seconde). Dit betekent dat als we gewoon op de springknop tikken, de snelheid van de sprong in plaats van gelijk te zijn aan mJumpSpeed
(Standaard 410), wordt verlaagd naar 200, en daarom zal de sprong korter zijn.
Omdat we nog geen niveaugeometrie hebben, zouden we de GrabLedge-implementatie voorlopig moeten overslaan.
Als het frame klaar is, kunnen we de vorige invoer bijwerken. Laten we hiervoor een nieuwe functie maken. Het enige wat we hier moeten doen is de sleutelstatuswaarden verplaatsen van de mInputs
array naar de mPrevInputs
rangschikking.
public void UpdatePrevInputs () var count = (byte) KeyInput.Count; voor (byte i = 0; i < count; ++i) mPrevInputs[i] = mInputs[i];
Helemaal aan het einde van de functie CharacterUpdate moeten we nog een aantal dingen doen. De eerste is om de fysica bij te werken.
UpdatePhysics ();
Nu de natuurkunde is bijgewerkt, kunnen we zien of we elk geluid moeten spelen. We willen een geluid afspelen wanneer het personage een oppervlak botst, maar nu kan het alleen de grond raken omdat de botsing met tilemap nog niet is geïmplementeerd.
Laten we kijken of het personage net op de grond is gevallen. Het is heel gemakkelijk om dit te doen met de huidige instellingen - we hoeven alleen maar op te zoeken of het personage nu op de grond staat, maar was niet in het vorige frame.
if (mOnGround &&! mWasOnGround) mAudioSource.PlayOneShot (mHitWallSfx, 0.5f);
Laten we tot slot de vorige ingangen bijwerken.
UpdatePrevInputs ();
Al met al is dit de manier waarop de functie CharacterUpdate nu moet worden weergegeven, met kleine verschillen, afhankelijk van het type engine of framework dat u gebruikt.
public void CharacterUpdate () switch (mCurrentState) case CharacterState.Stand: mWalkSfxTimer = cWalkSfxTime; mAnimator.Play ( "Stand"); mSpeed = Vector2.zero; if (! mOnGround) mCurrentState = CharacterState.Jump; breken; // als de linker of rechter toets wordt ingedrukt, maar niet beide als (KeyState (KeyInput.GoRight)! = KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Walk; breken; else if (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx); mCurrentState = CharacterState.Jump; breken; pauze; case CharacterState.Walk: mAnimator.Play ("Walk"); mWalkSfxTimer + = Time.deltaTime; if (mWalkSfxTimer> cWalkSfxTime) mWalkSfxTimer = 0.0f; mAudioSource.PlayOneShot (mWalkSfx); // als beide of geen van de links- of rechtermuisknop worden ingedrukt en stoppen met lopen en staan als (KeyState (KeyInput.GoRight) == KeyState (KeyInput.GoLeft)) mCurrentState = CharacterState.Stand; mSpeed = Vector2.zero; breken; else if (KeyState (KeyInput.GoRight)) if (mPushesRightWall) mSpeed.x = 0.0f; anders 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); // als er geen tegel is om op te lopen, valt als (KeyState (KeyInput.Jump)) mSpeed.y = mJumpSpeed; mAudioSource.PlayOneShot (mJumpSfx, 1.0f); mCurrentState = CharacterState.Jump; breken; else if (! mOnGround) mCurrentState = CharacterState.Jump; breken; pauze; case CharacterState.Jump: mWalkSfxTimer = cWalkSfxTime; mAnimator.Play ( "Ga"); 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; anders 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); // als we de grond raken als (mOnGround) // als er geen verplaatsingswijzigingstoestand is in staan als (m Ingangen [(int) KeyInput.GoRight] == mInputs [(int) KeyInput.GoLeft]) mCurrentState = CharacterState .Staan; mSpeed = Vector2.zero; mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); else // of ga naar rechts of naar links worden ingedrukt, dus we veranderen de status om te lopen mCurrentState = CharacterState.Walk; mSpeed.y = 0.0f; mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); pauze; case CharacterState.GrabLedge: pauze; UpdateFhysics (); if ((! mWasOnGround && mOnGround) || (! mWasAtCeiling && mAtCeiling) || (! mPushedLeftWall && mPushesLeftWall) || (! mPushedRightWall && mPushesRightWall)) mAudioSource.PlayOneShot (mHitWallSfx, 0.5f); UpdatePrevInputs ();
Laten we een Init-functie voor het personage schrijven. Deze functie neemt de invoerarrays als parameters. We zullen deze later uit de managersklasse leveren. Anders dan dit, moeten we dingen doen als:
public void CharacterInit (bool [] inputs, bool [] prevInputs)
We zullen hier een paar van de gedefinieerde constanten gebruiken.
public const float cWalkSpeed = 160.0f; openbare const float cJumpSpeed = 410.0f; openbare const float cMinJumpSpeed = 200.0f; openbare const float cHalfSizeY = 20.0f; public const float cHalfSizeX = 6.0f;
In het geval van de demo kunnen we de beginpositie instellen op de positie in de editor.
public void CharacterInit (bool [] inputs, bool [] prevInputs) mPosition = transform.position;
Voor de AABB moeten we de offset en de halve grootte instellen. De offset in het geval van de sprite van de demo moet slechts de helft zijn.
public void CharacterInit (bool [] inputs, bool [] prevInputs) mPosition = transform.position; mAABB.halfSize = new Vector2 (Constants.cHalfSizeX, Constants.cHalfSizeY); mAABBOffset.y = mAABB.halfSize.y;
Nu kunnen we voor de rest van de variabelen zorgen.
public void CharacterInit (bool [] inputs, bool [] prevInputs) mPosition = transform.position; mAABB.halfSize = new Vector2 (Constants.cHalfSizeX, Constants.cHalfSizeY); mAABBOffset.y = mAABB.halfSize.y; m Uitgangen = ingangen; mPrevInputs = prevInputs; mJumpSpeed = Constants.cJumpSpeed; mWalkSpeed = Constants.cWalkSpeed; mScale = Vector2.one;
We moeten deze functie van de spelmanager bellen. De manager kan op verschillende manieren worden ingesteld, afhankelijk van de tools die u gebruikt, maar over het algemeen is het idee hetzelfde. In de init van de manager moeten we de invoerarrays maken, een speler maken en deze initiëren.
public class Game public Character mPlayer; bool [] m Inputs; bool [] mPrevInputs; void Start () inputs = new bool [(int) KeyInput.Count]; prevInputs = new bool [(int) KeyInput.Count]; player.CharacterInit (ingangen, vorige invoer);
Bovendien moeten we in de update van de manager de invoer van de speler en de speler bijwerken.
void Update () inputs [(int) KeyInput.GoRight] = Input.GetKey (goRightKey); ingangen [(int) KeyInput.GoLeft] = Input.GetKey (goLeftKey); ingangen [(int) KeyInput.GoDown] = Input.GetKey (goDownKey); ingangen [(int) KeyInput.Jump] = Input.GetKey (goJumpKey); void FixedUpdate () player.CharacterUpdate ();
Merk op dat we de fysica van het personage updaten in de vaste update. Dit zorgt ervoor dat de sprongen altijd dezelfde hoogte hebben, ongeacht de framesnelheid waarmee onze game werkt. Er is een uitstekend artikel van Glenn Fiedler over het repareren van de tijdspanne, voor het geval u Unity niet gebruikt.
Op dit punt kunnen we de beweging van het personage testen en zien hoe het voelt. Als we het niet leuk vinden, kunnen we altijd de parameters veranderen of de manier waarop de snelheid wordt gewijzigd bij het indrukken van de toets.
De personageknoppen lijken misschien erg gewichtloos en niet zo aangenaam als een op beweging gebaseerde beweging, maar dit is allemaal een kwestie van wat voor soort besturing het beste bij je spel past. Gelukkig is het veranderen van de manier waarop het personage beweegt vrij eenvoudig; het is voldoende om te wijzigen hoe de snelheidswaarde verandert in de stappen lopen en springen.
Dat is het voor het eerste deel van de serie. We zijn geëindigd met een eenvoudig bewegingsschema voor personages, maar niet veel meer. Het belangrijkste is dat we de weg hebben geëffend voor het volgende deel, waarin we het personage interacteren met een tilemap.