Minor fix.

This commit is contained in:
genxium 2022-11-23 15:04:32 +08:00
parent 58c18ab7ae
commit becc56f672
9 changed files with 231 additions and 100 deletions

View File

@ -776,7 +776,7 @@ func (pR *Room) OnDismissed() {
pR.RenderCacheSize = 256 pR.RenderCacheSize = 256
pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize) pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize)
pR.DiscreteInputsBuffer = sync.Map{} pR.DiscreteInputsBuffer = sync.Map{}
pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 2)+1) pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 2) + 1)
pR.LastAllConfirmedInputFrameId = -1 pR.LastAllConfirmedInputFrameId = -1
pR.LastAllConfirmedInputFrameIdWithChange = -1 pR.LastAllConfirmedInputFrameIdWithChange = -1
@ -1161,6 +1161,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
// TODO: Write unit-test for this function to compare with its frontend counter part // TODO: Write unit-test for this function to compare with its frontend counter part
func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame { func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame {
// TODO: Derive "nextRenderFramePlayers[*].CharacterState" as the frontend counter-part!
nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity) nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity)
// Make a copy first // Make a copy first
for playerId, currPlayerDownsync := range currRenderFrame.Players { for playerId, currPlayerDownsync := range currRenderFrame.Players {

View File

@ -271,7 +271,7 @@ func Serve(c *gin.Context) {
VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio, VirtualGridToWorldRatio: pRoom.VirtualGridToWorldRatio,
SpAtkLookupFrames: pRoom.SpAtkLookupFrames, SpAtkLookupFrames: pRoom.SpAtkLookupFrames,
RenderCacheSize: pRoom.RenderCacheSize, RenderCacheSize: pRoom.RenderCacheSize,
} }
resp := &pb.WsResp{ resp := &pb.WsResp{

View File

@ -123,6 +123,8 @@ message MeleeBullet {
int32 offenderJoinIndex = 16; int32 offenderJoinIndex = 16;
int32 offenderPlayerId = 17; int32 offenderPlayerId = 17;
int32 offenderPlayerId = 17;
} }
message RoomDownsyncFrame { message RoomDownsyncFrame {

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
342.07256941030164, 210.4441731196186,
0, 0,
0, 0,
0, 0,

View File

@ -453,7 +453,7 @@
"array": [ "array": [
0, 0,
0, 0,
342.93857591513785, 210.4441731196186,
0, 0,
0, 0,
0, 0,

View File

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

View File

@ -23,23 +23,6 @@ module.export = cc.Class({
self.ctrl = joystickInputControllerScriptIns; self.ctrl = joystickInputControllerScriptIns;
}, },
scheduleNewDirection(newScheduledDirection, forceAnimSwitch) {
if (!newScheduledDirection) {
return;
}
if (forceAnimSwitch || null == this.activeDirection || (newScheduledDirection.dx != this.activeDirection.dx || newScheduledDirection.dy != this.activeDirection.dy)) {
this.activeDirection = newScheduledDirection;
if (this.animComp && this.animComp.node) {
if (0 > newScheduledDirection.dx) {
this.animComp.node.scaleX = (-1.0);
} else if (0 < newScheduledDirection.dx) {
this.animComp.node.scaleX = (1.0);
}
}
}
},
update(dt) {}, update(dt) {},
lateUpdate(dt) {}, lateUpdate(dt) {},

View File

@ -309,7 +309,7 @@ cc.Class({
self.recentRenderCache = new RingBuffer(self.renderCacheSize); self.recentRenderCache = new RingBuffer(self.renderCacheSize);
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
self.recentInputCache = new RingBuffer((self.renderCacheSize >> 2)+1); self.recentInputCache = new RingBuffer((self.renderCacheSize >> 2) + 1);
self.collisionSys = new collisions.Collisions(); self.collisionSys = new collisions.Collisions();
@ -768,18 +768,18 @@ cc.Class({
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;
newPlayerCollider.collisionPlayerIndex = collisionPlayerIndex; newPlayerCollider.collisionPlayerIndex = parseInt(collisionPlayerIndex);
newPlayerCollider.playerId = playerRichInfo.id; newPlayerCollider.playerId = parseInt(playerRichInfo.id);
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
safelyAddChild(self.node, newPlayerNode); safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
newPlayerNode.active = true; newPlayerNode.active = true;
playerScriptIns.scheduleNewDirection({ playerScriptIns.updateCharacterAnim({
dx: playerRichInfo.dir.dx, dx: playerRichInfo.dir.dx,
dy: playerRichInfo.dir.dy dy: playerRichInfo.dir.dy
}, true); }, playerRichInfo, true);
return [newPlayerNode, playerScriptIns]; return [newPlayerNode, playerScriptIns];
}, },
@ -978,7 +978,9 @@ cc.Class({
// TODO: check "rdf.players[playerId].characterState" instead, might have to play Atk/Atked anim! // TODO: check "rdf.players[playerId].characterState" instead, might have to play Atk/Atked anim!
if (null != delayedInputFrameForPrevRenderFrame) { if (null != delayedInputFrameForPrevRenderFrame) {
const decodedInput = self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[playerRichInfo.joinIndex - 1]); const decodedInput = self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[playerRichInfo.joinIndex - 1]);
playerRichInfo.scriptIns.scheduleNewDirection(decodedInput, false); playerRichInfo.scriptIns.updateCharacterAnim(decodedInput, immediatePlayerInfo, false);
} else {
playerRichInfo.scriptIns.updateCharacterAnim(null, immediatePlayerInfo, false);
} }
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
}); });
@ -1012,49 +1014,174 @@ cc.Class({
dx: currPlayerDownsync.dir.dx, dx: currPlayerDownsync.dir.dx,
dy: currPlayerDownsync.dir.dy, dy: currPlayerDownsync.dir.dy,
}, },
characterState: currPlayerDownsync.characterState,
speed: currPlayerDownsync.speed, speed: currPlayerDownsync.speed,
battleState: currPlayerDownsync.battleState, battleState: currPlayerDownsync.battleState,
score: currPlayerDownsync.score, score: currPlayerDownsync.score,
removed: currPlayerDownsync.removed, removed: currPlayerDownsync.removed,
joinIndex: currPlayerDownsync.joinIndex, joinIndex: currPlayerDownsync.joinIndex,
framesToRecover: (0 < currPlayerDownsync.framesToRecover ? currPlayerDownsync.framesToRecover - 1 : 0),
hp: currPlayerDownsync.hp,
maxHp: currPlayerDownsync.maxHp,
}; };
} }
const toRet = { const toRet = {
id: currRenderFrame.id + 1, id: currRenderFrame.id + 1,
players: nextRenderFramePlayers, players: nextRenderFramePlayers,
meleeBullets: []
}; };
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
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
bulletPushbacks[joinIndex - 1] = [0.0, 0.0];
effPushbacks[joinIndex - 1] = [0.0, 0.0];
const playerRichInfo = self.playerRichInfoArr[j];
const playerId = playerRichInfo.id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const player = currRenderFrame.players[playerId];
const newVx = player.virtualGridX;
const newVy = player.virtualGridY;
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]);
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
const colliderBullets = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
const removedBulletsAtCurrFrame = new Set();
for (let k in currRenderFrame.meleeBullets) {
const meleeBullet = currRenderFrame.meleeBullets[k];
if (
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= currRenderFrame.id
&&
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > currRenderFrame.id
) {
const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
const collisionOffenderIndex = self.collisionPlayerIndexPrefix + meleeBullet.offenderJoinIndex;
const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
const offender = currRenderFrame.players[meleeBullet.offenderPlayerId];
const xfac = Math.sign(offender.dir.dx),
yfac = 0; // By now, straight Punch offset doesn't respect "y-axis"
const x0 = offenderCollider.x + xfac * meleeBullet.hitboxOffset,
y0 = offenderCollider.y + yfac * meleeBullet.hitboxOffset;
const pts = [[0, 0], [xfac * meleeBullet.hitboxSize.x, 0], [xfac * meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
const newBulletCollider = collisionSys.createPolygon(x0, y0, pts);
newBulletCollider.collisionBulletIndex = collisionBulletIndex;
newBulletCollider.offenderPlayerId = meleeBullet.offenderPlayerId;
newBulletCollider.pushback = meleeBullet.pushback;
newBulletCollider.hitStunFrames = meleeBullet.hitStunFrames;
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
colliderBullets.set(collisionBulletIndex, newBulletCollider);
console.log(`A meleeBullet=${JSON.stringify(meleeBullet)} is added to collisionSys at renderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended`);
}
}
collisionSys.update();
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
colliderBullets.forEach((bulletCollider, collisionBulletIndex) => {
const potentials = bulletCollider.potentials();
let shouldRemove = false;
for (const potential of potentials) {
if (null != potential.playerId && potential.playerId == bulletCollider.offenderPlayerId) continue;
if (!bulletCollider.collides(potential, result1)) continue;
if (null != potential.playerId) {
const joinIndex = potential.collisionPlayerIndex - self.collisionPlayerIndexPrefix;
bulletPushbacks[joinIndex - 1][0] += result1.overlap_x * bulletCollider.pushback;
bulletPushbacks[joinIndex - 1][1] += result1.overlap_y * bulletCollider.pushback;
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.playerId];
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
const oldFrameToRecover = thatAckedPlayerInNextFrame.framesToRecover;
thatAckedPlayerInNextFrame.framesToRecover = oldFrameToRecover > bulletCollider.hitStunFrames ? oldFrameToRecover : bulletCollider.hitStunFrames; // In case the hit player is already stun, we take the larger "hitStunFrames"
}
shouldRemove = true;
}
if (shouldRemove) {
removedBulletsAtCurrFrame.add(collisionBulletIndex);
}
});
for (let k in currRenderFrame.meleeBullets) {
const meleeBullet = currRenderFrame.meleeBullets[k];
// [WARNING] remove from collisionSys ANYWAY for the convenience of rollback
const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
if (collisionSysMap.has(collisionBulletIndex)) {
const bulletCollider = collisionSysMap.get(collisionBulletIndex);
bulletCollider.remove();
collisionSysMap.delete(collisionBulletIndex);
}
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
toRet.meleeBullets.push(meleeBullet);
}
if (null != delayedInputFrame) { if (null != delayedInputFrame) {
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
const inputList = delayedInputFrame.inputList; const inputList = delayedInputFrame.inputList;
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order // Process player inputs
for (let j in self.playerRichInfoArr) { for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1; const joinIndex = parseInt(j) + 1;
effPushbacks[joinIndex - 1] = [0.0, 0.0]; effPushbacks[joinIndex - 1] = [0.0, 0.0];
const playerId = self.playerRichInfoArr[j].id; const playerRichInfo = self.playerRichInfoArr[j];
const playerId = playerRichInfo.id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const player = currRenderFrame.players[playerId]; const player = currRenderFrame.players[playerId];
if (0 < nextRenderFramePlayers[playerId].framesToRecover) {
// No need to process inputs for this player, but there might be bullet pushbacks on this player
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) {
playerCollider.x += bulletPushbacks[joinIndex - 1][0];
playerCollider.y += bulletPushbacks[joinIndex - 1][1];
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${player.framesToRecover}`);
}
continue;
}
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]); const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
/* const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
Reset "position" of players in "collisionSys" according to "virtual grid position". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics. const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
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}`);
// The online map is not yet ready for bullet shooting!
/*
nextRenderFramePlayers[playerId].framesToRecover = PunchAtkConfig.recoveryFrames;
const punch = window.pb.protos.MeleeBullet.create(PunchAtkConfig);
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
punch.offenderJoinIndex = joinIndex;
punch.offenderPlayerId = playerId;
punch.originatedRenderFrameId = currRenderFrame.id;
toRet.meleeBullets.push(punch);
console.log(`A rising-edge of meleeBullet=${JSON.stringify(punch)} is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Atk1[0];
*/ */
const newVx = player.virtualGridX + (decodedInput.dx + player.speed * decodedInput.dx); } else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
const newVy = player.virtualGridY + (decodedInput.dy + player.speed * decodedInput.dy); console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]); } else {
playerCollider.x = newCpos[0]; // No trigger, process movement inputs
playerCollider.y = newCpos[1]; if (0 != decodedInput.dx || 0 != decodedInput.dy) {
if (0 != decodedInput.dx || 0 != decodedInput.dy) { // Update directions and thus would eventually update moving animation accordingly
// Update directions and thus would eventually update moving animation accordingly nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx;
nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx; nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy;
nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy; nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Walking[0];
} else {
nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Idle1[0];
}
const movement = self.virtualGridToWorldPos(decodedInput.dx + player.speed * decodedInput.dx, decodedInput.dy + player.speed * decodedInput.dy);
playerCollider.x += movement[0];
playerCollider.y += movement[1];
} }
} }
collisionSys.update(); collisionSys.update(); // by now all "bulletCollider"s are removed
const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle? const result2 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
for (let j in self.playerRichInfoArr) { for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1; const joinIndex = parseInt(j) + 1;
@ -1064,10 +1191,10 @@ cc.Class({
const potentials = playerCollider.potentials(); const potentials = playerCollider.potentials();
for (const potential of potentials) { for (const potential of potentials) {
// Test if the player collides with the wall // Test if the player collides with the wall
if (!playerCollider.collides(potential, result)) continue; if (!playerCollider.collides(potential, result2)) continue;
// Push the player out of the wall // Push the player out of the wall
effPushbacks[joinIndex - 1][0] += result.overlap * result.overlap_x; effPushbacks[joinIndex - 1][0] += result2.overlap * result2.overlap_x;
effPushbacks[joinIndex - 1][1] += result.overlap * result.overlap_y; effPushbacks[joinIndex - 1][1] += result2.overlap * result2.overlap_y;
} }
} }
@ -1080,6 +1207,7 @@ cc.Class({
nextRenderFramePlayers[playerId].virtualGridX = newVpos[0]; nextRenderFramePlayers[playerId].virtualGridX = newVpos[0];
nextRenderFramePlayers[playerId].virtualGridY = newVpos[1]; nextRenderFramePlayers[playerId].virtualGridY = newVpos[1];
} }
} }
return toRet; return toRet;

View File

@ -5,25 +5,25 @@ const OnlineMap = require('./Map');
const PunchAtkConfig = { const PunchAtkConfig = {
// for offender // for offender
startupFrames: 2, startupFrames: 18,
activeFrames: 2, activeFrames: 42,
recoveryFrames: 4, // usually but not always "startupFrames+activeFrames" recoveryFrames: 60, // usually but not always "startupFrames+activeFrames"
recoveryFramesOnBlock: 4, // usually but not always the same as "recoveryFrames" recoveryFramesOnBlock: 60, // usually but not always the same as "recoveryFrames"
recoveryFramesOnHit: 4, // usually but not always the same as "recoveryFrames" recoveryFramesOnHit: 60, // usually but not always the same as "recoveryFrames"
moveforward: { moveforward: {
x: 0, x: 0,
y: 0, y: 0,
}, },
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
hitboxSize: { hitboxSize: {
x: 24.0, x: 32.0,
y: 24.0, y: 32.0,
}, },
// for defender // for defender
hitStunFrames: 0, hitStunFrames: 18,
blockStunFrames: 0, blockStunFrames: 9,
pushback: 10.0, pushback: 11.0,
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
damage: 5 damage: 5
}; };
@ -58,16 +58,18 @@ cc.Class({
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;
newPlayerCollider.collisionPlayerIndex = parseInt(collisionPlayerIndex);
newPlayerCollider.playerId = parseInt(playerRichInfo.id);
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
safelyAddChild(self.node, newPlayerNode); safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
newPlayerNode.active = true; newPlayerNode.active = true;
playerScriptIns.scheduleNewDirection({ playerScriptIns.updateCharacterAnim({
dx: playerRichInfo.dir.dx, dx: playerRichInfo.dir.dx,
dy: playerRichInfo.dir.dy dy: playerRichInfo.dir.dy
}, true); }, playerRichInfo, true);
return [newPlayerNode, playerScriptIns]; return [newPlayerNode, playerScriptIns];
}, },
@ -178,6 +180,8 @@ cc.Class({
virtualGridY: 0, virtualGridY: 0,
speed: 2 * self.worldToVirtualGridRatio, speed: 2 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0,
dir: { dir: {
dx: 0, dx: 0,
dy: 0 dy: 0
@ -190,6 +194,8 @@ cc.Class({
virtualGridY: 40 * self.worldToVirtualGridRatio, virtualGridY: 40 * self.worldToVirtualGridRatio,
speed: 2 * self.worldToVirtualGridRatio, speed: 2 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0,
dir: { dir: {
dx: 0, dx: 0,
dy: 0 dy: 0
@ -295,21 +301,25 @@ cc.Class({
for (let k in currRenderFrame.meleeBullets) { for (let k in currRenderFrame.meleeBullets) {
const meleeBullet = currRenderFrame.meleeBullets[k]; const meleeBullet = currRenderFrame.meleeBullets[k];
if ( if (
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames >= currRenderFrame.id meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= currRenderFrame.id
&& &&
meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames <= currRenderFrame.id meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > currRenderFrame.id
) { ) {
const collisionBulletIndex = self.collisionBulletIndexPrefix + melee.battleLocalId; const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
const collisionOffenderIndex = self.collisionPlayerIndexPrefix + melee.offenderJoinIndex; const collisionOffenderIndex = self.collisionPlayerIndexPrefix + meleeBullet.offenderJoinIndex;
const offenderCollider = collisionSysMap.get(collisionOffenderIndex); const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
const offender = currRenderFrame.players[melee.offenderPlayerId]; const offender = currRenderFrame.players[meleeBullet.offenderPlayerId];
const x0 = offenderCollider.x + offender.dir.dx * meleeBullet.hitboxOffset, const xfac = Math.sign(offender.dir.dx),
y0 = offenderCollider.y + offender.dir.dy * meleeBullet.hitboxOffset; yfac = 0; // By now, straight Punch offset doesn't respect "y-axis"
const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; const x0 = offenderCollider.x + xfac * meleeBullet.hitboxOffset,
y0 = offenderCollider.y + yfac * meleeBullet.hitboxOffset;
const pts = [[0, 0], [xfac * meleeBullet.hitboxSize.x, 0], [xfac * meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
const newBulletCollider = collisionSys.createPolygon(x0, y0, pts); const newBulletCollider = collisionSys.createPolygon(x0, y0, pts);
newBulletCollider.collisionBulletIndex = collisionBulletIndex; newBulletCollider.collisionBulletIndex = collisionBulletIndex;
newBulletCollider.offenderPlayerId = meleeBullet.offenderPlayerId;
newBulletCollider.pushback = meleeBullet.pushback; newBulletCollider.pushback = meleeBullet.pushback;
newBulletCollider.hitStunFrames = meleeBullet.hitStunFrames;
collisionSysMap.set(collisionBulletIndex, newBulletCollider); collisionSysMap.set(collisionBulletIndex, newBulletCollider);
colliderBullets.set(collisionBulletIndex, newBulletCollider); colliderBullets.set(collisionBulletIndex, newBulletCollider);
console.log(`A meleeBullet=${JSON.stringify(meleeBullet)} is added to collisionSys at renderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended`); console.log(`A meleeBullet=${JSON.stringify(meleeBullet)} is added to collisionSys at renderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended`);
@ -319,15 +329,16 @@ cc.Class({
collisionSys.update(); collisionSys.update();
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle? const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
colliderBullets.forEach((collisionBulletIndex, bulletCollider) => { colliderBullets.forEach((bulletCollider, collisionBulletIndex) => {
const potentials = bulletCollider.potentials(); const potentials = bulletCollider.potentials();
let shouldRemove = false; let shouldRemove = false;
for (const potential of potentials) { for (const potential of potentials) {
if (null != potential.playerId && potential.playerId == bulletCollider.offenderPlayerId) continue; if (null != potential.playerId && potential.playerId == bulletCollider.offenderPlayerId) continue;
if (!bulletCollider.collides(potential, result1)) continue; if (!bulletCollider.collides(potential, result1)) continue;
if (null != potential.playerId) { if (null != potential.playerId) {
bulletPushbacks[joinIndex - 1][0] += result1.overlap * result1.overlap_x * bulletCollider.pushback; const joinIndex = potential.collisionPlayerIndex - self.collisionPlayerIndexPrefix;
bulletPushbacks[joinIndex - 1][1] += result1.overlap * result1.overlap_y * bulletCollider.pushback; bulletPushbacks[joinIndex - 1][0] += result1.overlap_x * bulletCollider.pushback;
bulletPushbacks[joinIndex - 1][1] += result1.overlap_y * bulletCollider.pushback;
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.playerId]; const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.playerId];
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0]; thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
const oldFrameToRecover = thatAckedPlayerInNextFrame.framesToRecover; const oldFrameToRecover = thatAckedPlayerInNextFrame.framesToRecover;
@ -343,12 +354,13 @@ cc.Class({
for (let k in currRenderFrame.meleeBullets) { for (let k in currRenderFrame.meleeBullets) {
const meleeBullet = currRenderFrame.meleeBullets[k]; const meleeBullet = currRenderFrame.meleeBullets[k];
// [WARNING] remove from collisionSys ANYWAY for the convenience of rollback // [WARNING] remove from collisionSys ANYWAY for the convenience of rollback
if (collisionSysMap.has(meleeBullet.collisionBulletIndex)) { const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
const bulletCollider = collisionSysMap.get(meleeBullet.collisionBulletIndex); if (collisionSysMap.has(collisionBulletIndex)) {
const bulletCollider = collisionSysMap.get(collisionBulletIndex);
bulletCollider.remove(); bulletCollider.remove();
collisionSysMap.delete(meleeBullet.collisionBulletIndex); collisionSysMap.delete(collisionBulletIndex);
} }
if (removedBulletsAtCurrFrame.has(meleeBullet)) continue; if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
toRet.meleeBullets.push(meleeBullet); toRet.meleeBullets.push(meleeBullet);
} }
@ -364,8 +376,13 @@ cc.Class({
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const player = currRenderFrame.players[playerId]; const player = currRenderFrame.players[playerId];
if (0 < player.framesToRecover) { if (0 < nextRenderFramePlayers[playerId].framesToRecover) {
// No need to process inputs for this player // No need to process inputs for this player, but there might be bullet pushbacks on this player
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) {
playerCollider.x += bulletPushbacks[joinIndex - 1][0];
playerCollider.y += bulletPushbacks[joinIndex - 1][1];
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${player.framesToRecover}`);
}
continue; continue;
} }
@ -374,7 +391,6 @@ cc.Class({
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1])); const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel); const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
const playerInNextFrame = nextRenderFramePlayers[playerId];
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) { 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}`); console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
nextRenderFramePlayers[playerId].framesToRecover = PunchAtkConfig.recoveryFrames; nextRenderFramePlayers[playerId].framesToRecover = PunchAtkConfig.recoveryFrames;
@ -386,20 +402,20 @@ cc.Class({
toRet.meleeBullets.push(punch); toRet.meleeBullets.push(punch);
console.log(`A rising-edge of meleeBullet=${JSON.stringify(punch)} is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); console.log(`A rising-edge of meleeBullet=${JSON.stringify(punch)} is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
playerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0]; nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Atk1[0];
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) { } 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}`); console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
} else { } else {
// No trigger, process movement inputs // No trigger, process movement inputs
if (0 != decodedInput.dx || 0 != decodedInput.dy) { if (0 != decodedInput.dx || 0 != decodedInput.dy) {
// Update directions and thus would eventually update moving animation accordingly // Update directions and thus would eventually update moving animation accordingly
playerInNextFrame.dir.dx = decodedInput.dx; nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx;
playerInNextFrame.dir.dy = decodedInput.dy; nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy;
playerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0]; nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Walking[0];
} else { } else {
playerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; nextRenderFramePlayers[playerId].characterState = window.ATK_CHARACTER_STATE.Idle1[0];
} }
const movement = self.virtualGridToPlayerColliderPos(decodedInput.dx + player.speed * decodedInput.dx, decodedInput.dy + player.speed * decodedInput.dy, self.playerRichInfoArr[joinIndex - 1]); const movement = self.virtualGridToWorldPos(decodedInput.dx + player.speed * decodedInput.dx, decodedInput.dy + player.speed * decodedInput.dy);
playerCollider.x += movement[0]; playerCollider.x += movement[0];
playerCollider.y += movement[1]; playerCollider.y += movement[1];
} }