Fixed backend bullet collision handling.

This commit is contained in:
genxium 2022-11-25 08:21:03 +08:00
parent 2a1105efa4
commit 1593965950
6 changed files with 123 additions and 50 deletions

View File

@ -193,7 +193,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit.ColliderRadius = float64(24) // Hardcoded
pPlayerFromDbInit.ColliderRadius = float64(12) // Hardcoded
pR.Players[playerId] = pPlayerFromDbInit
pR.PlayerDownsyncSessionDict[playerId] = session
@ -787,26 +787,26 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0
pR.InputDelayFrames = 8
pR.NstDelayFrames = 8
pR.NstDelayFrames = 4
pR.InputScaleFrames = uint32(2)
pR.ServerFps = 60
pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for preventing FAST FRAME
dilutionFactor := 8
dilutionFactor := 12
pR.dilutedRollbackEstimatedDtNanos = int64(16666666 * (dilutionFactor) / (dilutionFactor - 1)) // [WARNING] Only used in controlling "battleMainLoop" to be keep a frame rate lower than that of the frontends, such that upon resync(i.e. BackendDynamicsEnabled=true), the frontends would have bigger chances to keep up with or even surpass the backend calculation
pR.BattleDurationFrames = 30 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = 2
pR.MaxChasingRenderFramesPerUpdate = 8
pR.MaxChasingRenderFramesPerUpdate = 5
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
punchSkillId := int32(1)
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
// for offender
StartupFrames: int32(18),
ActiveFrames: int32(42),
RecoveryFrames: int32(61), // 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
StartupFrames: int32(23),
ActiveFrames: int32(3),
RecoveryFrames: int32(61), // 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: int32(61),
RecoveryFramesOnHit: int32(61),
Moveforward: &Vec2D{
@ -822,7 +822,7 @@ func (pR *Room) OnDismissed() {
// for defender
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Pushback: float64(22.0),
Pushback: float64(11.0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
Damage: int32(5),
}
@ -1244,20 +1244,20 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
offenderCollider := collisionSysMap[collisionOffenderIndex]
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
xfac, yfac := float64(1.0), float64(0) // By now, straight Punch offset doesn't respect "y-axis"
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = float64(-1.0)
}
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio)
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
bulletCx, bulletCy := offenderCollider.X+xfac*meleeBullet.HitboxOffset, offenderCollider.Y+yfac*meleeBullet.HitboxOffset
newBulletCollider := GenerateRectColliderInCollisionSpace(bulletCx, bulletCy, xfac*meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, "MeleeBullet")
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet")
newBulletCollider.Data = meleeBullet
pR.Space.Add(newBulletCollider)
collisionSysMap[collisionBulletIndex] = newBulletCollider
bulletColliders[collisionBulletIndex] = newBulletCollider
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet is added to collisionSys at currRenderFrame.id=%v as start-up frames ended and active frame is not yet ended: %v, from offenderCollider=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(newBulletCollider.Shape.(*resolv.ConvexPolygon)), ConvexPolygonStr(offenderCollider.Shape.(*resolv.ConvexPolygon))))
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet is added to collisionSys at currRenderFrame.id=%v as start-up frames ended and active frame is not yet ended: %v, from offenderCollider=%v, xfac=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(newBulletCollider.Shape.(*resolv.ConvexPolygon)), ConvexPolygonStr(offenderCollider.Shape.(*resolv.ConvexPolygon)), xfac))
}
}
@ -1267,13 +1267,11 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
if collision := bulletCollider.Check(0, 0); collision != nil {
// FIXME: A bullet going to the "right" can hit a wall or a player (though couldn't identify the player), but if it goes to the "left" then it couldn't hit anything, why?
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
Logger.Info(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ sth at currRenderFrame.id=%v: %v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape)))
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case Player:
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
case *Player:
if meleeBullet.OffenderPlayerId != t.Id {
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
@ -1286,15 +1284,14 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
if meleeBullet.HitStunFrames > oldFramesToRecover {
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames
}
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape)))
}
}
default:
Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v", bulletShape, pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId))
Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape), pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId, obj.Data, obj.Data))
}
}
shouldRemove = true
} else {
Logger.Info(fmt.Sprintf("roomId=%v, meleeBullet doesn't collider w/ anything at currRenderFrame.id=%v: %v; p1=%v, p2=%v; bulletSpace=%p, p1Space=%p, p2Space=%p", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(collisionSysMap[COLLISION_PLAYER_INDEX_PREFIX+1].Shape.(*resolv.ConvexPolygon)), ConvexPolygonStr(collisionSysMap[COLLISION_PLAYER_INDEX_PREFIX+2].Shape.(*resolv.ConvexPolygon)), bulletCollider.Space, collisionSysMap[COLLISION_PLAYER_INDEX_PREFIX+1].Space, collisionSysMap[COLLISION_PLAYER_INDEX_PREFIX+2].Space))
}
if shouldRemove {
removedBulletsAtCurrFrame[collisionBulletIndex] = 1
@ -1360,7 +1357,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
Logger.Info(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else if decodedInput.BtnALevel < prevBtnALevel {
Logger.Info(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else {
// No bullet trigger, process movement inputs
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
@ -1434,7 +1431,7 @@ func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
func (pR *Room) refreshColliders(spaceW, spaceH int32) {
// Kindly note that by now, we've already got all the shapes in the tmx file into "pR.(Players | Barriers)" from "ParseTmxLayersAndGroups"
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) >> 1) // the approx minimum distance a player can move per frame in world coordinate
minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 1) // the approx minimum distance a player can move per frame in world coordinate
pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
for _, player := range pR.Players {
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)

View File

@ -4,6 +4,7 @@ go 1.19
require (
dnmshared v0.0.0
battle_srv v0.0.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/hajimehoshi/ebiten/v2 v2.4.7
github.com/solarlune/resolv v0.5.1
@ -26,3 +27,4 @@ require (
)
replace dnmshared => ../dnmshared
replace battle_srv => ../battle_srv

View File

@ -1,6 +1,7 @@
package main
import (
. "battle_srv/protos"
. "dnmshared"
. "dnmshared/sharedprotos"
"fmt"
@ -36,7 +37,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
virtualGridToWorldRatio := 0.1
playerDefaultSpeed := 20
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
playerColliderRadius := float64(16)
playerColliderRadius := float64(24)
playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
for i, playerPos := range playerPosList.Eles {
@ -84,6 +85,75 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
Logger.Info(fmt.Sprintf("effPushback={%v, %v}", effPushback.X, effPushback.Y))
}
}
meleeBullet := &MeleeBullet{
// for offender
StartupFrames: int32(18),
ActiveFrames: int32(1),
RecoveryFrames: int32(61),
RecoveryFramesOnBlock: int32(61),
RecoveryFramesOnHit: int32(61),
Moveforward: &Vec2D{
X: 0,
Y: 0,
},
HitboxOffset: float64(24.0),
HitboxSize: &Vec2D{
X: float64(45.0),
Y: float64(32.0),
},
// for defender
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Pushback: float64(22.0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
Damage: int32(5),
}
bulletLeftToRight := true
if bulletLeftToRight {
xfac := float64(1.0)
offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet")
space.Add(newBulletCollider)
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
if collision := newBulletCollider.Check(0, 0); collision != nil {
for _, obj := range collision.Objects {
objShape := obj.Shape.(*resolv.ConvexPolygon)
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, bulletShape, objShape); overlapped {
Logger.Warn(fmt.Sprintf("bullet ->: Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), pushbackX, pushbackY))
} else {
Logger.Warn(fmt.Sprintf("bullet ->: Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), overlapResult))
}
}
}
}
bulletRightToLeft := true
if bulletRightToLeft {
xfac := float64(-1.0)
offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet")
space.Add(newBulletCollider)
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
if collision := newBulletCollider.Check(0, 0); collision != nil {
for _, obj := range collision.Objects {
objShape := obj.Shape.(*resolv.ConvexPolygon)
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, bulletShape, objShape); overlapped {
Logger.Warn(fmt.Sprintf("bullet <-: Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), pushbackX, pushbackY))
} else {
Logger.Warn(fmt.Sprintf("bullet <-: Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(objShape), overlapResult))
}
}
}
}
return world
}
@ -98,6 +168,9 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) {
if o.HasTags("Player") {
drawColor := color.RGBA{0, 255, 0, 255}
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
} else if o.HasTags("MeleeBullet") {
drawColor := color.RGBA{0, 0, 255, 255}
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)
} else {
drawColor := color.RGBA{60, 60, 60, 255}
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)

View File

@ -529,8 +529,8 @@
0,
0,
1,
1,
1,
0.5,
0.5,
1
]
},
@ -648,7 +648,7 @@
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_N$enableBatch": true,
"_id": ""
},
{
@ -763,7 +763,7 @@
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_N$enableBatch": true,
"_id": ""
},
{
@ -878,7 +878,7 @@
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": false,
"_N$enableBatch": true,
"_id": ""
},
{

View File

@ -761,7 +761,7 @@ cc.Class({
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
playerScriptIns.mapNode = self.node;
const cpos = self.virtualGridToPlayerColliderPos(vx, vy, playerDownsyncInfo);
const cpos = self.virtualGridToPolygonColliderAnchorPos(vx, vy, playerDownsyncInfo.colliderRadius, playerDownsyncInfo.colliderRadius);
const d = playerDownsyncInfo.colliderRadius * 2,
x0 = cpos[0],
y0 = cpos[1];
@ -1044,7 +1044,7 @@ cc.Class({
const newVx = currPlayerDownsync.virtualGridX;
const newVy = currPlayerDownsync.virtualGridY;
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]);
const newCpos = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius);
playerCollider.x = newCpos[0];
playerCollider.y = newCpos[1];
}
@ -1064,15 +1064,16 @@ cc.Class({
const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
const offender = currRenderFrame.players[meleeBullet.offenderPlayerId];
let xfac = 1,
yfac = 0; // By now, straight Punch offset doesn't respect "y-axis"
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
if (0 > offender.dirX) {
xfac = -1;
}
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 offenderWpos = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
const bulletWx = offenderWpos[0] + xfac * meleeBullet.hitboxOffset;
const bulletWy = offenderWpos[1];
const bulletCpos = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5);
const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
const newBulletCollider = collisionSys.createPolygon(bulletCpos[0], bulletCpos[1], pts);
newBulletCollider.data = meleeBullet;
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
bulletColliders.set(collisionBulletIndex, newBulletCollider);
@ -1208,7 +1209,7 @@ cc.Class({
const playerId = self.playerRichInfoArr[j].id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const newVpos = self.playerColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j]);
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];
thatPlayerInNextFrame.virtualGridX = newVpos[0];
thatPlayerInNextFrame.virtualGridY = newVpos[1];
@ -1340,23 +1341,23 @@ cc.Class({
return [wx, wy];
},
playerWorldToCollisionPos(wx, wy, playerRichInfo) {
return [wx - playerRichInfo.colliderRadius, wy - playerRichInfo.colliderRadius];
worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) {
return [wx - halfBoundingW, wy - halfBoundingH];
},
playerColliderAnchorToWorldPos(cx, cy, playerRichInfo) {
return [cx + playerRichInfo.colliderRadius, cy + playerRichInfo.colliderRadius];
polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH) {
return [cx + halfBoundingW, cy + halfBoundingH];
},
playerColliderAnchorToVirtualGridPos(cx, cy, playerRichInfo) {
polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) {
const self = this;
const wpos = self.playerColliderAnchorToWorldPos(cx, cy, playerRichInfo);
const wpos = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH);
return self.worldToVirtualGridPos(wpos[0], wpos[1])
},
virtualGridToPlayerColliderPos(vx, vy, playerRichInfo) {
virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) {
const self = this;
const wpos = self.virtualGridToWorldPos(vx, vy);
return self.playerWorldToCollisionPos(wpos[0], wpos[1], playerRichInfo)
return self.worldToPolygonColliderAnchorPos(wpos[0], wpos[1], halfBoundingW, halfBoundingH)
},
});

View File

@ -42,8 +42,8 @@ cc.Class({
self.meleeSkillConfig = {
1: {
// for offender
startupFrames: 18,
activeFrames: 42,
startupFrames: 23,
activeFrames: 3,
recoveryFrames: 61, // 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: 61,
recoveryFramesOnHit: 61,
@ -60,7 +60,7 @@ cc.Class({
// for defender
hitStunFrames: 18,
blockStunFrames: 9,
pushback: 22.0,
pushback: 11.0,
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
damage: 5
}
@ -140,7 +140,7 @@ cc.Class({
joinIndex: 1,
virtualGridX: 0,
virtualGridY: 0,
speed: 2 * self.worldToVirtualGridRatio,
speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0,
@ -152,7 +152,7 @@ cc.Class({
joinIndex: 2,
virtualGridX: 80 * self.worldToVirtualGridRatio,
virtualGridY: 40 * self.worldToVirtualGridRatio,
speed: 2 * self.worldToVirtualGridRatio,
speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0,