mirror of
https://github.com/genxium/DelayNoMore
synced 2025-04-11 01:21:15 +00:00
443 lines
22 KiB
JavaScript
443 lines
22 KiB
JavaScript
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');
|
|
|
|
cc.Class({
|
|
extends: OnlineMap,
|
|
|
|
onDestroy() {
|
|
console.warn("+++++++ Map onDestroy()");
|
|
},
|
|
|
|
onLoad() {
|
|
const self = this;
|
|
window.mapIns = self;
|
|
self.showCriticalCoordinateLabels = false;
|
|
|
|
const mapNode = self.node;
|
|
const canvasNode = mapNode.parent;
|
|
|
|
self.mainCameraNode = self.canvasNode.getChildByName("Main Camera");
|
|
self.mainCamera = self.mainCameraNode.getComponent(cc.Camera);
|
|
for (let child of self.mainCameraNode.children) {
|
|
child.setScale(1 / self.mainCamera.zoomRatio);
|
|
}
|
|
self.widgetsAboveAllNode = self.mainCameraNode.getChildByName("WidgetsAboveAll");
|
|
self.mainCameraNode.setPosition(cc.v2());
|
|
|
|
/** Init required prefab ended. */
|
|
|
|
self.inputDelayFrames = 8;
|
|
self.inputScaleFrames = 2;
|
|
self.inputFrameUpsyncDelayTolerance = 2;
|
|
|
|
self.renderCacheSize = 1024;
|
|
self.serverFps = 60;
|
|
self.rollbackEstimatedDt = 0.016667;
|
|
self.rollbackEstimatedDtMillis = 16.667;
|
|
self.rollbackEstimatedDtNanos = 16666666;
|
|
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
|
|
|
|
self.worldToVirtualGridRatio = 1000;
|
|
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
|
|
self.meleeSkillConfig = {
|
|
1: {
|
|
// for offender
|
|
startupFrames: 10,
|
|
activeFrames: 20,
|
|
recoveryFrames: 34, // usually but not always "startupFrames+activeFrames", I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing
|
|
recoveryFramesOnBlock: 34,
|
|
recoveryFramesOnHit: 34,
|
|
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
|
|
// for defender
|
|
hitStunFrames: 18,
|
|
blockStunFrames: 9,
|
|
pushback: 8.0,
|
|
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
|
|
damage: 5,
|
|
hitboxSizeX: 24.0,
|
|
hitboxSizeY: 32.0,
|
|
selfMoveforwardX: 0,
|
|
selfMoveforwardY: 0,
|
|
}
|
|
};
|
|
|
|
/*
|
|
[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, "snapIntoPlatformOverlap" should be small enough such that the walking "velX" or jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
|
|
*/
|
|
self.snapIntoPlatformOverlap = 0.1;
|
|
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.gravityX, self.gravityY] = [0, -0.5 * self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2)
|
|
|
|
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
|
|
|
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
|
|
cc.loader.loadRes(fullPathOfTmxFile, cc.TiledMapAsset, (err, tmxAsset) => {
|
|
if (null != err) {
|
|
console.error(err);
|
|
return;
|
|
}
|
|
|
|
tiledMapIns.tmxAsset = null;
|
|
mapNode.removeAllChildren();
|
|
|
|
if (self.showCriticalCoordinateLabels) {
|
|
const drawer = new cc.Node();
|
|
drawer.setPosition(cc.v2(0, 0))
|
|
safelyAddChild(self.node, drawer);
|
|
setLocalZOrder(drawer, 999);
|
|
const g = drawer.addComponent(cc.Graphics);
|
|
g.lineWidth = 2;
|
|
self.g = g;
|
|
}
|
|
|
|
tiledMapIns.tmxAsset = tmxAsset;
|
|
const newMapSize = tiledMapIns.getMapSize();
|
|
const newTileSize = tiledMapIns.getTileSize();
|
|
self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
|
|
self.node.setPosition(cc.v2(0, 0));
|
|
|
|
self._resetCurrentMatch();
|
|
const spaceW = newMapSize.width * newTileSize.width;
|
|
const spaceH = newMapSize.height * newTileSize.height;
|
|
self.spaceOffsetX = (spaceW >> 1);
|
|
self.spaceOffsetY = (spaceH >> 1);
|
|
const minStep = 8;
|
|
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;
|
|
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
|
|
for (let boundaryObj of boundaryObjs.barriers) {
|
|
const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(boundaryObj.anchor.x, boundaryObj.anchor.y);
|
|
const gopkgsBoundaryPts = Array.from(boundaryObj, p => {
|
|
return gopkgs.NewVec2DJs(p.x, p.y);
|
|
});
|
|
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
|
|
const gopkgsBarrier = gopkgs.NewBarrierJs(gopkgsBoundary);
|
|
|
|
const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier");
|
|
self.gopkgsCollisionSys.Add(newBarrierCollider);
|
|
|
|
if (false && self.showCriticalCoordinateLabels) {
|
|
for (let i = 0; i < boundaryObj.length; ++i) {
|
|
const barrierVertLabelNode = new cc.Node();
|
|
switch (i % 4) {
|
|
case 0:
|
|
barrierVertLabelNode.color = cc.Color.RED;
|
|
break;
|
|
case 1:
|
|
barrierVertLabelNode.color = cc.Color.GRAY;
|
|
break;
|
|
case 2:
|
|
barrierVertLabelNode.color = cc.Color.BLACK;
|
|
break;
|
|
default:
|
|
barrierVertLabelNode.color = cc.Color.MAGENTA;
|
|
break;
|
|
}
|
|
const wx = boundaryObj.anchor.x + boundaryObj[i].x,
|
|
wy = boundaryObj.anchor.y + boundaryObj[i].y;
|
|
barrierVertLabelNode.setPosition(cc.v2(wx, wy));
|
|
const barrierVertLabel = barrierVertLabelNode.addComponent(cc.Label);
|
|
barrierVertLabel.fontSize = 12;
|
|
barrierVertLabel.lineHeight = barrierVertLabel.fontSize + 1;
|
|
barrierVertLabel.string = `(${wx.toFixed(1)}, ${wy.toFixed(1)})`;
|
|
safelyAddChild(self.node, barrierVertLabelNode);
|
|
setLocalZOrder(barrierVertLabelNode, 5);
|
|
|
|
barrierVertLabelNode.active = true;
|
|
}
|
|
|
|
}
|
|
// console.log("Created barrier: ", newBarrierCollider);
|
|
++barrierIdCounter;
|
|
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
|
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 startPlayer2 = gopkgs.NewPlayerDownsyncJs(11, self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[0], self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[1], 0, 0, 0, 0, 1 * self.worldToVirtualGridRatio, 0, window.ATK_CHARACTER_STATE.InAirIdle1[0], 2, 100, 100, true, 12);
|
|
|
|
const startRdf = gopkgs.NewRoomDownsyncFrameJs(window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, [startPlayer1, startPlayer2], []);
|
|
|
|
self.selfPlayerInfo = {
|
|
Id: 11,
|
|
JoinIndex: 2,
|
|
// For compatibility
|
|
id: 11,
|
|
joinIndex: 2,
|
|
};
|
|
self.onRoomDownsyncFrame(startRdf);
|
|
|
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
|
});
|
|
|
|
},
|
|
|
|
update(dt) {
|
|
const self = this;
|
|
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
|
|
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
|
|
if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
|
|
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
|
|
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
|
|
return;
|
|
}
|
|
try {
|
|
let st = performance.now();
|
|
let prevSelfInput = null,
|
|
currSelfInput = null;
|
|
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
|
|
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
|
|
const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId);
|
|
prevSelfInput = prevAndCurrInputs[0];
|
|
currSelfInput = prevAndCurrInputs[1];
|
|
}
|
|
|
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, false);
|
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
|
self.showDebugBoundaries(rdf);
|
|
++self.renderFrameId;
|
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
|
let t3 = performance.now();
|
|
} catch (err) {
|
|
console.error("Error during Map.update", err);
|
|
}
|
|
}
|
|
},
|
|
|
|
onRoomDownsyncFrame(rdf, accompaniedInputFrameDownsyncBatch) {
|
|
// This function is also applicable to "re-joining".
|
|
const self = window.mapIns;
|
|
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
|
|
if (!self.recentRenderCache) {
|
|
return;
|
|
}
|
|
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
|
|
return;
|
|
}
|
|
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id);
|
|
let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance);
|
|
let shouldForceResync = rdf.ShouldForceResync;
|
|
const notSelfUnconfirmed = (0 == (rdf.BackendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1))));
|
|
if (notSelfUnconfirmed) {
|
|
shouldForceDumping2 = false;
|
|
shouldForceResync = false;
|
|
self.othersForcedDownsyncRenderFrameDict.set(rdf.Id, rdf);
|
|
}
|
|
/*
|
|
TODO
|
|
|
|
If "BackendUnconfirmedMask" is non-all-1 and contains the current player, show a label/button to hint manual reconnection. Note that the continuity of "recentInputCache" is not a good indicator, because due to network delay upon a [type#1 forceConfirmation] a player might just lag in upsync networking and have all consecutive inputFrameIds locally.
|
|
*/
|
|
|
|
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
|
|
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
|
|
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
|
}
|
|
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
|
|
/*
|
|
Don't change
|
|
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
|
|
*/
|
|
return dumpRenderCacheRet;
|
|
}
|
|
|
|
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
|
self._initPlayerRichInfoDict(rdf.PlayersArr);
|
|
|
|
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
|
|
|
|
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id) {
|
|
console.log('On battle started! renderFrameId=', rdf.Id);
|
|
}
|
|
self.renderFrameId = rdf.Id;
|
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
|
// In this case it must be true that "rdf.id > chaserRenderFrameId".
|
|
self.chaserRenderFrameId = rdf.Id;
|
|
|
|
const canvasNode = self.canvasNode;
|
|
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
|
self.enableInputControls();
|
|
self.transitToState(ALL_MAP_STATES.VISUAL);
|
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
|
}
|
|
// [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics"
|
|
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 jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames);
|
|
const delayedInputFrameForPrevRenderFrame = self.recentInputCache.getByFrameId(jPrev);
|
|
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputFrame.inputList, (null == delayedInputFrameForPrevRenderFrame ? null : delayedInputFrameForPrevRenderFrame.inputList), 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 = rdf.PlayersArr;
|
|
for (let k in playersArr) {
|
|
const currPlayerDownsync = playersArr[k];
|
|
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[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 [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY);
|
|
const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(cx, cy);
|
|
const gopkgsBoundaryPts = [
|
|
gopkgs.NewVec2DJs(0, 0),
|
|
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, 0),
|
|
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap),
|
|
gopkgs.NewVec2DJs(0, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap)
|
|
];
|
|
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
|
|
const newPlayerCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
|
|
//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];
|
|
},
|
|
|
|
showDebugBoundaries(rdf) {
|
|
const self = this;
|
|
const leftPadding = self.snapIntoPlatformOverlap,
|
|
rightPadding = self.snapIntoPlatformOverlap,
|
|
topPadding = self.snapIntoPlatformOverlap,
|
|
bottomPadding = self.snapIntoPlatformOverlap;
|
|
if (self.showCriticalCoordinateLabels) {
|
|
let g = self.g;
|
|
g.clear();
|
|
|
|
const collisionSpaceObjs = gopkgs.GetCollisionSpaceObjsJs(self.gopkgsCollisionSys);
|
|
for (let k in collisionSpaceObjs) {
|
|
const body = collisionSpaceObjs[k];
|
|
let padding = 0;
|
|
if (null != body.Data && null != body.Data.JoinIndex) {
|
|
// character
|
|
if (1 == body.Data.JoinIndex) {
|
|
g.strokeColor = cc.Color.BLUE;
|
|
} else {
|
|
g.strokeColor = cc.Color.RED;
|
|
}
|
|
padding = self.snapIntoPlatformOverlap;
|
|
} else {
|
|
// barrier
|
|
g.strokeColor = cc.Color.WHITE;
|
|
}
|
|
const points = body.Shape.Points;
|
|
const wpos = [body.X-self.spaceOffsetX, body.Y-self.spaceOffsetY];
|
|
g.moveTo(wpos[0], wpos[1]);
|
|
const cnt = points.length;
|
|
for (let j = 0; j < cnt; j += 1) {
|
|
const x = wpos[0]+points[j][0],
|
|
y = wpos[1]+points[j][1];
|
|
g.lineTo(x, y);
|
|
}
|
|
g.lineTo(wpos[0], wpos[1]);
|
|
g.stroke();
|
|
}
|
|
}
|
|
},
|
|
});
|