diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:46:21 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:46:21 +0800 |
| commit | 997d2ae917a04baf77f99349ed00ea5df8892cfc (patch) | |
| tree | 95ff5cc13cd4f1425d06782d4ef4bd2795511a72 /src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs | |
| parent | f8ef6b68167b122eaeaa400db41c5d8481c9baa9 (diff) | |
| download | living-village-997d2ae917a04baf77f99349ed00ea5df8892cfc.tar.gz | |
p45: 角色动画帧升级 — 四向x(走4+待机2)原创点阵内嵌图集, 行走/停止/idle 接入渲染
Diffstat (limited to 'src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs b/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs new file mode 100644 index 0000000..eba03aa --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs @@ -0,0 +1,112 @@ +namespace LivingVillage.Desktop.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim +open LivingVillage.Desktop + +/// P45 角色动画帧升级回归:四向 x 6 帧原创点阵、帧序列确定性、行走推进、停止回 idle。 +[<TestClass>] +type P45CharacterAnimTests () = + + let assertEqualMsg (expected: 'a) (actual: 'a) (message: string) = + Assert.IsTrue((expected = actual), sprintf "%s (expected %A, actual %A)" message expected actual) + + [<TestMethod>] + member _.EveryCharacterFrameIsAWellFormedDotMatrix () = + for variant in 0 .. CharacterArt.variantCount - 1 do + for direction in 0 .. CharacterArt.directionCount - 1 do + for frame in 0 .. CharacterArt.frameCount - 1 do + let rows = CharacterArt.frameLetters variant direction frame + assertEqualMsg CharacterArt.sourceHeight rows.Length (sprintf "v%d d%d f%d 行数" variant direction frame) + for row in rows do + assertEqualMsg CharacterArt.sourceWidth row.Length (sprintf "v%d d%d f%d 行宽" variant direction frame) + Assert.IsTrue(rows |> List.exists (fun r -> r.Contains "S"), "角色须有皮肤像素") + + [<TestMethod>] + member _.WalkFramesAndIdleFramesDifferVisibly () = + let cellDiff (a: string list) (b: string list) : int = + List.zip a b + |> List.sumBy (fun (ra, rb) -> (Seq.zip ra rb |> Seq.filter (fun (x, y) -> x <> y) |> Seq.length)) + for variant in 0 .. CharacterArt.variantCount - 1 do + for direction in 0 .. CharacterArt.directionCount - 1 do + let frames = [ 0 .. CharacterArt.frameCount - 1 ] |> List.map (CharacterArt.frameLetters variant direction) + // 四帧行走两两相邻必须有可见差异。 + for i in 0 .. CharacterArt.walkFrameCount - 2 do + Assert.IsTrue(cellDiff frames.[i] frames.[i + 1] >= 4, sprintf "走帧 %d->%d 差异不足" i (i + 1)) + // 待机两帧(呼吸)必须不同。 + let idle1 = CharacterArt.frameLetters variant direction (CharacterArt.walkFrameCount) + let idle2 = CharacterArt.frameLetters variant direction (CharacterArt.walkFrameCount + 1) + Assert.IsTrue(cellDiff idle1 idle2 >= 4, "待机两帧差异不足") + // 行走帧与待机帧必须不同。 + Assert.IsTrue(cellDiff frames.[0] idle1 >= 4, "行走帧与待机帧相同") + + [<TestMethod>] + member _.FourDirectionsAreVisuallyDistinct () = + for variant in 0 .. CharacterArt.variantCount - 1 do + for frame in 0 .. CharacterArt.frameCount - 1 do + let heads = + [ 0 .. CharacterArt.directionCount - 1 ] + |> List.map (fun d -> (CharacterArt.frameLetters variant d frame) |> List.take 10) + Assert.IsTrue(heads.[0] <> heads.[1], "North 与 South 头部须不同") + Assert.IsTrue(heads.[2] <> heads.[3], "West 与 East 头部须不同") + Assert.IsTrue(heads.[0] <> heads.[2], "North 与 West 头部须不同") + + [<TestMethod>] + member _.FiveVariantsHaveDistinctPalettes () = + let palettes = [ 0 .. CharacterArt.variantCount - 1 ] |> List.map CharacterArt.variantColors + assertEqualMsg CharacterArt.variantCount (palettes |> List.distinct |> List.length) "5 个变体配色须互不相同" + + [<TestMethod>] + member _.AtlasDimensionsMatchTheSourceRectangleContract () = + assertEqualMsg 32 CharacterArt.cellWidth "cell 宽" + assertEqualMsg 48 CharacterArt.cellHeight "cell 高" + assertEqualMsg VillageArt.characterAtlasWidth CharacterArt.atlasWidth "图集总宽须与既有契约一致" + + [<TestMethod>] + member _.DirectionMapsToDistinctDeterministicSourceCells () = + let rect variant direction frame = + VillageArt.characterSourceRectangle + { Variant = variant; Direction = direction; Frame = frame } + for direction in [ VillageArt.NorthFacing; VillageArt.SouthFacing; VillageArt.WestFacing; VillageArt.EastFacing ] do + let a = rect VillageArt.IndigoRobe direction (VillageArt.WalkFrame VillageArt.FrameOne) + let b = rect VillageArt.IndigoRobe direction (VillageArt.WalkFrame VillageArt.FrameOne) + assertEqualMsg a b "同一输入的源矩形必须确定" + let xs = + [ VillageArt.NorthFacing; VillageArt.SouthFacing; VillageArt.WestFacing; VillageArt.EastFacing ] + |> List.map (fun d -> (rect VillageArt.IndigoRobe d (VillageArt.WalkFrame VillageArt.FrameOne)).X) + assertEqualMsg 4 (xs |> List.distinct |> List.length) "四向必须落在不同源格" + // 同向 6 帧源格逐一不同且按序排列。 + let frameXs = + [ VillageArt.WalkFrame VillageArt.FrameOne + VillageArt.WalkFrame VillageArt.FrameTwo + VillageArt.WalkFrame VillageArt.FrameThree + VillageArt.WalkFrame VillageArt.FrameFour + VillageArt.IdleFrame VillageArt.IdleOne + VillageArt.IdleFrame VillageArt.IdleTwo ] + |> List.map (fun f -> (rect VillageArt.IndigoRobe VillageArt.SouthFacing f).X) + assertEqualMsg 6 (frameXs |> List.distinct |> List.length) "同向 6 帧源格须互不同" + assertEqualMsg frameXs (List.sort frameXs) "帧源格须按帧序递增" + + [<TestMethod>] + member _.WalkingAdvancesFramesAndStoppingReturnsToIdle () = + // 行走:帧随 tick 按 WalkWindow 推进并循环。 + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameOne) (VillageArt.characterFrame true 0L) "走 0" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameTwo) (VillageArt.characterFrame true 4L) "走 4" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameThree) (VillageArt.characterFrame true 8L) "走 8" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameFour) (VillageArt.characterFrame true 12L) "走 12" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameOne) (VillageArt.characterFrame true 16L) "走 16 循环" + // 停止:无论 tick,回 idle 两帧呼吸循环。 + assertEqualMsg (VillageArt.IdleFrame VillageArt.IdleOne) (VillageArt.characterFrame false 0L) "停 0" + assertEqualMsg (VillageArt.IdleFrame VillageArt.IdleTwo) (VillageArt.characterFrame false 24L) "停 24" + assertEqualMsg (VillageArt.IdleFrame VillageArt.IdleOne) (VillageArt.characterFrame false 9999L) "停任意 tick 仍在 idle" + // 玩家行走规格:方向 + 是否移动 + tick 决定帧,四向共享同一帧序(方向只换源格)。 + let framesFor direction = + [ 0L; 4L; 8L; 12L ] |> List.map (fun t -> (VillageArt.avatarSpriteSpec direction true t).Frame) + let north = framesFor VillageArt.NorthFacing + assertEqualMsg [ VillageArt.WalkFrame VillageArt.FrameOne + VillageArt.WalkFrame VillageArt.FrameTwo + VillageArt.WalkFrame VillageArt.FrameThree + VillageArt.WalkFrame VillageArt.FrameFour ] north "方向->帧序确定" + for direction in [ VillageArt.SouthFacing; VillageArt.WestFacing; VillageArt.EastFacing ] do + assertEqualMsg north (framesFor direction) "四向帧序一致" |
