Fixes for simultaneous reconnection w.r.t. same room, and updates for documentation.

This commit is contained in:
genxium 2022-11-27 00:00:39 +08:00
parent 9bce561441
commit e0fb21f3fb
6 changed files with 134 additions and 331 deletions

View File

@ -96,7 +96,7 @@ func getPlayer(cond sq.Eq) (*Player, error) {
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
return &p, nil

View File

@ -476,7 +476,7 @@ func (pR *Room) StartBattle() {
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".
// [WARNING] DON'T send anything if the player is not yet active, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
continue
}
if 0 == pR.RenderFrameId {
@ -995,6 +995,12 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
This function is triggered by an upsync message via WebSocket, thus downsync sending is also available by now.
*/
currPlayerBattleState := atomic.LoadInt32(&(eachPlayer.BattleState))
if PlayerBattleStateIns.DISCONNECTED == currPlayerBattleState || PlayerBattleStateIns.LOST == currPlayerBattleState {
// [WARNING] DON'T try to send any message to an inactive player!
continue
}
switch targetPlayer.BattleState {
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame := &RoomDownsyncFrame{
@ -1338,7 +1344,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
if 0 < thatPlayerInNextFrame.FramesToRecover {
// 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.Y += bulletPushbacks[joinIndex-1].Y
// Update in the collision system
@ -1374,7 +1380,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
} else {
// No bullet trigger, process movement inputs
// 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 {
thatPlayerInNextFrame.DirX = decodedInput.Dx
thatPlayerInNextFrame.DirY = decodedInput.Dy

View File

@ -5,6 +5,7 @@ import (
. "dnmshared"
"fmt"
"go.uber.org/zap"
"strings"
"sync"
)
@ -21,11 +22,13 @@ var (
func (pPq *RoomHeap) PrintInOrder() {
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++ {
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) }

View File

@ -16,7 +16,6 @@ import (
"time"
. "dnmshared"
"runtime/debug"
)
const (
@ -47,9 +46,8 @@ func Serve(c *gin.Context) {
c.AbortWithStatus(http.StatusBadRequest)
return
}
Logger.Info("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token))
boundRoomId := 0
expectRoomId := 0
expectedRoomId := 0
var err error
if boundRoomIdStr, hasBoundRoomId := c.GetQuery("boundRoomId"); hasBoundRoomId {
boundRoomId, err = strconv.Atoi(boundRoomIdStr)
@ -58,27 +56,28 @@ func Serve(c *gin.Context) {
c.AbortWithStatus(http.StatusBadRequest)
return
}
Logger.Info("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId))
}
if expectRoomIdStr, hasExpectRoomId := c.GetQuery("expectedRoomId"); hasExpectRoomId {
expectRoomId, err = strconv.Atoi(expectRoomIdStr)
Logger.Debug("Finding PlayerLogin record for ws authentication:", zap.Any("intAuthToken", token), zap.Any("boundRoomId", boundRoomId))
} else if expectedRoomIdStr, hasExpectRoomId := c.GetQuery("expectedRoomId"); hasExpectRoomId {
expectedRoomId, err = strconv.Atoi(expectedRoomIdStr)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
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!
playerId, err := models.GetPlayerIdByToken(token)
if err != nil || playerId == 0 {
// 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)
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)
if err != nil {
@ -160,14 +159,14 @@ func Serve(c *gin.Context) {
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.
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()
defer func() {
(*(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() {
if r := recover(); r != nil {
@ -175,13 +174,12 @@ func Serve(c *gin.Context) {
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))))
playerSuccessfullyAddedToRoom := false
if 0 < boundRoomId {
if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent {
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)
if !res {
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
}
}
}
if 0 < expectRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectRoomId)]; existent {
} else if 0 < expectedRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectedRoomId)]; existent {
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) {
playerSuccessfullyAddedToRoom = true
} else if pRoom.AddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} 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
}
@ -221,7 +216,7 @@ func Serve(c *gin.Context) {
signalToCloseConnOfThisPlayer(Constants.RetCode.LocallyNoAvailableRoom, fmt.Sprintf("Cannot pop a (*Room) for playerId == %v!", playerId))
} else {
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)
if !res {
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 {
defer func() {
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))
}()

View File

@ -8,7 +8,8 @@
"__id__": 1
},
"optimizationPolicy": 0,
"asyncLoadAssets": false
"asyncLoadAssets": false,
"readonly": false
},
{
"__type__": "cc.Node",
@ -20,24 +21,20 @@
"__id__": 2
},
{
"__id__": 9
},
{
"__id__": 17
"__id__": 10
}
],
"_active": true,
"_level": 1,
"_components": [
{
"__id__": 20
"__id__": 13
},
{
"__id__": 21
"__id__": 14
}
],
"_prefab": {
"__id__": 22
"__id__": 15
},
"_opacity": 255,
"_color": {
@ -57,17 +54,6 @@
"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",
@ -83,231 +69,20 @@
1,
1
]
}
},
{
"__type__": "cc.Node",
"_name": "ruleNode",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__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",
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0,
"w": 1
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_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": ""
},
{
"__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",
"_name": "modeButton",
@ -317,21 +92,20 @@
},
"_children": [
{
"__id__": 10
"__id__": 3
}
],
"_active": true,
"_level": 2,
"_components": [
{
"__id__": 14
"__id__": 7
},
{
"__id__": 15
"__id__": 8
}
],
"_prefab": {
"__id__": 16
"__id__": 9
},
"_opacity": 255,
"_color": {
@ -351,17 +125,6 @@
"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",
@ -377,28 +140,39 @@
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",
"_name": "Label",
"_objFlags": 0,
"_parent": {
"__id__": 9
"__id__": 2
},
"_children": [],
"_active": true,
"_level": 0,
"_components": [
{
"__id__": 11
"__id__": 4
},
{
"__id__": 12
"__id__": 5
}
],
"_prefab": {
"__id__": 13
"__id__": 6
},
"_opacity": 255,
"_color": {
@ -418,17 +192,6 @@
"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",
@ -444,16 +207,33 @@
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",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 10
"__id__": 3
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_useOriginalSize": false,
"_string": "gameRule.mode",
"_N$string": "gameRule.mode",
@ -476,7 +256,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 10
"__id__": 3
},
"_enabled": true,
"_dataID": "gameRule.mode",
@ -498,9 +278,16 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 9
"__id__": 2
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "081ad337-20ca-4313-ae3e-bb6dee3547b7"
},
@ -515,12 +302,9 @@
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_state": 0,
"_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
},
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": ""
},
{
@ -528,9 +312,11 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 9
"__id__": 2
},
"_enabled": true,
"_normalMaterial": null,
"_grayMaterial": null,
"duration": 0.1,
"zoomScale": 1.2,
"clickEvents": [],
@ -589,7 +375,7 @@
"hoverSprite": null,
"_N$disabledSprite": null,
"_N$target": {
"__id__": 9
"__id__": 2
},
"_id": ""
},
@ -613,14 +399,13 @@
},
"_children": [],
"_active": true,
"_level": 2,
"_components": [
{
"__id__": 18
"__id__": 11
}
],
"_prefab": {
"__id__": 19
"__id__": 12
},
"_opacity": 255,
"_color": {
@ -640,17 +425,6 @@
"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",
@ -666,16 +440,35 @@
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",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 17
"__id__": 10
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "153d890a-fc37-4d59-8779-93a8fb19fa85"
},
@ -690,12 +483,9 @@
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_state": 0,
"_atlas": {
"__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4"
},
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": ""
},
{
@ -718,7 +508,7 @@
},
"_enabled": true,
"modeButton": {
"__id__": 15
"__id__": 8
},
"mapNode": null,
"_id": ""
@ -731,6 +521,13 @@
"__id__": 1
},
"_enabled": true,
"_materials": [
{
"__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
}
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_spriteFrame": {
"__uuid__": "7838f276-ab48-445a-b858-937dd27d9520"
},
@ -745,10 +542,7 @@
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_state": 0,
"_atlas": null,
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
"_id": ""
},
{

View File

@ -12,6 +12,11 @@ for (let k in window.ATK_CHARACTER_STATE) {
window.ATK_CHARACTER_STATE_ARR.push(window.ATK_CHARACTER_STATE[k]);
}
/*
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 animation -- can also suffice this requirement, yet if we want to develop 3D frontend in the future, working with skeletal animation will make a smoother transition.
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({
extends: BaseCharacter,
properties: {