diff options
Diffstat (limited to 'src/LivingVillage.Desktop')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 149 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj | 21 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Program.fs | 9 |
3 files changed, 179 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs new file mode 100644 index 0000000..960a8c6 --- /dev/null +++ b/src/LivingVillage.Desktop/Game.fs @@ -0,0 +1,149 @@ +namespace LivingVillage.Desktop + +open System +open Microsoft.Xna.Framework +open Microsoft.Xna.Framework.Graphics +open Microsoft.Xna.Framework.Input +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim + +type TileKind = + | Grass = 0 + | Path = 1 + | Water = 2 + | Flower = 3 + +module MapGen = + let generate (width: int) (height: int) : TileKind[,] = + let rnd = Random(20260918) + let map = Array2D.zeroCreate<TileKind> width height + for y in 0 .. height - 1 do + for x in 0 .. width - 1 do + let border = x = 0 || y = 0 || x = width - 1 || y = height - 1 + map.[x, y] <- + if border then TileKind.Water + elif rnd.NextDouble() < 0.04 then TileKind.Flower + else TileKind.Grass + let midX = width / 2 + let midY = height / 2 + for x in 1 .. width - 2 do + map.[x, midY] <- TileKind.Path + for y in 1 .. height - 2 do + map.[midX, y] <- TileKind.Path + map + +module Atlas = + let tilePixels = 32 + + let build (device: GraphicsDevice) : Texture2D = + let rnd = Random(7) + let c (r: int) (g: int) (b: int) = Color(r, g, b) + let tex = new Texture2D(device, tilePixels * 4, tilePixels) + let colors = Array.zeroCreate<Color> (tilePixels * tilePixels * 4) + for t in 0 .. 3 do + let baseColor, variance, isFlower = + match t with + | 1 -> (c 148 118 84), 12, false + | 2 -> (c 52 92 168), 10, false + | 3 -> (c 56 118 48), 14, true + | _ -> (c 56 118 48), 14, false + for y in 0 .. tilePixels - 1 do + for x in 0 .. tilePixels - 1 do + let jitter = rnd.Next(variance + 1) - variance / 2 + colors.[t * tilePixels * tilePixels + y * tilePixels + x] <- + c (int baseColor.R + jitter) (int baseColor.G + jitter) (int baseColor.B + jitter) + if isFlower then + for _ in 1 .. 6 do + let fx = rnd.Next(tilePixels - 2) + let fy = rnd.Next(tilePixels - 2) + let fc = if rnd.NextDouble() < 0.5 then c 240 220 80 else c 235 240 245 + for dy in 0 .. 1 do + for dx in 0 .. 1 do + colors.[t * tilePixels * tilePixels + (fy + dy) * tilePixels + fx + dx] <- fc + tex.SetData(colors) + tex + +type LivingVillageGame() as this = + inherit Game() + + let graphics = new GraphicsDeviceManager(this) + let mutable spriteBatch = Unchecked.defaultof<SpriteBatch> + let mutable atlas = Unchecked.defaultof<Texture2D> + let mutable pixel = Unchecked.defaultof<Texture2D> + let mutable map = MapGen.generate Sim.mapWidthTiles Sim.mapHeightTiles + let mutable world = Sim.initialWorld () + let mutable camera = Vector2.Zero + let mutable fpsFrames = 0 + let mutable fpsSeconds = 0.0 + + do + graphics.PreferredBackBufferWidth <- 1280 + graphics.PreferredBackBufferHeight <- 720 + graphics.SynchronizeWithVerticalRetrace <- true + this.IsFixedTimeStep <- true + this.TargetElapsedTime <- TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60L) + this.Window.Title <- "Living Village M0" + + member private this.CenterCamera() = + let viewport = this.GraphicsDevice.Viewport + let vw = float32 viewport.Width + let vh = float32 viewport.Height + let worldW = float32 (Sim.mapWidthTiles * Sim.tilePixels) + let worldH = float32 (Sim.mapHeightTiles * Sim.tilePixels) + let half = float32 Sim.tilePixels / 2.0f + let x = MathHelper.Clamp(world.Avatar.Pos.X + half - vw / 2.0f, 0.0f, max 0.0f (worldW - vw)) + let y = MathHelper.Clamp(world.Avatar.Pos.Y + half - vh / 2.0f, 0.0f, max 0.0f (worldH - vh)) + camera <- Vector2(x, y) + + override _.LoadContent() = + spriteBatch <- new SpriteBatch(this.GraphicsDevice) + atlas <- Atlas.build this.GraphicsDevice + pixel <- new Texture2D(this.GraphicsDevice, 1, 1) + pixel.SetData([| Color.White |]) + + override this.Update(gameTime: GameTime) = + let kb = Keyboard.GetState() + if kb.IsKeyDown(Keys.Escape) then this.Exit() + let input = + { MoveX = + (if kb.IsKeyDown(Keys.D) then 1.0f + elif kb.IsKeyDown(Keys.A) then -1.0f + else 0.0f) + MoveY = + (if kb.IsKeyDown(Keys.S) then 1.0f + elif kb.IsKeyDown(Keys.W) then -1.0f + else 0.0f) } + let ts = + { Dt = float32 gameTime.ElapsedGameTime.TotalSeconds + Input = input } + world <- Sim.step ts world + this.CenterCamera() + fpsFrames <- fpsFrames + 1 + fpsSeconds <- fpsSeconds + gameTime.ElapsedGameTime.TotalSeconds + if fpsSeconds >= 1.0 then + let fps = float fpsFrames / fpsSeconds + fpsFrames <- 0 + fpsSeconds <- 0.0 + this.Window.Title <- $"Living Village M0 | fps {fps:F1} | tick {world.Tick}" + printfn $"fps={fps:F1} tick={world.Tick} pos=({world.Avatar.Pos.X:F0},{world.Avatar.Pos.Y:F0})" + + override this.Draw(gameTime: GameTime) = + this.GraphicsDevice.Clear(Color.CornflowerBlue) + let viewport = this.GraphicsDevice.Viewport + let camX = int camera.X + let camY = int camera.Y + let tx0 = max 0 (camX / Sim.tilePixels) + let ty0 = max 0 (camY / Sim.tilePixels) + let tx1 = min (Sim.mapWidthTiles - 1) ((camX + viewport.Width) / Sim.tilePixels + 1) + let ty1 = min (Sim.mapHeightTiles - 1) ((camY + viewport.Height) / Sim.tilePixels + 1) + spriteBatch.Begin() + for y in ty0 .. ty1 do + for x in tx0 .. tx1 do + let src = Rectangle(int map.[x, y] * Sim.tilePixels, 0, Sim.tilePixels, Sim.tilePixels) + let dst = + Rectangle(x * Sim.tilePixels - camX, y * Sim.tilePixels - camY, Sim.tilePixels, Sim.tilePixels) + spriteBatch.Draw(atlas, dst, src, Color.White) + let avatarDst = + Rectangle(int world.Avatar.Pos.X - camX, int world.Avatar.Pos.Y - camY, Sim.tilePixels, Sim.tilePixels) + spriteBatch.Draw(pixel, avatarDst, Color.IndianRed) + spriteBatch.End() diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj new file mode 100644 index 0000000..9af59ac --- /dev/null +++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj @@ -0,0 +1,21 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net8.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Compile Include="Game.fs" /> + <Compile Include="Program.fs" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.5.1" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\LivingVillage.Kernel\LivingVillage.Kernel.fsproj" /> + </ItemGroup> + +</Project> diff --git a/src/LivingVillage.Desktop/Program.fs b/src/LivingVillage.Desktop/Program.fs new file mode 100644 index 0000000..0e2adcf --- /dev/null +++ b/src/LivingVillage.Desktop/Program.fs @@ -0,0 +1,9 @@ +namespace LivingVillage.Desktop + +module Program = + + [<EntryPoint>] + let main _ = + use game = new LivingVillageGame() + game.Run() + 0 |
