From 328760853c15c44d1de915d6c88e0dfd7ddc5af6 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 29 Sep 2026 10:38:53 +0800 Subject: p72: 角色行走四向动画帧提升(6 走 + 2 待机) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CharacterArt 运行时点阵图集从 4 走 + 2 待机扩为 6 走(新增左右低过渡步 4/5,由 1/2 镜像)+ 2 待机,格数 *8、图集 5120x48;VillageArt 帧循环 %6、characterSourceRectangle *8、characterAtlasWidth 5120。同步 P45/Prototype 帧断言、新增 P72 帧表完整性回归、脚本与素材重新生成、维护说明动画语义。白天正午村庄行走实跑证据落 docs/evidence/p72-*。Desktop 343/343、Kernel 120/120、digest 与 p70 一致。 --- .../LivingVillage.Desktop.Tests.fsproj | 1 + .../P45CharacterAnimTests.fs | 18 ++++++---- .../P72CharacterWalkTests.fs | 40 +++++++++++++++++++++ src/LivingVillage.Desktop.Tests/PrototypeTests.fs | 36 ++++++++++--------- .../Assets/jiangnan-characters.png | Bin 5585 -> 6911 bytes src/LivingVillage.Desktop/CharacterArt.fs | 31 ++++++++++------ src/LivingVillage.Desktop/Game.fs | 11 +++++- src/LivingVillage.Desktop/VillageArt.fs | 24 ++++++++----- 8 files changed, 119 insertions(+), 42 deletions(-) create mode 100644 src/LivingVillage.Desktop.Tests/P72CharacterWalkTests.fs (limited to 'src') diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index a736a83..432cd03 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -43,6 +43,7 @@ + diff --git a/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs b/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs index eba03aa..b9241ad 100644 --- a/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs +++ b/src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs @@ -5,7 +5,7 @@ open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop -/// P45 角色动画帧升级回归:四向 x 6 帧原创点阵、帧序列确定性、行走推进、停止回 idle。 +/// P45 角色动画帧升级回归:四向 x 8 帧(6 走 + 2 待机)原创点阵、帧序列确定性、行走推进、停止回 idle。 [] type P45CharacterAnimTests () = @@ -76,16 +76,18 @@ type P45CharacterAnimTests () = [ 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 帧源格逐一不同且按序排列。 + // 同向 8 帧源格逐一不同且按序排列。 let frameXs = [ VillageArt.WalkFrame VillageArt.FrameOne VillageArt.WalkFrame VillageArt.FrameTwo VillageArt.WalkFrame VillageArt.FrameThree VillageArt.WalkFrame VillageArt.FrameFour + VillageArt.WalkFrame VillageArt.FrameFive + VillageArt.WalkFrame VillageArt.FrameSix 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 8 (frameXs |> List.distinct |> List.length) "同向 8 帧源格须互不同" assertEqualMsg frameXs (List.sort frameXs) "帧源格须按帧序递增" [] @@ -95,18 +97,22 @@ type P45CharacterAnimTests () = 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 循环" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameFive) (VillageArt.characterFrame true 16L) "走 16" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameSix) (VillageArt.characterFrame true 20L) "走 20" + assertEqualMsg (VillageArt.WalkFrame VillageArt.FrameOne) (VillageArt.characterFrame true 24L) "走 24 循环" // 停止:无论 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) + [ 0L; 4L; 8L; 12L; 16L; 20L ] |> 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 "方向->帧序确定" + VillageArt.WalkFrame VillageArt.FrameFour + VillageArt.WalkFrame VillageArt.FrameFive + VillageArt.WalkFrame VillageArt.FrameSix ] north "方向->帧序确定" for direction in [ VillageArt.SouthFacing; VillageArt.WestFacing; VillageArt.EastFacing ] do assertEqualMsg north (framesFor direction) "四向帧序一致" diff --git a/src/LivingVillage.Desktop.Tests/P72CharacterWalkTests.fs b/src/LivingVillage.Desktop.Tests/P72CharacterWalkTests.fs new file mode 100644 index 0000000..781d22c --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P72CharacterWalkTests.fs @@ -0,0 +1,40 @@ +namespace LivingVillage.Desktop.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Desktop + +/// P72 回归:角色行走帧表完整性——四向各 8 格(6 走 + 2 待机), +/// 六张行走帧两两可见区分(含左右过渡步),且与待机帧不同。 +[] +type P72CharacterWalkTests () = + + let letters variant direction frame = CharacterArt.frameLetters variant direction frame + + 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) + + [] + member _.WalkFrameTableHasSixDistinctPosesPerDirection () = + Assert.AreEqual(8, CharacterArt.frameCount) + Assert.AreEqual(6, CharacterArt.walkFrameCount) + Assert.AreEqual(2, CharacterArt.idleFrameCount) + for variant in 0 .. CharacterArt.variantCount - 1 do + for direction in 0 .. CharacterArt.directionCount - 1 do + let walk = [ 0 .. CharacterArt.walkFrameCount - 1 ] |> List.map (letters variant direction) + // 6 张行走帧两两必须有可见差异(不允许重复姿态)。 + for i in 0 .. walk.Length - 1 do + for j in i + 1 .. walk.Length - 1 do + Assert.IsTrue( + cellDiff walk.[i] walk.[j] >= 4, + sprintf "v%d d%d 走帧 %d 与 %d 姿态重复" variant direction i j) + // 行走与待机必须不同。 + let idle = letters variant direction CharacterArt.walkFrameCount + Assert.IsTrue(cellDiff walk.[0] idle >= 4, "行走帧与待机帧相同") + + [] + member _.AtlasStridePlacesSixWalkThenTwoIdleCells () = + Assert.AreEqual(5120, CharacterArt.atlasWidth) + Assert.AreEqual(5120, VillageArt.characterAtlasWidth) + let stride = VillageArt.characterAtlasWidth / 32 / (5 * 4) + Assert.AreEqual(8, stride) diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs index 61fda7d..68e5b93 100644 --- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs +++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs @@ -326,7 +326,7 @@ type PrototypeTests () = Assert.IsTrue(first.PanelY + first.PanelHeight <= 720) [] - member _.NpcVariantsAreStableAndAnimationCoversFourDirectionsAndFourFrames () = + member _.NpcVariantsAreStableAndAnimationCoversFourDirectionsAndSixFrames () = let sameNpc = npcVisualVariant (NpcId 7) let repeatedNpc = npcVisualVariant (NpcId 7) let otherNpc = npcVisualVariant (NpcId 8) @@ -345,7 +345,9 @@ type PrototypeTests () = Assert.AreEqual(FrameTwo, (VillageArt.animationFrame 4L)) Assert.AreEqual(FrameThree, (VillageArt.animationFrame 8L)) Assert.AreEqual(FrameFour, (VillageArt.animationFrame 12L)) - Assert.AreEqual(FrameOne, (VillageArt.animationFrame 16L)) + Assert.AreEqual(FrameFive, (VillageArt.animationFrame 16L)) + Assert.AreEqual(FrameSix, (VillageArt.animationFrame 20L)) + Assert.AreEqual(FrameOne, (VillageArt.animationFrame 24L)) [] member _.VillagersUseFourDistinctVariantsDistinctFromTheAvatar () = @@ -363,28 +365,27 @@ type PrototypeTests () = rect.X) |> Set.ofList Assert.AreEqual(4, variantXes.Count) - Assert.AreEqual(3840, VillageArt.characterAtlasWidth) + Assert.AreEqual(5120, VillageArt.characterAtlasWidth) [] - member _.WalkAnimationCoversFourFramesDeterministically () = - // 16-tick cycle, new frame every 4 sim ticks, no wall-clock dependency. + member _.WalkAnimationCoversSixFramesDeterministically () = + // 24-tick cycle, new frame every 4 sim ticks, no wall-clock dependency. let frames = [ 0L; 4L; 8L; 12L; 16L; 20L ] |> List.map VillageArt.animationFrame - Assert.AreEqual([ FrameOne; FrameTwo; FrameThree; FrameFour; FrameOne; FrameTwo ], frames) + Assert.AreEqual([ FrameOne; FrameTwo; FrameThree; FrameFour; FrameFive; FrameSix ], frames) let cycleFrames = - [ 0L; 4L; 8L; 12L ] + [ 0L; 4L; 8L; 12L; 16L; 20L ] |> List.map VillageArt.animationFrame |> List.distinct - Assert.AreEqual([ FrameOne; FrameTwo; FrameThree; FrameFour ], cycleFrames) + Assert.AreEqual([ FrameOne; FrameTwo; FrameThree; FrameFour; FrameFive; FrameSix ], cycleFrames) - // Every frame maps to a distinct atlas cell column across the five variants. // Every frame maps to a distinct atlas cell column across the five variants. let perFrameX = - [ FrameOne; FrameTwo; FrameThree; FrameFour ] + [ FrameOne; FrameTwo; FrameThree; FrameFour; FrameFive; FrameSix ] |> List.map (fun frame -> let rect = characterSourceRectangle { Variant = IndigoRobe; Direction = NorthFacing; Frame = WalkFrame frame } rect.X / 32) - Assert.AreEqual([ 0; 1; 2; 3 ], perFrameX) + Assert.AreEqual([ 0; 1; 2; 3; 4; 5 ], perFrameX) // Idle breath poses occupy the two cells after the walk cycle for each (variant, direction). let idleXes = @@ -392,7 +393,7 @@ type PrototypeTests () = |> List.map (fun frame -> let rect = characterSourceRectangle { Variant = IndigoRobe; Direction = NorthFacing; Frame = IdleFrame frame } rect.X / 32) - Assert.AreEqual([ 4; 5 ], idleXes) + Assert.AreEqual([ 6; 7 ], idleXes) [] member _.ChineseCopyProvidesFormalSceneTimeMenuAndStatusLabels () = @@ -533,6 +534,9 @@ type PrototypeTests () = Assert.AreEqual(IdleFrame IdleTwo, VillageArt.characterFrame false 24L) Assert.AreEqual(WalkFrame FrameOne, VillageArt.characterFrame true 0L) Assert.AreEqual(WalkFrame FrameTwo, VillageArt.characterFrame true 4L) + Assert.AreEqual(WalkFrame FrameFive, VillageArt.characterFrame true 16L) + Assert.AreEqual(WalkFrame FrameSix, VillageArt.characterFrame true 20L) + Assert.AreEqual(WalkFrame FrameOne, VillageArt.characterFrame true 24L) Assert.AreEqual(VillageArt.characterFrame true 12L, VillageArt.characterFrame true 12L) [] @@ -550,18 +554,18 @@ type PrototypeTests () = Assert.IsTrue(peddler.WalkWindow < farmer.WalkWindow && farmer.WalkWindow < scholar.WalkWindow) Assert.IsTrue(peddler.IdleWindow < farmer.IdleWindow && farmer.IdleWindow < scholar.IdleWindow) - // Same tick, three trade-specific frames (at 12 ticks: Farmer IV, Peddler II, Scholar I). + // Same tick, three trade-specific frames (at 12 ticks: Farmer IV, Peddler VI, Scholar V). let frameAt trade tick = VillageArt.animationFrameFor (VillageArt.rhythmFor trade) tick let frames = [ VillageArt.Farmer; VillageArt.Peddler; VillageArt.Scholar ] |> List.map (fun trade -> frameAt trade 12L) - Assert.AreEqual([ FrameFour; FrameTwo; FrameOne ], frames) + Assert.AreEqual([ FrameFour; FrameSix; FrameFive ], frames) Assert.AreEqual(VillageArt.rhythmFor VillageArt.Scholar, VillageArt.rhythmFor VillageArt.Scholar) // npcSpriteSpec now follows the NPC's own trade cadence. Assert.AreEqual(WalkFrame FrameFour, (VillageArt.npcSpriteSpec (NpcId 0) VillageArt.SouthFacing true 12L).Frame) - Assert.AreEqual(WalkFrame FrameTwo, (VillageArt.npcSpriteSpec (NpcId 1) VillageArt.SouthFacing true 12L).Frame) - Assert.AreEqual(WalkFrame FrameOne, (VillageArt.npcSpriteSpec (NpcId 2) VillageArt.SouthFacing true 12L).Frame) + Assert.AreEqual(WalkFrame FrameSix, (VillageArt.npcSpriteSpec (NpcId 1) VillageArt.SouthFacing true 12L).Frame) + Assert.AreEqual(WalkFrame FrameFive, (VillageArt.npcSpriteSpec (NpcId 2) VillageArt.SouthFacing true 12L).Frame) [] member _.ChimneySmokeFramesAreDeterministicAndThreePhase () = diff --git a/src/LivingVillage.Desktop/Assets/jiangnan-characters.png b/src/LivingVillage.Desktop/Assets/jiangnan-characters.png index dd96e60..8698b39 100644 Binary files a/src/LivingVillage.Desktop/Assets/jiangnan-characters.png and b/src/LivingVillage.Desktop/Assets/jiangnan-characters.png differ diff --git a/src/LivingVillage.Desktop/CharacterArt.fs b/src/LivingVillage.Desktop/CharacterArt.fs index 9d02baf..bfe86ad 100644 --- a/src/LivingVillage.Desktop/CharacterArt.fs +++ b/src/LivingVillage.Desktop/CharacterArt.fs @@ -5,8 +5,9 @@ open Microsoft.Xna.Framework.Graphics /// P45 角色动画帧:全部原创手绘点阵,用像素代码内嵌生成图集,不依赖任何外部素材。 /// 布局与既有 `characterSourceRectangle` 完全一致:cell 32x48, -/// cellIndex = (variant*4 + direction)*6 + frame,共 5 变体 x 4 向 x 6 帧。 -/// direction 顺序 North/South/West/East(0..3),frame 顺序 走1/走2/走3/走4/待机1/待机2(0..5)。 +/// cellIndex = (variant*4 + direction)*8 + frame,共 5 变体 x 4 向 x 8 帧。 +/// direction 顺序 North/South/West/East(0..3), +/// frame 顺序 走1..走6/待机1/待机2(0..7);P72 将行走由 4 帧提升为 6 帧(含左右过渡步)。 module CharacterArt = let sourceWidth = 16 @@ -16,8 +17,8 @@ module CharacterArt = let cellHeight = sourceHeight * pixelScale let variantCount = 5 let directionCount = 4 - let frameCount = 6 - let walkFrameCount = 4 + let frameCount = 8 + let walkFrameCount = 6 let idleFrameCount = 2 let atlasWidth = cellWidth * variantCount * directionCount * frameCount @@ -102,6 +103,13 @@ module CharacterArt = "....BBBBBBBB...." "....RRRRRRRR...." ] + let private mirrorRow (row: string) : string = + System.String(row.ToCharArray() |> Array.rev) + + /// P72 过渡步:走 5/6 为走 2/3 的左右镜像,形成「左-右」对称的六帧循环。 + let private torsoWalk4 : string list = torsoWalk1 |> List.map mirrorRow + let private torsoWalk5 : string list = torsoWalk2 |> List.map mirrorRow + let private torsoIdle1 : string list = [ "...SRRRRRRRRS..." "...SRRRRRRRRS..." @@ -123,7 +131,8 @@ module CharacterArt = "....RRRRRRRR...." "....RRRRRRRR...." ] - /// 腿(6 行,行 18..23):四帧行走跨步姿态各不相同,待机两帧双脚并拢。 + /// 腿(6 行,行 18..23):六帧行走跨步姿态各不相同,待机两帧双脚并拢。 + /// 走 5/6 为走 2/3 的左右镜像(P72 过渡步),使左右跨步对称。 let private legsWalk0 : string list = [ "....RRRRRRRR...." "...RRRRRRRRRR..." @@ -154,7 +163,10 @@ module CharacterArt = ".....RRRRRR....." ".....RRR.RR....." ".....RRR.FF....." - ".....FFF........" ] + ".......FFFF....." ] + + let private legsWalk4 : string list = legsWalk1 |> List.map mirrorRow + let private legsWalk5 : string list = legsWalk2 |> List.map mirrorRow let private legsIdle : string list = [ "....RRRRRRRR...." @@ -165,13 +177,10 @@ module CharacterArt = ".....FFF.FF....." ] let private torsoByFrame : string list list = - [ torsoWalk0; torsoWalk1; torsoWalk2; torsoWalk3; torsoIdle1; torsoIdle2 ] + [ torsoWalk0; torsoWalk1; torsoWalk2; torsoWalk3; torsoWalk4; torsoWalk5; torsoIdle1; torsoIdle2 ] let private legsByFrame : string list list = - [ legsWalk0; legsWalk1; legsWalk2; legsWalk3; legsIdle; legsIdle ] - - let private mirrorRow (row: string) : string = - System.String(row.ToCharArray() |> Array.rev) + [ legsWalk0; legsWalk1; legsWalk2; legsWalk3; legsWalk4; legsWalk5; legsIdle; legsIdle ] let private headByDirection : string list list = [ headNorth; headSouth; headWest; headWest |> List.map mirrorRow ] diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index ff1dfe0..6252117 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -99,6 +99,9 @@ type LivingVillageGame() as this = // P36 证据钩子:复用 sample 脚本的走位/互动序列,但按状态抓 4 帧关键帧 // (靠近→提示出现→对话层打开且世界交互被隔离→关闭离开提示消失)后退出。 let p36ShotMode = Environment.GetEnvironmentVariable("LV_P36_SHOT") = "1" + // P72 evidence hook: with sample recording on, log each saved frame's per-NPC walk + // frame so the PNGs can be correlated with the deterministic 6-frame cycle. + let p72Log = Environment.GetEnvironmentVariable("LV_P72_LOG") = "1" let sampleMode = Environment.GetEnvironmentVariable("LV_AUTOPLAY_SAMPLE") = "1" || p36ShotMode let menuShotMode = Environment.GetEnvironmentVariable("LV_AUTOPLAY_MENU_SHOT") = "1" let daylightAutoplay = Environment.GetEnvironmentVariable("LV_AUTOPLAY_DAYLIGHT") = "1" @@ -2812,7 +2815,13 @@ type LivingVillageGame() as this = recordIndex <- recordIndex + 1 if recordIndex % recordEvery = 0 then System.IO.Directory.CreateDirectory recordDirectory |> ignore - this.SaveBackBuffer(sprintf "%s/%s%04d.png" recordDirectory recordName recordIndex) + let name = sprintf "%s/%s%04d.png" recordDirectory recordName recordIndex + this.SaveBackBuffer(name) + if p72Log then + printfn "p72-frame %s tick=%d" name world.Tick + for npc in world.Npcs do + let spec = VillageArt.npcSpriteSpecAtTarget npc.Id npc.Pos npc.Mind.Target world.Tick + printfn " npc=%A pos=(%.1f,%.1f) frame=%A moving=%b" npc.Id npc.Pos.X npc.Pos.Y spec.Frame (npc.Mind.Target <> npc.Pos) if menuShotMode then this.CaptureMenuShot() if p42ShotMode then diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs index 76abc4a..db594d2 100644 --- a/src/LivingVillage.Desktop/VillageArt.fs +++ b/src/LivingVillage.Desktop/VillageArt.fs @@ -108,13 +108,16 @@ module VillageArt = | FrameTwo | FrameThree | FrameFour + | FrameFive + | FrameSix /// Two-pose standing breath cycle used whenever a character is not moving. type IdleFrame = | IdleOne | IdleTwo - /// A character either walks (four-pose cycle) or breathes in place (two-pose cycle). + /// A character either walks (six-pose cycle, incl. left/right transition steps) or + /// breathes in place (two-pose cycle). type CharacterFrame = | WalkFrame of AnimationFrame | IdleFrame of IdleFrame @@ -363,13 +366,16 @@ module VillageArt = | Peddler -> { WalkWindow = 3L; IdleWindow = 20L; PhaseOffset = 1L } | Scholar -> { WalkWindow = 6L; IdleWindow = 36L; PhaseOffset = 2L } - /// Four-pose walk cycle at a rhythm's window (one pose per WalkWindow sim ticks). + /// Six-pose walk cycle at a rhythm's window (one pose per WalkWindow sim ticks). + /// P72: four strides became six by adding left/right transition steps. let animationFrameFor (rhythm: WalkRhythm) (tick: int64) : AnimationFrame = - match positiveModulo64 4L (tick / rhythm.WalkWindow + rhythm.PhaseOffset) with + match positiveModulo64 6L (tick / rhythm.WalkWindow + rhythm.PhaseOffset) with | 0L -> FrameOne | 1L -> FrameTwo | 2L -> FrameThree - | _ -> FrameFour + | 3L -> FrameFour + | 4L -> FrameFive + | _ -> FrameSix /// Two-pose standing breath cycle at a rhythm's IdleWindow, fully deterministic. let idleFrameFor (rhythm: WalkRhythm) (tick: int64) : IdleFrame = @@ -454,9 +460,11 @@ module VillageArt = | WalkFrame FrameTwo -> 1 | WalkFrame FrameThree -> 2 | WalkFrame FrameFour -> 3 - | IdleFrame IdleOne -> 4 - | IdleFrame IdleTwo -> 5 - let cellIndex = (variantIndex * 4 + directionIndex) * 6 + frameIndex + | WalkFrame FrameFive -> 4 + | WalkFrame FrameSix -> 5 + | IdleFrame IdleOne -> 6 + | IdleFrame IdleTwo -> 7 + let cellIndex = (variantIndex * 4 + directionIndex) * 8 + frameIndex Microsoft.Xna.Framework.Rectangle(cellIndex * 32, 0, 32, 48) let private rgba (r: int) (g: int) (b: int) (a: int) : XnaColor = XnaColor(r, g, b, a) @@ -566,7 +574,7 @@ module VillageArt = let worldTileCount = 32 let worldAtlasWidth = Sim.tilePixels * worldTileCount let worldAtlasHeight = Sim.tilePixels - let characterAtlasWidth = 32 * 5 * 4 * 6 + let characterAtlasWidth = 32 * 5 * 4 * 8 let characterAtlasHeight = 48 let interiorAtlasWidth = 392 let interiorAtlasHeight = 96 -- cgit v1.2.3