Compare commits

...

15 Commits
v0.7.4 ... v0.8

Author SHA1 Message Date
Wing
dd8b731ade Merge pull request #10 from genxium/force_confirmation_isolation
Improvement on smooth synchronization.
2022-12-01 15:25:57 +08:00
genxium
c4489e0912 Fixed backend downsyncToAll battleState filtering. 2022-12-01 15:23:43 +08:00
genxium
348c889e14 Fixes on resync. 2022-12-01 11:53:37 +08:00
yflu
c6473db561 In progress for fixing recovery upon reconnection. 2022-12-01 00:30:35 +08:00
genxium
e165d49cb1 Further simplified signaling. 2022-11-30 21:51:06 +08:00
genxium
26370dce61 Added necessary locking for backend InputsBuffer. 2022-11-30 16:53:48 +08:00
yflu
f3a12b2aa9 A temp broken commit. 2022-11-30 00:04:52 +08:00
genxium
1f5802ee14 Fixed multiple error handling spots. 2022-11-29 21:32:18 +08:00
yflu
080a384ade Working on reduction of resync received in frontend. 2022-11-29 12:49:49 +08:00
genxium
9469b27348 Updated use of GOPROXY param in Makefile. 2022-11-28 23:49:52 +08:00
Wing
ca5ba83b07 Merge pull request #9 from genxium/frame_anim_compatible
Frame anim compatible
2022-11-27 21:38:24 +08:00
genxium
b1f0cf2c57 Added frame anim compatibility for AttackingCharacter. 2022-11-27 21:33:34 +08:00
genxium
1b43e6d760 Reduced dragonbones exported resource size. 2022-11-27 19:38:26 +08:00
genxium
e0fb21f3fb Fixes for simultaneous reconnection w.r.t. same room, and updates for documentation. 2022-11-27 00:00:39 +08:00
genxium
9bce561441 Minor fix for README. 2022-11-26 00:14:11 +08:00
101 changed files with 24971 additions and 1886 deletions

View File

@@ -3,6 +3,7 @@
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md). This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
_(the following gif is sped up to ~1.5x for file size reduction, kindly note that around ~11s countdown, the attack animation is resumed from a partial progress)_ _(the following gif is sped up to ~1.5x for file size reduction, kindly note that around ~11s countdown, the attack animation is resumed from a partial progress)_
![gif_demo](./charts/melee_attack_fractional_anim_resume_spedup.gif) ![gif_demo](./charts/melee_attack_fractional_anim_resume_spedup.gif)
Please also checkout [this demo video](https://pan.baidu.com/s/1U1wb7KWyHorZElNWcS5HHA?pwd=30wh) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance. Please also checkout [this demo video](https://pan.baidu.com/s/1U1wb7KWyHorZElNWcS5HHA?pwd=30wh) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.

View File

@@ -1,5 +1,6 @@
PROJECTNAME=server.exe PROJECTNAME=server.exe
ROOT_DIR=. ROOT_DIR=.
GOPROXY=https://mirrors.aliyun.com/goproxy
all: help all: help
gen-constants: gen-constants:
@@ -13,13 +14,13 @@ run-test-and-hotreload:
ServerEnv=TEST CompileDaemon -log-prefix=false -build="go build" -command="./$(PROJECTNAME)" ServerEnv=TEST CompileDaemon -log-prefix=false -build="go build" -command="./$(PROJECTNAME)"
build: build:
go build -o $(ROOT_DIR)/$(PROJECTNAME) GOPROXY=$(GOPROXY) go build -o $(ROOT_DIR)/$(PROJECTNAME)
run-prod: build-prod run-prod: build-prod
./$(PROJECTNAME) ./$(PROJECTNAME)
build-prod: build-prod:
go build -ldflags "-s -w -X main.VERSION=$(shell git rev-parse --short HEAD)-$(shell date "+%Y%m%d-%H:%M:%S")" -o $(ROOT_DIR)/$(PROJECTNAME) GOPROXY=$(GOPROXY) go build -ldflags "-s -w -X main.VERSION=$(shell git rev-parse --short HEAD)-$(shell date "+%Y%m%d-%H:%M:%S")" -o $(ROOT_DIR)/$(PROJECTNAME)
.PHONY: help .PHONY: help

View File

@@ -96,7 +96,7 @@ func getPlayer(cond sq.Eq) (*Player, error) {
p.CreatedAt = int64(val.(int64)) p.CreatedAt = int64(val.(int64))
} }
} }
Logger.Info("Queried player from db", zap.Any("cond", cond), zap.Any("p", p), zap.Any("pd", pd), zap.Any("cols", cols), zap.Any("rowValues", vals)) Logger.Debug("Queried player from db", zap.Any("cond", cond), zap.Any("p", p), zap.Any("pd", pd), zap.Any("cols", cols), zap.Any("rowValues", vals))
} }
p.PlayerDownsync = pd p.PlayerDownsync = pd
return &p, nil return &p, nil

View File

@@ -1,5 +1,10 @@
package models package models
import (
. "battle_srv/protos"
"sync"
)
type RingBuffer struct { type RingBuffer struct {
Ed int32 // write index, open index Ed int32 // write index, open index
St int32 // read index, closed index St int32 // read index, closed index
@@ -21,6 +26,10 @@ func NewRingBuffer(n int32) *RingBuffer {
} }
func (rb *RingBuffer) Put(pItem interface{}) { func (rb *RingBuffer) Put(pItem interface{}) {
for rb.Cnt >= rb.N-1 {
// Make room for the new element
rb.Pop()
}
rb.Eles[rb.Ed] = pItem rb.Eles[rb.Ed] = pItem
rb.EdFrameId++ rb.EdFrameId++
rb.Cnt++ rb.Cnt++
@@ -69,5 +78,47 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
} }
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} { func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
if frameId >= rb.EdFrameId {
return nil
}
return rb.GetByOffset(frameId - rb.StFrameId) return rb.GetByOffset(frameId - rb.StFrameId)
} }
func (rb *RingBuffer) cloneInputFrameDownsyncsByFrameIdRange(stFrameId, edFrameId int32, mux *sync.Mutex) (int32, []*InputFrameDownsync) {
dst := make([]*InputFrameDownsync, 0, rb.Cnt)
if nil != mux {
mux.Lock()
defer func() {
mux.Unlock()
}()
}
prevFrameFound := true
j := stFrameId
for j < edFrameId {
tmp := rb.GetByFrameId(j)
if nil == tmp {
if false == prevFrameFound {
// The "id"s are always consecutive
break
} else {
prevFrameFound = false
continue
}
}
foo := tmp.(*InputFrameDownsync)
bar := &InputFrameDownsync{
InputFrameId: foo.InputFrameId,
InputList: make([]uint64, len(foo.InputList)),
ConfirmedList: foo.ConfirmedList,
}
for i, input := range foo.InputList {
bar.InputList[i] = input
}
dst = append(dst, bar)
j++
}
return j, dst
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/solarlune/resolv" "github.com/solarlune/resolv"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil" "io/ioutil"
"math"
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
@@ -36,8 +35,7 @@ const (
DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1) DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1)
DOWNSYNC_MSG_ACT_BATTLE_START = int32(0) DOWNSYNC_MSG_ACT_BATTLE_START = int32(0)
DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = int32(-98) DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = int32(-98)
DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED = int32(-97)
) )
const ( const (
@@ -70,7 +68,7 @@ const (
) )
const ( const (
DEFAULT_PLAYER_RADIUS = float64(16) DEFAULT_PLAYER_RADIUS = float64(12)
) )
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged. // These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
@@ -154,12 +152,12 @@ type Room struct {
State int32 State int32
Index int Index int
RenderFrameId int32 RenderFrameId int32
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback; Moreover when "true == BackendDynamicsEnabled" we always have "Room.CurDynamicsRenderFrameId >= Room.RenderFrameId" because each "all-confirmed inputFrame" is applied on "all applicable renderFrames" in one-go hence often sees a future "renderFrame" earlier
EffectivePlayerCount int32 EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup DismissalWaitGroup sync.WaitGroup
Barriers map[int32]*Barrier Barriers map[int32]*Barrier
InputsBuffer *RingBuffer // Indices are STRICTLY consecutive InputsBuffer *RingBuffer // Indices are STRICTLY consecutive
DiscreteInputsBuffer sync.Map // Indices are NOT NECESSARILY consecutive InputsBufferLock sync.Mutex
RenderFrameBuffer *RingBuffer RenderFrameBuffer *RingBuffer
LastAllConfirmedInputFrameId int32 LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32 LastAllConfirmedInputFrameIdWithChange int32
@@ -172,7 +170,8 @@ type Room struct {
BulletBattleLocalIdCounter int32 BulletBattleLocalIdCounter int32
dilutedRollbackEstimatedDtNanos int64 dilutedRollbackEstimatedDtNanos int64
BattleColliderInfo // Compositing to send centralized magic numbers
BattleColliderInfo // Compositing to send centralized magic numbers
} }
func (pR *Room) updateScore() { func (pR *Room) updateScore() {
@@ -372,7 +371,7 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
func (pR *Room) StartBattle() { func (pR *Room) StartBattle() {
if RoomBattleStateIns.WAITING != pR.State { if RoomBattleStateIns.WAITING != pR.State {
Logger.Warn("[StartBattle] Battle not started due to not being WAITING!", zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State)) Logger.Debug("[StartBattle] Battle not started due to not being WAITING!", zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State))
return return
} }
@@ -395,16 +394,18 @@ func (pR *Room) StartBattle() {
pR.refreshColliders(spaceW, spaceH) pR.refreshColliders(spaceW, spaceH)
/** /**
* Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached. * Will be triggered from a goroutine which executes the critical `Room.AddPlayerIfPossible`, thus the `battleMainLoop` should be detached.
* All of the consecutive stages, e.g. settlement, dismissal, should share the same goroutine with `battleMainLoop`. * All of the consecutive stages, e.g. settlement, dismissal, should share the same goroutine with `battleMainLoop`.
*/ *
* As "defer" is only applicable to function scope, the use of "pR.InputsBufferLock" within "battleMainLoop" is embedded into each subroutine call.
*/
battleMainLoop := func() { battleMainLoop := func() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r)) Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r))
pR.StopBattleForSettlement()
} }
Logger.Info("The `battleMainLoop` is stopped for:", zap.Any("roomId", pR.Id)) pR.StopBattleForSettlement()
Logger.Info(fmt.Sprintf("The `battleMainLoop` for roomId=%v is stopped@renderFrameId=%v, with battleDurationFrames=%v:\n%v", pR.Id, pR.RenderFrameId, pR.BattleDurationFrames, pR.InputsBufferString(false))) // This takes sometime to print
pR.onBattleStoppedForSettlement() pR.onBattleStoppedForSettlement()
}() }()
@@ -413,16 +414,12 @@ func (pR *Room) StartBattle() {
Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id)) Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id))
for { for {
stCalculation := utils.UnixtimeNano() stCalculation := utils.UnixtimeNano()
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
if elapsedNanosSinceLastFrameIdTriggered < pR.dilutedRollbackEstimatedDtNanos { if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos {
Logger.Debug(fmt.Sprintf("Avoiding too fast frame@roomId=%v, renderFrameId=%v: elapsedNanosSinceLastFrameIdTriggered=%v", pR.Id, pR.RenderFrameId, elapsedNanosSinceLastFrameIdTriggered)) Logger.Debug(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered))
continue
} }
if pR.RenderFrameId > pR.BattleDurationFrames { if pR.RenderFrameId > pR.BattleDurationFrames {
Logger.Info(fmt.Sprintf("The `battleMainLoop` for roomId=%v is stopped@renderFrameId=%v, with battleDurationFrames=%v:\n%v", pR.Id, pR.RenderFrameId, pR.BattleDurationFrames, pR.InputsBufferString(true)))
pR.StopBattleForSettlement()
return return
} }
@@ -430,155 +427,58 @@ func (pR *Room) StartBattle() {
return return
} }
// Prefab and buffer backend inputFrameDownsync if 0 == pR.RenderFrameId {
if pR.shouldPrefabInputFrameDownsync(pR.RenderFrameId) { for playerId, player := range pR.Players {
noDelayInputFrameId := pR.ConvertToInputFrameId(pR.RenderFrameId, 0) thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState)) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads
pR.prefabInputFrameDownsync(noDelayInputFrameId) // [WARNING] DON'T try to send any message to an inactive player!
} switch thatPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
pR.markConfirmationIfApplicable() case PlayerBattleStateIns.LOST:
unconfirmedMask := uint64(0) case PlayerBattleStateIns.EXPELLED_DURING_GAME:
if pR.BackendDynamicsEnabled { case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
// Force setting all-confirmed of buffered inputFrames periodically continue
unconfirmedMask = pR.forceConfirmationIfApplicable() }
} kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId)
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId)) }
/* Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id))
[WARNING]
Upon resynced on frontend, "refRenderFrameId" MUST BE CAPPED somehow by "upperToSendInputFrameId", if frontend resyncs itself to a more advanced value than given below, upon the next renderFrame tick on the frontend it might generate non-consecutive "nextInputFrameId > frontend.recentInputCache.edFrameId+1".
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
*/
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
if refRenderFrameId > pR.RenderFrameId {
refRenderFrameId = pR.RenderFrameId
} }
dynamicsDuration := int64(0) dynamicsDuration := int64(0)
unconfirmedMask := uint64(0)
// Prefab and buffer backend inputFrameDownsync
if pR.BackendDynamicsEnabled { if pR.BackendDynamicsEnabled {
if pR.shouldPrefabInputFrameDownsync(pR.RenderFrameId) {
noDelayInputFrameId := pR.ConvertToInputFrameId(pR.RenderFrameId, 0)
if existingInputFrame := pR.InputsBuffer.GetByFrameId(noDelayInputFrameId); nil == existingInputFrame {
pR.prefabInputFrameDownsync(noDelayInputFrameId, true)
}
}
// Force setting all-confirmed of buffered inputFrames periodically, kindly note that if "pR.BackendDynamicsEnabled", what we want to achieve is "recovery upon reconnection", which certainly requires "forceConfirmationIfApplicable" to move "pR.LastAllConfirmedInputFrameId" forward as much as possible
unconfirmedMask = pR.forceConfirmationIfApplicable()
if 0 <= pR.LastAllConfirmedInputFrameId { if 0 <= pR.LastAllConfirmedInputFrameId {
dynamicsStartedAt := utils.UnixtimeNano() dynamicsStartedAt := utils.UnixtimeNano()
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId" // Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames) nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId)) Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId))
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY) pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY)
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
} }
if 0 < unconfirmedMask {
// [WARNING] The following inequality are seldom true, but just to avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId". Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, unconfirmedMask))
if refRenderFrameId > pR.CurDynamicsRenderFrameId { // Otherwise no need to downsync immediately
refRenderFrameId = pR.CurDynamicsRenderFrameId pR.downsyncToAllPlayers(pR.LastAllConfirmedInputFrameId, unconfirmedMask, false)
}
}
for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
// [WARNING] DON'T send anything if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
continue
}
if 0 == pR.RenderFrameId {
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId)
} else {
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
toSendInputFrames := make([]*InputFrameDownsync, 0, pR.InputsBuffer.Cnt)
candidateToSendInputFrameId := pR.Players[playerId].LastSentInputFrameId + 1
if candidateToSendInputFrameId < pR.InputsBuffer.StFrameId {
// [WARNING] As "player.LastSentInputFrameId <= lastAllConfirmedInputFrameIdWithChange" for each iteration, and "lastAllConfirmedInputFrameIdWithChange <= lastAllConfirmedInputFrameId" where the latter is used to "applyInputFrameDownsyncDynamics" and then evict "pR.InputsBuffer", thus there's a very high possibility that "player.LastSentInputFrameId" is already evicted.
Logger.Warn(fmt.Sprintf("LastSentInputFrameId already popped: roomId=%v, playerId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, candidateToSendInputFrameId-1, player.AckingInputFrameId, pR.InputsBufferString(false)))
candidateToSendInputFrameId = pR.InputsBuffer.StFrameId
}
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId {
// A rejoined player, should guarantee that when it resyncs to "refRenderFrameId" a matching inputFrame to apply exists
candidateToSendInputFrameId = pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
Logger.Warn(fmt.Sprintf("Resetting refRenderFrame for rejoined player: roomId=%v, playerId=%v, refRenderFrameId=%v, candidateToSendInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, candidateToSendInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
}
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "candidateToSendInputFrameId" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
for candidateToSendInputFrameId <= upperToSendInputFrameId {
tmp := pR.InputsBuffer.GetByFrameId(candidateToSendInputFrameId)
if nil == tmp {
panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! InputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.InputsBufferString(false)))
}
f := tmp.(*InputFrameDownsync)
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
Logger.Debug("inputFrame lifecycle#3[sending]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", candidateToSendInputFrameId), zap.Any("inputFrameId-doublecheck", f.InputFrameId), zap.Any("InputsBuffer", pR.InputsBufferString(false)), zap.Any("ConfirmedList", f.ConfirmedList))
}
toSendInputFrames = append(toSendInputFrames, f)
candidateToSendInputFrameId++
}
if 0 >= len(toSendInputFrames) {
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId {
Logger.Warn(fmt.Sprintf("Not sending due to empty toSendInputFrames: roomId=%v, playerId=%v, refRenderFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
}
continue
}
/*
Resync helps
1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation"
2. reconnection
*/
shouldResync1 := (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId)
shouldResync2 := (0 < (unconfirmedMask & uint64(1<<uint32(player.JoinIndex-1)))) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
// shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
if pR.BackendDynamicsEnabled && (shouldResync1 || shouldResync2) {
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
if nil == tmp {
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, playerId=%v, candidateToSendInputFrameId=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, playerId, candidateToSendInputFrameId, pR.InputsBufferString(false), pR.RenderFrameBufferString()))
}
refRenderFrame := tmp.(*RoomDownsyncFrame)
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
} else {
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
}
pR.Players[playerId].LastSentInputFrameId = candidateToSendInputFrameId - 1
}
}
if pR.BackendDynamicsEnabled {
// Evict no longer required "RenderFrameBuffer"
for pR.RenderFrameBuffer.N < pR.RenderFrameBuffer.Cnt || (0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId) {
_ = pR.RenderFrameBuffer.Pop()
}
}
minToKeepInputFrameId := pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames) - pR.SpAtkLookupFrames
/*
[WARNING]
The following updates to "minToKeepInputFrameId" is necessary because when "false == pR.BackendDynamicsEnabled", the variable "refRenderFrameId" is not well defined.
*/
minLastSentInputFrameId := int32(math.MaxInt32)
for _, player := range pR.Players {
if PlayerBattleStateIns.ACTIVE != player.BattleState {
continue
}
if player.LastSentInputFrameId >= minLastSentInputFrameId {
continue
}
minLastSentInputFrameId = player.LastSentInputFrameId
}
if minLastSentInputFrameId < minToKeepInputFrameId {
minToKeepInputFrameId = minLastSentInputFrameId
}
for pR.InputsBuffer.N < pR.InputsBuffer.Cnt || (0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < minToKeepInputFrameId) {
f := pR.InputsBuffer.Pop().(*InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) {
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
Logger.Debug("inputFrame lifecycle#4[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("minToKeepInputFrameId", minToKeepInputFrameId), zap.Any("InputsBuffer", pR.InputsBufferString(false)))
} }
} }
pR.RenderFrameId++ pR.RenderFrameId++
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation) pR.LastRenderFrameIdTriggeredAt = utils.UnixtimeNano()
if elapsedInCalculation > pR.dilutedRollbackEstimatedDtNanos { elapsedInCalculation := (pR.LastRenderFrameIdTriggeredAt - stCalculation)
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, dilutedRollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.dilutedRollbackEstimatedDtNanos)) if elapsedInCalculation > pR.RollbackEstimatedDtNanos {
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.RollbackEstimatedDtNanos))
} }
time.Sleep(time.Duration(pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation)) time.Sleep(time.Duration(pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation))
} }
@@ -595,39 +495,60 @@ func (pR *Room) toDiscreteInputsBufferIndex(inputFrameId int32, joinIndex int32)
} }
func (pR *Room) OnBattleCmdReceived(pReq *WsReq) { func (pR *Room) OnBattleCmdReceived(pReq *WsReq) {
// [WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads!
if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped { if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped {
return return
} }
playerId := pReq.PlayerId playerId := pReq.PlayerId
var player *Player = nil
var existent bool = false
inputFrameUpsyncBatch := pReq.InputFrameUpsyncBatch inputFrameUpsyncBatch := pReq.InputFrameUpsyncBatch
ackingFrameId := pReq.AckingFrameId ackingFrameId := pReq.AckingFrameId
ackingInputFrameId := pReq.AckingInputFrameId ackingInputFrameId := pReq.AckingInputFrameId
if _, existent := pR.Players[playerId]; !existent { if player, existent = pR.Players[playerId]; !existent {
Logger.Warn(fmt.Sprintf("upcmd player doesn't exist: roomId=%v, playerId=%v", pR.Id, playerId)) Logger.Warn(fmt.Sprintf("upcmd player doesn't exist: roomId=%v, playerId=%v", pR.Id, playerId))
return return
} }
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingFrameId), pR.Players[playerId].AckingFrameId, ackingFrameId); !swapped { atomic.StoreInt32(&(player.AckingFrameId), ackingFrameId)
panic(fmt.Sprintf("Failed to update AckingFrameId to %v for roomId=%v, playerId=%v", ackingFrameId, pR.Id, playerId)) atomic.StoreInt32(&(player.AckingInputFrameId), ackingInputFrameId)
}
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingInputFrameId), pR.Players[playerId].AckingInputFrameId, ackingInputFrameId); !swapped {
panic(fmt.Sprintf("Failed to update AckingInputFrameId to %v for roomId=%v, playerId=%v", ackingInputFrameId, pR.Id, playerId))
}
Logger.Debug(fmt.Sprintf("InputsBufferLock about to lock: roomId=%v", pR.Id))
pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("InputsBufferLock locked: roomId=%v", pR.Id))
defer func() {
pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("InputsBufferLock unlocked: roomId=%v", pR.Id))
}()
for _, inputFrameUpsync := range inputFrameUpsyncBatch { for _, inputFrameUpsync := range inputFrameUpsyncBatch {
clientInputFrameId := inputFrameUpsync.InputFrameId clientInputFrameId := inputFrameUpsync.InputFrameId
if clientInputFrameId < pR.InputsBuffer.StFrameId { if clientInputFrameId < pR.InputsBuffer.StFrameId {
// The updates to "pR.InputsBuffer.StFrameId" is monotonically increasing, thus if "clientInputFrameId < pR.InputsBuffer.StFrameId" at any moment of time, it is obsolete in the future. // The updates to "pR.InputsBuffer.StFrameId" is monotonically increasing, thus if "clientInputFrameId < pR.InputsBuffer.StFrameId" at any moment of time, it is obsolete in the future.
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false))) Logger.Warn(fmt.Sprintf("Omitting obsolete inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
continue continue
} }
bufIndex := pR.toDiscreteInputsBufferIndex(clientInputFrameId, pReq.JoinIndex) if clientInputFrameId > pR.InputsBuffer.EdFrameId {
pR.DiscreteInputsBuffer.Store(bufIndex, inputFrameUpsync) Logger.Warn(fmt.Sprintf("Dropping too advanced inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v; is this player cheating?", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
continue
// TODO: "pR.DiscreteInputsBuffer" might become too large with outdated "inputFrameUpsync" items, maintain another queue orderd by timestamp to evict them }
var targetInputFrameDownsync *InputFrameDownsync = nil
if clientInputFrameId == pR.InputsBuffer.EdFrameId {
targetInputFrameDownsync = pR.prefabInputFrameDownsync(clientInputFrameId, false)
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-Prefabbed new inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
} else {
targetInputFrameDownsync = pR.InputsBuffer.GetByFrameId(clientInputFrameId).(*InputFrameDownsync)
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-stuffing inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
}
targetInputFrameDownsync.InputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
targetInputFrameDownsync.ConfirmedList |= uint64(1 << uint32(player.JoinIndex-1))
}
newAllConfirmedCount := pR.markConfirmationIfApplicable()
if 0 < newAllConfirmedCount {
// Downsync new all-confirmed inputFrames asap
unconfirmedMask := uint64(0)
pR.downsyncToAllPlayers(pR.LastAllConfirmedInputFrameId, unconfirmedMask, true)
} }
} }
@@ -772,7 +693,7 @@ func (pR *Room) OnDismissed() {
pR.WorldToVirtualGridRatio = float64(1000) pR.WorldToVirtualGridRatio = float64(1000)
pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations pR.VirtualGridToWorldRatio = float64(1.0) / pR.WorldToVirtualGridRatio // this is a one-off computation, should avoid division in iterations
pR.SpAtkLookupFrames = 5 pR.SpAtkLookupFrames = 5
pR.PlayerDefaultSpeed = int32(float64(2) * pR.WorldToVirtualGridRatio) // in virtual grids per frame pR.PlayerDefaultSpeed = int32(float64(1) * pR.WorldToVirtualGridRatio) // in virtual grids per frame
pR.Players = make(map[int32]*Player) pR.Players = make(map[int32]*Player)
pR.PlayersArr = make([]*Player, pR.Capacity) pR.PlayersArr = make([]*Player, pR.Capacity)
pR.CollisionSysMap = make(map[int32]*resolv.Object) pR.CollisionSysMap = make(map[int32]*resolv.Object)
@@ -782,8 +703,7 @@ func (pR *Room) OnDismissed() {
pR.Barriers = make(map[int32]*Barrier) pR.Barriers = make(map[int32]*Barrier)
pR.RenderCacheSize = 1024 pR.RenderCacheSize = 1024
pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize) pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize)
pR.DiscreteInputsBuffer = sync.Map{} pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 2) + 1)
pR.LastAllConfirmedInputFrameId = -1 pR.LastAllConfirmedInputFrameId = -1
pR.LastAllConfirmedInputFrameIdWithChange = -1 pR.LastAllConfirmedInputFrameIdWithChange = -1
@@ -796,38 +716,38 @@ func (pR *Room) OnDismissed() {
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 logging FAST FRAME
dilutionFactor := 12 dilutedServerFps := int64(58)
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 = pR.RollbackEstimatedDtNanos * (int64(pR.ServerFps) / dilutedServerFps)
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 = 5 pR.MaxChasingRenderFramesPerUpdate = 8
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(23), StartupFrames: int32(10),
ActiveFrames: int32(3), 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 RecoveryFrames: int32(34),
RecoveryFramesOnBlock: int32(61), RecoveryFramesOnBlock: int32(34),
RecoveryFramesOnHit: int32(61), RecoveryFramesOnHit: int32(34),
Moveforward: &Vec2D{ Moveforward: &Vec2D{
X: 0, X: 0,
Y: 0, Y: 0,
}, },
HitboxOffset: float64(24.0), // should be about the radius of the PlayerCollider HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider
HitboxSize: &Vec2D{ HitboxSize: &Vec2D{
X: float64(45.0), X: float64(23.0),
Y: float64(32.0), Y: float64(32.0),
}, },
// for defender // for defender
HitStunFrames: int32(18), HitStunFrames: int32(18),
BlockStunFrames: int32(9), BlockStunFrames: int32(9),
Pushback: float64(11.0), Pushback: float64(8.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),
} }
@@ -843,10 +763,16 @@ func (pR *Room) OnDismissed() {
} }
func (pR *Room) expelPlayerDuringGame(playerId int32) { func (pR *Room) expelPlayerDuringGame(playerId int32) {
defer pR.onPlayerExpelledDuringGame(playerId) if signalToCloseConnOfThisPlayer, existent := pR.PlayerSignalToCloseDict[playerId]; existent {
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "") // TODO: Specify an error code
}
pR.onPlayerExpelledDuringGame(playerId)
} }
func (pR *Room) expelPlayerForDismissal(playerId int32) { func (pR *Room) expelPlayerForDismissal(playerId int32) {
if signalToCloseConnOfThisPlayer, existent := pR.PlayerSignalToCloseDict[playerId]; existent {
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "") // TODO: Specify an error code
}
pR.onPlayerExpelledForDismissal(playerId) pR.onPlayerExpelledForDismissal(playerId)
} }
@@ -867,8 +793,9 @@ func (pR *Room) OnPlayerDisconnected(playerId int32) {
} }
}() }()
if _, existent := pR.Players[playerId]; existent { if player, existent := pR.Players[playerId]; existent {
switch pR.Players[playerId].BattleState { thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
switch thatPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED: case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST: case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME: case PlayerBattleStateIns.EXPELLED_DURING_GAME:
@@ -882,19 +809,20 @@ func (pR *Room) OnPlayerDisconnected(playerId int32) {
return return
} }
switch pR.State { currRoomBattleState := atomic.LoadInt32(&(pR.State))
switch currRoomBattleState {
case RoomBattleStateIns.WAITING: case RoomBattleStateIns.WAITING:
pR.onPlayerLost(playerId) pR.onPlayerLost(playerId)
delete(pR.Players, playerId) // Note that this statement MUST be put AFTER `pR.onPlayerLost(...)` to avoid nil pointer exception. delete(pR.Players, playerId) // Note that this statement MUST be put AFTER `pR.onPlayerLost(...)` to avoid nil pointer exception.
if 0 == pR.EffectivePlayerCount { if 0 == pR.EffectivePlayerCount {
pR.State = RoomBattleStateIns.IDLE atomic.StoreInt32(&(pR.State), RoomBattleStateIns.IDLE)
} }
pR.updateScore() pR.updateScore()
Logger.Info("Player disconnected while room is at RoomBattleStateIns.WAITING:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount)) Logger.Info("Player disconnected while room is at RoomBattleStateIns.WAITING:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount))
default: default:
pR.Players[playerId].BattleState = PlayerBattleStateIns.DISCONNECTED atomic.StoreInt32(&(pR.Players[playerId].BattleState), PlayerBattleStateIns.DISCONNECTED)
pR.clearPlayerNetworkSession(playerId) // Still need clear the network session pointers, because "OnPlayerDisconnected" is only triggered from "signalToCloseConnOfThisPlayer" in "ws/serve.go", when the same player reconnects the network session pointers will be re-assigned pR.clearPlayerNetworkSession(playerId) // Still need clear the network session pointers, because "OnPlayerDisconnected" is only triggered from "signalToCloseConnOfThisPlayer" in "ws/serve.go", when the same player reconnects the network session pointers will be re-assigned
Logger.Info("Player disconnected from room:", zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("roomId", pR.Id), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount)) Logger.Warn("OnPlayerDisconnected finished:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount))
} }
} }
@@ -905,23 +833,23 @@ func (pR *Room) onPlayerLost(playerId int32) {
} }
}() }()
if player, existent := pR.Players[playerId]; existent { if player, existent := pR.Players[playerId]; existent {
player.BattleState = PlayerBattleStateIns.LOST atomic.StoreInt32(&(player.BattleState), PlayerBattleStateIns.LOST)
pR.clearPlayerNetworkSession(playerId) pR.clearPlayerNetworkSession(playerId)
pR.EffectivePlayerCount-- pR.EffectivePlayerCount--
indiceInJoinIndexBooleanArr := int(player.JoinIndex - 1) indiceInJoinIndexBooleanArr := int(player.JoinIndex - 1)
if (0 <= indiceInJoinIndexBooleanArr) && (indiceInJoinIndexBooleanArr < len(pR.JoinIndexBooleanArr)) { if (0 <= indiceInJoinIndexBooleanArr) && (indiceInJoinIndexBooleanArr < len(pR.JoinIndexBooleanArr)) {
pR.JoinIndexBooleanArr[indiceInJoinIndexBooleanArr] = false pR.JoinIndexBooleanArr[indiceInJoinIndexBooleanArr] = false
} else { } else {
Logger.Warn("Room OnPlayerLost, pR.JoinIndexBooleanArr is out of range: ", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("indiceInJoinIndexBooleanArr", indiceInJoinIndexBooleanArr), zap.Any("len(pR.JoinIndexBooleanArr)", len(pR.JoinIndexBooleanArr))) Logger.Warn("Room OnPlayerLost, pR.JoinIndexBooleanArr is out of range: ", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("indiceInJoinIndexBooleanArr", indiceInJoinIndexBooleanArr), zap.Any("len(pR.JoinIndexBooleanArr)", len(pR.JoinIndexBooleanArr)))
} }
player.JoinIndex = MAGIC_JOIN_INDEX_INVALID player.JoinIndex = MAGIC_JOIN_INDEX_INVALID
Logger.Info("Room OnPlayerLost: ", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("resulted pR.JoinIndexBooleanArr", pR.JoinIndexBooleanArr)) Logger.Warn("OnPlayerLost: ", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("resulted pR.JoinIndexBooleanArr", pR.JoinIndexBooleanArr))
} }
} }
func (pR *Room) clearPlayerNetworkSession(playerId int32) { func (pR *Room) clearPlayerNetworkSession(playerId int32) {
if _, y := pR.PlayerDownsyncSessionDict[playerId]; y { if _, y := pR.PlayerDownsyncSessionDict[playerId]; y {
Logger.Info("sending termination symbol for:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id)) Logger.Debug("clearPlayerNetworkSession:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId))
delete(pR.PlayerDownsyncSessionDict, playerId) delete(pR.PlayerDownsyncSessionDict, playerId)
delete(pR.PlayerSignalToCloseDict, playerId) delete(pR.PlayerSignalToCloseDict, playerId)
} }
@@ -964,7 +892,7 @@ func (pR *Room) onPlayerAdded(playerId int32) {
} }
pR.updateScore() pR.updateScore()
Logger.Info("onPlayerAdded:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("joinIndex", pR.Players[playerId].JoinIndex), zap.Any("EffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("resulted pR.JoinIndexBooleanArr", pR.JoinIndexBooleanArr), zap.Any("RoomBattleState", pR.State)) Logger.Info("onPlayerAdded:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("joinIndex", pR.Players[playerId].JoinIndex), zap.Any("EffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("resulted pR.JoinIndexBooleanArr", pR.JoinIndexBooleanArr), zap.Any("RoomBattleState", pR.State))
} }
func (pR *Room) onPlayerReAdded(playerId int32) { func (pR *Room) onPlayerReAdded(playerId int32) {
@@ -985,41 +913,46 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
return false return false
} }
// Broadcast added or readded player info to all players in the same room Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-before: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount))
for _, eachPlayer := range pR.Players { switch targetPlayer.BattleState {
/* case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
[WARNING] playerAckedFrame := &RoomDownsyncFrame{
This `playerAckedFrame` is the first ever "RoomDownsyncFrame" for every "PersistentSessionClient on the frontend", and it goes right after each "BattleColliderInfo". Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players, true),
By making use of the sequential nature of each ws session, all later "RoomDownsyncFrame"s generated after `pRoom.StartBattle()` will be put behind this `playerAckedFrame`.
This function is triggered by an upsync message via WebSocket, thus downsync sending is also available by now.
*/
switch targetPlayer.BattleState {
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame := &RoomDownsyncFrame{
Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players, true),
}
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, eachPlayer.Id)
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame := &RoomDownsyncFrame{
Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players, true),
}
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED, eachPlayer.Id)
default:
} }
// Broadcast normally added player info to all players in the same room
for thatPlayerId, thatPlayer := range pR.Players {
/*
[WARNING]
This `playerAckedFrame` is the first ever "RoomDownsyncFrame" for every "PersistentSessionClient on the frontend", and it goes right after each "BattleColliderInfo".
By making use of the sequential nature of each ws session, all later "RoomDownsyncFrame"s generated after `pRoom.StartBattle()` will be put behind this `playerAckedFrame`.
This function is triggered by an upsync message via WebSocket, thus downsync sending is also available by now.
*/
thatPlayerBattleState := atomic.LoadInt32(&(thatPlayer.BattleState))
Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-middle: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, thatPlayerId=%v, thatPlayerBattleState=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, thatPlayer.Id, thatPlayerBattleState))
if thatPlayerId == targetPlayer.Id || (PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK == thatPlayerBattleState || PlayerBattleStateIns.ACTIVE == thatPlayerBattleState) {
Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-sending DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount))
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, thatPlayer.Id)
}
}
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
// only send resync info to the targetPlayer
// i.e. implies that "targetPlayer.LastSentInputFrameId == MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED"
pR.downsyncToSinglePlayer(playerId, targetPlayer, pR.LastAllConfirmedInputFrameId, uint64(0), false)
default:
} }
targetPlayer.BattleState = PlayerBattleStateIns.ACTIVE targetPlayer.BattleState = PlayerBattleStateIns.ACTIVE
Logger.Info(fmt.Sprintf("OnPlayerBattleColliderAcked: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount)) Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-post-downsync: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount))
if pR.Capacity == int(pR.EffectivePlayerCount) { if pR.Capacity == int(pR.EffectivePlayerCount) {
allAcked := true allAcked := true
for _, p := range pR.Players { for _, p := range pR.Players {
if PlayerBattleStateIns.ACTIVE != p.BattleState { if PlayerBattleStateIns.ACTIVE != p.BattleState {
Logger.Info("unexpectedly got an inactive player", zap.Any("roomId", pR.Id), zap.Any("playerId", p.Id), zap.Any("battleState", p.BattleState)) Logger.Warn("unexpectedly got an inactive player", zap.Any("roomId", pR.Id), zap.Any("playerId", p.Id), zap.Any("battleState", p.BattleState))
allAcked = false allAcked = false
break break
} }
@@ -1033,27 +966,29 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
return true return true
} }
func (pR *Room) sendSafely(roomDownsyncFrame *RoomDownsyncFrame, toSendFrames []*InputFrameDownsync, act int32, playerId int32) { func (pR *Room) sendSafely(roomDownsyncFrame *RoomDownsyncFrame, toSendInputFrameDownsyncs []*InputFrameDownsync, act int32, playerId int32) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
pR.PlayerSignalToCloseDict[playerId](Constants.RetCode.UnknownError, fmt.Sprintf("%v", r)) Logger.Error("sendSafely, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("panic", r))
} }
}() }()
pResp := &WsResp{ if playerDownsyncSession, existent := pR.PlayerDownsyncSessionDict[playerId]; existent {
Ret: int32(Constants.RetCode.Ok), pResp := &WsResp{
Act: act, Ret: int32(Constants.RetCode.Ok),
Rdf: roomDownsyncFrame, Act: act,
InputFrameDownsyncBatch: toSendFrames, Rdf: roomDownsyncFrame,
} InputFrameDownsyncBatch: toSendInputFrameDownsyncs,
}
theBytes, marshalErr := proto.Marshal(pResp) theBytes, marshalErr := proto.Marshal(pResp)
if nil != marshalErr { if nil != marshalErr {
panic(fmt.Sprintf("Error marshaling downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount)) panic(fmt.Sprintf("Error marshaling downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
} }
if err := pR.PlayerDownsyncSessionDict[playerId].WriteMessage(websocket.BinaryMessage, theBytes); nil != err { if err := playerDownsyncSession.WriteMessage(websocket.BinaryMessage, theBytes); nil != err {
panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount, err)) panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount, err))
}
} }
} }
@@ -1061,12 +996,24 @@ func (pR *Room) shouldPrefabInputFrameDownsync(renderFrameId int32) bool {
return ((renderFrameId & ((1 << pR.InputScaleFrames) - 1)) == 0) return ((renderFrameId & ((1 << pR.InputScaleFrames) - 1)) == 0)
} }
func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync { func (pR *Room) prefabInputFrameDownsync(inputFrameId int32, lockInputsBuffer bool) *InputFrameDownsync {
/* /*
Kindly note that on backend the prefab is much simpler than its frontend counterpart, because frontend will upsync its latest command immediately if there's any change w.r.t. its own prev cmd, thus if no upsync received from a frontend, Kindly note that on backend the prefab is much simpler than its frontend counterpart, because frontend will upsync its latest command immediately if there's any change w.r.t. its own prev cmd, thus if no upsync received from a frontend,
- EITHER it's due to local lag and bad network, - EITHER it's due to local lag and bad network,
- OR there's no change w.r.t. to its prev cmd. - OR there's no change w.r.t. to its prev cmd.
*/ */
if lockInputsBuffer {
Logger.Debug(fmt.Sprintf("prefabInputFrameDownsync-InputsBufferLock to about lock: roomId=%v", pR.Id))
pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("prefabInputFrameDownsync-InputsBufferLock locked: roomId=%v", pR.Id))
defer func() {
pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("prefabInputFrameDownsync-InputsBufferLock unlocked: roomId=%v", pR.Id))
}()
}
var currInputFrameDownsync *InputFrameDownsync = nil var currInputFrameDownsync *InputFrameDownsync = nil
if 0 == inputFrameId && 0 == pR.InputsBuffer.Cnt { if 0 == inputFrameId && 0 == pR.InputsBuffer.Cnt {
@@ -1096,34 +1043,45 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync
return currInputFrameDownsync return currInputFrameDownsync
} }
func (pR *Room) markConfirmationIfApplicable() { func (pR *Room) markConfirmationIfApplicable() int {
newAllConfirmedCount := 0
inputFrameId1 := pR.LastAllConfirmedInputFrameId + 1 inputFrameId1 := pR.LastAllConfirmedInputFrameId + 1
totPlayerCnt := uint32(pR.Capacity) totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
for inputFrameId := inputFrameId1; inputFrameId < pR.InputsBuffer.EdFrameId; inputFrameId++ { for inputFrameId := inputFrameId1; inputFrameId < pR.InputsBuffer.EdFrameId; inputFrameId++ {
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId) tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
if nil == tmp { if nil == tmp {
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic (Or maybe you're having a 'Room.RenderCacheSize' too small)! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false))) panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false)))
} }
shouldBreakConfirmation := false
inputFrameDownsync := tmp.(*InputFrameDownsync) inputFrameDownsync := tmp.(*InputFrameDownsync)
for _, player := range pR.Players {
bufIndex := pR.toDiscreteInputsBufferIndex(inputFrameId, player.JoinIndex) if allConfirmedMask != inputFrameDownsync.ConfirmedList {
tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex) // It's safe to "LoadAndDelete" here because the "inputFrameUpsync" of this player is already remembered by the corresponding "inputFrameDown". for _, player := range pR.Players {
if !loaded { thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
continue thatPlayerJoinMask := uint64(1 << uint32(player.JoinIndex-1))
if 0 == (inputFrameDownsync.ConfirmedList & thatPlayerJoinMask) {
if thatPlayerBattleState == PlayerBattleStateIns.ACTIVE {
shouldBreakConfirmation = true
break
} else {
Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false)))
}
}
} }
inputFrameUpsync := tmp.(*InputFrameUpsync)
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = inputFrameUpsync.Encoded
inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
} }
if allConfirmedMask == inputFrameDownsync.ConfirmedList { if shouldBreakConfirmation {
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
} else {
break break
} else {
newAllConfirmedCount += 1
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
} }
} }
Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable checking inputFrameId=[%v, %v) for roomId=%v, newAllConfirmedCount=%d: InputsBuffer=%v", inputFrameId1, pR.InputsBuffer.EdFrameId, pR.Id, newAllConfirmedCount, pR.InputsBufferString(false)))
return newAllConfirmedCount
} }
func (pR *Room) forceConfirmationIfApplicable() uint64 { func (pR *Room) forceConfirmationIfApplicable() uint64 {
@@ -1144,6 +1102,15 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#1], no need to force confirmation of it", inputFrameId2, pR.Id)) Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#1], no need to force confirmation of it", inputFrameId2, pR.Id))
return 0 return 0
} }
Logger.Debug(fmt.Sprintf("forceConfirmation-InputsBufferLock about to lock: roomId=%v", pR.Id))
pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("forceConfirmation-InputsBufferLock locked: roomId=%v", pR.Id))
defer func() {
pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("forceConfirmation-InputsBufferLock unlocked: roomId=%v", pR.Id))
}()
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId2) tmp := pR.InputsBuffer.GetByFrameId(inputFrameId2)
if nil == tmp { if nil == tmp {
panic(fmt.Sprintf("inputFrameId2=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId2, pR.Id, pR.InputsBufferString(false))) panic(fmt.Sprintf("inputFrameId2=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId2, pR.Id, pR.InputsBufferString(false)))
@@ -1171,6 +1138,14 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
totPlayerCnt := uint32(pR.Capacity) totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
Logger.Debug(fmt.Sprintf("applyInputFrameDownsyncDynamics-InputsBufferLock about to lock: roomId=%v", pR.Id))
pR.InputsBufferLock.Lock()
Logger.Debug(fmt.Sprintf("applyInputFrameDownsyncDynamics-InputsBufferLock locked: roomId=%v", pR.Id))
defer func() {
pR.InputsBufferLock.Unlock()
Logger.Debug(fmt.Sprintf("applyInputFrameDownsyncDynamics-InputsBufferLock unlocked: roomId=%v", pR.Id))
}()
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ { for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
currRenderFrameTmp := pR.RenderFrameBuffer.GetByFrameId(collisionSysRenderFrameId) currRenderFrameTmp := pR.RenderFrameBuffer.GetByFrameId(collisionSysRenderFrameId)
if nil == currRenderFrameTmp { if nil == currRenderFrameTmp {
@@ -1338,13 +1313,13 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
thatPlayerInNextFrame := nextRenderFramePlayers[playerId] thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
if 0 < thatPlayerInNextFrame.FramesToRecover { if 0 < thatPlayerInNextFrame.FramesToRecover {
// No need to process inputs for this player, but there might be bullet pushbacks on this player // No need to process inputs for this player, but there might be bullet pushbacks on this player
// Also note that in this case we keep "CharacterState" of this player from last render frame // Also note that in this case we keep "CharacterState" of this player from last render frame
playerCollider.X += bulletPushbacks[joinIndex-1].X playerCollider.X += bulletPushbacks[joinIndex-1].X
playerCollider.Y += bulletPushbacks[joinIndex-1].Y playerCollider.Y += bulletPushbacks[joinIndex-1].Y
// Update in the collision system // Update in the collision system
playerCollider.Update() playerCollider.Update()
if 0 != bulletPushbacks[joinIndex-1].X || 0 != bulletPushbacks[joinIndex-1].Y { if 0 != bulletPushbacks[joinIndex-1].X || 0 != bulletPushbacks[joinIndex-1].Y {
Logger.Info(fmt.Sprintf("roomId=%v, playerId=%v is pushed back by (%.2f, %.2f) by bullet impacts, now its framesToRecover is %d at currRenderFrame.id=%v", pR.Id, playerId, bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y, thatPlayerInNextFrame.FramesToRecover, currRenderFrame.Id)) Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v is pushed back by (%.2f, %.2f) by bullet impacts, now its framesToRecover is %d at currRenderFrame.id=%v", pR.Id, playerId, bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y, thatPlayerInNextFrame.FramesToRecover, currRenderFrame.Id))
} }
continue continue
} }
@@ -1368,13 +1343,13 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet) toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
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.Debug(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.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)) 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
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs // Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
if 0 != decodedInput.Dx || 0 != decodedInput.Dy { if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
thatPlayerInNextFrame.DirX = decodedInput.Dx thatPlayerInNextFrame.DirX = decodedInput.Dx
thatPlayerInNextFrame.DirY = decodedInput.Dy thatPlayerInNextFrame.DirY = decodedInput.Dy
@@ -1450,7 +1425,8 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
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)
playerCollider := GenerateRectCollider(wx, wy, player.ColliderRadius*2, player.ColliderRadius*2, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*3
playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player")
playerCollider.Data = player playerCollider.Data = player
pR.Space.Add(playerCollider) pR.Space.Add(playerCollider)
// Keep track of the collider in "pR.CollisionSysMap" // Keep track of the collider in "pR.CollisionSysMap"
@@ -1470,3 +1446,87 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
func (pR *Room) printBarrier(barrierCollider *resolv.Object) { func (pR *Room) printBarrier(barrierCollider *resolv.Object) {
Logger.Info(fmt.Sprintf("Barrier in roomId=%v: w=%v, h=%v, shape=%v", pR.Id, barrierCollider.W, barrierCollider.H, barrierCollider.Shape)) Logger.Info(fmt.Sprintf("Barrier in roomId=%v: w=%v, h=%v, shape=%v", pR.Id, barrierCollider.W, barrierCollider.H, barrierCollider.Shape))
} }
func (pR *Room) downsyncToAllPlayers(upperToSendInputFrameId int32, unconfirmedMask uint64, prohibitsInputsBufferLock bool) {
for playerId, player := range pR.Players {
thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
switch thatPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME:
case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL:
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK: // This is the reason why battleState filter is put at "downsyncToAllPlayers" instead of "downsyncToSinglePlayer"
continue
}
pR.downsyncToSinglePlayer(playerId, player, pR.LastAllConfirmedInputFrameId, unconfirmedMask, prohibitsInputsBufferLock)
}
}
func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, upperToSendInputFrameId int32, unconfirmedMask uint64, prohibitsInputsBufferLock bool) {
// TODO: DON'T try to send any message to an inactive player! However, note that player.BattleState could be modified from different threads and needs to be synced!
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
lowerToSentInputFrameId := player.LastSentInputFrameId + 1
/*
[WARNING]
Upon resynced on frontend, "refRenderFrameId" is now set to as advanced as possible, and it's the frontend's responsibility now to pave way for the "gap inputFrames"
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
*/
refRenderFrameId := pR.CurDynamicsRenderFrameId - 1
shouldResync1 := (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId)
shouldResync2 := (0 < (unconfirmedMask & uint64(1<<uint32(player.JoinIndex-1)))) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
// shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
shouldResyncOverall := (shouldResync1 || shouldResync2)
if shouldResyncOverall {
// A rejoined player, should guarantee that when it resyncs to "refRenderFrameId" a matching inputFrame to apply exists
lowerToSentInputFrameId = pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
Logger.Info(fmt.Sprintf("Resyncing player: roomId=%v, playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v", pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId))
// TODO: What if "lowerToSentInputFrameId > pR.InputsBuffer.StFrameId" now?
}
if lowerToSentInputFrameId > upperToSendInputFrameId {
Logger.Debug(fmt.Sprintf("Not sending due to potentially empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
return
}
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "j" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
var theInputsBufferLockToUse *sync.Mutex = &pR.InputsBufferLock
if prohibitsInputsBufferLock {
// Already locked in caller function
theInputsBufferLockToUse = nil
}
// [WARNING] Clone to deliberately avoid holding "pR.InputsBufferLock" while using network I/O
j, toSendInputFrameDownsyncs := pR.InputsBuffer.cloneInputFrameDownsyncsByFrameIdRange(lowerToSentInputFrameId, upperToSendInputFrameId+1, theInputsBufferLockToUse)
if 0 >= len(toSendInputFrameDownsyncs) {
Logger.Debug(fmt.Sprintf("Not sending due to actually empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, j, player.LastSentInputFrameId, player.AckingInputFrameId))
return
}
/*
Resync helps
1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation"
2. reconnection
*/
if pR.BackendDynamicsEnabled && shouldResyncOverall {
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
if nil == tmp {
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, renderFrameId=%v, playerId=%v, playerLastSentInputFrameId=%v, j=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, pR.RenderFrameId, playerId, player.LastSentInputFrameId, j, pR.InputsBufferString(false), pR.RenderFrameBufferString()))
}
refRenderFrame := tmp.(*RoomDownsyncFrame)
for playerId, player := range pR.Players {
refRenderFrame.Players[playerId].ColliderRadius = player.ColliderRadius // hardcoded for now
}
refRenderFrame.BackendUnconfirmedMask = unconfirmedMask
Logger.Warn(fmt.Sprintf("Sending refRenderFrameId=%v for roomId=%v, , playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v: InputsBuffer=%v", refRenderFrameId, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, j, pR.InputsBufferString(false)))
pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncs, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
} else {
pR.sendSafely(nil, toSendInputFrameDownsyncs, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
}
player.LastSentInputFrameId = j - 1
}

View File

@@ -5,6 +5,7 @@ import (
. "dnmshared" . "dnmshared"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
"strings"
"sync" "sync"
) )
@@ -21,11 +22,13 @@ var (
func (pPq *RoomHeap) PrintInOrder() { func (pPq *RoomHeap) PrintInOrder() {
pq := *pPq pq := *pPq
fmt.Printf("The RoomHeap instance now contains:\n") s := make([]string, 0)
s = append(s, fmt.Sprintf("The RoomHeap instance now contains:"))
for i := 0; i < len(pq); i++ { for i := 0; i < len(pq); i++ {
fmt.Printf("{index: %d, roomID: %d, score: %.2f} ", i, pq[i].Id, pq[i].Score) s = append(s, fmt.Sprintf("{index: %d, roomID: %d, score: %.2f} ", i, pq[i].Id, pq[i].Score))
} }
fmt.Printf("\n")
Logger.Debug(strings.Join(s, "\n"))
} }
func (pq RoomHeap) Len() int { return len(pq) } func (pq RoomHeap) Len() int { return len(pq) }

View File

@@ -1059,10 +1059,11 @@ type RoomDownsyncFrame struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"` CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"`
MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
BackendUnconfirmedMask uint64 `protobuf:"varint,5,opt,name=backendUnconfirmedMask,proto3" json:"backendUnconfirmedMask,omitempty"` // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
} }
func (x *RoomDownsyncFrame) Reset() { func (x *RoomDownsyncFrame) Reset() {
@@ -1125,6 +1126,13 @@ func (x *RoomDownsyncFrame) GetMeleeBullets() []*MeleeBullet {
return nil return nil
} }
func (x *RoomDownsyncFrame) GetBackendUnconfirmedMask() uint64 {
if x != nil {
return x.BackendUnconfirmedMask
}
return 0
}
var File_room_downsync_frame_proto protoreflect.FileDescriptor var File_room_downsync_frame_proto protoreflect.FileDescriptor
var file_room_downsync_frame_proto_rawDesc = []byte{ var file_room_downsync_frame_proto_rawDesc = []byte{
@@ -1377,7 +1385,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65,
0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x9a, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x38, 0x01, 0x22, 0xd2, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79,
0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74,
@@ -1389,14 +1397,18 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65,
0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d,
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x1a, 0x52, 0x0a, 0x0c, 0x50, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62,
0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x61, 0x73, 0x6b, 0x1a, 0x52, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e,
0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c,
0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View File

@@ -16,7 +16,6 @@ import (
"time" "time"
. "dnmshared" . "dnmshared"
"runtime/debug"
) )
const ( const (
@@ -47,9 +46,8 @@ func Serve(c *gin.Context) {
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
return return
} }
Logger.Info("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token))
boundRoomId := 0 boundRoomId := 0
expectRoomId := 0 expectedRoomId := 0
var err error var err error
if boundRoomIdStr, hasBoundRoomId := c.GetQuery("boundRoomId"); hasBoundRoomId { if boundRoomIdStr, hasBoundRoomId := c.GetQuery("boundRoomId"); hasBoundRoomId {
boundRoomId, err = strconv.Atoi(boundRoomIdStr) boundRoomId, err = strconv.Atoi(boundRoomIdStr)
@@ -58,27 +56,28 @@ func Serve(c *gin.Context) {
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
return return
} }
Logger.Info("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId)) Logger.Debug("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId))
} } else if expectedRoomIdStr, hasExpectRoomId := c.GetQuery("expectedRoomId"); hasExpectRoomId {
if expectRoomIdStr, hasExpectRoomId := c.GetQuery("expectedRoomId"); hasExpectRoomId { expectedRoomId, err = strconv.Atoi(expectedRoomIdStr)
expectRoomId, err = strconv.Atoi(expectRoomIdStr)
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
return return
} }
Logger.Info("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("expectedRoomId", expectRoomId)) Logger.Debug("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("expectedRoomId", expectedRoomId))
} else {
Logger.Debug("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token))
} }
// TODO: Wrap the following 2 stmts by sql transaction! // TODO: Wrap the following 2 stmts by sql transaction!
playerId, err := models.GetPlayerIdByToken(token) playerId, err := models.GetPlayerIdByToken(token)
if err != nil || playerId == 0 { if err != nil || playerId == 0 {
// TODO: Abort with specific message. // TODO: Abort with specific message.
Logger.Info("PlayerLogin record not found for ws authentication:", zap.Any("intAuthToken", token)) Logger.Warn("PlayerLogin record not found for ws authentication:", zap.Any("intAuthToken", token))
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
return return
} }
Logger.Info("PlayerLogin record has been found for ws authentication:", zap.Any("playerId", playerId)) Logger.Info("PlayerLogin record has been found for ws authentication:", zap.Any("playerId", playerId), zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId), zap.Any("expectedRoomId", expectedRoomId))
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
@@ -160,14 +159,14 @@ func Serve(c *gin.Context) {
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotFound, "") signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotFound, "")
} }
Logger.Info("Player has logged in and its profile is found from persistent storage:", zap.Any("playerId", playerId), zap.Any("play", pPlayer)) Logger.Debug("Player has logged in and its profile is found from persistent storage:", zap.Any("playerId", playerId), zap.Any("player", pPlayer))
// Find a room to join. // Find a room to join.
Logger.Info("About to acquire RoomHeapMux for player:", zap.Any("playerId", playerId)) Logger.Debug("About to acquire RoomHeapMux for player:", zap.Any("playerId", playerId))
(*(models.RoomHeapMux)).Lock() (*(models.RoomHeapMux)).Lock()
defer func() { defer func() {
(*(models.RoomHeapMux)).Unlock() (*(models.RoomHeapMux)).Unlock()
Logger.Info("Released RoomHeapMux for player:", zap.Any("playerId", playerId)) Logger.Debug("Released RoomHeapMux for player:", zap.Any("playerId", playerId))
}() }()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -175,13 +174,12 @@ func Serve(c *gin.Context) {
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "") signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "")
} }
}() }()
Logger.Info("Acquired RoomHeapMux for player:", zap.Any("playerId", playerId)) Logger.Debug("Acquired RoomHeapMux for player:", zap.Any("playerId", playerId))
// Logger.Info("The RoomHeapManagerIns has:", zap.Any("addr", fmt.Sprintf("%p", models.RoomHeapManagerIns)), zap.Any("size", len(*(models.RoomHeapManagerIns)))) // Logger.Info("The RoomHeapManagerIns has:", zap.Any("addr", fmt.Sprintf("%p", models.RoomHeapManagerIns)), zap.Any("size", len(*(models.RoomHeapManagerIns))))
playerSuccessfullyAddedToRoom := false playerSuccessfullyAddedToRoom := false
if 0 < boundRoomId { if 0 < boundRoomId {
if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent { if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent {
pRoom = tmpPRoom pRoom = tmpPRoom
Logger.Info("Successfully got:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId))
res := pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) res := pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if !res { if !res {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId)) Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId))
@@ -189,19 +187,16 @@ func Serve(c *gin.Context) {
playerSuccessfullyAddedToRoom = true playerSuccessfullyAddedToRoom = true
} }
} }
} } else if 0 < expectedRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectedRoomId)]; existent {
if 0 < expectRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectRoomId)]; existent {
pRoom = tmpRoom pRoom = tmpRoom
Logger.Info("Successfully got:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectRoomId))
if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) { if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true playerSuccessfullyAddedToRoom = true
} else if pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) { } else if pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true playerSuccessfullyAddedToRoom = true
} else { } else {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectRoomId)) Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
playerSuccessfullyAddedToRoom = false playerSuccessfullyAddedToRoom = false
} }
@@ -221,7 +216,7 @@ func Serve(c *gin.Context) {
signalToCloseConnOfThisPlayer(Constants.RetCode.LocallyNoAvailableRoom, fmt.Sprintf("Cannot pop a (*Room) for playerId == %v!", playerId)) signalToCloseConnOfThisPlayer(Constants.RetCode.LocallyNoAvailableRoom, fmt.Sprintf("Cannot pop a (*Room) for playerId == %v!", playerId))
} else { } else {
pRoom = tmpRoom pRoom = tmpRoom
Logger.Info("Successfully popped:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId)) Logger.Info("Successfully popped:\n", zap.Any("roomId", pRoom.Id), zap.Any("forPlayerId", playerId))
res := pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) res := pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if !res { if !res {
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId)) signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
@@ -361,7 +356,7 @@ func Serve(c *gin.Context) {
receivingLoopAgainstPlayer := func() error { receivingLoopAgainstPlayer := func() error {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
Logger.Error("Goroutine `receivingLoopAgainstPlayer`, recovery spot#1, recovered from: ", zap.Any("panic", r), zap.Any("callstack", debug.Stack())) Logger.Error("Goroutine `receivingLoopAgainstPlayer`, recovery spot#1, recovered from: ", zap.Any("panic", r))
} }
Logger.Info("Goroutine `receivingLoopAgainstPlayer` is stopped for:", zap.Any("playerId", playerId), zap.Any("roomId", pRoom.Id)) Logger.Info("Goroutine `receivingLoopAgainstPlayer` is stopped for:", zap.Any("playerId", playerId), zap.Any("roomId", pRoom.Id))
}() }()
@@ -370,7 +365,7 @@ func Serve(c *gin.Context) {
return nil return nil
} }
// Tries to receive from client-side in a non-blocking manner. // TODO: Is there any potential edge-trigger improvement like the epoll approach mentioned above for the following statement? See discussion in https://github.com/gorilla/websocket/issues/122
_, bytes, err := conn.ReadMessage() _, bytes, err := conn.ReadMessage()
if nil != err { if nil != err {
Logger.Error("About to `signalToCloseConnOfThisPlayer`", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err)) Logger.Error("About to `signalToCloseConnOfThisPlayer`", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err))

View File

@@ -5,7 +5,7 @@ all: help
GOPROXY=https://mirrors.aliyun.com/goproxy GOPROXY=https://mirrors.aliyun.com/goproxy
build: build:
go build -o $(ROOT_DIR)/$(PROJECTNAME) GOPROXY=$(GOPROXY) go build -o $(ROOT_DIR)/$(PROJECTNAME)
run: build run: build
./$(PROJECTNAME) ./$(PROJECTNAME)

View File

@@ -1 +0,0 @@
{"SubTexture":[{"width":23,"y":44,"height":22,"name":"biu","x":53},{"width":9,"y":68,"height":14,"name":"rightArm","x":76},{"y":35,"frameX":-1,"frameY":0,"width":27,"frameWidth":29,"height":32,"name":"yinmoqe00","frameHeight":32,"x":89},{"width":34,"y":1,"height":41,"name":"body","x":53},{"width":9,"y":44,"height":13,"name":"rightShoulder","x":78},{"y":50,"frameX":0,"frameY":0,"width":19,"frameWidth":19,"height":18,"name":"rightFrontArm","frameHeight":18,"x":23},{"width":14,"y":70,"height":14,"name":"rightHand","x":23},{"y":68,"frameX":0,"frameY":0,"width":12,"frameWidth":12,"height":12,"name":"leftArm","frameHeight":12,"x":62},{"width":13,"y":73,"height":12,"name":"leftShoulder","x":1},{"width":20,"y":50,"height":21,"name":"leftFrontArm","x":1},{"width":33,"y":1,"height":32,"name":"head","x":89},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"width":16,"y":68,"height":14,"name":"leftHand","x":44},{"y":59,"frameX":-1,"frameY":-2,"width":4,"frameWidth":8,"height":4,"name":"huomiao01","frameHeight":8,"x":78}],"width":128,"height":128,"name":"SoldierWaterGhost","imagePath":"SoldierWaterGhost_tex.png"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1 @@
{"width":128,"SubTexture":[{"frameWidth":23,"y":50,"frameX":-2,"frameHeight":22,"frameY":-2,"width":19,"height":19,"name":"biu","x":1},{"width":9,"y":50,"height":14,"name":"rightArm","x":42},{"frameWidth":29,"y":34,"frameX":-6,"frameHeight":32,"frameY":0,"width":20,"height":32,"name":"yinmoqe00","x":88},{"frameWidth":34,"y":1,"frameX":0,"frameHeight":41,"frameY":0,"width":33,"height":39,"name":"body","x":53},{"width":9,"y":56,"height":13,"name":"rightShoulder","x":74},{"frameWidth":19,"y":50,"frameX":0,"frameHeight":18,"frameY":0,"width":18,"height":17,"name":"rightFrontArm","x":22},{"width":14,"y":50,"height":14,"name":"rightHand","x":110},{"width":12,"y":42,"height":12,"name":"leftArm","x":74},{"width":13,"y":66,"height":12,"name":"leftShoulder","x":110},{"frameWidth":20,"y":42,"frameX":-1,"frameHeight":21,"frameY":0,"width":19,"height":21,"name":"leftFrontArm","x":53},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"frameWidth":33,"y":1,"frameX":-1,"frameHeight":32,"frameY":0,"width":32,"height":31,"name":"head","x":88},{"width":16,"y":34,"height":14,"name":"leftHand","x":110},{"frameWidth":8,"y":1,"frameX":-2,"frameHeight":8,"frameY":-3,"width":2,"height":2,"name":"huomiao01","x":122}],"height":128,"name":"SoldierWaterGhost","imagePath":"SoldierWaterGhost_tex.png"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Bottom",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "783f1240-d608-40be-8108-3013ab53cfe6"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "393e649b-addb-4f91-b687-438433026c8d"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "115ea7bb-d47f-4d3c-a52a-f46584346c3f",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "BottomLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "748c55f0-e761-40f6-b13b-e416b3d8a55c"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "6164bac7-9882-43ce-b3d0-9d062d6d0b49"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "a1bf7c7c-b9f7-4b65-86e3-f86a9e798fb6",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "BottomRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "34cf9fbb-8def-4faf-a56e-123b4c45706c"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b6709dd6-6ba7-4222-af38-de79ac80ce8b"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "d5af527a-9f0c-4398-b2dd-84426be7bd32",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Left",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "02cd24e3-1c4a-46d7-85af-9034c9445ba7"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "1f121837-a493-4a41-90e5-74ea560930ad"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "b60618d7-569d-4f13-bdeb-f20341fbadb6",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Right",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a6ec6a1c-dde5-459d-84f9-7b2b8a163e7b"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b5d11244-f30a-4b0d-b67b-23648d253d44"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "0b3fb38e-9110-4191-9b72-6b64a224d049",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Top",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "8967d249-e9cf-4e44-85e8-6b9377129d9e"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "492a57fb-6a5c-423a-bcfe-0695a7828881"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "1bc6de53-800b-4da3-ab8e-4a45e3aa4230",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "TopLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "96187689-85df-46e8-b4db-410eae03c135"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "6b002583-7688-43d3-b3fa-102ae0046628"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "ee0d670c-893e-4e4d-96dd-5571db18ee97",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "TopRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "ba89046f-8b70-4edb-9f61-534dff476325"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "bc1ef316-d6ad-4538-b223-a0fed8094609"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "596df84a-2e4e-4f1d-967c-a82649f564a8",
"subMetas": {}
}

View File

@@ -1,43 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "attackedLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.3333333333333333,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "02cd24e3-1c4a-46d7-85af-9034c9445ba7"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "8c522ad3-ee82-41a7-892e-05c05442e2e3"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "1f121837-a493-4a41-90e5-74ea560930ad"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "1c0ec5ec-51cb-467d-b597-8e69ce580cfd"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "8acc4e9f-3c47-4b66-9a9d-d012709680f6",
"subMetas": {}
}

View File

@@ -1,43 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "attackedRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.3333333333333333,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a6ec6a1c-dde5-459d-84f9-7b2b8a163e7b"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "d5ec0aaf-d4a9-4b2e-b9c1-bdc54b355b73"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b5d11244-f30a-4b0d-b67b-23648d253d44"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "360dfc7d-4ed1-4fb9-8d2f-7533d05a4830"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "c7cda0cd-dbce-4722-abd2-aeca28263a21",
"subMetas": {}
}

View File

@@ -1,7 +0,0 @@
{
"ver": "1.0.1",
"uuid": "135f388e-7e75-4ece-b267-4e07835cba74",
"isSubpackage": false,
"subpackageName": "",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Bottom",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "c79694bc-ff6f-416b-9047-b82f41fe791a"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "609c77a2-bdfe-4967-8de6-646532302c97"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "28194c48-ae3b-4197-8263-0d474ae8b9bc",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "BottomLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "07b6d385-3f51-48c1-8165-38756b3d84fa"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "c627bf3b-0e97-4423-aeea-54c7511894d6"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "e1c45a36-2022-4b18-a2db-b5e2e0a120ed",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "BottomRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "d0327836-1910-4c6a-9291-c8bb044c54f5"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "1d3e614a-bb2a-4b1d-87ca-0cddd6e03fff"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "126dff26-0ace-439d-89b5-b888aa52d159",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Left",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "c9528117-c878-41aa-ad5d-641fefcaa89f"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "c0e5d042-8bc1-449b-9a2d-7844129c5188"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "95c2d541-8f99-446a-a7e0-094130ce6d41",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Right",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "1054cf4c-69a5-4834-8966-03bc613d4483"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b88522bd-8b6b-44a1-9c84-5518ae7f5c2c"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "380f5fa0-f77f-434a-8f39-d545ee6823c5",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "Top",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "9702ca76-66c8-4ea9-a976-45f86e15830a"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "6390f1c3-b4cc-41df-acfb-645e8f90fb36"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "a306c6de-ccd8-492b-bfec-c6be0a4cbde2",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "TopLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "a669112a-f263-443d-9757-60d0372e0fe8"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "3ed70f56-3b60-4bda-9d2a-8d4b5ecb12f9"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "f496072b-51fd-4406-abbd-9885ac23f7a9",
"subMetas": {}
}

View File

@@ -1,31 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "TopRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.25,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "3d84f335-85c4-4dd0-a5d3-38f4da1e1611"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "6d36877f-dc27-4ebc-9407-14fbcf2314df"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "6405ad8b-3084-4b67-8c2e-9b4d34fa3d09",
"subMetas": {}
}

View File

@@ -1,43 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "attackedLeft",
"_objFlags": 0,
"_native": "",
"_duration": 0.3333333333333333,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "c9528117-c878-41aa-ad5d-641fefcaa89f"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "987c7dc0-e81f-4891-979d-0998794e6889"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "c0e5d042-8bc1-449b-9a2d-7844129c5188"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "9205c378-c50c-4303-af32-dbf9422375cf"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "af16cdcb-6e82-4be6-806d-9fc52ae99fff",
"subMetas": {}
}

View File

@@ -1,43 +0,0 @@
{
"__type__": "cc.AnimationClip",
"_name": "attackedRight",
"_objFlags": 0,
"_native": "",
"_duration": 0.3333333333333333,
"sample": 12,
"speed": 1,
"wrapMode": "2",
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "1054cf4c-69a5-4834-8966-03bc613d4483"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "8f3cf81e-1251-4013-b684-13f2830c7425"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b88522bd-8b6b-44a1-9c84-5518ae7f5c2c"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "69f1bd37-6628-4fd1-b0f2-08073d1edb29"
}
}
]
}
}
},
"events": []
}

View File

@@ -1,5 +0,0 @@
{
"ver": "2.1.0",
"uuid": "02eba566-4d22-4fa7-99d7-f032f5845421",
"subMetas": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"SubTexture":[{"y":50,"frameX":-2,"frameY":-2,"width":19,"frameWidth":23,"height":19,"name":"biu","frameHeight":22,"x":1},{"width":9,"y":50,"height":14,"name":"rightArm","x":42},{"y":34,"frameX":-6,"frameY":0,"width":20,"frameWidth":29,"height":32,"name":"yinmoqe00","frameHeight":32,"x":88},{"y":1,"frameX":0,"frameY":0,"width":33,"frameWidth":34,"height":39,"name":"body","frameHeight":41,"x":53},{"width":9,"y":56,"height":13,"name":"rightShoulder","x":74},{"y":50,"frameX":0,"frameY":0,"width":18,"frameWidth":19,"height":17,"name":"rightFrontArm","frameHeight":18,"x":22},{"width":14,"y":50,"height":14,"name":"rightHand","x":110},{"width":12,"y":42,"height":12,"name":"leftArm","x":74},{"width":13,"y":66,"height":12,"name":"leftShoulder","x":110},{"y":42,"frameX":-1,"frameY":0,"width":19,"frameWidth":20,"height":21,"name":"leftFrontArm","frameHeight":21,"x":53},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"y":1,"frameX":-1,"frameY":0,"width":32,"frameWidth":33,"height":31,"name":"head","frameHeight":32,"x":88},{"width":16,"y":34,"height":14,"name":"leftHand","x":110},{"y":1,"frameX":-2,"frameY":-3,"width":2,"frameWidth":8,"height":2,"name":"huomiao01","frameHeight":8,"x":122}],"width":128,"height":128,"name":"SoldierWaterGhost","imagePath":"SoldierWaterGhost_tex.png"} {"imagePath":"SoldierWaterGhost_tex.png","width":64,"height":64,"name":"SoldierWaterGhost","SubTexture":[{"frameY":-1,"y":27,"frameWidth":12,"frameHeight":11,"width":10,"height":10,"name":"biu","frameX":-1,"x":1},{"width":5,"y":23,"height":7,"name":"rightArm","x":40},{"frameY":0,"y":19,"frameWidth":15,"frameHeight":16,"width":10,"height":16,"name":"yinmoqe00","frameX":-3,"x":47},{"frameY":0,"y":1,"frameWidth":17,"frameHeight":21,"width":17,"height":20,"name":"body","frameX":0,"x":28},{"width":5,"y":37,"height":7,"name":"rightShoulder","x":42},{"frameY":0,"y":27,"frameWidth":10,"frameHeight":9,"width":9,"height":9,"name":"rightFrontArm","frameX":0,"x":13},{"width":7,"y":38,"height":7,"name":"rightHand","x":13},{"width":6,"y":36,"height":6,"name":"leftArm","x":34},{"width":7,"y":39,"height":6,"name":"leftShoulder","x":1},{"frameY":0,"y":23,"frameWidth":10,"frameHeight":11,"width":10,"height":11,"name":"leftFrontArm","frameX":-1,"x":28},{"width":25,"y":1,"height":24,"name":"head2","x":1},{"frameY":0,"y":1,"frameWidth":17,"frameHeight":16,"width":16,"height":16,"name":"head","frameX":-1,"x":47},{"width":8,"y":36,"height":7,"name":"leftHand","x":24},{"frameY":-2,"y":32,"frameWidth":4,"frameHeight":4,"width":1,"height":1,"name":"huomiao01","frameX":-1,"x":42}]}

View File

@@ -1,7 +1,7 @@
{ {
"ver": "1.0.0", "ver": "1.0.0",
"uuid": "e9e703e9-3589-4713-b889-28b23406d220", "uuid": "e9e703e9-3589-4713-b889-28b23406d220",
"atlasJson": "{\"SubTexture\":[{\"y\":50,\"frameX\":-2,\"frameY\":-2,\"width\":19,\"frameWidth\":23,\"height\":19,\"name\":\"biu\",\"frameHeight\":22,\"x\":1},{\"width\":9,\"y\":50,\"height\":14,\"name\":\"rightArm\",\"x\":42},{\"y\":34,\"frameX\":-6,\"frameY\":0,\"width\":20,\"frameWidth\":29,\"height\":32,\"name\":\"yinmoqe00\",\"frameHeight\":32,\"x\":88},{\"y\":1,\"frameX\":0,\"frameY\":0,\"width\":33,\"frameWidth\":34,\"height\":39,\"name\":\"body\",\"frameHeight\":41,\"x\":53},{\"width\":9,\"y\":56,\"height\":13,\"name\":\"rightShoulder\",\"x\":74},{\"y\":50,\"frameX\":0,\"frameY\":0,\"width\":18,\"frameWidth\":19,\"height\":17,\"name\":\"rightFrontArm\",\"frameHeight\":18,\"x\":22},{\"width\":14,\"y\":50,\"height\":14,\"name\":\"rightHand\",\"x\":110},{\"width\":12,\"y\":42,\"height\":12,\"name\":\"leftArm\",\"x\":74},{\"width\":13,\"y\":66,\"height\":12,\"name\":\"leftShoulder\",\"x\":110},{\"y\":42,\"frameX\":-1,\"frameY\":0,\"width\":19,\"frameWidth\":20,\"height\":21,\"name\":\"leftFrontArm\",\"frameHeight\":21,\"x\":53},{\"width\":50,\"y\":1,\"height\":47,\"name\":\"head2\",\"x\":1},{\"y\":1,\"frameX\":-1,\"frameY\":0,\"width\":32,\"frameWidth\":33,\"height\":31,\"name\":\"head\",\"frameHeight\":32,\"x\":88},{\"width\":16,\"y\":34,\"height\":14,\"name\":\"leftHand\",\"x\":110},{\"y\":1,\"frameX\":-2,\"frameY\":-3,\"width\":2,\"frameWidth\":8,\"height\":2,\"name\":\"huomiao01\",\"frameHeight\":8,\"x\":122}],\"width\":128,\"height\":128,\"name\":\"SoldierWaterGhost\",\"imagePath\":\"SoldierWaterGhost_tex.png\"}", "atlasJson": "{\"imagePath\":\"SoldierWaterGhost_tex.png\",\"width\":64,\"height\":64,\"name\":\"SoldierWaterGhost\",\"SubTexture\":[{\"frameY\":-1,\"y\":27,\"frameWidth\":12,\"frameHeight\":11,\"width\":10,\"height\":10,\"name\":\"biu\",\"frameX\":-1,\"x\":1},{\"width\":5,\"y\":23,\"height\":7,\"name\":\"rightArm\",\"x\":40},{\"frameY\":0,\"y\":19,\"frameWidth\":15,\"frameHeight\":16,\"width\":10,\"height\":16,\"name\":\"yinmoqe00\",\"frameX\":-3,\"x\":47},{\"frameY\":0,\"y\":1,\"frameWidth\":17,\"frameHeight\":21,\"width\":17,\"height\":20,\"name\":\"body\",\"frameX\":0,\"x\":28},{\"width\":5,\"y\":37,\"height\":7,\"name\":\"rightShoulder\",\"x\":42},{\"frameY\":0,\"y\":27,\"frameWidth\":10,\"frameHeight\":9,\"width\":9,\"height\":9,\"name\":\"rightFrontArm\",\"frameX\":0,\"x\":13},{\"width\":7,\"y\":38,\"height\":7,\"name\":\"rightHand\",\"x\":13},{\"width\":6,\"y\":36,\"height\":6,\"name\":\"leftArm\",\"x\":34},{\"width\":7,\"y\":39,\"height\":6,\"name\":\"leftShoulder\",\"x\":1},{\"frameY\":0,\"y\":23,\"frameWidth\":10,\"frameHeight\":11,\"width\":10,\"height\":11,\"name\":\"leftFrontArm\",\"frameX\":-1,\"x\":28},{\"width\":25,\"y\":1,\"height\":24,\"name\":\"head2\",\"x\":1},{\"frameY\":0,\"y\":1,\"frameWidth\":17,\"frameHeight\":16,\"width\":16,\"height\":16,\"name\":\"head\",\"frameX\":-1,\"x\":47},{\"width\":8,\"y\":36,\"height\":7,\"name\":\"leftHand\",\"x\":24},{\"frameY\":-2,\"y\":32,\"frameWidth\":4,\"frameHeight\":4,\"width\":1,\"height\":1,\"name\":\"huomiao01\",\"frameX\":-1,\"x\":42}]}",
"texture": "def168c3-3f07-43f9-a460-36b397c70a57", "texture": "def168c3-3f07-43f9-a460-36b397c70a57",
"subMetas": {} "subMetas": {}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -16,14 +16,14 @@
"trimType": "auto", "trimType": "auto",
"trimThreshold": 1, "trimThreshold": 1,
"rotated": false, "rotated": false,
"offsetX": -0.5, "offsetX": 0,
"offsetY": 24.5, "offsetY": 9,
"trimX": 1, "trimX": 1,
"trimY": 1, "trimY": 1,
"width": 125, "width": 62,
"height": 77, "height": 44,
"rawWidth": 128, "rawWidth": 64,
"rawHeight": 128, "rawHeight": 64,
"borderTop": 0, "borderTop": 0,
"borderBottom": 0, "borderBottom": 0,
"borderLeft": 0, "borderLeft": 0,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"width":128,"SubTexture":[{"frameWidth":23,"y":44,"frameHeight":22,"width":22,"frameX":-1,"height":22,"name":"biu","frameY":0,"x":53},{"width":9,"y":68,"height":14,"name":"rightArm","x":76},{"frameWidth":29,"y":35,"frameHeight":32,"width":26,"frameX":-1,"height":32,"name":"yinmoqe00","frameY":0,"x":89},{"width":34,"y":1,"height":41,"name":"body","x":53},{"width":9,"y":44,"height":13,"name":"rightShoulder","x":77},{"width":19,"y":50,"height":18,"name":"rightFrontArm","x":23},{"width":14,"y":70,"height":14,"name":"rightHand","x":23},{"width":12,"y":68,"height":12,"name":"leftArm","x":62},{"width":13,"y":73,"height":12,"name":"leftShoulder","x":1},{"width":20,"y":50,"height":21,"name":"leftFrontArm","x":1},{"width":33,"y":1,"height":32,"name":"head","x":89},{"width":50,"y":1,"height":47,"name":"head2","x":1},{"width":16,"y":68,"height":14,"name":"leftHand","x":44},{"frameWidth":8,"y":59,"frameHeight":8,"width":4,"frameX":-1,"height":4,"name":"huomiao01","frameY":-2,"x":77}],"height":128,"name":"SoldierFireGhost","imagePath":"SoldierFireGhost_tex.png"} {"width":64,"imagePath":"SoldierFireGhost_tex.png","SubTexture":[{"frameWidth":12,"y":42,"frameHeight":11,"width":11,"frameX":-1,"height":11,"name":"biu","frameY":0,"x":16},{"width":5,"y":42,"height":7,"name":"rightArm","x":29},{"frameWidth":15,"y":27,"frameHeight":16,"width":13,"frameX":-1,"height":16,"name":"yinmoqe00","frameY":0,"x":1},{"width":17,"y":1,"height":21,"name":"body","x":28},{"width":5,"y":42,"height":7,"name":"rightShoulder","x":36},{"width":10,"y":45,"height":9,"name":"rightFrontArm","x":1},{"width":7,"y":56,"height":7,"name":"rightHand","x":1},{"width":6,"y":55,"height":6,"name":"leftArm","x":32},{"width":7,"y":55,"height":6,"name":"leftShoulder","x":23},{"width":10,"y":27,"height":11,"name":"leftFrontArm","x":16},{"width":17,"y":24,"height":16,"name":"head","x":28},{"width":25,"y":1,"height":24,"name":"head2","x":1},{"width":8,"y":55,"height":7,"name":"leftHand","x":13},{"frameWidth":4,"y":51,"frameHeight":4,"width":2,"frameX":-1,"height":2,"name":"huomiao01","frameY":-1,"x":29}],"height":64,"name":"SoldierFireGhost"}

View File

@@ -1,7 +1,7 @@
{ {
"ver": "1.0.0", "ver": "1.0.0",
"uuid": "4a9187d5-a9ad-4464-a03c-d2f3cc277051", "uuid": "4a9187d5-a9ad-4464-a03c-d2f3cc277051",
"atlasJson": "{\"width\":128,\"SubTexture\":[{\"frameWidth\":23,\"y\":44,\"frameHeight\":22,\"width\":22,\"frameX\":-1,\"height\":22,\"name\":\"biu\",\"frameY\":0,\"x\":53},{\"width\":9,\"y\":68,\"height\":14,\"name\":\"rightArm\",\"x\":76},{\"frameWidth\":29,\"y\":35,\"frameHeight\":32,\"width\":26,\"frameX\":-1,\"height\":32,\"name\":\"yinmoqe00\",\"frameY\":0,\"x\":89},{\"width\":34,\"y\":1,\"height\":41,\"name\":\"body\",\"x\":53},{\"width\":9,\"y\":44,\"height\":13,\"name\":\"rightShoulder\",\"x\":77},{\"width\":19,\"y\":50,\"height\":18,\"name\":\"rightFrontArm\",\"x\":23},{\"width\":14,\"y\":70,\"height\":14,\"name\":\"rightHand\",\"x\":23},{\"width\":12,\"y\":68,\"height\":12,\"name\":\"leftArm\",\"x\":62},{\"width\":13,\"y\":73,\"height\":12,\"name\":\"leftShoulder\",\"x\":1},{\"width\":20,\"y\":50,\"height\":21,\"name\":\"leftFrontArm\",\"x\":1},{\"width\":33,\"y\":1,\"height\":32,\"name\":\"head\",\"x\":89},{\"width\":50,\"y\":1,\"height\":47,\"name\":\"head2\",\"x\":1},{\"width\":16,\"y\":68,\"height\":14,\"name\":\"leftHand\",\"x\":44},{\"frameWidth\":8,\"y\":59,\"frameHeight\":8,\"width\":4,\"frameX\":-1,\"height\":4,\"name\":\"huomiao01\",\"frameY\":-2,\"x\":77}],\"height\":128,\"name\":\"SoldierFireGhost\",\"imagePath\":\"SoldierFireGhost_tex.png\"}", "atlasJson": "{\"width\":64,\"imagePath\":\"SoldierFireGhost_tex.png\",\"SubTexture\":[{\"frameWidth\":12,\"y\":42,\"frameHeight\":11,\"width\":11,\"frameX\":-1,\"height\":11,\"name\":\"biu\",\"frameY\":0,\"x\":16},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightArm\",\"x\":29},{\"frameWidth\":15,\"y\":27,\"frameHeight\":16,\"width\":13,\"frameX\":-1,\"height\":16,\"name\":\"yinmoqe00\",\"frameY\":0,\"x\":1},{\"width\":17,\"y\":1,\"height\":21,\"name\":\"body\",\"x\":28},{\"width\":5,\"y\":42,\"height\":7,\"name\":\"rightShoulder\",\"x\":36},{\"width\":10,\"y\":45,\"height\":9,\"name\":\"rightFrontArm\",\"x\":1},{\"width\":7,\"y\":56,\"height\":7,\"name\":\"rightHand\",\"x\":1},{\"width\":6,\"y\":55,\"height\":6,\"name\":\"leftArm\",\"x\":32},{\"width\":7,\"y\":55,\"height\":6,\"name\":\"leftShoulder\",\"x\":23},{\"width\":10,\"y\":27,\"height\":11,\"name\":\"leftFrontArm\",\"x\":16},{\"width\":17,\"y\":24,\"height\":16,\"name\":\"head\",\"x\":28},{\"width\":25,\"y\":1,\"height\":24,\"name\":\"head2\",\"x\":1},{\"width\":8,\"y\":55,\"height\":7,\"name\":\"leftHand\",\"x\":13},{\"frameWidth\":4,\"y\":51,\"frameHeight\":4,\"width\":2,\"frameX\":-1,\"height\":2,\"name\":\"huomiao01\",\"frameY\":-1,\"x\":29}],\"height\":64,\"name\":\"SoldierFireGhost\"}",
"texture": "700d963b-2192-4219-a066-8be5b3db7453", "texture": "700d963b-2192-4219-a066-8be5b3db7453",
"subMetas": {} "subMetas": {}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -16,14 +16,14 @@
"trimType": "auto", "trimType": "auto",
"trimThreshold": 1, "trimThreshold": 1,
"rotated": false, "rotated": false,
"offsetX": -2.5, "offsetX": -9,
"offsetY": 21, "offsetY": 0,
"trimX": 1, "trimX": 1,
"trimY": 1, "trimY": 1,
"width": 121, "width": 44,
"height": 84, "height": 62,
"rawWidth": 128, "rawWidth": 64,
"rawHeight": 128, "rawHeight": 64,
"borderTop": 0, "borderTop": 0,
"borderBottom": 0, "borderBottom": 0,
"borderLeft": 0, "borderLeft": 0,

View File

@@ -1,6 +1,6 @@
{ {
"ver": "1.0.1", "ver": "1.0.1",
"uuid": "0e243c83-a137-4880-9bfe-9e1b57adc453", "uuid": "3b7d864f-c9fd-4030-a6dc-9814ee12fec1",
"isSubpackage": false, "isSubpackage": false,
"subpackageName": "", "subpackageName": "",
"subMetas": {} "subMetas": {}

View File

@@ -0,0 +1,229 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atk1",
"_objFlags": 0,
"_native": "",
"_duration": 0.5833333333333334,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "7fedc57f-6998-45fc-899d-ea5edd41ab13"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "d55c2f3c-9110-4627-b8b2-0f9395f0d145"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "1228879d-d28e-48e4-a3ce-5ceda0d1831f"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "e88dd5c4-cc5d-46e0-88de-e382e2bcadd7"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "df069f5c-7fc0-4731-bb7f-9d2032498e4c"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "4815763d-64e1-4b15-b795-38900861c651"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "0dca4f06-bf25-4e7f-ac3a-1687e284f5d6"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "de8db3c9-affd-45ef-a536-e2d28ebc8094"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "baaafb8b-9c1a-45ea-ba80-eb8048b61b57"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "9ffa7db6-1aec-4a1e-a8a9-118f2a6e50dc"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "f2f296ad-0e19-4b00-b061-8c9edff98d0d"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "3e546607-d27f-40fa-9967-771c55a12590"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "fece0129-e8ae-45be-b963-e2b821add0d3"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "f5f48bec-3d2a-49de-9416-26cc75d34e32"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "b083cfc9-a439-4600-b336-b50ca08b68c4"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "d04003f5-16c1-4510-898b-9148b76eb958"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "2159f038-bffa-480c-a2bd-c305868f2fd9"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "1d3de292-7dd4-4e28-b1d2-23fc38318390"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "3031ca7c-e1f7-4d14-b29e-5a7102cab245"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "987e9d14-b6fc-46c7-93ed-8914e42bfa8b"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "327db66e-2136-4d52-9727-399d60715a86"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "82ab2dcd-22e9-490f-96c4-4786018c91d0"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "36812959-85a6-40fb-9a38-1b4d871eba92"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "2941d74c-7b9c-43dc-a5c4-45952220d9a2"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "4aed2032-25d5-42fd-983a-4473e2985a99"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "3db216c1-3362-4404-8db6-11615bfa6e64"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "2cf0cb17-cbb0-4d64-9c91-83639a363158"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "9c057e2c-1dc4-4c6c-adb7-85bf691cefb5"
}
},
{
"frame": 0.4666666666666667,
"value": {
"__uuid__": "49c07a7f-2ace-4e2b-ba4c-724aad12ec5c"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "cec3c067-c958-4988-b113-2f10d7f4f5ea"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "ad57f789-a25a-411c-b130-62a8ab16177f"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "3c2a21a5-7c85-48f3-95ff-0d7a32e5e852"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "fe15eece-07a5-4d15-927b-af980aea7693"
}
},
{
"frame": 0.55,
"value": {
"__uuid__": "30ac103a-0f2b-45db-9442-461479beeb0d"
}
},
{
"frame": 0.5666666666666667,
"value": {
"__uuid__": "76272ec6-0721-4496-941b-5cb6a52b2c35"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "c738236a-0702-45f8-aa38-99457b051997",
"subMetas": {}
}

View File

@@ -0,0 +1,133 @@
{
"__type__": "cc.AnimationClip",
"_name": "Atked1",
"_objFlags": 0,
"_native": "",
"_duration": 0.31666666666666665,
"sample": 60,
"speed": 1,
"wrapMode": 1,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "6fd7cfcb-95b2-4197-a065-6bc48365f855"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "39aaeff6-3116-4e12-8ec4-9362a8a2fea2"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "fb4e42e6-0fae-48d4-a600-217e01d43e57"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "2af606d7-4b3d-4355-95d1-a3ab673d2a2e"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "be18e65b-346c-4efb-84cb-7f317fcaf8ac"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "d269af72-8b89-420c-ba9e-6892cda06e80"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "30539c9d-6dbb-4409-8562-6c24b12010c2"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "96b655f9-9a90-4cb1-86ab-0be881ad8983"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "a86f8107-3d8d-426e-9a17-c856c1ab292a"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "d7f99f14-9257-426a-9e90-a948e7ca8d23"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "809dcbcf-9edd-4b8b-8bc8-42394ce0a9c2"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "f3e74927-2eea-4306-ba0a-c89972ca88f1"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "af7a23f6-9b5c-4fe8-aa74-741f18500866"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "4cc6ecdb-3741-4a2e-87a0-a93739a38d37"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "f809b6d3-6f5b-4cf4-b148-5cf7fdf407a5"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "b1c9e526-fe26-42a8-9257-7e5549847273"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "db62c862-7553-4f99-b758-8c4a4aec438a"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "55f68905-bff7-48ea-b497-5077f49b2aca"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "b01f45f9-c394-4706-aae1-d1d26a84f48a"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6",
"subMetas": {}
}

View File

@@ -0,0 +1,432 @@
{
"__type__": "cc.AnimationClip",
"_name": "Idle1",
"_objFlags": 0,
"_native": "",
"_duration": 1.1166666666666667,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "91bba749-7338-4bdc-b9a2-0450a183378f"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "31b10e1f-7433-4c0d-b9a7-576adc0cdb51"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "80d4afc7-d21a-4eec-ab5e-ec833e1e905b"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "d75070b5-3dbe-4d77-886e-8851f31e8ac8"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "a30e880e-1ff1-434d-bdbc-a3a2e0de671b"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "87cec524-adf7-48a6-af37-b1bae14b9317"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "d415a590-b4bf-4590-834a-b075705e063a"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "e8b7eb99-0b52-4f01-8dd3-6c1ff23a8552"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "599c03fd-c5f1-45b1-9cd8-0164f8e82fe0"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "0f71de95-9947-40da-929f-dd25bf43884e"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "99980a57-3af2-4c23-84b8-87759ee79e10"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "ab001dd3-587c-4a23-adbc-5f0d3eedccaf"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "9869ec96-1c78-482e-bff8-95d0b064d0f6"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "0863d34e-0116-4134-b17e-32dae8f1589f"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "10454569-56bb-4721-9a94-49015f5e11cc"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "b13c9ee9-a497-43b3-9b54-83250bc62cc6"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "80e898d9-10a9-4817-988c-7571744173e7"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "0e4e927d-0c67-43a1-aade-a6b5ea3fe498"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "6c327da9-60a2-4b63-89ca-05e7bb9b8c5f"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "9e71217f-0580-481e-b2ea-8886eb7cf492"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "ed851c4e-6a42-4643-8d50-1bac3d55b1ac"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "3a4738ba-bf3c-47cf-98f7-7e68aba0c96c"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "d035e717-c8ab-4cdc-b59f-c61c9e85990a"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "4bf6ea06-54bf-4c25-87f7-c7d9dca56fab"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "a4b324b1-1191-43ee-ac76-a4536d1dbac7"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "4e2ac5fa-1c68-4cbd-80e6-a94af579796d"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "64cd0353-429a-4a47-a6fc-b0f12e565e94"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "3fa0ae73-f8f8-455f-82c3-24a428379e95"
}
},
{
"frame": 0.4666666666666667,
"value": {
"__uuid__": "a4679596-56d5-44f2-a349-7579ad7fbe33"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "3293d474-dfb3-412a-aca1-ce76f70a2181"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "9794aec3-5e18-4df4-bae2-296f7c5ed935"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "4373d7b7-ea22-4ba2-a6df-c0672d4310a2"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "89b6617e-2131-4bc6-840a-048af7944bb8"
}
},
{
"frame": 0.55,
"value": {
"__uuid__": "9c267a7b-ab24-49b4-a2f3-26d885799f23"
}
},
{
"frame": 0.5666666666666667,
"value": {
"__uuid__": "df3255ea-00af-47c2-9deb-4ccd27a62706"
}
},
{
"frame": 0.5833333333333334,
"value": {
"__uuid__": "e15242c9-29ba-411a-a93f-dddad5613956"
}
},
{
"frame": 0.6,
"value": {
"__uuid__": "ef3f8029-d5cc-4024-95ca-1f2aa062a0d5"
}
},
{
"frame": 0.6166666666666667,
"value": {
"__uuid__": "7b48f205-a1e8-4233-8c72-11ae9c0ccea7"
}
},
{
"frame": 0.6333333333333333,
"value": {
"__uuid__": "ffc69bce-fd84-42d8-b8c9-fe05e6c7fa03"
}
},
{
"frame": 0.65,
"value": {
"__uuid__": "81805795-ab34-47c1-ab45-57f54d28e72f"
}
},
{
"frame": 0.6666666666666666,
"value": {
"__uuid__": "adae26a6-7991-4583-966c-4364ba35474d"
}
},
{
"frame": 0.6833333333333333,
"value": {
"__uuid__": "ab51a779-10b7-4039-ac45-6ada002e702f"
}
},
{
"frame": 0.7,
"value": {
"__uuid__": "173d4746-0af5-40ae-a472-ba6406c700dc"
}
},
{
"frame": 0.7166666666666667,
"value": {
"__uuid__": "4054d644-4b5c-473d-ad2c-1c1eb06b01d7"
}
},
{
"frame": 0.7333333333333333,
"value": {
"__uuid__": "0f55009f-71dc-4e70-8152-7dbc2a16a17c"
}
},
{
"frame": 0.75,
"value": {
"__uuid__": "d273f52f-4ec1-4ccf-8820-92879682213e"
}
},
{
"frame": 0.7666666666666667,
"value": {
"__uuid__": "37c2f336-a3c8-4b46-8031-d48c3f36c675"
}
},
{
"frame": 0.7833333333333333,
"value": {
"__uuid__": "8cee57cb-5e79-4fe9-8368-745b38a37021"
}
},
{
"frame": 0.8,
"value": {
"__uuid__": "4e67aa7d-d6a6-405b-99c4-ef5be609fdf4"
}
},
{
"frame": 0.8166666666666667,
"value": {
"__uuid__": "b0b7fc1a-f42a-4977-bfef-93841bdfa2f8"
}
},
{
"frame": 0.8333333333333334,
"value": {
"__uuid__": "352bbb78-d87c-425d-b39e-a401432f0070"
}
},
{
"frame": 0.85,
"value": {
"__uuid__": "9aaadf6a-808f-44f4-b7a8-392138851168"
}
},
{
"frame": 0.8666666666666667,
"value": {
"__uuid__": "a319abd0-a689-400c-8f2a-94ccc70e50f9"
}
},
{
"frame": 0.8833333333333333,
"value": {
"__uuid__": "2efd4c0f-10c1-41be-81b5-40b3d10cb863"
}
},
{
"frame": 0.9,
"value": {
"__uuid__": "8b94f28d-7da8-4e83-a9d6-46072b24a847"
}
},
{
"frame": 0.9166666666666666,
"value": {
"__uuid__": "1f153a21-9a57-4bf7-a7bb-fa2a2e3b9484"
}
},
{
"frame": 0.9333333333333333,
"value": {
"__uuid__": "2d3ad677-51b8-4d53-924c-dea3f66f0510"
}
},
{
"frame": 0.95,
"value": {
"__uuid__": "68aabf9d-5b21-4405-b92d-18a837895c37"
}
},
{
"frame": 0.9666666666666667,
"value": {
"__uuid__": "cb893520-96b5-4ce4-a9dd-90ca4eac1882"
}
},
{
"frame": 0.9833333333333333,
"value": {
"__uuid__": "498ee98f-7211-4495-99fe-662aee098217"
}
},
{
"frame": 1,
"value": {
"__uuid__": "da05be2e-e07f-4081-a99d-9c1b1459b862"
}
},
{
"frame": 1.0166666666666666,
"value": {
"__uuid__": "46e64686-1071-4db2-8f3d-4a0a00dc8e10"
}
},
{
"frame": 1.0333333333333334,
"value": {
"__uuid__": "5a082e4d-d615-411f-880e-b03bda363cea"
}
},
{
"frame": 1.05,
"value": {
"__uuid__": "6d582b00-b973-4397-8866-32f531d4366e"
}
},
{
"frame": 1.0666666666666667,
"value": {
"__uuid__": "1de67f2c-e9ea-438d-8f30-b7c40159bc92"
}
},
{
"frame": 1.0833333333333333,
"value": {
"__uuid__": "6e98330a-9709-44e1-8704-aa6430bd8236"
}
},
{
"frame": 1.1,
"value": {
"__uuid__": "b2557433-9168-4b78-a9ac-78b33633b1d8"
}
}
]
}
}
},
"events": [
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "",
"params": []
}
]
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "252b321f-81f4-485c-85bd-ea44d298cb76",
"subMetas": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

View File

@@ -0,0 +1,12 @@
{
"ver": "2.3.3",
"uuid": "143f86bf-12d1-4e83-bdb9-a80ccc8b57d2",
"type": "raw",
"wrapMode": "clamp",
"filterMode": "bilinear",
"premultiplyAlpha": false,
"genMipmaps": false,
"packable": true,
"platformSettings": {},
"subMetas": {}
}

View File

@@ -0,0 +1,385 @@
{
"__type__": "cc.AnimationClip",
"_name": "Walking",
"_objFlags": 0,
"_native": "",
"_duration": 1.0166666666666666,
"sample": 60,
"speed": 1,
"wrapMode": 2,
"curveData": {
"comps": {
"cc.Sprite": {
"spriteFrame": [
{
"frame": 0,
"value": {
"__uuid__": "5a9af6ac-cccc-4964-90a1-7136468d64ae"
}
},
{
"frame": 0.016666666666666666,
"value": {
"__uuid__": "e62f3718-263c-4a93-8405-55aca440180f"
}
},
{
"frame": 0.03333333333333333,
"value": {
"__uuid__": "700a3fef-00d2-437a-a323-cc79802d76f5"
}
},
{
"frame": 0.05,
"value": {
"__uuid__": "bf8ce5ef-5171-45ab-97dd-8c9da2dbcd5a"
}
},
{
"frame": 0.06666666666666667,
"value": {
"__uuid__": "0894f61a-e4a0-458d-890e-aa3d60e4bf4f"
}
},
{
"frame": 0.08333333333333333,
"value": {
"__uuid__": "1995a986-6421-427c-92fe-4e3bae551e67"
}
},
{
"frame": 0.1,
"value": {
"__uuid__": "4ca1ce37-410d-4437-8b7a-498292e74bc6"
}
},
{
"frame": 0.11666666666666667,
"value": {
"__uuid__": "1e23a398-6051-4a69-a71d-7ea4909da77e"
}
},
{
"frame": 0.13333333333333333,
"value": {
"__uuid__": "ef18849a-0d4d-4bfe-a707-073958511ca6"
}
},
{
"frame": 0.15,
"value": {
"__uuid__": "f217d368-9fc9-48cd-b635-059a7bc472a4"
}
},
{
"frame": 0.16666666666666666,
"value": {
"__uuid__": "b312cebf-3945-40c7-a367-3bc1d6689109"
}
},
{
"frame": 0.18333333333333332,
"value": {
"__uuid__": "476ddde6-8fd9-40cc-a789-0a08ebff5352"
}
},
{
"frame": 0.2,
"value": {
"__uuid__": "b27f06dc-217d-4b68-a747-4c1d3c27151f"
}
},
{
"frame": 0.21666666666666667,
"value": {
"__uuid__": "83d15d82-f23d-40c4-b4ef-da4ce6964fc9"
}
},
{
"frame": 0.23333333333333334,
"value": {
"__uuid__": "7cc81715-c432-4ee2-bf67-a77a4dadcd89"
}
},
{
"frame": 0.25,
"value": {
"__uuid__": "463ae409-a045-40d1-ac44-1f2aac439065"
}
},
{
"frame": 0.26666666666666666,
"value": {
"__uuid__": "84e68a5e-f23b-4570-ac4f-d85f1eba292c"
}
},
{
"frame": 0.2833333333333333,
"value": {
"__uuid__": "ce73c3ce-b543-4f9b-892c-27b76a9c84dc"
}
},
{
"frame": 0.3,
"value": {
"__uuid__": "25280250-fab5-464c-b26e-1a5f1733aa09"
}
},
{
"frame": 0.31666666666666665,
"value": {
"__uuid__": "abf595ad-a861-482c-8f5a-43f243516a5b"
}
},
{
"frame": 0.3333333333333333,
"value": {
"__uuid__": "238ce540-4c4a-41c0-967c-32218e3dfd3c"
}
},
{
"frame": 0.35,
"value": {
"__uuid__": "0a3f1bbf-788b-4d78-b737-2ec5364fe5a4"
}
},
{
"frame": 0.36666666666666664,
"value": {
"__uuid__": "c6397500-a2d9-4d68-b874-299bbc4a2173"
}
},
{
"frame": 0.38333333333333336,
"value": {
"__uuid__": "914aeb36-e7ab-4676-8016-8cf3a4fef6c6"
}
},
{
"frame": 0.4,
"value": {
"__uuid__": "1c7a050e-c52b-4402-911d-bc2d9ffed627"
}
},
{
"frame": 0.4166666666666667,
"value": {
"__uuid__": "74eaaf1d-5de5-411d-86d4-839022712426"
}
},
{
"frame": 0.43333333333333335,
"value": {
"__uuid__": "b42fe407-2c81-4960-a697-0125815e1bc7"
}
},
{
"frame": 0.45,
"value": {
"__uuid__": "9395f625-e627-4a0a-85cb-05f9b3a99d48"
}
},
{
"frame": 0.4666666666666667,
"value": {
"__uuid__": "5805ad62-293f-4d81-ad59-ac290ba2e2d3"
}
},
{
"frame": 0.48333333333333334,
"value": {
"__uuid__": "9c79bea6-dc45-4113-ace8-c7145cc889db"
}
},
{
"frame": 0.5,
"value": {
"__uuid__": "cabc4d77-a49d-4c43-9144-01c5283fe206"
}
},
{
"frame": 0.5166666666666667,
"value": {
"__uuid__": "0514d19b-a3ad-478d-99da-fa91190148b7"
}
},
{
"frame": 0.5333333333333333,
"value": {
"__uuid__": "57646810-7598-4723-b12f-5e15e8299aaa"
}
},
{
"frame": 0.55,
"value": {
"__uuid__": "65e5cbd3-15f3-4526-a347-db55ac24bf51"
}
},
{
"frame": 0.5666666666666667,
"value": {
"__uuid__": "1555d6f4-1bdf-4b64-99b2-bb13195257f4"
}
},
{
"frame": 0.5833333333333334,
"value": {
"__uuid__": "843e335d-fdb6-4a23-9949-3c81d977fb21"
}
},
{
"frame": 0.6,
"value": {
"__uuid__": "781f66e7-dd7f-4ff5-a38e-9423762b4b52"
}
},
{
"frame": 0.6166666666666667,
"value": {
"__uuid__": "ef990cae-899c-451f-91fd-7a42ac949534"
}
},
{
"frame": 0.6333333333333333,
"value": {
"__uuid__": "a2df488a-e91b-46b0-8c25-2ba8551ab539"
}
},
{
"frame": 0.65,
"value": {
"__uuid__": "6650a6e0-6ddd-42ec-af68-1a1a032e19b5"
}
},
{
"frame": 0.6666666666666666,
"value": {
"__uuid__": "9a54c669-7f7d-41e4-8939-1853967d0dbf"
}
},
{
"frame": 0.6833333333333333,
"value": {
"__uuid__": "da04432e-3845-465b-9ddf-ce2bd7c7174e"
}
},
{
"frame": 0.7,
"value": {
"__uuid__": "aa7a792d-22dc-4d28-8d27-52ddfcd8d147"
}
},
{
"frame": 0.7166666666666667,
"value": {
"__uuid__": "ceb00ecc-d391-4b19-b9d6-e53792590c46"
}
},
{
"frame": 0.7333333333333333,
"value": {
"__uuid__": "33b3fa05-9894-4393-b9ce-f819c5818bf3"
}
},
{
"frame": 0.75,
"value": {
"__uuid__": "972cc5ec-ca1c-4127-8e21-f339df92a4fd"
}
},
{
"frame": 0.7666666666666667,
"value": {
"__uuid__": "3874c9de-2b47-4327-b4f8-c61d3896cefe"
}
},
{
"frame": 0.7833333333333333,
"value": {
"__uuid__": "4a6f08e6-7f96-4451-8f71-7bee8cd63cf2"
}
},
{
"frame": 0.8,
"value": {
"__uuid__": "256f3d2b-4cb9-4523-bf7a-c48380e249bb"
}
},
{
"frame": 0.8166666666666667,
"value": {
"__uuid__": "4c40c070-9cba-44f9-acd6-2408f569f21c"
}
},
{
"frame": 0.8333333333333334,
"value": {
"__uuid__": "454bfff4-b8a1-4f90-b708-bbf295b99498"
}
},
{
"frame": 0.85,
"value": {
"__uuid__": "f0288374-f487-4870-b921-8baf851bb865"
}
},
{
"frame": 0.8666666666666667,
"value": {
"__uuid__": "2fd06c6a-c3de-4b7f-bef7-1cba61c9909c"
}
},
{
"frame": 0.8833333333333333,
"value": {
"__uuid__": "d4d08680-1b77-4b44-92bd-be27c03747fa"
}
},
{
"frame": 0.9,
"value": {
"__uuid__": "5a85b0ce-7a4e-4d84-b4fb-089cdff45265"
}
},
{
"frame": 0.9166666666666666,
"value": {
"__uuid__": "ac515982-6ac2-462c-b186-c4468b489e39"
}
},
{
"frame": 0.9333333333333333,
"value": {
"__uuid__": "92571718-c75c-4c09-9aac-b1b043bb70a7"
}
},
{
"frame": 0.95,
"value": {
"__uuid__": "a248383c-2cf6-487e-8941-a05f6c08bc60"
}
},
{
"frame": 0.9666666666666667,
"value": {
"__uuid__": "c4f035dc-a01e-4b8e-a1b7-f4faf9515e99"
}
},
{
"frame": 0.9833333333333333,
"value": {
"__uuid__": "a6f9c9b4-1129-4b6a-b4c3-2a3d425c746a"
}
},
{
"frame": 1,
"value": {
"__uuid__": "d99d68fa-73c7-4057-98ec-9aa45864c050"
}
}
]
}
}
},
"events": []
}

View File

@@ -0,0 +1,5 @@
{
"ver": "2.1.0",
"uuid": "f51bb583-0010-48f3-a6a1-451a78ac2d65",
"subMetas": {}
}

View File

@@ -134,4 +134,5 @@ message RoomDownsyncFrame {
map<int32, PlayerDownsync> players = 2; map<int32, PlayerDownsync> players = 2;
int64 countdownNanos = 3; int64 countdownNanos = 3;
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
} }

View File

@@ -33,14 +33,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 22 "__id__": 20
}, },
{ {
"__id__": 23 "__id__": 21
} }
], ],
"_prefab": { "_prefab": {
"__id__": 24 "__id__": 22
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -490,15 +490,12 @@
}, },
{ {
"__id__": 15 "__id__": 15
},
{
"__id__": 18
} }
], ],
"_active": true, "_active": true,
"_components": [], "_components": [],
"_prefab": { "_prefab": {
"__id__": 21 "__id__": 19
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -549,7 +546,7 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "SoldierElf", "_name": "SoldierWaterGhost",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 11 "__id__": 11
@@ -624,13 +621,13 @@
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
} }
], ],
"_armatureName": "SoldierElf", "_armatureName": "SoldierWaterGhost",
"_animationName": "Idle1", "_animationName": "Idle1",
"_preCacheMode": 0, "_preCacheMode": 0,
"_cacheMode": 0, "_cacheMode": 0,
"playTimes": -1, "playTimes": -1,
"premultipliedAlpha": false, "premultipliedAlpha": false,
"_armatureKey": "affcd973-4743-48e5-9bcd-339180a6101b#24d7bb8f-577c-4e5d-b730-56613ca8685d", "_armatureKey": "2ae76843-1f61-48cf-bbfb-384c0dcf77e1#e9e703e9-3589-4713-b889-28b23406d220",
"_accTime": 0, "_accTime": 0,
"_playCount": 0, "_playCount": 0,
"_frameCache": null, "_frameCache": null,
@@ -638,17 +635,17 @@
"_playing": false, "_playing": false,
"_armatureCache": null, "_armatureCache": null,
"_N$dragonAsset": { "_N$dragonAsset": {
"__uuid__": "affcd973-4743-48e5-9bcd-339180a6101b" "__uuid__": "2ae76843-1f61-48cf-bbfb-384c0dcf77e1"
}, },
"_N$dragonAtlasAsset": { "_N$dragonAtlasAsset": {
"__uuid__": "24d7bb8f-577c-4e5d-b730-56613ca8685d" "__uuid__": "e9e703e9-3589-4713-b889-28b23406d220"
}, },
"_N$_defaultArmatureIndex": 0, "_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 1, "_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0, "_N$_defaultCacheMode": 0,
"_N$timeScale": 1, "_N$timeScale": 1,
"_N$debugBones": false, "_N$debugBones": false,
"_N$enableBatch": true, "_N$enableBatch": false,
"_id": "" "_id": ""
}, },
{ {
@@ -659,7 +656,7 @@
"asset": { "asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a" "__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
}, },
"fileId": "3fs20Yd8dIO68/1Wx2oVLh", "fileId": "42Rmp/YOdMOYWzJwr3ET1h",
"sync": false "sync": false
}, },
{ {
@@ -674,10 +671,13 @@
"_components": [ "_components": [
{ {
"__id__": 16 "__id__": 16
},
{
"__id__": 17
} }
], ],
"_prefab": { "_prefab": {
"__id__": 17 "__id__": 18
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -689,8 +689,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 0, "width": 1425,
"height": 0 "height": 1024
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -727,7 +727,33 @@
"_id": "" "_id": ""
}, },
{ {
"__type__": "dragonBones.ArmatureDisplay", "__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 15
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "252b321f-81f4-485c-85bd-ea44d298cb76"
},
{
"__uuid__": "f51bb583-0010-48f3-a6a1-451a78ac2d65"
},
{
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997"
},
{
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
@@ -739,146 +765,23 @@
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
} }
], ],
"_armatureName": "SoldierFireGhost", "_srcBlendFactor": 770,
"_animationName": "Idle1", "_dstBlendFactor": 771,
"_preCacheMode": 0, "_spriteFrame": null,
"_cacheMode": 0, "_type": 0,
"playTimes": -1, "_sizeMode": 1,
"premultipliedAlpha": false, "_fillType": 0,
"_armatureKey": "36230012-8df3-4e85-afad-76ec47d0e4d7#4a9187d5-a9ad-4464-a03c-d2f3cc277051", "_fillCenter": {
"_accTime": 0,
"_playCount": 0,
"_frameCache": null,
"_curFrame": null,
"_playing": false,
"_armatureCache": null,
"_N$dragonAsset": {
"__uuid__": "36230012-8df3-4e85-afad-76ec47d0e4d7"
},
"_N$dragonAtlasAsset": {
"__uuid__": "4a9187d5-a9ad-4464-a03c-d2f3cc277051"
},
"_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": true,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
},
"fileId": "a8ZUEyoP1Ec5azSkL7Z/9h",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "SoldierWaterGhost",
"_objFlags": 0,
"_parent": {
"__id__": 11
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 19
}
],
"_prefab": {
"__id__": 20
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": 0, "y": 0
"z": 0
}, },
"_skewX": 0, "_fillStart": 0,
"_skewY": 0, "_fillRange": 0,
"_is3DNode": false, "_isTrimmedMode": true,
"_groupIndex": 0, "_atlas": {
"groupIndex": 0, "__uuid__": "145769c8-a259-42bc-8cce-6e035f493c70"
"_id": ""
},
{
"__type__": "dragonBones.ArmatureDisplay",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 18
}, },
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_armatureName": "SoldierWaterGhost",
"_animationName": "Idle1",
"_preCacheMode": 0,
"_cacheMode": 0,
"playTimes": -1,
"premultipliedAlpha": false,
"_armatureKey": "a9d7bbc2-134b-4eb4-ba16-6541f3e51e06#e9e703e9-3589-4713-b889-28b23406d220",
"_accTime": 0,
"_playCount": 0,
"_frameCache": null,
"_curFrame": null,
"_playing": false,
"_armatureCache": null,
"_N$dragonAsset": {
"__uuid__": "a9d7bbc2-134b-4eb4-ba16-6541f3e51e06"
},
"_N$dragonAtlasAsset": {
"__uuid__": "e9e703e9-3589-4713-b889-28b23406d220"
},
"_N$_defaultArmatureIndex": 0,
"_N$_animationIndex": 8,
"_N$_defaultCacheMode": 0,
"_N$timeScale": 1,
"_N$debugBones": false,
"_N$enableBatch": true,
"_id": "" "_id": ""
}, },
{ {
@@ -889,7 +792,7 @@
"asset": { "asset": {
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a" "__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
}, },
"fileId": "42Rmp/YOdMOYWzJwr3ET1h", "fileId": "4c+Ci1MGpP47N3wJ0ujhEm",
"sync": false "sync": false
}, },
{ {

View File

@@ -8,7 +8,8 @@
"__id__": 1 "__id__": 1
}, },
"optimizationPolicy": 0, "optimizationPolicy": 0,
"asyncLoadAssets": false "asyncLoadAssets": false,
"readonly": false
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
@@ -20,24 +21,20 @@
"__id__": 2 "__id__": 2
}, },
{ {
"__id__": 9 "__id__": 10
},
{
"__id__": 17
} }
], ],
"_active": true, "_active": true,
"_level": 1,
"_components": [ "_components": [
{ {
"__id__": 20 "__id__": 13
}, },
{ {
"__id__": 21 "__id__": 14
} }
], ],
"_prefab": { "_prefab": {
"__id__": 22 "__id__": 15
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -57,17 +54,6 @@
"x": 0.5, "x": 0.5,
"y": 0.5 "y": 0.5
}, },
"_quat": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_skewX": 0,
"_skewY": 0,
"groupIndex": 0,
"_id": "",
"_trs": { "_trs": {
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
@@ -83,231 +69,20 @@
1, 1,
1 1
] ]
}
},
{
"__type__": "cc.Node",
"_name": "ruleNode",
"_objFlags": 0,
"_parent": {
"__id__": 1
}, },
"_children": [ "_eulerAngles": {
{ "__type__": "cc.Vec3",
"__id__": 3
}
],
"_active": true,
"_level": 2,
"_components": [
{
"__id__": 7
}
],
"_prefab": {
"__id__": 8
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 644,
"height": 793
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_quat": {
"__type__": "cc.Quat",
"x": 0, "x": 0,
"y": 0, "y": 0,
"z": 0, "z": 0
"w": 1
}, },
"_skewX": 0, "_skewX": 0,
"_skewY": 0, "_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0, "groupIndex": 0,
"_id": "",
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
257,
0,
0,
0,
0,
1,
1,
1,
1
]
}
},
{
"__type__": "cc.Node",
"_name": "rule",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_level": 3,
"_components": [
{
"__id__": 4
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 6
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 550,
"height": 560
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_quat": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_skewX": 0,
"_skewY": 0,
"groupIndex": 0,
"_id": "",
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
48,
12,
0,
0,
0,
0,
1,
1,
1,
1
]
}
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_useOriginalSize": false,
"_string": "gameRule.tip",
"_N$string": "gameRule.tip",
"_fontSize": 38,
"_lineHeight": 70,
"_enableWrapText": true,
"_N$file": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_batchAsBitmap": false,
"_N$horizontalAlign": 1,
"_N$verticalAlign": 1,
"_N$fontFamily": "Arial",
"_N$overflow": 1,
"_N$cacheMode": 0,
"_id": "" "_id": ""
}, },
{
"__type__": "744dcs4DCdNprNhG0xwq6FK",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_dataID": "gameRule.tip",
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "32b8e752-8362-4783-a4a6-1160af8b7109"
},
"fileId": "11EBmT5DNNXbQDiC9n1CEy",
"sync": false
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 2
},
"_enabled": true,
"_spriteFrame": {
"__uuid__": "0fe43223-61fc-4cb8-95bd-bd9e8f01ce8f"
},
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_state": 0,
"_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
},
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__uuid__": "32b8e752-8362-4783-a4a6-1160af8b7109"
},
"fileId": "9exF2/yWJPwK/biWKavf16",
"sync": false
},
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "modeButton", "_name": "modeButton",
@@ -317,21 +92,20 @@
}, },
"_children": [ "_children": [
{ {
"__id__": 10 "__id__": 3
} }
], ],
"_active": true, "_active": true,
"_level": 2,
"_components": [ "_components": [
{ {
"__id__": 14 "__id__": 7
}, },
{ {
"__id__": 15 "__id__": 8
} }
], ],
"_prefab": { "_prefab": {
"__id__": 16 "__id__": 9
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -351,17 +125,6 @@
"x": 0.5, "x": 0.5,
"y": 0.5 "y": 0.5
}, },
"_quat": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_skewX": 0,
"_skewY": 0,
"groupIndex": 0,
"_id": "",
"_trs": { "_trs": {
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
@@ -377,28 +140,39 @@
1, 1,
1 1
] ]
} },
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Label", "_name": "Label",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 9 "__id__": 2
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_level": 0,
"_components": [ "_components": [
{ {
"__id__": 11 "__id__": 4
}, },
{ {
"__id__": 12 "__id__": 5
} }
], ],
"_prefab": { "_prefab": {
"__id__": 13 "__id__": 6
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -418,17 +192,6 @@
"x": 0.5, "x": 0.5,
"y": 0.5 "y": 0.5
}, },
"_quat": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_skewX": 0,
"_skewY": 0,
"groupIndex": 0,
"_id": "",
"_trs": { "_trs": {
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
@@ -444,16 +207,33 @@
1, 1,
1 1
] ]
} },
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
}, },
{ {
"__type__": "cc.Label", "__type__": "cc.Label",
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 10 "__id__": 3
}, },
"_enabled": true, "_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_useOriginalSize": false, "_useOriginalSize": false,
"_string": "gameRule.mode", "_string": "gameRule.mode",
"_N$string": "gameRule.mode", "_N$string": "gameRule.mode",
@@ -476,7 +256,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 10 "__id__": 3
}, },
"_enabled": true, "_enabled": true,
"_dataID": "gameRule.mode", "_dataID": "gameRule.mode",
@@ -498,9 +278,16 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 9 "__id__": 2
}, },
"_enabled": true, "_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": { "_spriteFrame": {
"__uuid__": "081ad337-20ca-4313-ae3e-bb6dee3547b7" "__uuid__": "081ad337-20ca-4313-ae3e-bb6dee3547b7"
}, },
@@ -515,12 +302,9 @@
"_fillStart": 0, "_fillStart": 0,
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": true,
"_state": 0,
"_atlas": { "_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
}, },
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": "" "_id": ""
}, },
{ {
@@ -528,9 +312,11 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 9 "__id__": 2
}, },
"_enabled": true, "_enabled": true,
"_normalMaterial": null,
"_grayMaterial": null,
"duration": 0.1, "duration": 0.1,
"zoomScale": 1.2, "zoomScale": 1.2,
"clickEvents": [], "clickEvents": [],
@@ -589,7 +375,7 @@
"hoverSprite": null, "hoverSprite": null,
"_N$disabledSprite": null, "_N$disabledSprite": null,
"_N$target": { "_N$target": {
"__id__": 9 "__id__": 2
}, },
"_id": "" "_id": ""
}, },
@@ -613,14 +399,13 @@
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_level": 2,
"_components": [ "_components": [
{ {
"__id__": 18 "__id__": 11
} }
], ],
"_prefab": { "_prefab": {
"__id__": 19 "__id__": 12
}, },
"_opacity": 255, "_opacity": 255,
"_color": { "_color": {
@@ -640,17 +425,6 @@
"x": 0.5, "x": 0.5,
"y": 0.5 "y": 0.5
}, },
"_quat": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_skewX": 0,
"_skewY": 0,
"groupIndex": 0,
"_id": "",
"_trs": { "_trs": {
"__type__": "TypedArray", "__type__": "TypedArray",
"ctor": "Float64Array", "ctor": "Float64Array",
@@ -666,16 +440,35 @@
1, 1,
1 1
] ]
} },
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
}, },
{ {
"__type__": "cc.Sprite", "__type__": "cc.Sprite",
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 17 "__id__": 10
}, },
"_enabled": true, "_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": { "_spriteFrame": {
"__uuid__": "153d890a-fc37-4d59-8779-93a8fb19fa85" "__uuid__": "153d890a-fc37-4d59-8779-93a8fb19fa85"
}, },
@@ -690,12 +483,9 @@
"_fillStart": 0, "_fillStart": 0,
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": true,
"_state": 0,
"_atlas": { "_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
}, },
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": "" "_id": ""
}, },
{ {
@@ -718,7 +508,7 @@
}, },
"_enabled": true, "_enabled": true,
"modeButton": { "modeButton": {
"__id__": 15 "__id__": 8
}, },
"mapNode": null, "mapNode": null,
"_id": "" "_id": ""
@@ -731,6 +521,13 @@
"__id__": 1 "__id__": 1
}, },
"_enabled": true, "_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": { "_spriteFrame": {
"__uuid__": "7838f276-ab48-445a-b858-937dd27d9520" "__uuid__": "7838f276-ab48-445a-b858-937dd27d9520"
}, },
@@ -745,10 +542,7 @@
"_fillStart": 0, "_fillStart": 0,
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": true,
"_state": 0,
"_atlas": null, "_atlas": null,
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": "" "_id": ""
}, },
{ {

View File

@@ -0,0 +1,151 @@
[
{
"__type__": "cc.Prefab",
"_name": "",
"_objFlags": 0,
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"asyncLoadAssets": false,
"readonly": false
},
{
"__type__": "cc.Node",
"_name": "SoldierFireGhost",
"_objFlags": 0,
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 2
},
{
"__id__": 3
}
],
"_prefab": {
"__id__": 4
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1425,
"height": 1024
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"_defaultClip": null,
"_clips": [
{
"__uuid__": "252b321f-81f4-485c-85bd-ea44d298cb76"
},
null,
null,
null,
{
"__uuid__": "c738236a-0702-45f8-aa38-99457b051997"
},
{
"__uuid__": "f51bb583-0010-48f3-a6a1-451a78ac2d65"
},
{
"__uuid__": "c69bcceb-d7d1-4e33-9623-e2a374a0a6b6"
}
],
"playOnLoad": false,
"_id": ""
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": null,
"_type": 0,
"_sizeMode": 1,
"_fillType": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_atlas": {
"__uuid__": "145769c8-a259-42bc-8cce-6e035f493c70"
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "44k8wsxglPsJSrMCX58Yve",
"sync": false
}
]

View File

@@ -0,0 +1,8 @@
{
"ver": "1.2.5",
"uuid": "e3fc2487-17d1-4ff9-ae1f-38e974509077",
"optimizationPolicy": "AUTO",
"asyncLoadAssets": false,
"readonly": false,
"subMetas": {}
}

View File

@@ -72,14 +72,17 @@
"__id__": 3 "__id__": 3
}, },
{ {
"__id__": 5 "__id__": 8
},
{
"__id__": 10
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{
"__id__": 29
},
{
"__id__": 30
},
{ {
"__id__": 31 "__id__": 31
}, },
@@ -88,12 +91,6 @@
}, },
{ {
"__id__": 33 "__id__": 33
},
{
"__id__": 34
},
{
"__id__": 35
} }
], ],
"_prefab": null, "_prefab": null,
@@ -144,79 +141,6 @@
"groupIndex": 0, "groupIndex": 0,
"_id": "daDUxCjRFEHak7fx7LvgSJ" "_id": "daDUxCjRFEHak7fx7LvgSJ"
}, },
{
"__type__": "cc.Node",
"_name": "BackgroundMap",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
}
],
"_prefab": null,
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
0,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": "e5aYJXDGZJFIN3+Qa0Msap"
},
{
"__type__": "cc.TiledMap",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 3
},
"_enabled": true,
"_tmxFile": null,
"_id": "19rX2IPNVFHLVxuIoHEHCh"
},
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Map", "_name": "Map",
@@ -228,13 +152,13 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 6 "__id__": 4
}, },
{ {
"__id__": 7 "__id__": 5
}, },
{ {
"__id__": 30 "__id__": 28
} }
], ],
"_prefab": null, "_prefab": null,
@@ -248,8 +172,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 3200, "width": 2048,
"height": 3200 "height": 2048
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -267,8 +191,8 @@
0, 0,
0, 0,
1, 1,
1, 1.5,
1, 1.5,
1 1
] ]
}, },
@@ -290,7 +214,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 5 "__id__": 3
}, },
"_enabled": true, "_enabled": true,
"_tmxFile": null, "_tmxFile": null,
@@ -301,7 +225,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 5 "__id__": 3
}, },
"_enabled": true, "_enabled": true,
"canvasNode": { "canvasNode": {
@@ -311,7 +235,7 @@
"__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a" "__uuid__": "59bff7a2-23e1-4d69-bce7-afb37eae196a"
}, },
"joystickInputControllerNode": { "joystickInputControllerNode": {
"__id__": 8 "__id__": 6
}, },
"confirmLogoutPrefab": { "confirmLogoutPrefab": {
"__uuid__": "8e8c1a65-623d-42ba-97a7-820ce518ea11" "__uuid__": "8e8c1a65-623d-42ba-97a7-820ce518ea11"
@@ -320,10 +244,10 @@
"__uuid__": "670b477e-61a1-4778-879b-35913f7c79d2" "__uuid__": "670b477e-61a1-4778-879b-35913f7c79d2"
}, },
"boundRoomIdLabel": { "boundRoomIdLabel": {
"__id__": 17 "__id__": 15
}, },
"countdownLabel": { "countdownLabel": {
"__id__": 23 "__id__": 21
}, },
"resultPanelPrefab": { "resultPanelPrefab": {
"__uuid__": "c4cfe3bd-c59e-4d5b-95cb-c933b120e184" "__uuid__": "c4cfe3bd-c59e-4d5b-95cb-c933b120e184"
@@ -351,17 +275,17 @@
"_name": "JoystickContainer", "_name": "JoystickContainer",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 9 "__id__": 7
}, },
"_children": [ "_children": [
{ {
"__id__": 24 "__id__": 22
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 29 "__id__": 27
} }
], ],
"_prefab": null, "_prefab": null,
@@ -417,17 +341,17 @@
"_name": "WidgetsAboveAll", "_name": "WidgetsAboveAll",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 10 "__id__": 8
}, },
"_children": [ "_children": [
{ {
"__id__": 12 "__id__": 10
}, },
{ {
"__id__": 8 "__id__": 6
}, },
{ {
"__id__": 22 "__id__": 20
} }
], ],
"_active": true, "_active": true,
@@ -489,13 +413,13 @@
}, },
"_children": [ "_children": [
{ {
"__id__": 9 "__id__": 7
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 11 "__id__": 9
} }
], ],
"_prefab": null, "_prefab": null,
@@ -523,7 +447,7 @@
"array": [ "array": [
0, 0,
0, 0,
210.23252687912068, 216.50635094610968,
0, 0,
0, 0,
0, 0,
@@ -551,7 +475,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 10 "__id__": 8
}, },
"_enabled": true, "_enabled": true,
"_cullingMask": 4294967295, "_cullingMask": 4294967295,
@@ -587,20 +511,20 @@
"_name": "DebugInfo", "_name": "DebugInfo",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 9 "__id__": 7
}, },
"_children": [ "_children": [
{ {
"__id__": 13 "__id__": 11
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 20 "__id__": 18
}, },
{ {
"__id__": 21 "__id__": 19
} }
], ],
"_prefab": null, "_prefab": null,
@@ -656,23 +580,23 @@
"_name": "RoomIdIndicator", "_name": "RoomIdIndicator",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 12 "__id__": 10
}, },
"_children": [ "_children": [
{ {
"__id__": 14 "__id__": 12
}, },
{ {
"__id__": 16 "__id__": 14
} }
], ],
"_active": false, "_active": false,
"_components": [ "_components": [
{ {
"__id__": 18 "__id__": 16
}, },
{ {
"__id__": 19 "__id__": 17
} }
], ],
"_prefab": null, "_prefab": null,
@@ -728,13 +652,13 @@
"_name": "label", "_name": "label",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 13 "__id__": 11
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 15 "__id__": 13
} }
], ],
"_prefab": null, "_prefab": null,
@@ -790,7 +714,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 14 "__id__": 12
}, },
"_enabled": true, "_enabled": true,
"_materials": [], "_materials": [],
@@ -818,13 +742,13 @@
"_name": "BoundRoomIdLabel", "_name": "BoundRoomIdLabel",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 13 "__id__": 11
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 17 "__id__": 15
} }
], ],
"_prefab": null, "_prefab": null,
@@ -880,7 +804,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 16 "__id__": 14
}, },
"_enabled": true, "_enabled": true,
"_materials": [], "_materials": [],
@@ -908,7 +832,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 13 "__id__": 11
}, },
"_enabled": true, "_enabled": true,
"_layoutSize": { "_layoutSize": {
@@ -941,7 +865,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 13 "__id__": 11
}, },
"_enabled": true, "_enabled": true,
"_materials": [], "_materials": [],
@@ -971,7 +895,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 12 "__id__": 10
}, },
"_enabled": true, "_enabled": true,
"_materials": [], "_materials": [],
@@ -997,7 +921,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 12 "__id__": 10
}, },
"_enabled": true, "_enabled": true,
"_layoutSize": { "_layoutSize": {
@@ -1030,13 +954,13 @@
"_name": "CountdownSeconds", "_name": "CountdownSeconds",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 9 "__id__": 7
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 23 "__id__": 21
} }
], ],
"_prefab": null, "_prefab": null,
@@ -1092,7 +1016,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 22 "__id__": 20
}, },
"_enabled": true, "_enabled": true,
"_materials": [ "_materials": [
@@ -1122,20 +1046,20 @@
"_name": "JoystickBG", "_name": "JoystickBG",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 8 "__id__": 6
}, },
"_children": [ "_children": [
{ {
"__id__": 25 "__id__": 23
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 27 "__id__": 25
}, },
{ {
"__id__": 28 "__id__": 26
} }
], ],
"_prefab": null, "_prefab": null,
@@ -1191,13 +1115,13 @@
"_name": "Joystick", "_name": "Joystick",
"_objFlags": 0, "_objFlags": 0,
"_parent": { "_parent": {
"__id__": 24 "__id__": 22
}, },
"_children": [], "_children": [],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 26 "__id__": 24
} }
], ],
"_prefab": null, "_prefab": null,
@@ -1253,7 +1177,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 25 "__id__": 23
}, },
"_enabled": true, "_enabled": true,
"_materials": [ "_materials": [
@@ -1287,7 +1211,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 24 "__id__": 22
}, },
"_enabled": true, "_enabled": true,
"_materials": [ "_materials": [
@@ -1321,7 +1245,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 24 "__id__": 22
}, },
"_enabled": true, "_enabled": true,
"alignMode": 0, "alignMode": 0,
@@ -1348,7 +1272,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 8 "__id__": 6
}, },
"_enabled": true, "_enabled": true,
"alignMode": 0, "alignMode": 0,
@@ -1375,7 +1299,7 @@
"_name": "", "_name": "",
"_objFlags": 0, "_objFlags": 0,
"node": { "node": {
"__id__": 5 "__id__": 3
}, },
"_enabled": true, "_enabled": true,
"BGMEffect": { "BGMEffect": {
@@ -1394,7 +1318,7 @@
"__uuid__": "261d1d7d-a5cc-4cb7-a737-194427055fd4" "__uuid__": "261d1d7d-a5cc-4cb7-a737-194427055fd4"
}, },
"mapNode": { "mapNode": {
"__id__": 5 "__id__": 3
}, },
"_id": "3crA1nz5xPSLAnCSLQIPOq" "_id": "3crA1nz5xPSLAnCSLQIPOq"
}, },
@@ -1424,7 +1348,7 @@
}, },
"_enabled": true, "_enabled": true,
"map": { "map": {
"__id__": 5 "__id__": 3
}, },
"_id": "84o2sgpN1NRqlN9x7mSzBj" "_id": "84o2sgpN1NRqlN9x7mSzBj"
}, },
@@ -1437,7 +1361,7 @@
}, },
"_enabled": true, "_enabled": true,
"mapNode": { "mapNode": {
"__id__": 5 "__id__": 3
}, },
"speed": 5000, "speed": 5000,
"_id": "76ImpM7XtPSbiLHDXdsJa+" "_id": "76ImpM7XtPSbiLHDXdsJa+"
@@ -1478,16 +1402,16 @@
}, },
"_enabled": true, "_enabled": true,
"translationListenerNode": { "translationListenerNode": {
"__id__": 8 "__id__": 6
}, },
"zoomingListenerNode": { "zoomingListenerNode": {
"__id__": 5 "__id__": 3
}, },
"stickhead": { "stickhead": {
"__id__": 25 "__id__": 23
}, },
"base": { "base": {
"__id__": 24 "__id__": 22
}, },
"joyStickEps": 0.1, "joyStickEps": 0.1,
"magicLeanLowerBound": 0.414, "magicLeanLowerBound": 0.414,
@@ -1503,7 +1427,7 @@
"__id__": 2 "__id__": 2
}, },
"mapNode": { "mapNode": {
"__id__": 5 "__id__": 3
}, },
"linearMovingEps": 0.1, "linearMovingEps": 0.1,
"scaleByEps": 0.0375, "scaleByEps": 0.0375,

View File

@@ -191,8 +191,8 @@
0, 0,
0, 0,
1, 1,
1, 2,
1, 2,
1 1
] ]
}, },

View File

@@ -12,6 +12,17 @@ for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]); window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]);
} }
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET = new Set();
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Idle1[0]);
window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.add(window.ATK_CHARACTER_STATE.Walking[0]);
/*
Kindly note that the use of dragonBones anim is an informed choice for the feasibility of "gotoAndPlayByFrame", which is a required feature by "Map.rollbackAndChase". You might find that "cc.Animation" -- the traditional frame anim -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal anim will make a smoother transition.
Moreover, frame anim doesn't support "compositie playing" and consumes more memory (yet less CPU) than a same skeletal anim, thus should only be used properly.
I've also spent sometime in extending "ccc wrapped dragoneBones.ArmatureDisplay" for enabling "gotoAndPlayByFrame" in CACHE mode (in REALTIME mode it's just the same as what's done here), but the debugging is an unexpected brainteaser -- not worth the time.
*/
cc.Class({ cc.Class({
extends: BaseCharacter, extends: BaseCharacter,
properties: { properties: {
@@ -30,18 +41,22 @@ cc.Class({
setSpecies(speciesName) { setSpecies(speciesName) {
this.speciesName = speciesName; this.speciesName = speciesName;
this.effAnimNode = this.animNode.getChildByName(this.speciesName);
this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
this.animComp.playAnimation(ATK_CHARACTER_STATE.Idle1[1]); // [WARNING] This is the only exception ccc's wrapper is used!
this.effAnimNode.active = true;
}, },
onLoad() { onLoad() {
BaseCharacter.prototype.onLoad.call(this); BaseCharacter.prototype.onLoad.call(this);
this.effAnimNode = this.animNode.getChildByName(this.speciesName);
this.animComp = this.effAnimNode.getComponent(dragonBones.ArmatureDisplay);
if (!this.animComp) {
this.animComp = this.effAnimNode.getComponent(cc.Animation);
this.effAnimNode.anchorY = 0.0; // Otherwise the frame anim will show with an incorrect y-offset even if the collider boundaries are all correct!
}
this.effAnimNode.active = true;
}, },
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) { updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) {
const underlyingAnimationCtrl = this.animComp._armature.animation; // ALWAYS use the dragonBones api instead of ccc's wrapper! // As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevRdfPlayer.characterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation.
// Update directions // Update directions
if (this.animComp && this.animComp.node) { if (this.animComp && this.animComp.node) {
if (0 > rdfPlayer.dirX) { if (0 > rdfPlayer.dirX) {
@@ -51,51 +66,64 @@ cc.Class({
} }
} }
// Update per character state
let newCharacterState = rdfPlayer.characterState; let newCharacterState = rdfPlayer.characterState;
let prevCharacterState = (null == prevRdfPlayer ? window.ATK_CHARACTER_STATE.Idle1[0] : prevRdfPlayer.characterState);
const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1]; const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
const playingAnimName = underlyingAnimationCtrl.lastAnimationName; let playingAnimName = null;
const isPlaying = underlyingAnimationCtrl.isPlaying; let underlyingAnimationCtrl = null;
// As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevCharacterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation. if (this.animComp instanceof dragonBones.ArmatureDisplay) {
if (newCharacterState != prevCharacterState) { underlyingAnimationCtrl = this.animComp._armature.animation; // ALWAYS use the dragonBones api instead of ccc's wrapper!
if (newAnimName == playingAnimName) { playingAnimName = underlyingAnimationCtrl.lastAnimationName;
if (ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState) {
// No need to interrupt
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, , to: ${JSON.stringify(rdfPlayer)}`);
return;
}
}
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
} else { } else {
// newCharacterState == prevCharacterState underlyingAnimationCtrl = this.animComp.currentClip;
if (newAnimName != playingAnimName) { playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name);
// the playing animation was falsely predicted }
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
} else { // It turns out that "prevRdfPlayer.characterState" is not useful in this function :)
if (!(ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState)) { if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// yet there's still a chance that the playing anim is not put at the current frame // No need to interrupt
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl); // console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
} return;
} }
if (this.animComp instanceof dragonBones.ArmatureDisplay) {
this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName);
} else {
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName);
} }
}, },
_interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl) { _interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
if (ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState) { if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover" // No "framesToRecover"
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, playing new ${newAnimName} from the beginning: while the playing anim is ${playAnimation}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, , to: ${JSON.stringify(rdfPlayer)}`); //console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1); underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
} else { } else {
const animationData = underlyingAnimationCtrl._animations[newAnimName]; const animationData = underlyingAnimationCtrl._animations[newAnimName];
let fromAnimFrame = (animationData.frameCount - rdfPlayer.framesToRecover); let fromAnimFrame = (animationData.frameCount - rdfPlayer.framesToRecover);
if (fromAnimFrame > 0) { if (fromAnimFrame < 0) {
} else if (fromAnimFrame < 0) {
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames // For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames
fromAnimFrame = 0; fromAnimFrame = 0;
} }
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, fromAnimFrame, 1); underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, fromAnimFrame, 1);
} }
}, },
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover"
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
this.animComp.play(newAnimName, 0);
return;
}
// The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor.
const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE
let fromTime = (targetClip.duration - rdfPlayer.framesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here?
if (fromTime < 0) {
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames
fromTime = 0;
}
this.animComp.play(newAnimName, fromTime);
},
}); });

View File

@@ -104,32 +104,6 @@ cc.Class({
return (0 == inputFrameId % 10); return (0 == inputFrameId % 10);
}, },
dumpToRenderCache: function(rdf) {
const self = this;
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId - 1; // Keep at least 1 prev render frame for anim triggering
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
self.recentRenderCache.pop();
}
const ret = self.recentRenderCache.setByFrameId(rdf, rdf.id);
return ret;
},
dumpToInputCache: function(inputFrameDownsync) {
const self = this;
let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames) - self.spAtkLookupFrames; // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding delayedInputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction.
if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) {
minToKeepInputFrameId = self.lastAllConfirmedInputFrameId;
}
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
self.recentInputCache.pop();
}
const ret = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (-1 < self.lastAllConfirmedInputFrameId && self.recentInputCache.stFrameId > self.lastAllConfirmedInputFrameId) {
console.error("Invalid input cache dumped! lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
}
return ret;
},
_convertToInputFrameId(renderFrameId, inputDelayFrames) { _convertToInputFrameId(renderFrameId, inputDelayFrames) {
if (renderFrameId < inputDelayFrames) return 0; if (renderFrameId < inputDelayFrames) return 0;
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames); return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
@@ -153,28 +127,35 @@ cc.Class({
null == self.ctrl || null == self.ctrl ||
null == self.selfPlayerInfo null == self.selfPlayerInfo
) { ) {
return [null, null]; throw `noDelayInputFrameId=${inputFrameId} couldn't be generated: recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
let previousSelfInput = null,
currSelfInput = null;
const joinIndex = self.selfPlayerInfo.joinIndex; const joinIndex = self.selfPlayerInfo.joinIndex;
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId); // [WARNING] The while-loop here handles a situation where the "resync rdf & accompaniedInputFrameDownsyncBatch" mismatched and we have to predict some "gap-inputFrames"!
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]); while (self.recentInputCache.edFrameId <= inputFrameId) {
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
// If "forceConfirmation" is active on backend, we shouldn't override the already downsynced "inputFrameDownsync"s. // If "forceConfirmation" is active on backend, there's a chance that the already downsynced "inputFrameDownsync"s are ahead of a locally generating inputFrameId, in this case we respect the downsynced one.
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId); const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) { if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]]; console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
}
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
currSelfInput = self.ctrl.getEncodedInput();
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({
inputFrameId: self.recentInputCache.edFrameId,
inputList: prefabbedInputList,
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
});
self.recentInputCache.put(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
} }
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
const currSelfInput = self.ctrl.getEncodedInput();
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
const prefabbedInputFrameDownsync = {
inputFrameId: inputFrameId,
inputList: prefabbedInputList,
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
};
self.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
return [previousSelfInput, currSelfInput]; return [previousSelfInput, currSelfInput];
}, },
@@ -203,7 +184,7 @@ cc.Class({
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) { for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i); const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
if (null == inputFrameDownsync) { if (null == inputFrameDownsync) {
console.error("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, ": latestLocalInputFrameId=", latestLocalInputFrameId, ", recentInputCache=", self._stringifyRecentInputCache(false)); console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
} else { } else {
const inputFrameUpsync = { const inputFrameUpsync = {
inputFrameId: i, inputFrameId: i,
@@ -225,6 +206,9 @@ cc.Class({
}).finish(); }).finish();
window.sendSafely(reqData); window.sendSafely(reqData);
self.lastUpsyncInputFrameId = latestLocalInputFrameId; self.lastUpsyncInputFrameId = latestLocalInputFrameId;
if (self.lastUpsyncInputFrameId >= self.recentInputCache.edFrameId) {
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
}, },
onEnable() { onEnable() {
@@ -312,7 +296,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 >> 1) + 1);
self.collisionSys = new collisions.Collisions(); self.collisionSys = new collisions.Collisions();
@@ -320,6 +304,8 @@ cc.Class({
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
self.collisionSysMap = new Map(); self.collisionSysMap = new Map();
console.log(`collisionSys & collisionSysMap reset`);
self.transitToState(ALL_MAP_STATES.VISUAL); self.transitToState(ALL_MAP_STATES.VISUAL);
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
@@ -413,7 +399,10 @@ cc.Class({
/** Init required prefab ended. */ /** Init required prefab ended. */
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) { window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
console.log(`Received parsedBattleColliderInfo via ws`);
// TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE".
Object.assign(self, parsedBattleColliderInfo); Object.assign(self, parsedBattleColliderInfo);
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
@@ -458,7 +447,7 @@ cc.Class({
const x0 = boundaryObj.anchor.x, const x0 = boundaryObj.anchor.x,
y0 = boundaryObj.anchor.y; y0 = boundaryObj.anchor.y;
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
return [p.x, p.y]; return [p.x, p.y];
})); }));
@@ -493,10 +482,10 @@ cc.Class({
} }
} }
// console.log("Created barrier: ", newBarrier);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.collisionSysMap.set(collisionBarrierIndex, newBarrier); self.collisionSysMap.set(collisionBarrierIndex, newBarrierCollider);
// console.log(`Created new barrier collider: ${collisionBarrierIndex}`);
} }
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
@@ -509,6 +498,7 @@ cc.Class({
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK, act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
}).finish(); }).finish();
window.sendSafely(reqData); window.sendSafely(reqData);
console.log(`Sent UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK via ws`);
}); });
}; };
@@ -571,17 +561,21 @@ cc.Class({
this._inputControlEnabled = false; this._inputControlEnabled = false;
}, },
onRoomDownsyncFrame(rdf) { onRoomDownsyncFrame(rdf, accompaniedInputFrameDownsyncBatch) {
// This function is also applicable to "re-joining". // This function is also applicable to "re-joining".
const self = window.mapIns; const self = window.mapIns;
if (rdf.id < self.lastAllConfirmedRenderFrameId) { if (!self.recentRenderCache) {
return window.RING_BUFF_FAILED_TO_SET; return;
} }
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
return;
}
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id);
const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance);
const dumpRenderCacheRet = self.dumpToRenderCache(rdf); const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) { if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
console.error("Something is wrong while setting the RingBuffer by frameId!"); throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
return dumpRenderCacheRet;
} }
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) { if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
/* /*
@@ -592,13 +586,7 @@ cc.Class({
return dumpRenderCacheRet; return dumpRenderCacheRet;
} }
// The logic below applies to ( || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) // The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id);
} else {
console.log('On battle resynced! renderFrameId=', rdf.id);
}
const players = rdf.players; const players = rdf.players;
self._initPlayerRichInfoDict(players); self._initPlayerRichInfoDict(players);
@@ -612,11 +600,24 @@ cc.Class({
if (null == self.renderFrameId || self.renderFrameId <= rdf.id) { if (null == self.renderFrameId || self.renderFrameId <= rdf.id) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly // In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id);
} else {
self.hideFindingPlayersGUI(rdf);
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`);
}
self.renderFrameId = rdf.id; self.renderFrameId = rdf.id;
self.lastRenderFrameIdTriggeredAt = performance.now(); self.lastRenderFrameIdTriggeredAt = performance.now();
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId". // In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
self.lastAllConfirmedRenderFrameId = rdf.id; self.lastAllConfirmedRenderFrameId = rdf.id;
self.chaserRenderFrameId = rdf.id; self.chaserRenderFrameId = rdf.id;
const candidateLastAllConfirmedInputFrame = self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames);
if (self.lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame) {
self.lastAllConfirmedInputFrame = candidateLastAllConfirmedInputFrame;
}
const canvasNode = self.canvasNode; const canvasNode = self.canvasNode;
self.ctrl = canvasNode.getComponent("TouchEventsManager"); self.ctrl = canvasNode.getComponent("TouchEventsManager");
@@ -650,9 +651,12 @@ cc.Class({
}, },
onInputFrameDownsyncBatch(batch) { onInputFrameDownsyncBatch(batch) {
// TODO: find some kind of synchronization mechanism against "_generateInputFrameUpsync"!
const self = this; const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState if (!self.recentInputCache) {
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) { return;
}
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
return; return;
} }
@@ -663,6 +667,7 @@ cc.Class({
if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) { if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) {
continue; continue;
} }
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId); const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
if (null != localInputFrame if (null != localInputFrame
&& &&
@@ -672,10 +677,12 @@ cc.Class({
) { ) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId; firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
} }
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase". // [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1; inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
self.dumpToInputCache(inputFrameDownsync); const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_FAILED_TO_SET == ret) {
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
} }
if (null == firstPredictedYetIncorrectInputFrameId) return; if (null == firstPredictedYetIncorrectInputFrameId) return;
@@ -716,7 +723,7 @@ cc.Class({
logBattleStats() { logBattleStats() {
const self = this; const self = this;
let s = []; let s = [];
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}`); s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i); const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
@@ -761,15 +768,18 @@ cc.Class({
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy); newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const d = playerDownsyncInfo.colliderRadius * 2, const colliderWidth = playerDownsyncInfo.colliderRadius * 2,
[x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, playerDownsyncInfo.colliderRadius, playerDownsyncInfo.colliderRadius), colliderHeight = playerDownsyncInfo.colliderRadius * 3;
pts = [[0, 0], [d, 0], [d, d], [0, d]]; const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight),
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]];
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.data = playerDownsyncInfo; newPlayerCollider.data = playerDownsyncInfo;
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.colliderRadius}`);
safelyAddChild(self.node, newPlayerNode); safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
@@ -783,7 +793,8 @@ cc.Class({
const self = this; const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) { if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt; const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) { 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); // console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
return; return;
} }
@@ -822,17 +833,13 @@ cc.Class({
*/ */
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls! // [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now();
let t3 = performance.now(); let t3 = performance.now();
} catch (err) { } catch (err) {
console.error("Error during Map.update", err); console.error("Error during Map.update", err);
self.onBattleStopped(); // TODO: Popup to ask player to refresh browser
} finally { } finally {
// Update countdown
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
if (self.countdownNanos <= 0) {
self.onBattleStopped(self.playerRichInfoDict);
return;
}
const countdownSeconds = parseInt(self.countdownNanos / 1000000000); const countdownSeconds = parseInt(self.countdownNanos / 1000000000);
if (isNaN(countdownSeconds)) { if (isNaN(countdownSeconds)) {
console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`); console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`);
@@ -840,8 +847,6 @@ cc.Class({
if (null != self.countdownLabel) { if (null != self.countdownLabel) {
self.countdownLabel.string = countdownSeconds; self.countdownLabel.string = countdownSeconds;
} }
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now();
} }
} }
}, },
@@ -921,9 +926,6 @@ cc.Class({
const self = this; const self = this;
if (null == self.findingPlayerNode.parent) return; if (null == self.findingPlayerNode.parent) return;
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode); self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
if (null != rdf) {
self._initPlayerRichInfoDict(rdf.players);
}
}, },
onBattleReadyToStart(rdf) { onBattleReadyToStart(rdf) {
@@ -967,15 +969,21 @@ cc.Class({
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false); playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
} }
// Update countdown
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
if (self.countdownNanos <= 0) {
self.onBattleStopped(self.playerRichInfoDict);
}
}, },
getCachedInputFrameDownsyncWithPrediction(inputFrameId) { getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this; const self = this;
let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) { const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); if (null != inputFrameDownsync && null != lastAllConfirmedInputFrame && inputFrameId > self.lastAllConfirmedInputFrameId) {
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
if (i == self.selfPlayerInfo.joinIndex - 1) continue; if (i == (self.selfPlayerInfo.joinIndex - 1)) continue;
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input! inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
} }
} }
@@ -1007,11 +1015,7 @@ cc.Class({
}; };
} }
const toRet = { const nextRenderFrameMeleeBullets = [];
id: currRenderFrame.id + 1,
players: nextRenderFramePlayers,
meleeBullets: []
};
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order 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 const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
@@ -1104,7 +1108,7 @@ cc.Class({
collisionSysMap.delete(collisionBulletIndex); collisionSysMap.delete(collisionBulletIndex);
} }
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue; if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
toRet.meleeBullets.push(meleeBullet); nextRenderFrameMeleeBullets.push(meleeBullet);
} }
// Process player inputs // Process player inputs
@@ -1145,7 +1149,7 @@ cc.Class({
punch.offenderJoinIndex = joinIndex; punch.offenderJoinIndex = joinIndex;
punch.offenderPlayerId = playerId; punch.offenderPlayerId = playerId;
punch.originatedRenderFrameId = currRenderFrame.id; punch.originatedRenderFrameId = currRenderFrame.id;
toRet.meleeBullets.push(punch); nextRenderFrameMeleeBullets.push(punch);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`); // console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); // console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
@@ -1198,7 +1202,11 @@ cc.Class({
} }
return toRet; return window.pb.protos.RoomDownsyncFrame.create({
id: currRenderFrame.id + 1,
players: nextRenderFramePlayers,
meleeBullets: nextRenderFrameMeleeBullets,
});
}, },
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) { rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
@@ -1206,31 +1214,24 @@ cc.Class({
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted. This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
*/ */
const self = this; const self = this;
let prevLatestRdf = null; let i = renderFrameIdSt,
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame" prevLatestRdf = null,
if (null == latestRdf) { latestRdf = null;
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [prevLatestRdf, latestRdf];
}
if (renderFrameIdSt >= renderFrameIdEd) { do {
return [prevLatestRdf, latestRdf]; latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
} if (null == latestRdf) {
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRenderFrame) {
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`); console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
return [prevLatestRdf, latestRdf]; return [prevLatestRdf, latestRdf];
} }
const j = self._convertToInputFrameId(i, self.inputDelayFrames); const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j); const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
if (null == delayedInputFrame) { if (null == delayedInputFrame) {
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); // Shouldn't happen!
return [prevLatestRdf, latestRdf]; throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
prevLatestRdf = latestRdf; prevLatestRdf = latestRdf;
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap); latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, prevLatestRdf, collisionSys, collisionSysMap);
if ( if (
self._allConfirmed(delayedInputFrame.confirmedList) self._allConfirmed(delayedInputFrame.confirmedList)
&& &&
@@ -1248,8 +1249,9 @@ cc.Class({
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = latestRdf.id; self.chaserRenderFrameId = latestRdf.id;
} }
self.dumpToRenderCache(latestRdf); self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id);
} ++i;
} while (i < renderFrameIdEd);
return [prevLatestRdf, latestRdf]; return [prevLatestRdf, latestRdf];
}, },

View File

@@ -33,6 +33,7 @@ cc.Class({
self.inputScaleFrames = 2; self.inputScaleFrames = 2;
self.inputFrameUpsyncDelayTolerance = 2; self.inputFrameUpsyncDelayTolerance = 2;
self.renderCacheSize = 1024;
self.rollbackEstimatedDt = 0.016667; self.rollbackEstimatedDt = 0.016667;
self.rollbackEstimatedDtMillis = 16.667; self.rollbackEstimatedDtMillis = 16.667;
self.rollbackEstimatedDtNanos = 16666666; self.rollbackEstimatedDtNanos = 16666666;
@@ -42,25 +43,25 @@ cc.Class({
self.meleeSkillConfig = { self.meleeSkillConfig = {
1: { 1: {
// for offender // for offender
startupFrames: 23, startupFrames: 10,
activeFrames: 3, 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: 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: 61, recoveryFramesOnBlock: 34,
recoveryFramesOnHit: 61, recoveryFramesOnHit: 34,
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: 45.0, x: 23.0,
y: 32.0, y: 32.0,
}, },
// for defender // for defender
hitStunFrames: 18, hitStunFrames: 18,
blockStunFrames: 9, blockStunFrames: 9,
pushback: 11.0, pushback: 8.0,
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
damage: 5 damage: 5
} }
@@ -151,7 +152,7 @@ cc.Class({
id: 11, id: 11,
joinIndex: 2, joinIndex: 2,
virtualGridX: 80 * self.worldToVirtualGridRatio, virtualGridX: 80 * self.worldToVirtualGridRatio,
virtualGridY: 40 * self.worldToVirtualGridRatio, virtualGridY: 0 * self.worldToVirtualGridRatio,
speed: 1 * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio,
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.Idle1[0], characterState: window.ATK_CHARACTER_STATE.Idle1[0],
@@ -162,7 +163,7 @@ cc.Class({
} }
}); });
self.selfPlayerInfo = { self.selfPlayerInfo = {
id: 10 id: 11
}; };
self._initPlayerRichInfoDict(startRdf.players); self._initPlayerRichInfoDict(startRdf.players);
self.onRoomDownsyncFrame(startRdf); self.onRoomDownsyncFrame(startRdf);

View File

@@ -13,6 +13,10 @@ var RingBuffer = function(capacity) {
}; };
RingBuffer.prototype.put = function(item) { RingBuffer.prototype.put = function(item) {
while (this.cnt >= this.n - 1) {
// Make room for the new element
this.pop();
}
this.eles[this.ed] = item this.eles[this.ed] = item
this.edFrameId++; this.edFrameId++;
this.cnt++; this.cnt++;
@@ -61,40 +65,41 @@ RingBuffer.prototype.getArrIdxByOffset = function(offsetFromSt) {
}; };
RingBuffer.prototype.getByFrameId = function(frameId) { RingBuffer.prototype.getByFrameId = function(frameId) {
if (frameId >= this.edFrameId) return null;
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId); const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
return (null == arrIdx ? null : this.eles[arrIdx]); return (null == arrIdx ? null : this.eles[arrIdx]);
}; };
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly. // [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
RingBuffer.prototype.setByFrameId = function(item, frameId) { RingBuffer.prototype.setByFrameId = function(item, frameId) {
if (frameId < this.stFrameId) { const oldStFrameId = this.stFrameId,
console.error("Invalid putByFrameId#1: stFrameId=", this.stFrameId, ", edFrameId=", this.edFrameId, ", incoming item=", item); oldEdFrameId = this.edFrameId;
return window.RING_BUFF_FAILED_TO_SET; if (frameId < oldStFrameId) {
return [window.RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId];
} }
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId); // By now "this.stFrameId <= frameId"
if (null != arrIdx) {
this.eles[arrIdx] = item; if (oldEdFrameId > frameId) {
return window.RING_BUFF_CONSECUTIVE_SET; const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
if (null != arrIdx) {
this.eles[arrIdx] = item;
return [window.RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId];
}
} }
// When "null == arrIdx", should it still be deemed consecutive if "frameId == edFrameId" prior to the reset? // By now "this.edFrameId <= frameId"
let ret = window.RING_BUFF_CONSECUTIVE_SET; let ret = window.RING_BUFF_CONSECUTIVE_SET;
if (this.edFrameId < frameId) { if (oldEdFrameId < frameId) {
this.st = this.ed = 0; this.st = this.ed = 0;
this.stFrameId = this.edFrameId = frameId; this.stFrameId = this.edFrameId = frameId;
this.cnt = 0; this.cnt = 0;
ret = window.RING_BUFF_NON_CONSECUTIVE_SET; ret = window.RING_BUFF_NON_CONSECUTIVE_SET;
} }
this.eles[this.ed] = item // By now "this.edFrameId == frameId"
this.edFrameId++; this.put(item);
this.cnt++;
this.ed++;
if (this.ed >= this.n) {
this.ed -= this.n; // Deliberately not using "%" operator for performance concern
}
return ret; return [ret, oldStFrameId, oldEdFrameId];
}; };
module.exports = RingBuffer; module.exports = RingBuffer;

View File

@@ -5,7 +5,6 @@ window.UPSYNC_MSG_ACT_PLAYER_CMD = 2;
window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3; window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3;
window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = -98; window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = -98;
window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED = -97;
window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1; window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1;
window.DOWNSYNC_MSG_ACT_BATTLE_START = 0; window.DOWNSYNC_MSG_ACT_BATTLE_START = 0;
window.DOWNSYNC_MSG_ACT_HB_REQ = 1; window.DOWNSYNC_MSG_ACT_HB_REQ = 1;
@@ -135,7 +134,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob" clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
clientSession.onopen = function(evt) { clientSession.onopen = function(evt) {
console.log("The WS clientSession is opened. clientSession.id=", clientSession.id); console.log("The WS clientSession is opened.");
window.clientSession = clientSession; window.clientSession = clientSession;
if (null == onopenCb) return; if (null == onopenCb) return;
onopenCb(); onopenCb();
@@ -147,17 +146,14 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
} }
try { try {
const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data)); const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data));
// console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
switch (resp.act) { switch (resp.act) {
case window.DOWNSYNC_MSG_ACT_HB_REQ: case window.DOWNSYNC_MSG_ACT_HB_REQ:
window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage window.handleHbRequirements(resp);
break; break;
case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED:
mapIns.onPlayerAdded(resp.rdf); mapIns.onPlayerAdded(resp.rdf);
break; break;
case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED:
// Deliberately left blank for now
mapIns.hideFindingPlayersGUI(resp.rdf);
break;
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START: case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
mapIns.onBattleReadyToStart(resp.rdf); mapIns.onBattleReadyToStart(resp.rdf);
break; break;
@@ -172,16 +168,10 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
break; break;
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC: case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
if (null == resp.inputFrameDownsyncBatch || 0 >= resp.inputFrameDownsyncBatch.length) { if (null == resp.inputFrameDownsyncBatch || 0 >= resp.inputFrameDownsyncBatch.length) {
console.error(`Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, the incoming resp= console.error(`Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, the incoming resp=${JSON.stringify(resp, null, 2)}`);
${JSON.stringify(resp, null, 2)}`);
return; return;
} }
const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1); mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
const renderFrameIdConsecutive = (resp.rdf.id <= mapIns.renderFrameId + mapIns.renderFrameIdLagTolerance);
console.warn(`Got resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, inputFrameIdConsecutive=${inputFrameIdConsecutive}, renderFrameIdConsecutive=${renderFrameIdConsecutive}`);
// The following order of execution is important
mapIns.onRoomDownsyncFrame(resp.rdf);
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
break; break;
default: default:
break; break;

Some files were not shown because too many files have changed in this diff Show More