First successful run in OfflineMap using gopkgs dynamics.

This commit is contained in:
genxium 2022-12-24 22:27:31 +08:00
parent cb571f56e8
commit 72782735d3
6 changed files with 211154 additions and 190 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"ver": "1.0.5", "ver": "1.0.5",
"uuid": "565a29f2-d75b-45a2-8596-b27e6415960d", "uuid": "c402c9d1-1d32-4dbb-993a-6bfa0099fa2b",
"isPlugin": true, "isPlugin": true,
"loadPluginInWeb": true, "loadPluginInWeb": true,
"loadPluginInNative": true, "loadPluginInNative": true,

View File

@ -461,7 +461,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.05530045313827, 216.50635094610968,
0, 0,
0, 0,
0, 0,

View File

@ -76,7 +76,7 @@ cc.Class({
self.snapIntoPlatformOverlap = 0.1; self.snapIntoPlatformOverlap = 0.1;
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on" self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame) self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
[self.gravityX, self.gravityY] = [0, -0.5*self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2) [self.gravityX, self.gravityY] = [0, -0.5 * self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2)
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
@ -109,10 +109,11 @@ cc.Class({
self._resetCurrentMatch(); self._resetCurrentMatch();
const spaceW = newMapSize.width * newTileSize.width; const spaceW = newMapSize.width * newTileSize.width;
const spaceH = newMapSize.height * newTileSize.height; const spaceH = newMapSize.height * newTileSize.height;
const spaceOffsetX = (spaceW >> 1); self.spaceOffsetX = (spaceW >> 1);
const spaceOffsetY = (spaceH >> 1); self.spaceOffsetY = (spaceH >> 1);
const minStep = 8; const minStep = 8;
self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs(spaceW, spaceH, minStep, minStep); self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs(spaceW, spaceH, minStep, minStep);
self.gopkgsCollisionSysMap = {}; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions!
let barrierIdCounter = 0; let barrierIdCounter = 0;
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
@ -124,7 +125,7 @@ cc.Class({
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts); const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
const gopkgsBarrier = gopkgs.NewBarrierJs(gopkgsBoundary); const gopkgsBarrier = gopkgs.NewBarrierJs(gopkgsBoundary);
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, spaceOffsetX, spaceOffsetY, gopkgsBarrier, "Barrier"); const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
self.gopkgsCollisionSys.Add(newBarrierCollider); self.gopkgsCollisionSys.Add(newBarrierCollider);
if (false && self.showCriticalCoordinateLabels) { if (false && self.showCriticalCoordinateLabels) {
@ -161,7 +162,7 @@ cc.Class({
// console.log("Created barrier: ", newBarrierCollider); // console.log("Created barrier: ", newBarrierCollider);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.collisionSysMap.set(collisionBarrierIndex, newBarrierCollider); self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
} }
const startPlayer1 = gopkgs.NewPlayerDownsyncJs(10, self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[0], self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[1], 0, 0, 0, 0, 1 * self.worldToVirtualGridRatio, 0, window.ATK_CHARACTER_STATE.InAirIdle1[0], 1, 100, 100, true, 12); const startPlayer1 = gopkgs.NewPlayerDownsyncJs(10, self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[0], self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[1], 0, 0, 0, 0, 1 * self.worldToVirtualGridRatio, 0, window.ATK_CHARACTER_STATE.InAirIdle1[0], 1, 100, 100, true, 12);
@ -171,7 +172,11 @@ cc.Class({
const startRdf = gopkgs.NewRoomDownsyncFrameJs(window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, [startPlayer1, startPlayer2], []); const startRdf = gopkgs.NewRoomDownsyncFrameJs(window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, [startPlayer1, startPlayer2], []);
self.selfPlayerInfo = { self.selfPlayerInfo = {
id: 11 Id: 11,
JoinIndex: 2,
// For compatibility
id: 11,
joinIndex: 2,
}; };
self.onRoomDownsyncFrame(startRdf); self.onRoomDownsyncFrame(startRdf);
@ -200,7 +205,7 @@ cc.Class({
currSelfInput = prevAndCurrInputs[1]; currSelfInput = prevAndCurrInputs[1];
} }
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, false);
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
self.showDebugBoundaries(rdf); self.showDebugBoundaries(rdf);
++self.renderFrameId; ++self.renderFrameId;
@ -229,7 +234,7 @@ cc.Class({
if (notSelfUnconfirmed) { if (notSelfUnconfirmed) {
shouldForceDumping2 = false; shouldForceDumping2 = false;
shouldForceResync = false; shouldForceResync = false;
self.othersForcedDownsyncRenderFrameDict.set(rdf.id, rdf); self.othersForcedDownsyncRenderFrameDict.set(rdf.Id, rdf);
} }
/* /*
TODO TODO
@ -250,8 +255,7 @@ cc.Class({
} }
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) // The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
const players = rdf.Players; self._initPlayerRichInfoDict(gopkgs.GetPlayersArrJs(rdf));
self._initPlayerRichInfoDict(players);
if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) { if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly // In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
@ -273,4 +277,121 @@ cc.Class({
// [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics" // [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics"
return dumpRenderCacheRet; return dumpRenderCacheRet;
}, },
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
const self = this;
let prevLatestRdf = null,
latestRdf = null;
for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) {
const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRdf) {
throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`;
}
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.recentInputCache.getByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing
if (null == delayedInputFrame) {
// Shouldn't happen!
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
const delayedInputFrameJs = gopkgs.NewInputFrameDownsyncJs(j, delayedInputFrame.inputList, delayedInputFrame.confirmedList);
const jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames);
const delayedInputFrameForPrevRenderFrame = self.recentInputCache.getByFrameId(jPrev);
const delayedInputFrameForPrevRenderFrameJs = gopkgs.NewInputFrameDownsyncJs(jPrev, delayedInputFrameForPrevRenderFrame.inputList, delayedInputFrameForPrevRenderFrame.confirmedList);
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputFrameJs, delayedInputFrameForPrevRenderFrameJs, currRdf, collisionSys, collisionSysMap, self.gravityX, self.gravityY, self.jumpingInitVelY, self.inputDelayFrames, self.inputScaleFrames, self.spaceOffsetX, self.spaceOffsetY, self.snapIntoPlatformOverlap, self.snapIntoPlatformThreshold, self.worldToVirtualGridRatio, self.virtualGridToWorldRatio);
if (true == isChasing) {
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = nextRdf.id;
} else if (nextRdf.id == self.chaserRenderFrameId + 1) {
self.chaserRenderFrameId = nextRdf.id; // To avoid redundant calculation
}
self.recentRenderCache.setByFrameId(nextRdf, nextRdf.id);
prevLatestRdf = currRdf;
latestRdf = nextRdf;
}
return [prevLatestRdf, latestRdf];
},
_initPlayerRichInfoDict(playersArr) {
const self = this;
for (let k in playersArr) {
const immediatePlayerInfo = playersArr[k];
const playerId = immediatePlayerInfo.Id;
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.JoinIndex, immediatePlayerInfo.VirtualGridX, immediatePlayerInfo.VirtualGridY, immediatePlayerInfo);
Object.assign(self.playerRichInfoDict.get(playerId), {
node: nodeAndScriptIns[0],
scriptIns: nodeAndScriptIns[1],
});
if (self.selfPlayerInfo.Id == playerId) {
self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo);
nodeAndScriptIns[1].showArrowTipNode();
}
}
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
self.playerRichInfoArr[playerRichInfo.JoinIndex - 1] = playerRichInfo;
});
},
applyRoomDownsyncFrameDynamics(rdf, prevRdf) {
const self = this;
const playersArr = gopkgs.GetPlayersArrJs(rdf);
for (let k in playersArr) {
const currPlayerDownsync = playersArr[k];
const prevRdfPlayer = (null == prevRdf ? null : gopkgs.GetPlayersArrJs(prevRdf)[k]);
const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY);
const playerRichInfo = self.playerRichInfoArr[k];
playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed);
currPlayerDownsync.characterState = currPlayerDownsync.CharacterState;
currPlayerDownsync.dirX = currPlayerDownsync.DirX;
currPlayerDownsync.dirY = currPlayerDownsync.DirY;
currPlayerDownsync.framesToRecover = currPlayerDownsync.FrameToRecover;
playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false);
}
},
spawnPlayerNode(joinIndex, vx, vy, playerDownsyncInfo) {
const self = this;
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
if (1 == joinIndex) {
playerScriptIns.setSpecies("SoldierWaterGhost");
} else if (2 == joinIndex) {
playerScriptIns.setSpecies("UltramanTiga");
}
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node;
const halfColliderWidth = playerDownsyncInfo.ColliderRadius,
halfColliderHeight = playerDownsyncInfo.ColliderRadius + playerDownsyncInfo.ColliderRadius; // avoid multiplying
const colliderWidth = halfColliderWidth + halfColliderWidth,
colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying
const newPlayerCollider = gopkgs.GenerateRectColliderJs(wx, wy, colliderWidth, colliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
self.gopkgsCollisionSys.Add(newPlayerCollider);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
self.gopkgsCollisionSysMap[collisionPlayerIndex] = newPlayerCollider;
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.ColliderRadius}`);
safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5);
newPlayerNode.active = true;
playerDownsyncInfo.characterState = playerDownsyncInfo.CharacterState;
playerDownsyncInfo.dirX = playerDownsyncInfo.DirX;
playerDownsyncInfo.dirY = playerDownsyncInfo.DirY;
playerDownsyncInfo.framesToRecover = playerDownsyncInfo.FrameToRecover;
playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true);
return [newPlayerNode, playerScriptIns];
},
}); });

View File

@ -6,6 +6,7 @@ all: help
GOPROXY=https://goproxy.io GOPROXY=https://goproxy.io
serve: serve:
gopherjs clean
gopherjs serve $(PROJECTNAME) gopherjs serve $(PROJECTNAME)
build: build:

View File

@ -61,6 +61,23 @@ func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, meleeBullets
}) })
} }
func NewInputFrameDownsyncJs(inputFrameId int32, inputList []uint64, confirmedList uint64) *js.Object {
return js.MakeFullWrapper(&InputFrameDownsync{
InputFrameId: inputFrameId,
InputList: inputList,
ConfirmedList: confirmedList,
})
}
func GetPlayersArrJs(rdf *RoomDownsyncFrame) []*js.Object {
// We couldn't just use the existing getters or field names to access non-primitive fields in Js
ret := make([]*js.Object, 0, len(rdf.PlayersArr))
for _, player := range rdf.PlayersArr {
ret = append(ret, js.MakeFullWrapper(player))
}
return ret
}
func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
/* /*
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows. [WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
@ -70,13 +87,15 @@ func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding
space.Add(a); space.Add(a);
``` ```
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good. The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime.
*/ */
return js.MakeWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag)) return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
} }
func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
return js.MakeWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag)) return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
} }
func CheckCollisionJs(obj *resolv.Object, dx, dy float64) *js.Object { func CheckCollisionJs(obj *resolv.Object, dx, dy float64) *js.Object {
@ -98,8 +117,11 @@ func main() {
"NewPlayerDownsyncJs": NewPlayerDownsyncJs, "NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs, "NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs, "NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewInputFrameDownsyncJs": NewInputFrameDownsyncJs,
"GenerateRectColliderJs": GenerateRectColliderJs, "GenerateRectColliderJs": GenerateRectColliderJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs, "GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetPlayersArrJs": GetPlayersArrJs,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
"CheckCollisionJs": CheckCollisionJs, "CheckCollisionJs": CheckCollisionJs,
}) })
} }