feat: 帧动画Timeline问题处理

This commit is contained in:
2026-08-02 02:22:46 +08:00
parent 20a458ec78
commit 64c7245d9e
12 changed files with 2411 additions and 1247 deletions
@@ -15,325 +15,325 @@ MonoBehaviour:
id: intro
displayName: "\u68C0\u7D22"
frames:
- sprite: {fileID: 7572446778344178281, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 504396963, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 0.aseprite"
frameName: "\u68C0\u7D22(1) 0.aseprite"
sourceIndex: 0
- sprite: {fileID: -362215467011434174, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 291796385, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 1.aseprite"
frameName: "\u68C0\u7D22(1) 1.aseprite"
sourceIndex: 1
- sprite: {fileID: -4996972306367436102, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 624098569, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 2.aseprite"
frameName: "\u68C0\u7D22(1) 2.aseprite"
sourceIndex: 2
- sprite: {fileID: -6337999013739407263, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1172030325, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 3.aseprite"
frameName: "\u68C0\u7D22(1) 3.aseprite"
sourceIndex: 3
- sprite: {fileID: 6998681854070186983, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1079787295, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 4.aseprite"
frameName: "\u68C0\u7D22(1) 4.aseprite"
sourceIndex: 4
- sprite: {fileID: 6998681854070186983, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1311865483, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 5.aseprite"
frameName: "\u68C0\u7D22(1) 5.aseprite"
sourceIndex: 5
- sprite: {fileID: 4876995554275767483, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -127897218, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 6.aseprite"
frameName: "\u68C0\u7D22(1) 6.aseprite"
sourceIndex: 6
- sprite: {fileID: 4876995554275767483, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 2097211279, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 7.aseprite"
frameName: "\u68C0\u7D22(1) 7.aseprite"
sourceIndex: 7
- sprite: {fileID: -1351993313840321639, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1750020038, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 8.aseprite"
frameName: "\u68C0\u7D22(1) 8.aseprite"
sourceIndex: 8
- sprite: {fileID: -1351993313840321639, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 242499730, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 9.aseprite"
frameName: "\u68C0\u7D22(1) 9.aseprite"
sourceIndex: 9
- sprite: {fileID: 2953902384930482259, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -345723938, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 10.aseprite"
frameName: "\u68C0\u7D22(1) 10.aseprite"
sourceIndex: 10
- sprite: {fileID: 2953902384930482259, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -225466995, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 11.aseprite"
frameName: "\u68C0\u7D22(1) 11.aseprite"
sourceIndex: 11
- sprite: {fileID: -8223510296345293682, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1197766663, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 12.aseprite"
frameName: "\u68C0\u7D22(1) 12.aseprite"
sourceIndex: 12
- sprite: {fileID: -8223510296345293682, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -122112883, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 13.aseprite"
frameName: "\u68C0\u7D22(1) 13.aseprite"
sourceIndex: 13
- sprite: {fileID: 3051864550455069342, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1850433033, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 14.aseprite"
frameName: "\u68C0\u7D22(1) 14.aseprite"
sourceIndex: 14
- sprite: {fileID: 3051864550455069342, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -126566680, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 15.aseprite"
frameName: "\u68C0\u7D22(1) 15.aseprite"
sourceIndex: 15
- sprite: {fileID: 651861321318766745, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -675809634, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 16.aseprite"
frameName: "\u68C0\u7D22(1) 16.aseprite"
sourceIndex: 16
- sprite: {fileID: 651861321318766745, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1660958210, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 17.aseprite"
frameName: "\u68C0\u7D22(1) 17.aseprite"
sourceIndex: 17
- sprite: {fileID: 6360117332621625605, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1487325629, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 18.aseprite"
frameName: "\u68C0\u7D22(1) 18.aseprite"
sourceIndex: 18
- sprite: {fileID: 2110206911040411466, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1348725402, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 19.aseprite"
frameName: "\u68C0\u7D22(1) 19.aseprite"
sourceIndex: 19
- sprite: {fileID: -8552977822448111447, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -459077972, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 20.aseprite"
frameName: "\u68C0\u7D22(1) 20.aseprite"
sourceIndex: 20
- sprite: {fileID: -2421474681753995496, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1340036015, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 21.aseprite"
frameName: "\u68C0\u7D22(1) 21.aseprite"
sourceIndex: 21
- sprite: {fileID: -7492713014907810856, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 243863421, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 22.aseprite"
frameName: "\u68C0\u7D22(1) 22.aseprite"
sourceIndex: 22
- sprite: {fileID: -7492713014907810856, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -843151036, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 23.aseprite"
frameName: "\u68C0\u7D22(1) 23.aseprite"
sourceIndex: 23
- sprite: {fileID: -7720457564735758752, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -2125577238, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 24.aseprite"
frameName: "\u68C0\u7D22(1) 24.aseprite"
sourceIndex: 24
- sprite: {fileID: -7720457564735758752, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1214599644, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 25.aseprite"
frameName: "\u68C0\u7D22(1) 25.aseprite"
sourceIndex: 25
- sprite: {fileID: 1880112101706155564, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1425693306, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 26.aseprite"
frameName: "\u68C0\u7D22(1) 26.aseprite"
sourceIndex: 26
- sprite: {fileID: -3623330345461600317, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 156535619, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 27.aseprite"
frameName: "\u68C0\u7D22(1) 27.aseprite"
sourceIndex: 27
- sprite: {fileID: -4821937940866556909, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -171496837, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 28.aseprite"
frameName: "\u68C0\u7D22(1) 28.aseprite"
sourceIndex: 28
- sprite: {fileID: -4821937940866556909, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1856768746, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 29.aseprite"
frameName: "\u68C0\u7D22(1) 29.aseprite"
sourceIndex: 29
- sprite: {fileID: -1230447193968539563, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1997184234, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 30.aseprite"
frameName: "\u68C0\u7D22(1) 30.aseprite"
sourceIndex: 30
- sprite: {fileID: -1230447193968539563, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -791780188, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 31.aseprite"
frameName: "\u68C0\u7D22(1) 31.aseprite"
sourceIndex: 31
- sprite: {fileID: -3452787567901838671, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 349163604, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 32.aseprite"
frameName: "\u68C0\u7D22(1) 32.aseprite"
sourceIndex: 32
- sprite: {fileID: 2122325496977185700, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 976052101, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 33.aseprite"
frameName: "\u68C0\u7D22(1) 33.aseprite"
sourceIndex: 33
- sprite: {fileID: -3452787567901838671, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1121699258, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 34.aseprite"
frameName: "\u68C0\u7D22(1) 34.aseprite"
sourceIndex: 34
- sprite: {fileID: -3452787567901838671, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1630996947, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 35.aseprite"
frameName: "\u68C0\u7D22(1) 35.aseprite"
sourceIndex: 35
- sprite: {fileID: -3838368664758933209, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 743051906, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 36.aseprite"
frameName: "\u68C0\u7D22(1) 36.aseprite"
sourceIndex: 36
- sprite: {fileID: -3838368664758933209, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 867729767, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 37.aseprite"
frameName: "\u68C0\u7D22(1) 37.aseprite"
sourceIndex: 37
- sprite: {fileID: -3838368664758933209, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1393316141, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 38.aseprite"
frameName: "\u68C0\u7D22(1) 38.aseprite"
sourceIndex: 38
- sprite: {fileID: -3838368664758933209, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -31726791, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 39.aseprite"
frameName: "\u68C0\u7D22(1) 39.aseprite"
sourceIndex: 39
- sprite: {fileID: -8974752115445585213, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 948510867, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 40.aseprite"
frameName: "\u68C0\u7D22(1) 40.aseprite"
sourceIndex: 40
- sprite: {fileID: -8974752115445585213, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1388676038, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 41.aseprite"
frameName: "\u68C0\u7D22(1) 41.aseprite"
sourceIndex: 41
- sprite: {fileID: -8974752115445585213, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -119360074, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 42.aseprite"
frameName: "\u68C0\u7D22(1) 42.aseprite"
sourceIndex: 42
- sprite: {fileID: -8974752115445585213, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1367469597, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 43.aseprite"
frameName: "\u68C0\u7D22(1) 43.aseprite"
sourceIndex: 43
- sprite: {fileID: 862258456695450341, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -659839448, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 44.aseprite"
frameName: "\u68C0\u7D22(1) 44.aseprite"
sourceIndex: 44
- sprite: {fileID: -5458642827783654469, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1966371428, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 45.aseprite"
frameName: "\u68C0\u7D22(1) 45.aseprite"
sourceIndex: 45
- sprite: {fileID: 4197853090659475812, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -282229650, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 46.aseprite"
frameName: "\u68C0\u7D22(1) 46.aseprite"
sourceIndex: 46
- sprite: {fileID: -1155378306054861757, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1093358863, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 47.aseprite"
frameName: "\u68C0\u7D22(1) 47.aseprite"
sourceIndex: 47
- sprite: {fileID: -1204860713, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 492557654, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 48.aseprite"
frameName: "\u68C0\u7D22(1) 48.aseprite"
sourceIndex: 48
- sprite: {fileID: -1204860713, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1027775630, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 49.aseprite"
frameName: "\u68C0\u7D22(1) 49.aseprite"
sourceIndex: 49
- sprite: {fileID: 2080999944, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 480031666, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 50.aseprite"
frameName: "\u68C0\u7D22(1) 50.aseprite"
sourceIndex: 50
- sprite: {fileID: 433384928, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1866608668, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 51.aseprite"
frameName: "\u68C0\u7D22(1) 51.aseprite"
sourceIndex: 51
- sprite: {fileID: -804306678, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -6198359, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 52.aseprite"
frameName: "\u68C0\u7D22(1) 52.aseprite"
sourceIndex: 52
- sprite: {fileID: 472963922, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1797269849, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 53.aseprite"
frameName: "\u68C0\u7D22(1) 53.aseprite"
sourceIndex: 53
- sprite: {fileID: 318794721, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 111881738, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 54.aseprite"
frameName: "\u68C0\u7D22(1) 54.aseprite"
sourceIndex: 54
- sprite: {fileID: 318794721, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -408735183, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 55.aseprite"
frameName: "\u68C0\u7D22(1) 55.aseprite"
sourceIndex: 55
- sprite: {fileID: 1203749929, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1501946279, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 56.aseprite"
frameName: "\u68C0\u7D22(1) 56.aseprite"
sourceIndex: 56
- sprite: {fileID: 1203749929, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1313030664, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 57.aseprite"
frameName: "\u68C0\u7D22(1) 57.aseprite"
sourceIndex: 57
- sprite: {fileID: 1203749929, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1750040965, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 58.aseprite"
frameName: "\u68C0\u7D22(1) 58.aseprite"
sourceIndex: 58
- sprite: {fileID: 1203749929, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1504143884, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 59.aseprite"
frameName: "\u68C0\u7D22(1) 59.aseprite"
sourceIndex: 59
- sprite: {fileID: -1984404506, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1501118800, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 60.aseprite"
frameName: "\u68C0\u7D22(1) 60.aseprite"
sourceIndex: 60
- sprite: {fileID: 1047793495, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 904547507, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 61.aseprite"
frameName: "\u68C0\u7D22(1) 61.aseprite"
sourceIndex: 61
- sprite: {fileID: 174328586, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -913400871, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 62.aseprite"
frameName: "\u68C0\u7D22(1) 62.aseprite"
sourceIndex: 62
- sprite: {fileID: 1047793495, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1051838451, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 63.aseprite"
frameName: "\u68C0\u7D22(1) 63.aseprite"
sourceIndex: 63
- sprite: {fileID: -1198657009, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 763501950, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 64.aseprite"
frameName: "\u68C0\u7D22(1) 64.aseprite"
sourceIndex: 64
- sprite: {fileID: -1198657009, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1197653475, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 65.aseprite"
frameName: "\u68C0\u7D22(1) 65.aseprite"
sourceIndex: 65
- sprite: {fileID: -1198657009, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 791718236, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 66.aseprite"
frameName: "\u68C0\u7D22(1) 66.aseprite"
sourceIndex: 66
- sprite: {fileID: -1198657009, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -163897732, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 67.aseprite"
frameName: "\u68C0\u7D22(1) 67.aseprite"
sourceIndex: 67
- sprite: {fileID: -342147049, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1215937852, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 68.aseprite"
frameName: "\u68C0\u7D22(1) 68.aseprite"
sourceIndex: 68
- sprite: {fileID: -342147049, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1449555021, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 69.aseprite"
frameName: "\u68C0\u7D22(1) 69.aseprite"
sourceIndex: 69
- sprite: {fileID: -342147049, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 314772697, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 70.aseprite"
frameName: "\u68C0\u7D22(1) 70.aseprite"
sourceIndex: 70
- sprite: {fileID: -342147049, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1243762998, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 71.aseprite"
frameName: "\u68C0\u7D22(1) 71.aseprite"
sourceIndex: 71
- sprite: {fileID: 1733318757, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1165852085, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 72.aseprite"
frameName: "\u68C0\u7D22(1) 72.aseprite"
sourceIndex: 72
- sprite: {fileID: 1733318757, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -111638770, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 73.aseprite"
frameName: "\u68C0\u7D22(1) 73.aseprite"
sourceIndex: 73
- sprite: {fileID: 1733318757, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -451272891, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 74.aseprite"
frameName: "\u68C0\u7D22(1) 74.aseprite"
sourceIndex: 74
- sprite: {fileID: 1733318757, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -1843965549, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 50
frameName: "\u68C0\u7D22 75.aseprite"
frameName: "\u68C0\u7D22(1) 75.aseprite"
sourceIndex: 75
- sprite: {fileID: -84262779, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -822097726, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 76.aseprite"
frameName: "\u68C0\u7D22(1) 76.aseprite"
sourceIndex: 76
- sprite: {fileID: 1947069765, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 576137929, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 77.aseprite"
frameName: "\u68C0\u7D22(1) 77.aseprite"
sourceIndex: 77
- sprite: {fileID: 1547981314, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: -314263957, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 78.aseprite"
frameName: "\u68C0\u7D22(1) 78.aseprite"
sourceIndex: 78
- sprite: {fileID: 1904593827, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
- sprite: {fileID: 1140765657, guid: 060544baeb316214698dd0ea788a63dd, type: 3}
durationMs: 200
frameName: "\u68C0\u7D22 79.aseprite"
frameName: "\u68C0\u7D22(1) 79.aseprite"
sourceIndex: 79
speed: 1
defaultEndBehavior: 2
@@ -348,5 +348,5 @@ MonoBehaviour:
asepriteJson: {fileID: 4900000, guid: 9281f1c256bc53140a36bb9439d2d5d6, type: 3}
pivot: {x: 0.5, y: 0.5}
manageSpriteSlicing: 1
lastSourceHash: a0c976cb23f6f387b5a0d81c36299682ec4e5b14a0894eb63b5aaf19fc228739
lastSourceHash: e24b62891974cbe19bc0868bcfa298b82c6d4f485faea653b802122dd6ea79a1
lastImportedTagName: intro
@@ -648,8 +648,7 @@
"size": { "w": 6144, "h": 4320 },
"scale": "1",
"frameTags": [
{ "name": "intro", "from": 0, "to": 47, "direction": "forward", "color": "#000000ff" },
{ "name": "loop", "from": 48, "to": 79, "direction": "forward", "color": "#000000ff" }
{ "name": "intro", "from": 0, "to": 79, "direction": "forward", "color": "#000000ff" }
],
"layers": [
{ "name": "black", "opacity": 89, "blendMode": "normal" },
File diff suppressed because it is too large Load Diff
@@ -24,7 +24,7 @@ MonoBehaviour:
- m_Version: 1
m_Start: 0
m_ClipIn: 0
m_Asset: {fileID: 3051057393504210916}
m_Asset: {fileID: -2259439808282263793}
m_Duration: 5.2
m_TimeScale: 1
m_ParentTrack: {fileID: -5039998276998606052}
@@ -92,6 +92,21 @@ MonoBehaviour:
m_DisplayName: "\u68C0\u7D22"
m_Markers:
m_Objects: []
--- !u!114 &-2259439808282263793
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c908631f9d7446ac86914dce3f1927af, type: 3}
m_Name: FrameAnimationTimelineClip
m_EditorClassIdentifier:
frameClip: {fileID: 11400000, guid: dc9666f5991e1be4bb0e5f149055f91a, type: 2}
synchronizedDurationSource: {fileID: 11400000, guid: dc9666f5991e1be4bb0e5f149055f91a, type: 2}
hasSynchronizedDuration: 1
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -113,18 +128,3 @@ MonoBehaviour:
m_ScenePreview: 1
m_DurationMode: 0
m_MarkerTrack: {fileID: 0}
--- !u!114 &3051057393504210916
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c908631f9d7446ac86914dce3f1927af, type: 3}
m_Name: FrameAnimationTimelineClip
m_EditorClassIdentifier:
frameClip: {fileID: 11400000, guid: dc9666f5991e1be4bb0e5f149055f91a, type: 2}
synchronizedDurationSource: {fileID: 11400000, guid: dc9666f5991e1be4bb0e5f149055f91a, type: 2}
hasSynchronizedDuration: 1
@@ -11141,7 +11141,7 @@ MeshRenderer:
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
- {fileID: 5536230373871310787, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
@@ -11185,8 +11185,8 @@ MonoBehaviour:
m_Calls: []
m_text: "\u5F85\u529E\u6E05\u5355"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
m_sharedMaterial: {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
m_fontAsset: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
m_sharedMaterial: {fileID: 5536230373871310787, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
File diff suppressed because it is too large Load Diff
@@ -89,6 +89,7 @@ namespace AibisDream.FrameAnimation
private bool hasStarted;
private bool hasCompleted;
private bool hasCompletedFirstTerminalLoopCycle;
private SeekIndex seekIndex;
public string PlayableId => plan.PlayableId;
public string CurrentClipId => CurrentStep?.Clip != null ? CurrentStep.Clip.Id : string.Empty;
@@ -140,10 +141,49 @@ namespace AibisDream.FrameAnimation
internal FrameAnimationSessionEvaluation Seek(double realSeconds, Action<Sprite> applySprite)
{
Restart(applySprite);
return realSeconds <= 0d
? FrameAnimationSessionEvaluation.Running
: Evaluate(realSeconds, 1f, applySprite);
if (!TryGetSeekIndex(out var index, out var error))
{
hasStarted = true;
hasCompleted = true;
return FrameAnimationSessionEvaluation.Failed(error.Code, error.Message);
}
hasStarted = true;
hasCompleted = false;
hasCompletedFirstTerminalLoopCycle = false;
var clampedSeconds = Math.Max(0d, realSeconds);
var terminalStepIndex = index.Steps.Length - 1;
var terminalStep = index.Steps[terminalStepIndex];
if (terminalStep.EndBehavior == FrameClipEndBehavior.Loop &&
clampedSeconds + Epsilon >= terminalStep.StartSeconds)
{
SeekTerminalLoop(terminalStepIndex, terminalStep, clampedSeconds);
ApplyCurrentFrame(applySprite);
return FrameAnimationSessionEvaluation.Running;
}
if (!double.IsPositiveInfinity(index.TotalDurationSeconds) &&
clampedSeconds + Epsilon >= index.TotalDurationSeconds)
{
stepIndex = terminalStepIndex;
frameIndex = terminalStep.FrameEndSeconds.Length - 1;
frameElapsedSeconds = 0d;
hasCompleted = true;
ApplyCurrentFrame(applySprite);
return FrameAnimationSessionEvaluation.Completed(terminalStep.EndBehavior);
}
stepIndex = FindFirstBoundaryAfter(index.StepEndSeconds, clampedSeconds);
if (stepIndex < 0 || stepIndex >= index.Steps.Length)
{
stepIndex = terminalStepIndex;
}
var seekStep = index.Steps[stepIndex];
SeekWithinStep(seekStep, clampedSeconds - seekStep.StartSeconds);
ApplyCurrentFrame(applySprite);
return FrameAnimationSessionEvaluation.Running;
}
public FrameAnimationSessionEvaluation Evaluate(
@@ -273,6 +313,169 @@ namespace AibisDream.FrameAnimation
return clip.TotalDurationMs / 1000d;
}
private bool TryGetSeekIndex(
out SeekIndex index,
out FrameAnimationPlaybackError error)
{
if (seekIndex != null)
{
index = seekIndex;
error = FrameAnimationPlaybackError.None;
return true;
}
if (plan.Steps == null || plan.Steps.Count == 0)
{
index = null;
error = new FrameAnimationPlaybackError(
FrameAnimationPlaybackErrorCode.InvalidPlayableData,
"播放计划不包含任何可播放 Step。");
return false;
}
var steps = new SeekStep[plan.Steps.Count];
var stepEndSeconds = new double[steps.Length];
var elapsedSeconds = 0d;
for (var currentStepIndex = 0; currentStepIndex < steps.Length; currentStepIndex++)
{
var resolvedStep = plan.Steps[currentStepIndex];
if (resolvedStep?.Clip == null || resolvedStep.Clip.FrameCount <= 0)
{
index = null;
error = new FrameAnimationPlaybackError(
FrameAnimationPlaybackErrorCode.InvalidPlayableData,
"播放期间 Clip 或 Frame 数据失效。");
return false;
}
if (!FrameAnimationValueUtility.IsValidSpeed(resolvedStep.PlaybackSpeed))
{
index = null;
error = new FrameAnimationPlaybackError(
FrameAnimationPlaybackErrorCode.InvalidSpeed,
"播放期间有效速度变为无效值。");
return false;
}
var frameEndSeconds = new double[resolvedStep.Clip.FrameCount];
var stepDurationSeconds = 0d;
for (var currentFrameIndex = 0;
currentFrameIndex < resolvedStep.Clip.FrameCount;
currentFrameIndex++)
{
var frame = resolvedStep.Clip.Frames[currentFrameIndex];
if (frame == null || frame.DurationMs <= 0)
{
index = null;
error = new FrameAnimationPlaybackError(
FrameAnimationPlaybackErrorCode.InvalidPlayableData,
"播放期间 Frame 数据失效或 durationMs 不再有效。");
return false;
}
if (resolvedStep.PlaybackSpeed == 0f)
{
frameEndSeconds[currentFrameIndex] = double.PositiveInfinity;
}
else
{
stepDurationSeconds += frame.DurationMs / 1000d / resolvedStep.PlaybackSpeed;
frameEndSeconds[currentFrameIndex] = stepDurationSeconds;
}
}
if (resolvedStep.PlaybackSpeed == 0f)
{
stepDurationSeconds = double.PositiveInfinity;
}
steps[currentStepIndex] = new SeekStep(
elapsedSeconds,
stepDurationSeconds,
resolvedStep.PlaybackSpeed,
resolvedStep.TerminalEndBehavior,
frameEndSeconds);
elapsedSeconds += stepDurationSeconds;
stepEndSeconds[currentStepIndex] = elapsedSeconds;
}
seekIndex = new SeekIndex(steps, stepEndSeconds, elapsedSeconds);
index = seekIndex;
error = FrameAnimationPlaybackError.None;
return true;
}
private void SeekTerminalLoop(
int terminalStepIndex,
SeekStep terminalStep,
double realSeconds)
{
stepIndex = terminalStepIndex;
if (terminalStep.PlaybackSpeed == 0f ||
double.IsPositiveInfinity(terminalStep.DurationSeconds))
{
frameIndex = 0;
frameElapsedSeconds = 0d;
return;
}
var terminalElapsedSeconds = Math.Max(0d, realSeconds - terminalStep.StartSeconds);
hasCompletedFirstTerminalLoopCycle =
terminalElapsedSeconds + Epsilon >= terminalStep.DurationSeconds;
var localSeconds = terminalElapsedSeconds % terminalStep.DurationSeconds;
if (localSeconds + Epsilon >= terminalStep.DurationSeconds)
{
localSeconds = 0d;
}
SeekWithinStep(terminalStep, localSeconds);
}
private void SeekWithinStep(SeekStep seekStep, double localRealSeconds)
{
if (seekStep.PlaybackSpeed == 0f)
{
frameIndex = 0;
frameElapsedSeconds = 0d;
return;
}
var clampedLocalSeconds = Math.Max(0d, localRealSeconds);
frameIndex = FindFirstBoundaryAfter(seekStep.FrameEndSeconds, clampedLocalSeconds);
if (frameIndex < 0 || frameIndex >= seekStep.FrameEndSeconds.Length)
{
frameIndex = seekStep.FrameEndSeconds.Length - 1;
}
var frameStartRealSeconds = frameIndex == 0
? 0d
: seekStep.FrameEndSeconds[frameIndex - 1];
frameElapsedSeconds = Math.Max(
0d,
(clampedLocalSeconds - frameStartRealSeconds) * seekStep.PlaybackSpeed);
}
private static int FindFirstBoundaryAfter(double[] boundaries, double seconds)
{
var low = 0;
var high = boundaries.Length;
var comparisonSeconds = seconds + Epsilon;
while (low < high)
{
var middle = low + ((high - low) >> 1);
if (boundaries[middle] > comparisonSeconds)
{
high = middle;
}
else
{
low = middle + 1;
}
}
return low;
}
private void ApplyCurrentFrame(Action<Sprite> applySprite)
{
var step = CurrentStep;
@@ -283,5 +486,45 @@ namespace AibisDream.FrameAnimation
applySprite?.Invoke(step.Clip.Frames[frameIndex]?.Sprite);
}
private sealed class SeekIndex
{
internal SeekStep[] Steps { get; }
internal double[] StepEndSeconds { get; }
internal double TotalDurationSeconds { get; }
internal SeekIndex(
SeekStep[] steps,
double[] stepEndSeconds,
double totalDurationSeconds)
{
Steps = steps;
StepEndSeconds = stepEndSeconds;
TotalDurationSeconds = totalDurationSeconds;
}
}
private sealed class SeekStep
{
internal double StartSeconds { get; }
internal double DurationSeconds { get; }
internal float PlaybackSpeed { get; }
internal FrameClipEndBehavior EndBehavior { get; }
internal double[] FrameEndSeconds { get; }
internal SeekStep(
double startSeconds,
double durationSeconds,
float playbackSpeed,
FrameClipEndBehavior endBehavior,
double[] frameEndSeconds)
{
StartSeconds = startSeconds;
DurationSeconds = durationSeconds;
PlaybackSpeed = playbackSpeed;
EndBehavior = endBehavior;
FrameEndSeconds = frameEndSeconds;
}
}
}
}
@@ -18,6 +18,7 @@ namespace AibisDream.FrameAnimation
private FrameAnimationPlaybackSession session;
private FrameAnimationPlaybackHandle activeHandle;
private FrameAnimationPlaybackState state = FrameAnimationPlaybackState.Stopped;
private Action<Sprite> applySpriteCallback;
private bool _skipInitialPlayOnEnable;
private bool hasStartedPlayback;
@@ -100,7 +101,7 @@ namespace AibisDream.FrameAnimation
return;
}
var evaluation = session.Evaluate(Time.deltaTime, speed, ApplySprite);
var evaluation = session.Evaluate(Time.deltaTime, speed, ApplySpriteCallback);
if (evaluation.IsFailed)
{
FailActive(evaluation.Error.Code, evaluation.Error.Message);
@@ -255,7 +256,7 @@ namespace AibisDream.FrameAnimation
session = new FrameAnimationPlaybackSession(plan);
activeHandle = handle;
state = FrameAnimationPlaybackState.Playing;
session.Start(ApplySprite);
session.Start(ApplySpriteCallback);
return handle;
}
@@ -332,7 +333,7 @@ namespace AibisDream.FrameAnimation
return;
}
var evaluation = session.Evaluate(deltaTimeSeconds, speed, ApplySprite);
var evaluation = session.Evaluate(deltaTimeSeconds, speed, ApplySpriteCallback);
if (evaluation.IsFailed)
{
FailActive(evaluation.Error.Code, evaluation.Error.Message);
@@ -399,7 +400,7 @@ namespace AibisDream.FrameAnimation
state = FrameAnimationPlaybackState.Playing;
target = resolvedTarget;
target.Enabled = true;
session.Start(ApplySprite);
session.Start(ApplySpriteCallback);
return handle;
}
@@ -517,12 +518,15 @@ namespace AibisDream.FrameAnimation
private void ApplySprite(Sprite sprite)
{
if (target != null && target.IsValid)
if (target != null && target.IsValid && target.Sprite != sprite)
{
target.Sprite = sprite;
}
}
private Action<Sprite> ApplySpriteCallback =>
applySpriteCallback ??= ApplySprite;
private bool ShouldWaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle)
{
if (playbackHandle.IsCompleted || activeHandle != playbackHandle || session == null)
@@ -559,12 +563,15 @@ namespace AibisDream.FrameAnimation
case FrameClipEndBehavior.Clear:
if (target != null && target.IsValid)
{
target.Enabled = true;
target.Sprite = null;
if (!target.Enabled)
{
target.Enabled = true;
}
ApplySprite(null);
}
break;
case FrameClipEndBehavior.HideTarget:
if (target != null && target.IsValid)
if (target != null && target.IsValid && target.Enabled)
{
target.Enabled = false;
}
@@ -579,11 +586,17 @@ namespace AibisDream.FrameAnimation
case FrameAnimationStopMode.HoldCurrentFrame:
break;
case FrameAnimationStopMode.Clear:
target.Enabled = true;
target.Sprite = null;
if (!target.Enabled)
{
target.Enabled = true;
}
ApplySprite(null);
break;
case FrameAnimationStopMode.HideTarget:
target.Enabled = false;
if (target.Enabled)
{
target.Enabled = false;
}
break;
}
}
@@ -17,6 +17,7 @@ namespace AibisDream.FrameAnimation
private FrameAnimationPlaybackSession session;
private FrameAnimationPlaybackHandle activeHandle;
private FrameAnimationPlaybackState state = FrameAnimationPlaybackState.Stopped;
private Action<Sprite> applySpriteCallback;
private bool skipInitialPlayOnEnable;
private bool timelineControlled;
private FrameClip timelineClip;
@@ -130,7 +131,7 @@ namespace AibisDream.FrameAnimation
session = new FrameAnimationPlaybackSession(plan);
activeHandle = handle;
state = FrameAnimationPlaybackState.Playing;
session.Start(ApplySprite);
session.Start(ApplySpriteCallback);
return handle;
}
@@ -251,12 +252,14 @@ namespace AibisDream.FrameAnimation
timelineClip = value;
session = new FrameAnimationPlaybackSession(plan);
session.Start(ApplySprite);
}
target.Enabled = true;
if (!target.Enabled)
{
target.Enabled = true;
}
state = FrameAnimationPlaybackState.Playing;
var evaluation = session.Seek(Math.Max(0d, timeSeconds), ApplySprite);
var evaluation = session.Seek(Math.Max(0d, timeSeconds), ApplySpriteCallback);
if (evaluation.IsFailed)
{
session = null;
@@ -299,6 +302,13 @@ namespace AibisDream.FrameAnimation
return SampleTimeline(value, Math.Max(0d, duration - 0.000001d));
}
if (value.DefaultEndBehavior == FrameClipEndBehavior.Clear ||
value.DefaultEndBehavior == FrameClipEndBehavior.HideTarget)
{
ApplyEndBehavior(value.DefaultEndBehavior);
return FrameAnimationPlaybackError.None;
}
var sampleError = SampleTimeline(value, GetEffectiveClipDurationSeconds(value));
if (sampleError.Code == FrameAnimationPlaybackErrorCode.None)
{
@@ -310,8 +320,11 @@ namespace AibisDream.FrameAnimation
internal void RestoreTimelineBaseline()
{
if (!timelineControlled || target == null || !target.IsValid) return;
target.Sprite = timelineBaselineSprite;
target.Enabled = timelineBaselineEnabled;
ApplySprite(timelineBaselineSprite);
if (target.Enabled != timelineBaselineEnabled)
{
target.Enabled = timelineBaselineEnabled;
}
session = null;
timelineClip = null;
state = FrameAnimationPlaybackState.Stopped;
@@ -364,7 +377,7 @@ namespace AibisDream.FrameAnimation
return;
}
var evaluation = session.Evaluate(deltaTimeSeconds, speed, ApplySprite);
var evaluation = session.Evaluate(deltaTimeSeconds, speed, ApplySpriteCallback);
if (evaluation.IsFailed) FailActive(evaluation.Error.Code, evaluation.Error.Message);
else if (evaluation.IsCompleted) CompleteNaturally(evaluation.EndBehavior);
}
@@ -386,7 +399,7 @@ namespace AibisDream.FrameAnimation
state = FrameAnimationPlaybackState.Playing;
target = resolvedTarget;
target.Enabled = true;
session.Start(ApplySprite);
session.Start(ApplySpriteCallback);
return handle;
}
@@ -525,9 +538,15 @@ namespace AibisDream.FrameAnimation
private void ApplySprite(Sprite sprite)
{
if (target != null && target.IsValid) target.Sprite = sprite;
if (target != null && target.IsValid && target.Sprite != sprite)
{
target.Sprite = sprite;
}
}
private Action<Sprite> ApplySpriteCallback =>
applySpriteCallback ??= ApplySprite;
private bool ShouldWaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle)
{
return !playbackHandle.IsCompleted && activeHandle == playbackHandle && session != null &&
@@ -556,12 +575,18 @@ namespace AibisDream.FrameAnimation
case FrameClipEndBehavior.Clear:
if (target != null && target.IsValid)
{
target.Enabled = true;
target.Sprite = null;
if (!target.Enabled)
{
target.Enabled = true;
}
ApplySprite(null);
}
break;
case FrameClipEndBehavior.HideTarget:
if (target != null && target.IsValid) target.Enabled = false;
if (target != null && target.IsValid && target.Enabled)
{
target.Enabled = false;
}
break;
}
}
@@ -571,11 +596,17 @@ namespace AibisDream.FrameAnimation
switch (mode)
{
case FrameAnimationStopMode.Clear:
target.Enabled = true;
target.Sprite = null;
if (!target.Enabled)
{
target.Enabled = true;
}
ApplySprite(null);
break;
case FrameAnimationStopMode.HideTarget:
target.Enabled = false;
if (target.Enabled)
{
target.Enabled = false;
}
break;
}
}
@@ -103,7 +103,6 @@ namespace AibisDream.FrameAnimation.Timeline
var clipInfo = clips[activeIndex];
terminalClip = clipInfo.Asset?.FrameClip;
var localTime = playable.GetInput(activeIndex).GetTime();
localTime = NormalizeLoopTime(terminalClip, localTime);
ReportError(currentPlayer.SampleTimeline(terminalClip, localTime), currentPlayer);
return;
}
@@ -177,21 +176,6 @@ namespace AibisDream.FrameAnimation.Timeline
return selected;
}
private static double NormalizeLoopTime(FrameClip clip, double localTime)
{
if (clip == null || clip.DefaultEndBehavior != FrameClipEndBehavior.Loop)
{
return Math.Max(0d, localTime);
}
var duration = FrameAnimationTimelineClip.GetEffectiveDuration(clip);
if (duration <= 0d)
{
return 0d;
}
return Math.Max(0d, localTime) % duration;
}
private void ReleaseCurrentPlayer()
{
if (currentPlayer != null)
@@ -128,6 +128,141 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
Assert.That(session.CurrentFrameIndex, Is.EqualTo(2));
}
[Test]
public void PlaybackSession_SeekSamplesVariableDurationsAtExactBoundaries()
{
var clip = CreateClip(
"Variable",
FrameClipEndBehavior.HoldLastFrame,
1f,
100,
200,
50);
Assert.That(
FrameAnimationResolver.TryResolveClip(
clip,
default,
out var plan,
out var error),
Is.True,
error.ToString());
var session = new FrameAnimationPlaybackSession(plan);
AssertSeekFrame(session, -1d, 0, false);
AssertSeekFrame(session, 0.099d, 0, false);
AssertSeekFrame(session, 0.1d, 1, false);
AssertSeekFrame(session, 0.299d, 1, false);
AssertSeekFrame(session, 0.3d, 2, false);
AssertSeekFrame(session, 0.35d, 2, true);
}
[Test]
public void PlaybackSession_SeekSupportsBackwardMovementAndMultipleStepSpeeds()
{
var intro = CreateClip(
"Intro",
FrameClipEndBehavior.HoldLastFrame,
1f,
100,
100);
var idle = CreateClip(
"Idle",
FrameClipEndBehavior.HoldLastFrame,
1f,
100,
100);
var plan = new ResolvedPlaybackPlan(
"Flow",
true,
new[]
{
new ResolvedPlaybackStep(
intro,
"intro-node",
2f,
FrameClipEndBehavior.HoldLastFrame),
new ResolvedPlaybackStep(
idle,
"idle-node",
0.5f,
FrameClipEndBehavior.Loop)
});
var session = new FrameAnimationPlaybackSession(plan);
AssertSeekFrame(session, 0.31d, 1, false);
Assert.That(session.CurrentClipId, Is.EqualTo(idle.Id));
Assert.That(session.CurrentNodeId, Is.EqualTo("idle-node"));
AssertSeekFrame(session, 0.5d, 0, false);
Assert.That(session.HasCompletedFirstTerminalLoopCycle, Is.True);
AssertSeekFrame(session, 0.05d, 1, false);
Assert.That(session.CurrentClipId, Is.EqualTo(intro.Id));
Assert.That(session.CurrentNodeId, Is.EqualTo("intro-node"));
Assert.That(session.HasCompletedFirstTerminalLoopCycle, Is.False);
}
[Test]
public void PlaybackSession_SeekHandlesLoopCyclesAndZeroSpeed()
{
var loop = CreateClip("Loop", FrameClipEndBehavior.Loop, 1f, 100, 200);
Assert.That(
FrameAnimationResolver.TryResolveClip(
loop,
default,
out var loopPlan,
out var loopError),
Is.True,
loopError.ToString());
var loopSession = new FrameAnimationPlaybackSession(loopPlan);
AssertSeekFrame(loopSession, 0.299d, 1, false);
Assert.That(loopSession.HasCompletedFirstTerminalLoopCycle, Is.False);
AssertSeekFrame(loopSession, 0.3d, 0, false);
Assert.That(loopSession.HasCompletedFirstTerminalLoopCycle, Is.True);
AssertSeekFrame(loopSession, 0.4d, 1, false);
var stopped = CreateClip("Stopped", FrameClipEndBehavior.Loop, 0f, 100, 100);
Assert.That(
FrameAnimationResolver.TryResolveClip(
stopped,
default,
out var stoppedPlan,
out var stoppedError),
Is.True,
stoppedError.ToString());
var stoppedSession = new FrameAnimationPlaybackSession(stoppedPlan);
AssertSeekFrame(stoppedSession, 100d, 0, false);
Assert.That(stoppedSession.HasCompletedFirstTerminalLoopCycle, Is.False);
}
[Test]
public void PlaybackSession_SeekLongClipAppliesOnlyResolvedFrame()
{
var clip = CreateClip(
"Long",
FrameClipEndBehavior.HoldLastFrame,
1f,
Enumerable.Repeat(10, 1000).ToArray());
Assert.That(
FrameAnimationResolver.TryResolveClip(
clip,
default,
out var plan,
out var error),
Is.True,
error.ToString());
var session = new FrameAnimationPlaybackSession(plan);
var applyCount = 0;
var evaluation = session.Seek(9.999d, _ => applyCount++);
Assert.That(evaluation.IsCompleted, Is.False);
Assert.That(session.CurrentFrameIndex, Is.EqualTo(999));
Assert.That(applyCount, Is.EqualTo(1));
}
[Test]
public void EndBehavior_NodeOverrideHasHighestPrecedence()
{
@@ -180,6 +315,22 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
Assert.That(handle.Result.RequestId, Is.EqualTo(42));
}
private static void AssertSeekFrame(
FrameAnimationPlaybackSession session,
double seconds,
int expectedFrameIndex,
bool expectedCompleted)
{
var applyCount = 0;
var evaluation = session.Seek(seconds, _ => applyCount++);
var snapshot = session.Snapshot;
Assert.That(evaluation.IsCompleted, Is.EqualTo(expectedCompleted));
Assert.That(snapshot.FrameIndex, Is.EqualTo(expectedFrameIndex));
Assert.That(snapshot.IsCompleted, Is.EqualTo(expectedCompleted));
Assert.That(applyCount, Is.EqualTo(1));
}
private FrameClip CreateClip(
string id,
FrameClipEndBehavior endBehavior,
@@ -59,6 +59,97 @@ namespace AibisDream.FrameAnimation.Tests.PlayMode
Assert.That(renderer.sprite, Is.SameAs(first));
}
[UnityTest]
public IEnumerator Director_RespectsClipInSpeedMultiplierAndBackwardSeek()
{
var first = CreateSprite(Color.red);
var second = CreateSprite(Color.green);
var third = CreateSprite(Color.blue);
var clip = CreateClip(first, second, third);
var playerObject = Track(new GameObject("Timeline Time Mapping"));
var renderer = playerObject.AddComponent<SpriteRenderer>();
var player = playerObject.AddComponent<FrameClipPlayer>();
var setup = CreateTimeline(player, clip);
setup.Clip.clipIn = 0.1d;
setup.Clip.timeScale = 2d;
setup.Director.Play();
setup.Director.time = 0.025d;
setup.Director.Evaluate();
yield return null;
Assert.That(renderer.sprite, Is.SameAs(second));
setup.Director.time = 0.075d;
setup.Director.Evaluate();
Assert.That(renderer.sprite, Is.SameAs(third));
setup.Director.time = 0d;
setup.Director.Evaluate();
Assert.That(renderer.sprite, Is.SameAs(second));
}
[UnityTest]
public IEnumerator Director_ExtendedLoopClipWrapsAtSourceDuration()
{
var first = CreateSprite(Color.yellow);
var second = CreateSprite(Color.cyan);
var clip = CreateClip(FrameClipEndBehavior.Loop, first, second);
var playerObject = Track(new GameObject("Timeline Loop"));
var renderer = playerObject.AddComponent<SpriteRenderer>();
var player = playerObject.AddComponent<FrameClipPlayer>();
var setup = CreateTimeline(player, clip);
setup.Clip.duration = 0.6d;
setup.Director.Play();
setup.Director.time = 0.25d;
setup.Director.Evaluate();
yield return null;
Assert.That(renderer.sprite, Is.SameAs(first));
setup.Director.time = 0.35d;
setup.Director.Evaluate();
Assert.That(renderer.sprite, Is.SameAs(second));
}
[UnityTest]
public IEnumerator Director_AppliesClearAndHideTerminalBehaviors()
{
var clearObject = Track(new GameObject("Timeline Clear"));
var clearRenderer = clearObject.AddComponent<SpriteRenderer>();
var clearPlayer = clearObject.AddComponent<FrameClipPlayer>();
var clearSetup = CreateTimeline(
clearPlayer,
CreateClip(
FrameClipEndBehavior.Clear,
CreateSprite(Color.magenta)));
clearSetup.Director.Play();
clearSetup.Director.time = clearSetup.Clip.duration;
clearSetup.Director.Evaluate();
yield return null;
Assert.That(clearRenderer.enabled, Is.True);
Assert.That(clearRenderer.sprite, Is.Null);
var hideObject = Track(new GameObject("Timeline Hide"));
var hideRenderer = hideObject.AddComponent<SpriteRenderer>();
var hidePlayer = hideObject.AddComponent<FrameClipPlayer>();
var hideSetup = CreateTimeline(
hidePlayer,
CreateClip(
FrameClipEndBehavior.HideTarget,
CreateSprite(Color.white)));
hideSetup.Director.Play();
hideSetup.Director.time = hideSetup.Clip.duration;
hideSetup.Director.Evaluate();
yield return null;
Assert.That(hideRenderer.enabled, Is.False);
}
private TimelineSetup CreateTimeline(FrameClipPlayer player, FrameClip clip)
{
var timeline = Track(ScriptableObject.CreateInstance<TimelineAsset>());
@@ -72,10 +163,17 @@ namespace AibisDream.FrameAnimation.Tests.PlayMode
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
director.SetGenericBinding(track, player);
return new TimelineSetup(director);
return new TimelineSetup(director, timelineClip);
}
private FrameClip CreateClip(params Sprite[] sprites)
{
return CreateClip(FrameClipEndBehavior.HoldLastFrame, sprites);
}
private FrameClip CreateClip(
FrameClipEndBehavior endBehavior,
params Sprite[] sprites)
{
var clip = Track(ScriptableObject.CreateInstance<FrameClip>());
var frames = new List<FrameAnimationFrame>();
@@ -93,7 +191,7 @@ namespace AibisDream.FrameAnimation.Tests.PlayMode
"Timeline",
frames,
1f,
FrameClipEndBehavior.HoldLastFrame);
endBehavior);
return clip;
}
@@ -116,12 +214,14 @@ namespace AibisDream.FrameAnimation.Tests.PlayMode
private readonly struct TimelineSetup
{
internal TimelineSetup(PlayableDirector director)
internal TimelineSetup(PlayableDirector director, TimelineClip clip)
{
Director = director;
Clip = clip;
}
internal PlayableDirector Director { get; }
internal TimelineClip Clip { get; }
}
}
}