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 (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