mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-16 20:16:37 +00:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e0fb21f3fb | ||
|
9bce561441 | ||
|
52be2a6a79 | ||
|
fa491b357d | ||
|
695eacaabc |
@@ -2,10 +2,11 @@
|
||||
|
||||
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 2x for file size reduction)_
|
||||

|
||||
_(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)_
|
||||
|
||||
Please also checkout [this demo video](https://pan.baidu.com/s/1fy0CuFKnVP_Gn2cDfrj6yg?pwd=q5uc) 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.
|
||||
|
||||
The video mainly shows the following features.
|
||||
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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) }
|
||||
|
@@ -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))
|
||||
}()
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.3 MiB |
BIN
charts/melee_attack_fractional_anim_resume_spedup.gif
Normal file
BIN
charts/melee_attack_fractional_anim_resume_spedup.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 MiB |
@@ -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": ""
|
||||
},
|
||||
{
|
||||
|
@@ -454,7 +454,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
210.7364624020594,
|
||||
216.50635094610968,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -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: {
|
||||
@@ -32,7 +37,7 @@ cc.Class({
|
||||
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]);
|
||||
this.animComp.playAnimation(ATK_CHARACTER_STATE.Idle1[1]); // [WARNING] This is the only exception ccc's wrapper is used!
|
||||
this.effAnimNode.active = true;
|
||||
},
|
||||
|
||||
@@ -41,6 +46,7 @@ cc.Class({
|
||||
},
|
||||
|
||||
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) {
|
||||
const underlyingAnimationCtrl = this.animComp._armature.animation; // ALWAYS use the dragonBones api instead of ccc's wrapper!
|
||||
// Update directions
|
||||
if (this.animComp && this.animComp.node) {
|
||||
if (0 > rdfPlayer.dirX) {
|
||||
@@ -53,13 +59,48 @@ cc.Class({
|
||||
// Update per character state
|
||||
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 playingAnimName = underlyingAnimationCtrl.lastAnimationName;
|
||||
const isPlaying = underlyingAnimationCtrl.isPlaying;
|
||||
|
||||
// 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 (newCharacterState != prevCharacterState) {
|
||||
const newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
|
||||
// Anim is edge-triggered
|
||||
if (newAnimName == this.animComp.animationName) {
|
||||
console.warn(`JoinIndex=${rdfPlayer.joinIndex}, possibly playing weird anim by resetting anim to ${newAnimName} while the playing anim is also ${this.animComp.animationName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
|
||||
if (newAnimName == playingAnimName) {
|
||||
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.animComp.playAnimation(newAnimName);
|
||||
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
|
||||
} else {
|
||||
// newCharacterState == prevCharacterState
|
||||
if (newAnimName != playingAnimName) {
|
||||
// the playing animation was falsely predicted
|
||||
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
|
||||
} else {
|
||||
if (!(ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState)) {
|
||||
// yet there's still a chance that the playing anim is not put at the current frame
|
||||
this._interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_interruptPlayingAnimAndPlayNewAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl) {
|
||||
if (ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState) {
|
||||
// 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)}`);
|
||||
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
|
||||
} else {
|
||||
const animationData = underlyingAnimationCtrl._animations[newAnimName];
|
||||
let fromAnimFrame = (animationData.frameCount - rdfPlayer.framesToRecover);
|
||||
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
|
||||
fromAnimFrame = 0;
|
||||
}
|
||||
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, fromAnimFrame, 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user