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.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
pPlayerFromDbInit.ColliderRadius = float64(24) // Hardcoded pPlayerFromDbInit.ColliderRadius = float64(12) // Hardcoded
pR.Players[playerId] = pPlayerFromDbInit pR.Players[playerId] = pPlayerFromDbInit
pR.PlayerDownsyncSessionDict[playerId] = session pR.PlayerDownsyncSessionDict[playerId] = session
@ -787,26 +787,26 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameId = 0 pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0 pR.CurDynamicsRenderFrameId = 0
pR.InputDelayFrames = 8 pR.InputDelayFrames = 8
pR.NstDelayFrames = 8 pR.NstDelayFrames = 4
pR.InputScaleFrames = uint32(2) pR.InputScaleFrames = uint32(2)
pR.ServerFps = 60 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.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 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.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.BattleDurationFrames = 30 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = 2 pR.InputFrameUpsyncDelayTolerance = 2
pR.MaxChasingRenderFramesPerUpdate = 8 pR.MaxChasingRenderFramesPerUpdate = 5
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work! pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
punchSkillId := int32(1) punchSkillId := int32(1)
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0) pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{ pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
// for offender // for offender
StartupFrames: int32(18), StartupFrames: int32(23),
ActiveFrames: int32(42), ActiveFrames: int32(3),
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 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), RecoveryFramesOnBlock: int32(61),
RecoveryFramesOnHit: int32(61), RecoveryFramesOnHit: int32(61),
Moveforward: &Vec2D{ Moveforward: &Vec2D{
@ -822,7 +822,7 @@ func (pR *Room) OnDismissed() {
// for defender // for defender
HitStunFrames: int32(18), HitStunFrames: int32(18),
BlockStunFrames: int32(9), BlockStunFrames: int32(9),
Pushback: float64(22.0), Pushback: float64(11.0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
Damage: int32(5), Damage: int32(5),
} }
@ -1244,20 +1244,20 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
offenderCollider := collisionSysMap[collisionOffenderIndex] offenderCollider := collisionSysMap[collisionOffenderIndex]
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId] 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 { if 0 > offender.DirX {
xfac = float64(-1.0) 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 := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet")
newBulletCollider := GenerateRectColliderInCollisionSpace(bulletCx, bulletCy, xfac*meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, "MeleeBullet")
newBulletCollider.Data = meleeBullet newBulletCollider.Data = meleeBullet
pR.Space.Add(newBulletCollider) pR.Space.Add(newBulletCollider)
collisionSysMap[collisionBulletIndex] = newBulletCollider collisionSysMap[collisionBulletIndex] = newBulletCollider
bulletColliders[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 collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon) bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
if collision := bulletCollider.Check(0, 0); collision != nil { 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] 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 { for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) { switch t := obj.Data.(type) {
case Player: case *Player:
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
if meleeBullet.OffenderPlayerId != t.Id { if meleeBullet.OffenderPlayerId != t.Id {
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped { if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
xfac := float64(1.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"
@ -1286,15 +1284,14 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
if meleeBullet.HitStunFrames > oldFramesToRecover { if meleeBullet.HitStunFrames > oldFramesToRecover {
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames 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: 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 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 { if shouldRemove {
removedBulletsAtCurrFrame[collisionBulletIndex] = 1 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)) 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 { } 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 { } else {
// No bullet trigger, process movement inputs // No bullet trigger, process movement inputs
if 0 != decodedInput.Dx || 0 != decodedInput.Dy { 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) { 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" // 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 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 { for _, player := range pR.Players {
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio) wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)

View File

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

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
. "battle_srv/protos"
. "dnmshared" . "dnmshared"
. "dnmshared/sharedprotos" . "dnmshared/sharedprotos"
"fmt" "fmt"
@ -36,7 +37,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
virtualGridToWorldRatio := 0.1 virtualGridToWorldRatio := 0.1
playerDefaultSpeed := 20 playerDefaultSpeed := 20
minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2) minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2)
playerColliderRadius := float64(16) playerColliderRadius := float64(24)
playerColliders := make([]*resolv.Object, len(playerPosList.Eles)) playerColliders := make([]*resolv.Object, len(playerPosList.Eles))
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep)
for i, playerPos := range playerPosList.Eles { 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)) 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 return world
} }
@ -98,6 +168,9 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) {
if o.HasTags("Player") { if o.HasTags("Player") {
drawColor := color.RGBA{0, 255, 0, 255} drawColor := color.RGBA{0, 255, 0, 255}
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor) 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 { } else {
drawColor := color.RGBA{60, 60, 60, 255} drawColor := color.RGBA{60, 60, 60, 255}
DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor) DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor)

View File

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

View File

@ -761,7 +761,7 @@ cc.Class({
newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1])); newPlayerNode.setPosition(cc.v2(wpos[0], wpos[1]));
playerScriptIns.mapNode = self.node; 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, const d = playerDownsyncInfo.colliderRadius * 2,
x0 = cpos[0], x0 = cpos[0],
y0 = cpos[1]; y0 = cpos[1];
@ -1044,7 +1044,7 @@ cc.Class({
const newVx = currPlayerDownsync.virtualGridX; const newVx = currPlayerDownsync.virtualGridX;
const newVy = currPlayerDownsync.virtualGridY; 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.x = newCpos[0];
playerCollider.y = newCpos[1]; playerCollider.y = newCpos[1];
} }
@ -1064,15 +1064,16 @@ cc.Class({
const offenderCollider = collisionSysMap.get(collisionOffenderIndex); const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
const offender = currRenderFrame.players[meleeBullet.offenderPlayerId]; const offender = currRenderFrame.players[meleeBullet.offenderPlayerId];
let xfac = 1, let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
yfac = 0; // By now, straight Punch offset doesn't respect "y-axis"
if (0 > offender.dirX) { if (0 > offender.dirX) {
xfac = -1; xfac = -1;
} }
const x0 = offenderCollider.x + xfac * meleeBullet.hitboxOffset, const offenderWpos = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
y0 = offenderCollider.y + yfac * meleeBullet.hitboxOffset; const bulletWx = offenderWpos[0] + xfac * meleeBullet.hitboxOffset;
const pts = [[0, 0], [xfac * meleeBullet.hitboxSize.x, 0], [xfac * meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; const bulletWy = offenderWpos[1];
const newBulletCollider = collisionSys.createPolygon(x0, y0, pts); 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; newBulletCollider.data = meleeBullet;
collisionSysMap.set(collisionBulletIndex, newBulletCollider); collisionSysMap.set(collisionBulletIndex, newBulletCollider);
bulletColliders.set(collisionBulletIndex, newBulletCollider); bulletColliders.set(collisionBulletIndex, newBulletCollider);
@ -1208,7 +1209,7 @@ cc.Class({
const playerId = self.playerRichInfoArr[j].id; const playerId = self.playerRichInfoArr[j].id;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const newVpos = self.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]; const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
thatPlayerInNextFrame.virtualGridX = newVpos[0]; thatPlayerInNextFrame.virtualGridX = newVpos[0];
thatPlayerInNextFrame.virtualGridY = newVpos[1]; thatPlayerInNextFrame.virtualGridY = newVpos[1];
@ -1340,23 +1341,23 @@ cc.Class({
return [wx, wy]; return [wx, wy];
}, },
playerWorldToCollisionPos(wx, wy, playerRichInfo) { worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) {
return [wx - playerRichInfo.colliderRadius, wy - playerRichInfo.colliderRadius]; return [wx - halfBoundingW, wy - halfBoundingH];
}, },
playerColliderAnchorToWorldPos(cx, cy, playerRichInfo) { polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH) {
return [cx + playerRichInfo.colliderRadius, cy + playerRichInfo.colliderRadius]; return [cx + halfBoundingW, cy + halfBoundingH];
}, },
playerColliderAnchorToVirtualGridPos(cx, cy, playerRichInfo) { polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) {
const self = this; 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]) return self.worldToVirtualGridPos(wpos[0], wpos[1])
}, },
virtualGridToPlayerColliderPos(vx, vy, playerRichInfo) { virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) {
const self = this; const self = this;
const wpos = self.virtualGridToWorldPos(vx, vy); 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 = { self.meleeSkillConfig = {
1: { 1: {
// for offender // for offender
startupFrames: 18, startupFrames: 23,
activeFrames: 42, 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 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, recoveryFramesOnBlock: 61,
recoveryFramesOnHit: 61, recoveryFramesOnHit: 61,
@ -60,7 +60,7 @@ cc.Class({
// for defender // for defender
hitStunFrames: 18, hitStunFrames: 18,
blockStunFrames: 9, blockStunFrames: 9,
pushback: 22.0, pushback: 11.0,
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
damage: 5 damage: 5
} }
@ -140,7 +140,7 @@ cc.Class({
joinIndex: 1, joinIndex: 1,
virtualGridX: 0, virtualGridX: 0,
virtualGridY: 0, virtualGridY: 0,
speed: 2 * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0], characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0, framesToRecover: 0,
@ -152,7 +152,7 @@ cc.Class({
joinIndex: 2, joinIndex: 2,
virtualGridX: 80 * self.worldToVirtualGridRatio, virtualGridX: 80 * self.worldToVirtualGridRatio,
virtualGridY: 40 * self.worldToVirtualGridRatio, virtualGridY: 40 * self.worldToVirtualGridRatio,
speed: 2 * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0], characterState: window.ATK_CHARACTER_STATE.Idle1[0],
framesToRecover: 0, framesToRecover: 0,