summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/CharacterArt.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/CharacterArt.fs')
-rw-r--r--src/LivingVillage.Desktop/CharacterArt.fs242
1 files changed, 242 insertions, 0 deletions
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