I denna handledning lär du dig att skapa rörliga plattformar och se till att objekt som rider dem bevarar deras relativa position. Vi hanterar även om det krossas mellan en plattform och marken.
Denna handledning bygger på Basic Platformer Physics serien. Specifikt använder vi koden baserat på 8: e delen av handledningen som utgångspunkt, med några ändringar. Kolla in handledningsserien, och särskilt sista delen. Principerna bakom implementeringen kommer att stå även om du använder en annan fysiklösning, men koden kommer att vara kompatibel med den version som presenteras i handledningsserien.
Du kan ladda ner demo från bilagorna. Använd WASD nycklar för att flytta karaktären, Rymden att gissa en klontecken, och P att koka en rörlig plattform. Den högra musknappen skapar en kakel. Du kan använda rullhjulet eller piltangenterna för att välja en kakel du vill placera. Glidarna ändrar storleken på spelarens karaktär.
Demon har publicerats under Enhet 2017.2b4, och källkoden är också kompatibel med den här versionen av Unity.
Låt oss först skapa ett manus för en rörlig plattform.
Låt oss börja med att skapa objektets klass.
allmän klass MovingPlatform: MovingObject
Låt oss nu initiera några grundläggande parametrar för objektet i init-funktionen.
public void Init () mAABB.HalfSize = ny Vector2 (32.0f, 8.0f); mSlopeWallHeight = 0; mMovingSpeed = 100,0f; mIsKinematic = true; mSpeed.x = mMovingSpeed;
Vi ställer in storlek och hastighet, och vi gör kollematikens collider, vilket innebär att det inte kommer att flyttas av vanliga föremål. Vi ställer också in mSlopeWallHeight
till 0, vilket innebär att plattformen inte kommer att klättra på sluttningarna - det kommer alltid att behandlas som väggar.
Beteendet för den här rörliga plattformen kommer att vara just detta: starta rörelsen rätt, och när du möter ett hinder, ändra riktningen 90 grader medsols.
public void CustomUpdate () if (mPS.pushesRightTile &&! mPS.pushesBottomTile) mSpeed.y = -mMovingSpeed; annars om (mPS.pushesBottomTile &&! mPS.pushesLeftTile) mSpeed.x = -mMovingSpeed; annars om (mPS.pushesLeftTile &&! mPS.pushesTopTile) mSpeed.y = mMovingSpeed; annars om (mPS.pushesTopTile &&! mPS.pushesRightTile) mSpeed.x = mMovingSpeed; UpdatePhysics ();
Här är mönstret visualiserat:
Just nu, om ett tecken står på en plattform, kommer plattformen enkelt att glida från under den, som om det inte fanns någon friktion mellan föremålen. Vi ska försöka åtgärda det genom att kopiera plattformens kompensation.
Först och främst vill vi vara medvetna om vilket föremål, om någon, vår karaktär står på. Låt oss förklara en hänvisning till det objektet i MovingObject
klass.
allmänhet MovingObject mMountParent = null;
Nu, i UpdatePhysicsResponse
, om vi upptäcker att vi kolliderar med ett objekt under oss, kan vi tilldela denna referens. Låt oss skapa en funktion som kommer att tilldela referensen först.
public void TryAutoMount (MovingObject-plattform) if (mMountParent == null) mMountParent = plattform;
Låt oss nu använda det på lämpliga platser, det är där vi säger att vårt objekt kolliderar med ett annat objekt under det.
annars om (överlapp.y == 0,0f) om (other.mAABB.Center.y> mAABB.Center.y) mPS.pushesTopObject = true; mSpeed.y = Mathf.Min (mSpeed.y, 0.0f); annars TryAutoMount (andra); mPS.pushesBottomObject = true; mSpeed.y = Mathf.Max (mSpeed.y, 0,0f); Fortsätta;
Första platsen är när vi kontrollerar om föremålen rör sig.
om (överlapp.y < 0.0f) mPS.pushesTopObject = true; mSpeed.y = Mathf.Min(mSpeed.y, 0.0f); else TryAutoMount(other); mPS.pushesBottomObject = true; mSpeed.y = Mathf.Max(mSpeed.y, 0.0f);
Den andra platsen är när de överlappar varandra.
Nu när vi har täckt det här, låt oss hantera rörelsen för vårt objekt. Låt oss ändra UpdatePhysics
funktion från föregående handledning.
Låt oss förklara en klassvariabel för offset som vi behöver flytta vår karaktär.
offentliga Vector2 mOffset;
Låt oss nu ersätta den gamla lokala offseten med klass ett.
mOffset = mSpeed * Time.deltaTime;
Om objektet ligger på en plattform, låt oss lägga till plattformens rörelse till offset.
mOffset = mSpeed * Time.deltaTime; om (mMountParent! = null) if (HasCollisionDataFor (mMountParent)) mOffset + = mMountParent.mPosition - mMountParent.mOldPosition; annars mMountParent = null;
Observera att vi också kontrollerar här om vi fortfarande har kontakt med objektet. Om så inte är fallet ställer vi in mMountParent
till null
, för att markera att det här objektet inte längre rider på någon annan.
Låt oss sedan flytta positionen av vårt objekt med den förskjutningen. Vi ska inte använda vår Flytta
funktion, men helt enkelt ändra positionen. Så i kollisionskontrollen mellan föremålen, som äger rum strax efter UpdatePhysics
, vi får resultatet för positionerna i denna ram istället för den tidigare.
mOffset = mSpeed * Time.deltaTime; om (mMountParent! = null) if (HasCollisionDataFor (mMountParent)) mOffset + = mMountParent.mPosition - mMountParent.mOldPosition; annars mMountParent = null; mPosition + = RoundVector (mOffset + mReminder); mAABB.Center = mPosition;
Låt oss nu flytta till UpdatePhysicsP2
, som kallas efter kollisionerna mellan föremålen har lösts. Här ångrar vi vår tidigare rörelse, som inte har kontrollerats om det är giltigt eller inte.
public void UpdatePhysicsP2 () mPosition - = RoundVector (mOffset + mReminder); mAABB.Center = mPosition;
Därefter fortsätter vi vidare till UpdatePhysicsResponse
att tillämpa ett drag utöver överlappning med andra objekt. Här har vi tidigare ändrat positionen direkt, men nu istället låt oss ändra mOffset
, så denna position förändring blir löst senare när vi använder vår Flytta
fungera.
om (minstaOverlap == Mathf.Abs (overlap.x)) float offsetX = overlap.x * speedRatioX; mOffset.x + = offsetX; offsetSum.x + = offsetX; om (överlappning.x < 0.0f) mPS.pushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f); else mPS.pushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f); else float offsetY = overlap.y * speedRatioY; mOffset.y += offsetY; offsetSum.y += offsetY; if (overlap.y < 0.0f) mPS.pushesTopObject = true; mSpeed.y = Mathf.Min(mSpeed.y, 0.0f); else TryAutoMount(other); mPS.pushesBottomObject = true; mSpeed.y = Mathf.Max(mSpeed.y, 0.0f);
Nu kan vi gå tillbaka till UpdatePhysicsP2
, där vi bara ringa UpdatePhysicsResponse
och Flytta
fungerar som vi gjorde tidigare, för att få rätt positionstillstånd.
mPosition - = RoundVector (mOffset + mReminder); mAABB.Center = mPosition; UpdatePhysicsResponse (); om (mOffset! = Vector2.zero) Flytta (mOffset, mSpeed, ref mPosition, ref mReminder, mAABB, ref mPS);
På grund av hur vi beställer fysikuppdateringarna, om barnobjektet uppdateras före föräldern, kommer barnet ständigt att förlora kontakten med plattformen när de reser upp / ner.
För att åtgärda detta, när vi ställer in mMountParent
, Om plattformen ligger bakom barnet i uppdateringskön byter vi dessa två, så förälderobjektet uppdaterar alltid först. Låt oss göra den ändringen i TryAutoMount
fungera.
public void TryAutoMount (MovingObject-plattform) if (mMountParent == null) mMountParent = plattform; om (platform.mUpdateId> mUpdateId) mGame.SwapUpdateIds (den här plattformen);
Som du kan se, om uppdaterings-id för plattformsobjektet är större än barnet, ändras objektets uppdateringsorder, vilket tar bort problemet.
Det är ganska mycket när det gäller att limma karaktären till den rörliga plattformen.
Det är ganska enkelt att upptäcka att krossas. I UpdatePhysicsResponse
, vi behöver se om överlappningen mot ett kinematiskt objekt rör oss till en vägg.
Låt oss ta hand om X-axeln först:
om (minstaOverlap == Mathf.Abs (overlap.x)) float offsetX = overlap.x * speedRatioX; mOffset.x + = offsetX; offsetSum.x + = offsetX; om (överlappning.x < 0.0f) mPS.pushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f); else mPS.pushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f);
Om objektet är på vår högra sida och vi redan trycker mot en vänster vägg, låt oss ringa en Krossa
funktion, som vi ska implementera senare. Gör detsamma för andra sidan.
om (överlappning.x < 0.0f) if (other.mIsKinematic && mPS.pushesLeftTile) Crush(); mPS.pushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f); else if (other.mIsKinematic && mPS.pushesRightTile) Crush(); mPS.pushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f);
Låt oss upprepa det för Y-axeln.
om (överlapp.y < 0.0f) if (other.mIsKinematic && mPS.pushesBottomTile) Crush(); mPS.pushesTopObject = true; mSpeed.y = Mathf.Min(mSpeed.y, 0.0f); else if (other.mIsKinematic && mPS.pushesTopTile) Crush(); TryAutoMount(other); mPS.pushesBottomObject = true; mSpeed.y = Mathf.Max(mSpeed.y, 0.0f);
De Krossa
funktionen kommer helt enkelt att flytta karaktären till mitten av kartan för demo.
public void Crush () mPosition = mMap.mPosition + new Vector3 (mMap.mWidth / 2 * Map.cTileSize, mMap.mHeight / 2 * Map.cTileSize);
Resultatet är att karaktären teleporteras när den krossas av en plattform.
Detta var en kort handledning eftersom det inte är en stor utmaning att lägga till rörliga plattformar, särskilt om du känner till fysiksystemet bra. Låna från hela koden i fysikhandledningsserien var det faktiskt en mycket smidig process.
Denna handledning har begärts ett par gånger, så jag hoppas att du tycker att den är användbar! Tack för att du läste och vi ses nästa gång!