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 | |
| parent | f8ef6b68167b122eaeaa400db41c5d8481c9baa9 (diff) | |
| download | living-village-997d2ae917a04baf77f99349ed00ea5df8892cfc.tar.gz | |
p45: 角色动画帧升级 — 四向x(走4+待机2)原创点阵内嵌图集, 行走/停止/idle 接入渲染
Diffstat (limited to 'src')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj | 1 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/P45CharacterAnimTests.fs | 112 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/CharacterArt.fs | 242 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 70 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj | 1 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/VillageArt.fs | 4 |
6 files changed, 429 insertions, 1 deletions
diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index 68ec033..dd51f36 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -24,6 +24,7 @@ <Compile Include="P42LaunchFxTests.fs" /> <Compile Include="P43HudTests.fs" /> <Compile Include="P44DialogTests.fs" /> + <Compile Include="P45CharacterAnimTests.fs" /> <Compile Include="SampleTests.fs" /> </ItemGroup> 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) "四向帧序一致" diff --git a/src/LivingVillage.Desktop/CharacterArt.fs b/src/LivingVillage.Desktop/CharacterArt.fs new file mode 100644 index 0000000..9d02baf --- /dev/null +++ b/src/LivingVillage.Desktop/CharacterArt.fs @@ -0,0 +1,242 @@ +namespace LivingVillage.Desktop + +open Microsoft.Xna.Framework +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)。 +module CharacterArt = + + let sourceWidth = 16 + let sourceHeight = 24 + let pixelScale = 2 + let cellWidth = sourceWidth * pixelScale + let cellHeight = sourceHeight * pixelScale + let variantCount = 5 + let directionCount = 4 + let frameCount = 6 + let walkFrameCount = 4 + let idleFrameCount = 2 + let atlasWidth = cellWidth * variantCount * directionCount * frameCount + + // ---- 点阵字母:. 透明,其余为调色板索引 ---- + // H 头发 S 皮肤 E 眼睛 K 描边 T 草帽 R 外衣 r 外衣暗部 B 腰带 F 鞋 + + /// 头部(10 行)按朝向:North 后脑、South 正面、West 左侧脸、East 右侧脸(镜像)。 + let private headNorth : string list = + [ "................" + "....HHHHHHHH...." + "...HHHHHHHHHH..." + "...HHHHHHHHHH..." + "...HHHHHHHHHH..." + "...HHHHHHHHHH..." + "....HHHHHHHH...." + "....HHHHHHHH...." + ".....HHHHHH....." + "......KKKK......" ] + + let private headSouth : string list = + [ "................" + "....HHHHHHHH...." + "...HHHHHHHHHH..." + "...HSSSSSSSSH..." + "...HSSESSESSH..." + "...HSSSSSSSSH..." + "...HSSSSSSSSH..." + "....SSSSSSSS...." + ".....SSSSSS....." + "......KKKK......" ] + + let private headWest : string list = + [ "................" + "....HHHHHHHH...." + "...HHHHHHHHHH..." + "...HHHSSSSHHH..." + "...HHSESSSHHH..." + "...HHSSSSSHHH..." + "....HHSSSSHH...." + "....HSSSSSSH...." + ".....SSSSS......" + "......KKKK......" ] + + /// 躯干(8 行,行 10..17):走/待机的左右手臂高度不同,形成摆臂。 + let private torsoWalk0 : string list = + [ "...RRRRRRRRRS..." + "...RRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRR..." + "...SRRRRRRRRR..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." ] + + let private torsoWalk1 : string list = + [ "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." ] + + let private torsoWalk2 : string list = + [ "...SRRRRRRRRR..." + "...SRRRRRRRRR..." + "...SRRRRRRRRS..." + "...RRRRRRRRRS..." + "...RRRRRRRRRS..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." ] + + let private torsoWalk3 : string list = + [ "...SRRRRRRRRR..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...RRRRRRRRRS..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." ] + + let private torsoIdle1 : string list = + [ "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." ] + + /// 待机第二帧:手臂收短、腰带整体上提 2 行,读作呼吸起伏。 + let private torsoIdle2 : string list = + [ "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "...SRRRRRRRRS..." + "....RRRRRRRR...." + "....BBBBBBBB...." + "....RRRRRRRR...." + "....RRRRRRRR...." + "....RRRRRRRR...." ] + + /// 腿(6 行,行 18..23):四帧行走跨步姿态各不相同,待机两帧双脚并拢。 + let private legsWalk0 : string list = + [ "....RRRRRRRR...." + "...RRRRRRRRRR..." + "..RRR......RRR.." + "..RRR......RRR.." + "..FFF......FFF.." + "..FFF......FFF.." ] + + let private legsWalk1 : string list = + [ "....RRRRRRRR...." + "....RRRRRRRR...." + ".....RRRRRR....." + ".....RR.RRR....." + ".....FF.RRR....." + "........FFF....." ] + + let private legsWalk2 : string list = + [ "....RRRRRRRR...." + "...RRRRRRRRR...." + "...RRR....RRR..." + "...RRR....RR...." + "...FFF....FF...." + "...FFF.........." ] + + let private legsWalk3 : string list = + [ "....RRRRRRRR...." + "....RRRRRRRR...." + ".....RRRRRR....." + ".....RRR.RR....." + ".....RRR.FF....." + ".....FFF........" ] + + let private legsIdle : string list = + [ "....RRRRRRRR...." + "....RRRRRRRR...." + ".....RRRRRR....." + ".....RRR.RR....." + ".....FFF.FF....." + ".....FFF.FF....." ] + + let private torsoByFrame : string list list = + [ torsoWalk0; torsoWalk1; torsoWalk2; torsoWalk3; 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) + + let private headByDirection : string list list = + [ headNorth; headSouth; headWest; headWest |> List.map mirrorRow ] + + /// 某个 (变体, 朝向, 帧) 的 24 行点阵字母;草帽变体在头部叠加草帽。 + let frameLetters (variantIndex: int) (directionIndex: int) (frameIndex: int) : string list = + let head = headByDirection.[((directionIndex % directionCount) + directionCount) % directionCount] + let torso = torsoByFrame.[((frameIndex % frameCount) + frameCount) % frameCount] + let legs = legsByFrame.[((frameIndex % frameCount) + frameCount) % frameCount] + let composed = head @ torso @ legs + if (variantIndex % variantCount + variantCount) % variantCount = 4 then + composed + |> List.mapi (fun row line -> + if row = 1 || row = 2 then + line |> Seq.map (fun c -> if c = '.' then '.' else 'T') |> System.String.Concat + elif row = 3 then + line.ToCharArray() + |> Array.mapi (fun column c -> if column >= 2 && column <= 13 then 'T' else c) + |> System.String + else line) + else composed + + /// 变体外衣主色 / 暗部 / 腰带;决定 5 个村民的观感差异。 + let variantColors (variantIndex: int) : Color * Color * Color = + match ((variantIndex % variantCount) + variantCount) % variantCount with + | 0 -> Color(66, 84, 148), Color(44, 58, 110), Color(214, 198, 150) // 靛蓝长衫 + | 1 -> Color(178, 132, 66), Color(128, 92, 44), Color(70, 60, 50) // 赭黄马甲 + | 2 -> Color(74, 150, 128), Color(48, 110, 92), Color(232, 214, 160) // 青碧束带 + | 3 -> Color(118, 124, 134), Color(84, 90, 100), Color(58, 62, 70) // 灰帽 + | _ -> Color(146, 96, 70), Color(108, 70, 52), Color(90, 74, 48) // 斗笠农夫 + + let private colorOf (variantIndex: int) (letter: char) : Color option = + let robe, robeShade, belt = variantColors variantIndex + match letter with + | 'H' -> Some(Color(44, 36, 34)) + | 'S' -> Some(Color(234, 194, 152)) + | 'E' -> Some(Color(24, 22, 28)) + | 'K' -> Some(Color(28, 30, 38)) + | 'T' -> Some(Color(216, 190, 120)) + | 'R' -> Some robe + | 'r' -> Some robeShade + | 'B' -> Some belt + | 'F' -> Some(Color(58, 42, 34)) + | _ -> None + + let private cellIndex (variantIndex: int) (directionIndex: int) (frameIndex: int) : int = + (variantIndex * directionCount + directionIndex) * frameCount + frameIndex + + /// 像素点阵 -> 直通 alpha 图集纹理。仅用 SetData,无外部素材。 + let buildAtlas (device: GraphicsDevice) : Texture2D = + let pixels = Array.zeroCreate<Color> (atlasWidth * cellHeight) + for variant in 0 .. variantCount - 1 do + for direction in 0 .. directionCount - 1 do + for frame in 0 .. frameCount - 1 do + let originX = cellIndex variant direction frame * cellWidth + let rows = frameLetters variant direction frame + for y in 0 .. sourceHeight - 1 do + let row = rows.[y] + for x in 0 .. sourceWidth - 1 do + match colorOf variant row.[x] with + | Some color -> + for sy in 0 .. pixelScale - 1 do + for sx in 0 .. pixelScale - 1 do + pixels.[(y * pixelScale + sy) * atlasWidth + originX + x * pixelScale + sx] <- color + | None -> () + let texture = new Texture2D(device, atlasWidth, cellHeight) + texture.SetData pixels + texture diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index 00ae04b..3e39b09 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -316,6 +316,11 @@ type LivingVillageGame() as this = let mutable p44Step = 0 let mutable p44PendingName = "" let mutable p44Pending = false + // P45 证据钩子:LV_P45_SHOT=1 时拍角色 idle / 行走朝下 / 行走朝右 三帧(真实游戏截图)。 + let p45ShotMode = Environment.GetEnvironmentVariable("LV_P45_SHOT") = "1" + let mutable p45Step = 0 + let mutable p45PendingName = "" + let mutable p45Pending = false let mutable autoplayFrames = 0 let mutable flowStep = 0 let mutable flowHold = 0 @@ -894,6 +899,61 @@ type LivingVillageGame() as this = (m5View.Prompt |> Option.defaultValue "<none>") p44Step <- p44Step + 1 + member private this.PrepareP45Shot () = + let baseTick = M6Presentation.resolveStartTick (Some 12.0) false + let setTick (tick: int64) = + world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds } + if not p45Pending then + match p45Step with + | 0 -> + setTick baseTick + world <- + { world with + Avatar = { world.Avatar with Pos = { X = float32 (100 * Sim.tilePixels); Y = float32 (100 * Sim.tilePixels) } } } + avatarFacing <- VillageArt.SouthFacing + avatarMoving <- false + this.CenterCamera() + p45PendingName <- "p45-char-a" + p45Pending <- true + | 1 -> + setTick (baseTick + 4L) + avatarFacing <- VillageArt.SouthFacing + avatarMoving <- true + this.CenterCamera() + p45PendingName <- "p45-char-b" + p45Pending <- true + | 2 -> + setTick (baseTick + 8L) + avatarFacing <- VillageArt.EastFacing + avatarMoving <- true + this.CenterCamera() + p45PendingName <- "p45-char-c" + p45Pending <- true + | _ -> + printfn "p45-shot=done" + this.Exit() + + /// P45 证据:在 `Draw` 末尾落盘,并打印角色目标矩形 / 朝向 / 帧,供裁剪与帧差分析。 + member private this.CaptureP45Shot () = + if p45Pending then + p45Pending <- false + System.IO.Directory.CreateDirectory recordDirectory |> ignore + this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p45PendingName) + let dest = VillageArt.characterDestination world.Avatar.Pos camera + let spec = VillageArt.avatarSpriteSpec avatarFacing avatarMoving world.Tick + printfn + "p45-shot=%s tick=%d facing=%s moving=%b dest=%d,%d,%d,%d frame=%s" + p45PendingName + world.Tick + (sprintf "%A" avatarFacing) + avatarMoving + dest.X + dest.Y + dest.Width + dest.Height + (sprintf "%A" spec.Frame) + p45Step <- p45Step + 1 + /// P20 第三步取证:真走位巡游(Sim.step 驱动)沿桥/路/民居门移动,用于桥面行走帧与昼夜录制。 member private this.RunRiverscapeTour () = if not riverscapeTourWatchStarted then @@ -1254,6 +1314,14 @@ type LivingVillageGame() as this = else this.DispatchMenuInput Confirm elif menu.Page = Playing then this.PrepareP44Shot() + elif p45ShotMode then + if menu.Page = MainMenu then + this.DispatchMenuInput Confirm + elif menu.Page = OccupationSelect then + if menu.Selected <> 0 then this.DispatchMenuInput Down + else this.DispatchMenuInput Confirm + elif menu.Page = Playing then + this.PrepareP45Shot() elif menu.Page = Playing then this.UpdatePlaying gameTime kb pressed pressedAny elif menuShotMode then @@ -1661,6 +1729,8 @@ type LivingVillageGame() as this = this.CaptureP43Shot() if p44ShotMode && menu.Page = Playing then this.CaptureP44Shot() + if p45ShotMode && menu.Page = Playing then + this.CaptureP45Shot() /// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。 member private this.DrawSplash() = diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj index 76e9b06..51fb32e 100644 --- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj +++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj @@ -10,6 +10,7 @@ <Compile Include="ProceduralMap.fs" /> <Compile Include="MapGen.fs" /> <Compile Include="WorldBootstrap.fs" /> + <Compile Include="CharacterArt.fs" /> <Compile Include="VillageArt.fs" /> <Compile Include="ChineseText.fs" /> <Compile Include="CjkGlyphAtlas.fs" /> diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs index 0f176aa..98e4cdf 100644 --- a/src/LivingVillage.Desktop/VillageArt.fs +++ b/src/LivingVillage.Desktop/VillageArt.fs @@ -764,7 +764,9 @@ module VillageArt = selectedGroundAtlasRelativePath () |> Option.map (fun path -> loadTexture device path cc0TileAtlasWidth cc0TileAtlasHeight) { TileAtlas = loadTexture device worldAtlasRelativePath worldAtlasWidth worldAtlasHeight - CharacterAtlas = loadTexture device (selectedCharacterAtlasRelativePath ()) cc0CharacterAtlasWidth cc0CharacterCellHeight + // P45:角色改用原创点阵像素代码内嵌生成的动画图集(5 变体 x 4 向 x 6 帧), + // 不再采样任何外部/占位角色素材;尺寸与既有 characterSourceRectangle 完全一致。 + CharacterAtlas = CharacterArt.buildAtlas device InteriorAtlas = loadTexture device interiorAtlasRelativePath interiorAtlasWidth interiorAtlasHeight Cc0TileAtlas = cc0 } |
