Updated frontend animation trigger mechanism.

This commit is contained in:
genxium 2022-11-25 11:20:05 +08:00
parent 1593965950
commit c58e690a47
9 changed files with 92 additions and 97 deletions

View File

@ -2,9 +2,10 @@
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md). This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
![gif_demo](./charts/along_wall_interaction_with_reconnection.gif) _(the following gif is sped up to 2x for file size reduction)_
![gif_demo](./charts/melee_attack_2.gif)
Please also checkout [this demo video](https://pan.baidu.com/s/1YkfuHjNLzlFVnKiEj6wrDQ?pwd=tkr5) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance. Please also checkout [this demo video](https://pan.baidu.com/s/1fy0CuFKnVP_Gn2cDfrj6yg?pwd=q5uc) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
The video mainly shows the following features. The video mainly shows the following features.
- The backend receives inputs from frontend peers and broadcasts back for synchronization. - The backend receives inputs from frontend peers and broadcasts back for synchronization.

View File

@ -69,6 +69,10 @@ const (
ATK_CHARACTER_STATE_ATKED1 = 3 ATK_CHARACTER_STATE_ATKED1 = 3
) )
const (
DEFAULT_PLAYER_RADIUS = float64(16)
)
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged. // These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
var DIRECTION_DECODER = [][]int32{ var DIRECTION_DECODER = [][]int32{
{0, 0}, {0, 0},
@ -193,7 +197,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit.ColliderRadius = float64(12) // Hardcoded pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pR.Players[playerId] = pPlayerFromDbInit pR.Players[playerId] = pPlayerFromDbInit
pR.PlayerDownsyncSessionDict[playerId] = session pR.PlayerDownsyncSessionDict[playerId] = session
@ -218,15 +222,16 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
* -- YFLu * -- YFLu
*/ */
defer pR.onPlayerReAdded(playerId) defer pR.onPlayerReAdded(playerId)
pR.PlayerDownsyncSessionDict[playerId] = session
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
pEffectiveInRoomPlayerInstance := pR.Players[playerId] pEffectiveInRoomPlayerInstance := pR.Players[playerId]
pEffectiveInRoomPlayerInstance.AckingFrameId = -1 pEffectiveInRoomPlayerInstance.AckingFrameId = -1
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1 pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
pEffectiveInRoomPlayerInstance.ColliderRadius = float64(16) // Hardcoded pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
pR.PlayerDownsyncSessionDict[playerId] = session
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
Logger.Warn("ReAddPlayerIfPossible finished.", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("joinIndex", pEffectiveInRoomPlayerInstance.JoinIndex), zap.Any("playerBattleState", pEffectiveInRoomPlayerInstance.BattleState), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("AckingFrameId", pEffectiveInRoomPlayerInstance.AckingFrameId), zap.Any("AckingInputFrameId", pEffectiveInRoomPlayerInstance.AckingInputFrameId), zap.Any("LastSentInputFrameId", pEffectiveInRoomPlayerInstance.LastSentInputFrameId)) Logger.Warn("ReAddPlayerIfPossible finished.", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("joinIndex", pEffectiveInRoomPlayerInstance.JoinIndex), zap.Any("playerBattleState", pEffectiveInRoomPlayerInstance.BattleState), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("AckingFrameId", pEffectiveInRoomPlayerInstance.AckingFrameId), zap.Any("AckingInputFrameId", pEffectiveInRoomPlayerInstance.AckingInputFrameId), zap.Any("LastSentInputFrameId", pEffectiveInRoomPlayerInstance.LastSentInputFrameId))
return true return true
@ -813,7 +818,7 @@ func (pR *Room) OnDismissed() {
X: 0, X: 0,
Y: 0, Y: 0,
}, },
HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider HitboxOffset: float64(24.0), // should be about the radius of the PlayerCollider
HitboxSize: &Vec2D{ HitboxSize: &Vec2D{
X: float64(45.0), X: float64(45.0),
Y: float64(32.0), Y: float64(32.0),
@ -945,6 +950,14 @@ func (pR *Room) onPlayerAdded(playerId int32) {
panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount)) panic(fmt.Sprintf("onPlayerAdded error, nil == playerPos, roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
} }
pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio) pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio)
// Hardcoded initial character orientation/facing
if 0 == (pR.Players[playerId].JoinIndex % 2) {
pR.Players[playerId].DirX = -2
pR.Players[playerId].DirY = 0
} else {
pR.Players[playerId].DirX = +2
pR.Players[playerId].DirY = 0
}
break break
} }

View File

@ -1,3 +1,8 @@
# Double playback speed of a video
```
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
```
# GIF creation cmd reference # GIF creation cmd reference
``` ```
ffmpeg -ss 12 -t 13 -i input.mp4 -vf "fps=10,scale=480:-1" -loop 0 output.gif ffmpeg -ss 12 -t 13 -i input.mp4 -vf "fps=10,scale=480:-1" -loop 0 output.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

BIN
charts/melee_attack_2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

View File

@ -529,8 +529,8 @@
0, 0,
0, 0,
1, 1,
0.5, 1,
0.5, 1,
1 1
] ]
}, },

View File

@ -40,27 +40,26 @@ cc.Class({
BaseCharacter.prototype.onLoad.call(this); BaseCharacter.prototype.onLoad.call(this);
}, },
updateCharacterAnim(newScheduledDirection, rdfPlayer, forceAnimSwitch) { updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) {
if (0 == rdfPlayer.framesToRecover) {
// Update directions // Update directions
if (forceAnimSwitch || null == this.activeDirection || (null != newScheduledDirection && (newScheduledDirection.dx != this.activeDirection.dx || newScheduledDirection.dy != this.activeDirection.dy))) {
this.activeDirection = newScheduledDirection;
if (this.animComp && this.animComp.node) { if (this.animComp && this.animComp.node) {
if (0 > newScheduledDirection.dx) { if (0 > rdfPlayer.dirX) {
this.animComp.node.scaleX = (-1.0); this.animComp.node.scaleX = (-1.0);
} else if (0 < newScheduledDirection.dx) { } else if (0 < rdfPlayer.dirX) {
this.animComp.node.scaleX = (1.0); this.animComp.node.scaleX = (1.0);
} }
} }
}
}
// Update per character state // Update per character state
let newCharacterState = rdfPlayer.characterState; let newCharacterState = rdfPlayer.characterState;
let prevCharacterState = (null == prevRdfPlayer ? window.ATK_CHARACTER_STATE.Idle1[0] : prevRdfPlayer.characterState);
if (newCharacterState != prevCharacterState) {
// Anim is edge-triggered
const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1]; const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
if (newAnimName != this.animComp.animationName) { if (newAnimName != this.animComp.animationName) {
this.animComp.playAnimation(newAnimName); this.animComp.playAnimation(newAnimName);
// console.log(`JoinIndex=${this.joinIndex}, Resetting anim to ${newAnimName}, dir changed: (${oldDx}, ${oldDy}) -> (${newScheduledDirection.dx}, ${newScheduledDirection.dy})`); console.log(`JoinIndex=${rdfPlayer.joinIndex}, Resetting anim to ${newAnimName}, state changed: (${prevCharacterState}, prevRdfPlayer is null? ${null == prevRdfPlayer}) -> (${newCharacterState})`);
}
} }
}, },
}); });

View File

@ -630,7 +630,7 @@ cc.Class({
} }
self.transitToState(ALL_MAP_STATES.VISUAL); self.transitToState(ALL_MAP_STATES.VISUAL);
self.battleState = ALL_BATTLE_STATES.IN_BATTLE; self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
self.applyRoomDownsyncFrameDynamics(rdf); self.applyRoomDownsyncFrameDynamics(rdf, self.recentRenderCache.getByFrameId(rdf.id - 1));
return dumpRenderCacheRet; return dumpRenderCacheRet;
}, },
@ -752,20 +752,14 @@ cc.Class({
playerScriptIns.setSpecies("SoldierWaterGhost"); playerScriptIns.setSpecies("SoldierWaterGhost");
} else if (2 == joinIndex) { } else if (2 == joinIndex) {
playerScriptIns.setSpecies("SoldierFireGhost"); playerScriptIns.setSpecies("SoldierFireGhost");
if (0 == playerDownsyncInfo.dirX && 0 == playerDownsyncInfo.dirY) {
playerScriptIns.animComp.node.scaleX = (-1.0);
}
} }
const wpos = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy);
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const cpos = self.virtualGridToPolygonColliderAnchorPos(vx, vy, playerDownsyncInfo.colliderRadius, playerDownsyncInfo.colliderRadius);
const d = playerDownsyncInfo.colliderRadius * 2, const d = playerDownsyncInfo.colliderRadius * 2,
x0 = cpos[0], [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, playerDownsyncInfo.colliderRadius, playerDownsyncInfo.colliderRadius),
y0 = cpos[1]; pts = [[0, 0], [d, 0], [d, d], [0, d]];
let pts = [[0, 0], [d, 0], [d, d], [0, d]];
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts); const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
@ -776,10 +770,7 @@ cc.Class({
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
newPlayerNode.active = true; newPlayerNode.active = true;
playerScriptIns.updateCharacterAnim({ playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true);
dx: playerDownsyncInfo.dirX,
dy: playerDownsyncInfo.dirY,
}, playerDownsyncInfo, true);
return [newPlayerNode, playerScriptIns]; return [newPlayerNode, playerScriptIns];
}, },
@ -798,9 +789,7 @@ cc.Class({
currSelfInput = null; currSelfInput = null;
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) { if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId); [prevSelfInput, currSelfInput] = self._generateInputFrameUpsync(noDelayInputFrameId);
prevSelfInput = prevAndCurrInputs[0];
currSelfInput = prevAndCurrInputs[1];
} }
let t0 = performance.now(); let t0 = performance.now();
@ -820,14 +809,15 @@ cc.Class({
let t2 = performance.now(); let t2 = performance.now();
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. // Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
/* /*
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId); const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
if (nonTrivialChaseEnded) { if (nonTrivialChaseEnded) {
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId); console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
} }
*/ */
self.applyRoomDownsyncFrameDynamics(rdf); // [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
let t3 = performance.now(); let t3 = performance.now();
} catch (err) { } catch (err) {
console.error("Error during Map.update", err); console.error("Error during Map.update", err);
@ -962,26 +952,17 @@ cc.Class({
} }
}, },
applyRoomDownsyncFrameDynamics(rdf) { applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
const self = this; const self = this;
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames)); for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) {
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
const immediatePlayerInfo = rdf.players[playerId]; const immediatePlayerInfo = rdf.players[playerId];
const wpos = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY); const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]);
const dx = (wpos[0] - playerRichInfo.node.x); const [wx, wy] = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY);
const dy = (wpos[1] - playerRichInfo.node.y); //const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y));
//const justJiggling = (self.jigglingEps1D >= Math.abs(dx) && self.jigglingEps1D >= Math.abs(dy)); playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.node.setPosition(wpos[0], wpos[1]);
// TODO: check "rdf.players[playerId].characterState" instead, might have to play Atk/Atked anim!
if (null != delayedInputFrameForPrevRenderFrame) {
const decodedInput = self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[playerRichInfo.joinIndex - 1]);
playerRichInfo.scriptIns.updateCharacterAnim(decodedInput, immediatePlayerInfo, false);
} else {
playerRichInfo.scriptIns.updateCharacterAnim(null, immediatePlayerInfo, false);
}
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
}); playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
}
}, },
getCachedInputFrameDownsyncWithPrediction(inputFrameId) { getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
@ -1044,9 +1025,7 @@ cc.Class({
const newVx = currPlayerDownsync.virtualGridX; const newVx = currPlayerDownsync.virtualGridX;
const newVy = currPlayerDownsync.virtualGridY; const newVy = currPlayerDownsync.virtualGridY;
const newCpos = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius); [playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius);
playerCollider.x = newCpos[0];
playerCollider.y = newCpos[1];
} }
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision // Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
@ -1068,12 +1047,11 @@ cc.Class({
if (0 > offender.dirX) { if (0 > offender.dirX) {
xfac = -1; xfac = -1;
} }
const offenderWpos = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
const bulletWx = offenderWpos[0] + xfac * meleeBullet.hitboxOffset; const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset;
const bulletWy = offenderWpos[1]; const bulletWy = offenderWy;
const bulletCpos = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5); const [bulletCx, bulletCy] = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5), pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; const newBulletCollider = collisionSys.createPolygon(bulletCx, bulletCy, pts);
const newBulletCollider = collisionSys.createPolygon(bulletCpos[0], bulletCpos[1], pts);
newBulletCollider.data = meleeBullet; newBulletCollider.data = meleeBullet;
collisionSysMap.set(collisionBulletIndex, newBulletCollider); collisionSysMap.set(collisionBulletIndex, newBulletCollider);
bulletColliders.set(collisionBulletIndex, newBulletCollider); bulletColliders.set(collisionBulletIndex, newBulletCollider);
@ -1180,9 +1158,9 @@ cc.Class({
} else { } else {
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
} }
const movement = self.virtualGridToWorldPos(decodedInput.dx + currPlayerDownsync.speed * decodedInput.dx, decodedInput.dy + currPlayerDownsync.speed * decodedInput.dy); const [movementX, movementY] = self.virtualGridToWorldPos(decodedInput.dx + currPlayerDownsync.speed * decodedInput.dx, decodedInput.dy + currPlayerDownsync.speed * decodedInput.dy);
playerCollider.x += movement[0]; playerCollider.x += movementX;
playerCollider.y += movement[1]; playerCollider.y += movementY;
} }
} }
@ -1209,10 +1187,8 @@ cc.Class({
const playerId = self.playerRichInfoArr[j].id; const playerId = self.playerRichInfoArr[j].id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const newVpos = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius);
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
thatPlayerInNextFrame.virtualGridX = newVpos[0]; [thatPlayerInNextFrame.virtualGridX, thatPlayerInNextFrame.virtualGridY] = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius);
thatPlayerInNextFrame.virtualGridY = newVpos[1];
} }
} }
@ -1225,29 +1201,30 @@ cc.Class({
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted. This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
*/ */
const self = this; const self = this;
let prevLatestRdf = null;
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame" let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
if (null == latestRdf) { if (null == latestRdf) {
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`); console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
return latestRdf; return [prevLatestRdf, latestRdf];
} }
if (renderFrameIdSt >= renderFrameIdEd) { if (renderFrameIdSt >= renderFrameIdEd) {
return latestRdf; return [prevLatestRdf, latestRdf];
} }
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) { for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"! const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRenderFrame) { if (null == currRenderFrame) {
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`); console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
return latestRdf; return [prevLatestRdf, latestRdf];
} }
const j = self._convertToInputFrameId(i, self.inputDelayFrames); const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j); const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
if (null == delayedInputFrame) { if (null == delayedInputFrame) {
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
return latestRdf; return [prevLatestRdf, latestRdf];
} }
prevLatestRdf = latestRdf;
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap); latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
if ( if (
self._allConfirmed(delayedInputFrame.confirmedList) self._allConfirmed(delayedInputFrame.confirmedList)
@ -1269,7 +1246,7 @@ cc.Class({
self.dumpToRenderCache(latestRdf); self.dumpToRenderCache(latestRdf);
} }
return latestRdf; return [prevLatestRdf, latestRdf];
}, },
_initPlayerRichInfoDict(players) { _initPlayerRichInfoDict(players) {
@ -1280,16 +1257,16 @@ cc.Class({
const immediatePlayerInfo = players[playerId]; const immediatePlayerInfo = players[playerId];
self.playerRichInfoDict.set(playerId, immediatePlayerInfo); self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, self.playerRichInfoDict.get(playerId)); const [theNode, theScriptIns] = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, immediatePlayerInfo);
Object.assign(self.playerRichInfoDict.get(playerId), { Object.assign(self.playerRichInfoDict.get(playerId), {
node: nodeAndScriptIns[0], node: theNode,
scriptIns: nodeAndScriptIns[1] scriptIns: theScriptIns,
}); });
if (self.selfPlayerInfo.id == playerId) { if (self.selfPlayerInfo.id == playerId) {
self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo); self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo);
nodeAndScriptIns[1].showArrowTipNode(); theScriptIns.showArrowTipNode();
} }
} }
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
@ -1351,13 +1328,13 @@ cc.Class({
polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) { polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) {
const self = this; const self = this;
const wpos = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH); const [wx, wy] = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH);
return self.worldToVirtualGridPos(wpos[0], wpos[1]) return self.worldToVirtualGridPos(wx, wy)
}, },
virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) { virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) {
const self = this; const self = this;
const wpos = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
return self.worldToPolygonColliderAnchorPos(wpos[0], wpos[1], halfBoundingW, halfBoundingH) return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
}, },
}); });

View File

@ -191,8 +191,8 @@ cc.Class({
currSelfInput = prevAndCurrInputs[1]; currSelfInput = prevAndCurrInputs[1];
} }
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
self.applyRoomDownsyncFrameDynamics(rdf); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
let t3 = performance.now(); let t3 = performance.now();
} catch (err) { } catch (err) {
console.error("Error during Map.update", err); console.error("Error during Map.update", err);