From 66dfcaa0f5d3132feb2f3b7fd7a2bd11293b3ea9 Mon Sep 17 00:00:00 2001 From: genxium Date: Sat, 10 Dec 2022 12:30:44 +0800 Subject: [PATCH] Fixes for platform snapping. --- frontend/assets/resources/map/dungeon/map.tmx | 36 +++-- frontend/assets/scripts/AttackingCharacter.js | 22 +++ frontend/assets/scripts/OfflineMap.js | 129 ++++++++---------- 3 files changed, 103 insertions(+), 84 deletions(-) diff --git a/frontend/assets/resources/map/dungeon/map.tmx b/frontend/assets/resources/map/dungeon/map.tmx index f2eb67f..e970d9c 100644 --- a/frontend/assets/resources/map/dungeon/map.tmx +++ b/frontend/assets/resources/map/dungeon/map.tmx @@ -1,11 +1,11 @@ - + - eJzt2qFu21AUgOEo1UhBtYJOKh/bmxRMRYNFfZPxwcGi7j3nSInkXTmx47k+1/d84EOJIuf+PtZ14pvdbncDAAAAAAAAAAAAAAAAAAAz3B5FHwfree059X9NKLpDZP/90an/S+fpjH2D9P+3/zn6t6ff/2mk/20FrfT/uP5D9G/b4bs/HA2tTXlNiG6l/7r9M9Bf/+gOkf0/zRDdTH/99ddf/5j+NdBff/31119//bfV/9fE990f6d9W//fO25nXDv8//+l81z+8/9w1Pf2m+PM/6a+//vofjuWxoH/7/R+L/uZff/3j+38esWT/Pv1j+491H+r/u/PjSqf+9n/19J/a/nln/78VU/tPbb/Xf1PG+g81/ta5O9O+7F96vpL+cf2H2t/1lN0/ov+Xzlf9q+l/6brf7780/evtP2ePeC391+8/5Rwo13LJvmOW7L+voEWN/Zda40vmtNe/nf7R9M99Duivv/76R7eI6h+9/tGy3/9Fr380/XPTPzf9c9M/N/1z0z83/XPTPzf9c9M/N/1z0z83/XPTPzf9c9N/vvtCdEv9t9e//IxL9G+vfzT9l5vV6Jb6rzv/LdB/XbVdOzz/Pb1V9Kzqv/35r43+80Xdsy3dP7rDVvu3QP/tzaz+5l//bfSvfU+g//r959B/vf5b3s/rX9f8107/3PTPLXt/4jsAAAAAAAAAAAAAAAAA6/sLjbyILg== + eJzt2zFv2kAYgGFElCVD1AytlL1b/0mGKlPHTvkn3Tt27NT+zxoJJHqysXGMv/N9z/BMRsjc6zsOMHe73e4OAAAAAAAAAAAAAAAAAABmeDiKPg/W83bm1P8toegOkf33R6f+3zsvA/YN0v///kP0b895/5eR/g8VtNL/dv376N+2w2v/eNQ3NuWaEN1K/3X7Z6C//tEdIvvfzxDdTH/99ddf/5j+NdBff/31119//bfV/+fExz0d6d9W/z+d3wPHDr8//+181T+8/9wxPX2n+OOd9Ndff/0P5/Jc0L/9/s9Ff/Nff/3j+38YsWT/c/rH9h/r3tf/V+fblU797f/q6T+1/evO/n8rpvaf2n6v/6aM9e9r/KXzONC+7F96vZL+cf372j+eKbvfov+nzmf9q+l/ad0/7780/evtP2ePeC391+8/5Roox3LJvmOW7L+voEWN/Zca40vmtNe/nf7R9M99Deivv/76R7eI6h89/tGyf/6LHv9o+uemf27656Z/bvrnpn9u+uemf27656Z/bvrnpn9u+uemf27656b/fE+F6Jb6b7t/+XylW9yjpn89/c3/uvuPzc/olvqvO/9boP+6als73P89ruX/BOifm/7jc3/o2Nh+cAt7Qu//l9u3tt7rf938b132/hn/86n/evO/9j2B/uv3n0P/9fpveT+vf13zv3b656Z/btn7E98BAAAAAAAAAAAAAAAAWN8/Hh2Hvw== @@ -170,23 +170,23 @@ - + - + - + - + - + @@ -198,19 +198,19 @@ - + - + - + - + @@ -222,7 +222,19 @@ - + + + + + + + + + + + + + diff --git a/frontend/assets/scripts/AttackingCharacter.js b/frontend/assets/scripts/AttackingCharacter.js index 3d446a3..c0d00fb 100644 --- a/frontend/assets/scripts/AttackingCharacter.js +++ b/frontend/assets/scripts/AttackingCharacter.js @@ -10,6 +10,28 @@ window.ATK_CHARACTER_STATE = { InAirAtked1: [6, "Atked1"], }; +window.toInAirConjugate = function(foo) { + switch (foo) { + case window.ATK_CHARACTER_STATE.Idle1[0]: + case window.ATK_CHARACTER_STATE.Walking[0]: + return window.ATK_CHARACTER_STATE.InAirIdle1[0]; + case window.ATK_CHARACTER_STATE.Atk1[0]: + return window.ATK_CHARACTER_STATE.InAirAtk1[0]; + case window.ATK_CHARACTER_STATE.Atked1[0]: + return window.ATK_CHARACTER_STATE.InAirAtked1[0]; + + case window.ATK_CHARACTER_STATE.InAirIdle1[0]: + return window.ATK_CHARACTER_STATE.Idle1[0]; + case window.ATK_CHARACTER_STATE.InAirAtk1[0]: + return window.ATK_CHARACTER_STATE.Atk1[0]; + case window.ATK_CHARACTER_STATE.InAirAtked1[0]: + return window.ATK_CHARACTER_STATE.Atked1[0]; + default: + console.warn(`Invalid characterState ${foo} received, no in air conjugate is available!`); + return null; + } +} + window.ATK_CHARACTER_STATE_ARR = []; for (let k in window.ATK_CHARACTER_STATE) { window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]); diff --git a/frontend/assets/scripts/OfflineMap.js b/frontend/assets/scripts/OfflineMap.js index 4d9ae2e..f3f5ad2 100644 --- a/frontend/assets/scripts/OfflineMap.js +++ b/frontend/assets/scripts/OfflineMap.js @@ -2,6 +2,12 @@ const i18n = require('LanguageData'); i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field const OnlineMap = require('./Map'); +/* +[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..." + +Moreover, this "snapIntoPlatformOverlap" should be small enough such that the jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame). +*/ +const snapIntoPlatformOverlap = 0.1; cc.Class({ extends: OnlineMap, @@ -238,13 +244,16 @@ cc.Class({ const nextRenderFrameMeleeBullets = []; - const movements = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order - const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order - const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order + // Guaranteed determinism regardless of traversal order + const jumpTriggered = new Array(self.playerRichInfoArr.length); + const movements = new Array(self.playerRichInfoArr.length); + const bulletPushbacks = new Array(self.playerRichInfoArr.length); + const effPushbacks = new Array(self.playerRichInfoArr.length); // Reset playerCollider position from the "virtual grid position" for (let j in self.playerRichInfoArr) { const joinIndex = parseInt(j) + 1; + jumpTriggered[joinIndex - 1] = false; movements[joinIndex - 1] = [0.0, 0.0]; bulletPushbacks[joinIndex - 1] = [0.0, 0.0]; effPushbacks[joinIndex - 1] = [0.0, 0.0]; @@ -259,7 +268,7 @@ cc.Class({ const newVy = currPlayerDownsync.virtualGridY; [playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius); - // Process gravity before anyother interaction + // Process gravity before anyother interaction, by now "currPlayerDownsync.velX & velY" are properly snapped to be parallel to the edge of its standing platform if necessary [movements[joinIndex - 1][0], movements[joinIndex - 1][1]] = self.virtualGridToWorldPos(currPlayerDownsync.velX, currPlayerDownsync.velY); playerCollider.x += movements[joinIndex - 1][0]; playerCollider.y += movements[joinIndex - 1][1]; @@ -375,6 +384,22 @@ cc.Class({ const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1])); const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel); const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel); + /* + [WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState". + */ + if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) { + const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState); + const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState); + if ( + !characStateAlreadyInAir + && + characStateIsInterruptWaivable + ) { + thatPlayerInNextFrame.velY = currPlayerDownsync.speed * 4.5; + jumpTriggered[joinIndex - 1] = true; + console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnB at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}, nextVelY=${thatPlayerInNextFrame.velY}, characStateAlreadyInAir=${characStateAlreadyInAir}, characStateIsInterruptWaivable=${characStateIsInterruptWaivable}`); + } + } if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) { // console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); @@ -390,61 +415,21 @@ cc.Class({ // console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`); // console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); - if (!window.ATK_CHARACTER_STATE_IN_AIR_SET.has(currPlayerDownsync.characterState)) { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0]; - } else { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.InAirAtk1[0]; - } + thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0]; } } else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) { // console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); } else { - // No bullet trigger, process movement inputs - if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) { - const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState); - const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState); - console.log(`playerId=${playerId} triggered a rising-edge of btnB at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}, characStateAlreadyInAir=${characStateAlreadyInAir}, characStateIsInterruptWaivable=${characStateIsInterruptWaivable}`); - if ( - !characStateAlreadyInAir - && - characStateIsInterruptWaivable - ) { - thatPlayerInNextFrame.velY = currPlayerDownsync.speed * 4; - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.InAirIdle1[0]; - if (window.ATK_CHARACTER_STATE.Walking[0] == currPlayerDownsync.characterState) { - console.warn(`curRenderFrameId=${currRenderFrame.id}, playerId=${playerId}, joinIndex=${joinIndex} characterState set to AirIdle1 by jumping`); - } - } + // No bullet trigger, process joystick movement inputs (except for jumping). + if (0 != decodedInput.dx || 0 != decodedInput.dy) { + // Update directions and thus would eventually update moving animation accordingly + thatPlayerInNextFrame.dirX = decodedInput.dx; + thatPlayerInNextFrame.dirY = decodedInput.dy; + thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0]; + thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed; } else { - if (0 != decodedInput.dx || 0 != decodedInput.dy) { - // Update directions and thus would eventually update moving animation accordingly - thatPlayerInNextFrame.dirX = decodedInput.dx; - thatPlayerInNextFrame.dirY = decodedInput.dy; - if (!window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState)) { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0]; - thatPlayerInNextFrame.velX = currPlayerDownsync.speed * decodedInput.dx; - if (window.ATK_CHARACTER_STATE.Idle1[0] == currPlayerDownsync.characterState || window.ATK_CHARACTER_STATE.InAirIdle1[0] == currPlayerDownsync.characterState) { - console.warn(`curRenderFrameId=${currRenderFrame.id}, playerId=${playerId}, joinIndex=${joinIndex} characterState set to Walking by dir input`); - } - } else { - // There's no characterState of "InAirWalking" :) - thatPlayerInNextFrame.velX = currPlayerDownsync.speed * decodedInput.dx; - } - } else { - if (!window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState)) { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; - thatPlayerInNextFrame.velX = 0; - if (window.ATK_CHARACTER_STATE.Walking[0] == currPlayerDownsync.characterState) { - console.warn(`curRenderFrameId=${currRenderFrame.id}, playerId=${playerId}, joinIndex=${joinIndex} characterState set to Idle1 by no dir input`); - } - } else { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.InAirIdle1[0]; - if (window.ATK_CHARACTER_STATE.Walking[0] == currPlayerDownsync.characterState) { - console.warn(`curRenderFrameId=${currRenderFrame.id}, playerId=${playerId}, joinIndex=${joinIndex} characterState set to AirIdle1 by no dir input but already in air`); - } - // let inertia carry "velX" when in air - } - } + thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; + thatPlayerInNextFrame.velX = 0; } } } @@ -462,7 +447,7 @@ cc.Class({ const currPlayerDownsync = currRenderFrame.players[playerId]; const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; let fallStopping = false; - let remainsNotInAir = false; + let [snappedIntoPlatformEx, snappedIntoPlatformEy] = [null, null]; for (const potential of potentials) { // Test if the player collides with the wall if (!playerCollider.collides(potential, result2)) continue; @@ -470,33 +455,33 @@ cc.Class({ let [pushbackX, pushbackY] = [result2.overlap * result2.overlap_x, result2.overlap * result2.overlap_y]; if (null == potential.data) { // "null == potential.data" implies a barrier + const remainsNotInAir = (!currPlayerDownsync.inAir); const localFallStopping = (currPlayerDownsync.inAir && 0 > pushbackY); // prevents false fall-stopping on the lateral sides - const localRemainsNotInAir = (!currPlayerDownsync.inAir); - // [WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..." - if (localFallStopping) { - pushbackY = 0.95 * pushbackY; - fallStopping = true; - } - if (localRemainsNotInAir) { - pushbackY = 0; - remainsNotInAir = true; + if (remainsNotInAir || localFallStopping) { + fallStopping |= localFallStopping; + [pushbackX, pushbackY] = [(result2.overlap - snapIntoPlatformOverlap) * result2.overlap_x, (result2.overlap - snapIntoPlatformOverlap) * result2.overlap_y] + // [overlay_x, overlap_y] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once! + snappedIntoPlatformEx = -result2.overlap_y; + snappedIntoPlatformEy = result2.overlap_x; + if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY) { + [snappedIntoPlatformEx, snappedIntoPlatformEy] = [-snappedIntoPlatformEx, -snappedIntoPlatformEy]; + } } } // What if we're on the edge of 2 barriers? Would adding up make an unexpected bounce? effPushbacks[joinIndex - 1][0] += pushbackX; effPushbacks[joinIndex - 1][1] += pushbackY; } - if (fallStopping) { - thatPlayerInNextFrame.velX = 0; - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; - thatPlayerInNextFrame.velY = 0; + if (false == jumpTriggered[joinIndex-1] && null != snappedIntoPlatformEx && null != snappedIntoPlatformEy) { thatPlayerInNextFrame.inAir = false; - if (window.ATK_CHARACTER_STATE.Walking[0] == currPlayerDownsync.characterState) { - console.warn(`curRenderFrameId=${currRenderFrame.id}, playerId=${playerId}, joinIndex=${joinIndex} characterState set to Idle1 by fallStopping`); + if (fallStopping) { + thatPlayerInNextFrame.velY = 0; // shuts off the velocity component introduced by gravity, otherwise the character will slip down along a slope } + const dotProd = thatPlayerInNextFrame.velX * snappedIntoPlatformEx + thatPlayerInNextFrame.velY * snappedIntoPlatformEy; + [thatPlayerInNextFrame.velX, thatPlayerInNextFrame.velY] = [dotProd * snappedIntoPlatformEx, dotProd * snappedIntoPlatformEy]; } - if (remainsNotInAir) { - thatPlayerInNextFrame.inAir = false; + if (currPlayerDownsync.inAir != thatPlayerInNextFrame.inAir) { + thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState); } }