├── icon.png
├── sample
├── image.png
├── Connection.otf
├── Connection.spritefont
├── Content.mgcb
├── sample.fsproj
└── Program.fs
├── sample-running.webp
├── .vscode
├── tasks.json
└── launch.json
├── src
├── GameRunner.fs
├── fsharp-gamecore.fsproj
├── Helpers.fs
├── GameModel.fs
└── GameLoop.fs
├── .config
└── dotnet-tools.json
├── .github
└── workflows
│ └── dotnet.yml
├── LICENSE
├── fsharp-gamecore.sln
├── README.md
├── SIL Open Font License.txt
└── .gitignore
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisPritchard/fsharp-gamecore/HEAD/icon.png
--------------------------------------------------------------------------------
/sample/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisPritchard/fsharp-gamecore/HEAD/sample/image.png
--------------------------------------------------------------------------------
/sample-running.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisPritchard/fsharp-gamecore/HEAD/sample-running.webp
--------------------------------------------------------------------------------
/sample/Connection.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisPritchard/fsharp-gamecore/HEAD/sample/Connection.otf
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet build",
9 | "type": "shell",
10 | "group": "build",
11 | "presentation": {
12 | "reveal": "silent"
13 | },
14 | "problemMatcher": "$msCompile"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/sample/Connection.spritefont:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Connection.otf
5 | 50
6 | 0
7 | true
8 |
9 |
10 |
11 |
12 | ~
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/sample/Content.mgcb:
--------------------------------------------------------------------------------
1 |
2 | #----------------------------- Global Properties ----------------------------#
3 |
4 | /outputDir:bin
5 | /intermediateDir:obj
6 | /platform:Windows
7 | /config:
8 | /profile:Reach
9 | /compress:False
10 |
11 | #-------------------------------- References --------------------------------#
12 |
13 |
14 | #---------------------------------- Content ---------------------------------#
15 |
16 | #begin Connection.spritefont
17 | /importer:FontDescriptionImporter
18 | /processor:FontDescriptionProcessor
19 | /processorParam:PremultiplyAlpha=True
20 | /processorParam:TextureFormat=Compressed
21 | /build:Connection.spritefont
22 |
23 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/sample/bin/Debug/netcoreapp2.1/sample.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}/sample",
15 | "console": "internalConsole",
16 | "stopAtEntry": false,
17 | "internalConsoleOptions": "openOnSessionStart"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/GameRunner.fs:
--------------------------------------------------------------------------------
1 | module GameCore.GameRunner
2 |
3 | open GameCore.GameModel
4 | open GameCore.GameLoop
5 |
6 | open Microsoft.Xna.Framework
7 |
8 | /// Entry point to start the game. Takes a config and two
9 | /// methods: one for advancing the model and one to get a view.
10 | let runGame config (advanceModel : RunState -> 'T option -> 'T option) getView =
11 | use loop = new GameLoop<'T> (config, advanceModel, getView)
12 | loop.Run ()
13 |
14 | /// A simplified version of runGame
15 | /// with a specified window size and assets list instead of a full config model
16 | let runWindowedGame windowSize assetsToLoad advanceModel getView =
17 | let config = {
18 | clearColour = Some Color.AliceBlue
19 | resolution = Windowed windowSize
20 | assetsToLoad = assetsToLoad
21 | fpsFont = None
22 | mouseVisible = true
23 | }
24 | runGame config advanceModel getView
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-mgcb": {
6 | "version": "3.8.4",
7 | "commands": [
8 | "mgcb"
9 | ]
10 | },
11 | "dotnet-mgcb-editor": {
12 | "version": "3.8.4",
13 | "commands": [
14 | "mgcb-editor"
15 | ]
16 | },
17 | "dotnet-mgcb-editor-linux": {
18 | "version": "3.8.4",
19 | "commands": [
20 | "mgcb-editor-linux"
21 | ]
22 | },
23 | "dotnet-mgcb-editor-windows": {
24 | "version": "3.8.4",
25 | "commands": [
26 | "mgcb-editor-windows"
27 | ]
28 | },
29 | "dotnet-mgcb-editor-mac": {
30 | "version": "3.8.4",
31 | "commands": [
32 | "mgcb-editor-mac"
33 | ]
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: Build-Release
5 |
6 | on:
7 | push:
8 | branches: ["master"]
9 | pull_request:
10 | branches: ["master"]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v4
20 | with:
21 | dotnet-version: 8.0.x
22 | - name: Restore dependencies
23 | run: dotnet restore
24 | - name: Release Build
25 | run: dotnet build --configuration Release --no-restore
26 | - name: Pack NuGet package
27 | run: dotnet pack --configuration Release --no-build --output nupkgs
28 | - name: Push to NuGet.org
29 | run: dotnet nuget push "nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Christopher Pritchard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/sample/sample.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 |
8 |
9 |
10 | Always
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/fsharp-gamecore.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsharp-gamecore", "src\fsharp-gamecore.fsproj", "{BC6BF893-DDE4-4294-9BD5-E0E6780599E9}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|x64.ActiveCfg = Debug|Any CPU
23 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|x64.Build.0 = Debug|Any CPU
24 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Debug|x86.Build.0 = Debug|Any CPU
26 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|x64.ActiveCfg = Release|Any CPU
29 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|x64.Build.0 = Release|Any CPU
30 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|x86.ActiveCfg = Release|Any CPU
31 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9}.Release|x86.Build.0 = Release|Any CPU
32 | EndGlobalSection
33 | GlobalSection(SolutionProperties) = preSolution
34 | HideSolutionNode = FALSE
35 | EndGlobalSection
36 | GlobalSection(NestedProjects) = preSolution
37 | {BC6BF893-DDE4-4294-9BD5-E0E6780599E9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/src/fsharp-gamecore.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 |
7 |
8 | true
9 | 1.0.0.1
10 | fsharp-gamecore
11 | FSharp GameCore
12 | Chris Pritchard
13 | A fleshed-out game loop from MonoGame with supporting classes, intended to be
14 | used as the core loop of larger games. 2D only. All types and methods exposed are
15 | immutable and functional, just like the lambda gods intended.
16 | monogame;fsharp
17 | icon.png
18 | LICENSE
19 | https://github.com/ChrisPritchard/fsharp-gamecore
20 | README.md
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | $(GitCommitHash)
43 | unknown
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Helpers.fs:
--------------------------------------------------------------------------------
1 | module GameCore.Helpers
2 |
3 | open Microsoft.Xna.Framework
4 | open Microsoft.Xna.Framework.Input
5 | open GameModel
6 | open System.Text
7 | open Microsoft.Xna.Framework.Graphics
8 |
9 | let internal asVector2 (x, y) = new Vector2(float32 x, float32 y)
10 |
11 | let internal asRectangle (x, y, width, height) =
12 | new Rectangle (x, y, width, height)
13 |
14 | let internal asFloatRect (x, y, width, height) =
15 | float32 x, float32 y, float32 width, float32 height
16 |
17 | let internal updateKeyboardInfo (keyboard: KeyboardState) (existing: KeyboardInfo) =
18 | let pressed = keyboard.GetPressedKeys() |> Set.ofArray
19 | {
20 | pressed = pressed |> Set.toList
21 | keysDown = Set.difference pressed (existing.pressed |> Set.ofList) |> Set.toList
22 | keysUp = Set.difference (existing.pressed |> Set.ofList) pressed |> Set.toList
23 | }
24 |
25 | let internal getMouseInfo (mouse: MouseState) =
26 | {
27 | position = mouse.X, mouse.Y
28 | pressed = mouse.LeftButton = ButtonState.Pressed, mouse.RightButton = ButtonState.Pressed
29 | }
30 |
31 | let internal lineSpacingRatio = 1.f/4.f
32 |
33 | let internal measureText (font: SpriteFont) (text: string) =
34 | let mutable asMeasured = font.MeasureString text
35 | let lineGap = float32 font.LineSpacing * lineSpacingRatio
36 | asMeasured.X, asMeasured.Y - lineGap
37 |
38 | let internal measureParagraph (font: SpriteFont) (sb: StringBuilder) =
39 | let mutable asMeasured = font.MeasureString sb
40 | let lineGap = float32 font.LineSpacing * lineSpacingRatio
41 | asMeasured.X, asMeasured.Y - lineGap
42 |
43 | let internal stringBuilder lines =
44 | let rec addLines (sb: StringBuilder) =
45 | function
46 | | [] -> sb
47 | | [s: string] -> sb.Append s
48 | | (s: string)::rest -> addLines (sb.AppendLine s) rest
49 | addLines (new StringBuilder ()) lines
50 |
51 | let internal getScaleAndPosition (mx, my) lineCount (x, y) fontSize origin =
52 | let h =
53 | if lineCount = 1 then float32 fontSize
54 | else
55 | float32 fontSize +
56 | ((float32 fontSize / 3.f) * 4.f) * float32 (lineCount - 1)
57 |
58 | let scale = h / my
59 | let w = mx * scale
60 |
61 | let x, y = float32 x, float32 y
62 | let fx, fy =
63 | match origin with
64 | | TopLeft -> x, y
65 | | Left -> x, y - (h / 2.f)
66 | | BottomLeft -> x, y - h
67 | | Top -> x - (w / 2.f), y
68 | | Centre -> x - (w / 2.f), y - (h / 2.f)
69 | | Bottom -> x - (w / 2.f), y - h
70 | | TopRight -> x - w, y
71 | | Right -> x - w, y - (h / 2.f)
72 | | BottomRight -> x - w, y - h
73 |
74 | scale, fx, fy
--------------------------------------------------------------------------------
/sample/Program.fs:
--------------------------------------------------------------------------------
1 |
2 | open GameCore.GameModel
3 | open GameCore.GameRunner
4 |
5 | open Microsoft.Xna.Framework
6 | open Microsoft.Xna.Framework.Input
7 |
8 | []
9 | let main _ =
10 |
11 | let (width, height) = 640, 480
12 |
13 | let advanceModel runState model =
14 | match model with
15 | // The model will start as none, so this is where the initial model is specified
16 | | None -> Some 0
17 | // if None is returned from an updateModel function, this will exit the application
18 | | _ when wasJustPressed Keys.Escape runState -> None
19 | // business as usual, update the model due to state changes or time etc.
20 | | Some n -> Some <| n + 1
21 |
22 | // should return a list of GameCore.Model.ViewArtifacts
23 | let getView (runState: RunState) model =
24 | let (centrex, centrey) = width/2, height/2
25 | [
26 | // setting a red square in the middle of the screen
27 | yield Colour ((centrex - 100, centrey - 40, 200, 80), Color.Red)
28 |
29 | // rendering the model (an ever increasing int) centre screen
30 | yield Text ("connection", sprintf "%i" model, (centrex, centrey), 40, Centre, Color.White)
31 |
32 | // rendering some multiline text in the top left of the screen
33 | let sampleParagraph = [
34 | "this"
35 | "is"
36 | "some"
37 | "sample"
38 | "text"
39 | ]
40 | yield Paragraph ("connection", sampleParagraph, (20, 20), 20, TopLeft, Color.White)
41 |
42 | // rendering text in all different alignments
43 | let px, py = 100, 300
44 | yield! [
45 | -50,-50,"TL",TopLeft
46 | -50,0,"L",Left
47 | -50,50,"BL",BottomLeft
48 | 0,-50,"T",Top
49 | 0,0,"C",Centre
50 | 0,50,"B",Bottom
51 | 50,-50,"TR",TopRight
52 | 50,0,"R",Right
53 | 50,50,"BR",BottomRight
54 | ] |> List.collect (fun (dx, dy, text, origin) -> [
55 | yield Text ("connection", text, (px + dx, py + dy), 18, origin, Color.White)
56 | yield Colour ((px + dx, py + dy, 2, 2), Color.Red)
57 | ])
58 |
59 | // rendering a moving image at the bottom of the screen
60 | let x = centrex + (int runState.elapsed / 10 % (centrex - 50))
61 | yield Image ("sample", (x, centrey + 80, 80, 80), Color.White)
62 | ]
63 |
64 | let config = {
65 | clearColour = Some Color.Black
66 | resolution = Windowed (width, height)
67 | // a list of GameCore.Model.Loadable values
68 | assetsToLoad = [
69 | // the first part is the assetKey of the font, referenced in getView and the fps counter
70 | Font ("connection", "./connection")
71 | // this texture is used for the image rendered at the bottom of the sample
72 | Texture ("sample", "image.png")
73 | ]
74 | // this will have FPS rendered in the top right, topping out at about 60 if all is well.
75 | fpsFont = Some "connection"
76 | mouseVisible = true
77 | }
78 |
79 | // this starts the game. a simplified approach is below
80 | runGame config advanceModel getView
81 |
82 | // if you don't need an fps counter, or care about the clear colour, then the above could be:
83 | // let assets = [ Font ("connection", "./connection"); Texture ("sample", "image.png") ]
84 | // runWindowedGame (width, height) assets advanceModel getView
85 |
86 | 0
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fsharp-gamecore
2 |
3 |  [](https://www.nuget.org/packages/fsharp-gamecore/)
4 |
5 | A fleshed-out game loop from MonoGame with supporting classes, intended to be used as the core loop of larger games.
6 |
7 | This is for **2D games only**, i.e. those that use 2d textures like sprites and raw colours. It also supports sounds, music and fonts.
8 |
9 | Designed so that all XNA bits and necessary mutable fields are wrapped inside the internal `GameLoop` class, allowing a parent application to remain purely functional and almost game engine agnostic. Entry point is the `runGame` method from the GameRunner module.
10 |
11 | ## Samples
12 |
13 | In this repository (or if you follow the repo url, if using Nuget), there is a samples folder containing a simple game demonstrating the use of the various hooks. For more advanced usage, check other projects on my Github. DungeonRaider () uses this repo, for example.
14 |
15 | 
16 |
17 | > In this animation image the framerate is not preserved, but the number in the top left is the real framerate that was recorded.
18 |
19 | ## License
20 |
21 | Provided under **MIT** (except for the font, see below). Previously it was Unilicense, but I need to use some code downstream thats MIT so this is easier. Hopefully this isn't a problem for anyone.
22 |
23 | ## Font and its License
24 |
25 | The sample project includes a font that is compiled by the monogame pipeline. The font used is 'Connection', from here:
26 |
27 | This font is provided under the **SIL Open Font License**, a copy of which lies in the root of this repository.
28 |
29 | ## Version History
30 |
31 | ### Updates for 1.0.0
32 |
33 | Bumped version of dotnet and other packages, refreshed readme etc, added github actions for build and release.
34 |
35 | Now using Monogame 3.8.4, which required a little migration.
36 |
37 | ### Updates for 0.0.8
38 |
39 | The framerate was uncapped, and the way fps is calculated fixed to be more accurate. Finally, a member was added to GameLoop allowing access to the loaded texture asset map (again for use by FG-ImGui)
40 |
41 | ### Updates for 0.0.7
42 |
43 | The ability to show the mouse cursor was added, but primarily 0.0.7 was about exposing the game loop class so that other projects that need to override it can do so (e.g. FSharp-GameCore-ImGui)
44 |
45 | ### Updates for 0.0.6
46 |
47 | The previous iteration with its destRects didn't work out: the size of the text became constrained, but unpredictable in practice. 0.0.6 has changed this back to the old signature, but with font height in pixels instead of scale. This works much better.
48 |
49 | Another, minor change is that the origins for text drawing have been expanded to centre plus all eight points of the compass. This is where the text will be drawn from relative to its given position, and an example of this is in the sample project.
50 |
51 | ### Updates for 0.0.5
52 |
53 | The way text is drawn has changed in a breaking way, but for the better: instead of specifying position, origin and scale, with the last being basically trial and error based on how the font was specified, you instead specify a destination rect and a alignment within that rect. The scale is dynamically calculated to ensure the text can fit inside that rect, and then adjustments are made based on alignment.
54 |
55 | This makes text drawing more accurate and easy, while also aligning the specification of text view artifacts with the way images and colours are specified (which also just take rects). This also fixes a bug where drawing centre-aligned text at lower scales would not be centred properly, and it now trims off extra line spacing gaps at the bottom of text.
56 |
57 | 0.0.5 also includes:
58 |
59 | - the ability to draw multiline text via Paragraph (as compared to text, and which takes a string list of lines)
60 | - a simplified game runner 'runWindowedGame' that takes a window size and asset list instead of a config object, and defaults to aliceBlue for clear colour and no fpsCounter
61 |
62 | ### Updates for 0.0.4 vs 0.0.3
63 |
64 | I've reworked so the meta config of the game is buried in a record type, and so the entry to the game is via a functional method (no more use of the ugly class).
65 |
--------------------------------------------------------------------------------
/SIL Open Font License.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017, Jasper @ KineticPlasma Fonts (cannotintospacefonts@gmail.com),
2 | with Reserved Font Name Connection.
3 |
4 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
5 | This license is copied below, and is also available with a FAQ at:
6 | http://scripts.sil.org/OFL
7 |
8 |
9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 |
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font creation
16 | efforts of academic and linguistic communities, and to provide a free and
17 | open framework in which fonts may be shared and improved in partnership
18 | with others.
19 |
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded,
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply
27 | to any document created using the fonts or their derivatives.
28 |
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 |
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 |
37 | "Original Version" refers to the collection of Font Software components as
38 | distributed by the Copyright Holder(s).
39 |
40 | "Modified Version" refers to any derivative made by adding to, deleting,
41 | or substituting -- in part or in whole -- any of the components of the
42 | Original Version, by changing formats or by porting the Font Software to a
43 | new environment.
44 |
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 |
48 | PERMISSION & CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
51 | redistribute, and sell modified and unmodified copies of the Font
52 | Software, subject to the following conditions:
53 |
54 | 1) Neither the Font Software nor any of its individual components,
55 | in Original or Modified Versions, may be sold by itself.
56 |
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 |
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the corresponding
66 | Copyright Holder. This restriction only applies to the primary font name as
67 | presented to the users.
68 |
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 |
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created
79 | using the Font Software.
80 |
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 |
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
95 |
--------------------------------------------------------------------------------
/src/GameModel.fs:
--------------------------------------------------------------------------------
1 | module GameCore.GameModel
2 |
3 | open Microsoft.Xna.Framework
4 | open Microsoft.Xna.Framework.Input
5 | open Microsoft.Xna.Framework.Graphics
6 | open Microsoft.Xna.Framework.Audio
7 | open Microsoft.Xna.Framework.Media
8 |
9 | /// Fullscreen or windowed, at a given width and height
10 | type Resolution =
11 | | Windowed of int * int
12 | | FullScreen of int * int
13 |
14 | /// Definitions of assets to load on start, e.g. named texture files.
15 | /// IMPORTANT: all paths are relative paths to content files, e.g. /Content/Sprite.png,
16 | /// except for fonts, which MUST be relative paths (without extensions) to spritefonts built using the content pipeline.
17 | /// This is because fonts cannot be direct loaded, and must be processed via the pipeline.
18 | type Loadable =
19 | /// key (how it is referenced) and path (full relative path to file)
20 | | Texture of key:string * path:string
21 | /// key (how it is referenced), texturePath (full relative path to file),
22 | /// keyPath (full relativePath to csv that maps keys to dims in image)
23 | | TextureMap of key:string * texturePath:string * keyPath:string
24 | /// key (how it is referenced) and path (full relative path (without extension) to spriteFont)
25 | | Font of key:string * path:string
26 | /// key (how it is referenced) and path (full relative path to file)
27 | | Sound of key:string * path:string
28 | /// key (how it is referenced) and path (full relative path to file)
29 | | Song of key:string * path:string
30 |
31 | /// Config settings for the game to run. Things like assets to load,
32 | /// the resolution, whether or not to clear each frame and with what colour etc
33 | type GameConfig = {
34 | /// If specified, each draw will be blanked by the colour specified
35 | clearColour: Color option
36 | /// Resolution to render the game (in future this will be changable post init)
37 | resolution: Resolution
38 | /// All assets (like images, sounds etc) that the game will use
39 | assetsToLoad: Loadable list
40 | /// Whether to render an FPS counter in the top right.
41 | /// The string is the asset key of a font asset, specified
42 | /// under assetsToLoad (it will not work without a font loaded)
43 | fpsFont: string option
44 | /// Whether or not the mouse cursor should be visible in the render window
45 | /// If false and you want a mouse cursor, you will need to render one yourself
46 | mouseVisible: bool
47 | }
48 |
49 | /// Where the position is relative to the text drawn
50 | and Origin =
51 | | TopLeft | Left | BottomLeft
52 | | Top | Centre | Bottom
53 | | TopRight | Right | BottomRight
54 |
55 | /// Definitions of things to be drawn (or played) in the main draw method
56 | type ViewArtifact =
57 | /// destRect (position on screen) and colour
58 | | Colour of destRect: (int*int*int*int) * colour:Color
59 | /// assetKey (loaded image to use), destRect (position on screen) and colour (effectively shading)
60 | | Image of assetKey:string * destRect: (int*int*int*int) * colour:Color
61 | /// assetKey (loaded image to use), mapKey (which portion of the image to use), destRect (position on screen) and colour (effectively shading)
62 | | MappedImage of assetKey:string * mapKey:string * destRect: (int*int*int*int) * colour:Color
63 | /// assetKey (loaded font to use), position on screen, fontSize (in pixels), origin of position vs text and colour
64 | | Text of assetKey:string * text:string * position: (int*int) * fontSize:int * origin:Origin * colour:Color
65 | /// assetKey (loaded font to use), position on screen, fontSize (in pixels), origin of position vs text and colour
66 | | Paragraph of assetKey:string * lines:string list * position: (int*int) * fontSize:int * origin:Origin * colour:Color
67 | /// assetKey of loaded sound to use
68 | | SoundEffect of string
69 | /// assetKey of loaded song to use
70 | | Music of string
71 |
72 | /// The current state of the game. Basically elapsed time and the state of the keyboard or mouse
73 | type RunState = {
74 | elapsed: float
75 | keyboard: KeyboardInfo
76 | mouse: MouseInfo
77 | }
78 | /// The current state of the keyboard
79 | and KeyboardInfo = {
80 | pressed: Keys list;
81 | keysDown: Keys list;
82 | keysUp: Keys list
83 | }
84 | /// The current state of the mouse
85 | and MouseInfo = {
86 | position: int * int
87 | pressed: bool * bool
88 | }
89 |
90 | /// Returns whether the specified key was pressed in the last update
91 | let wasJustPressed key runState = List.contains key runState.keyboard.keysDown
92 | /// Returns whether any of the keys specified were pressed in the last update
93 | let wasAnyJustPressed keyList runState = keyList |> List.exists (fun k -> wasJustPressed k runState)
94 | /// Returns whether the specified key is currently pressed as of the last update
95 | let isPressed key runState = List.contains key runState.keyboard.pressed
96 | /// Returns whether any of the keys specified are pressed as of the last update
97 | let isAnyPressed keyList runState = keyList |> List.exists (fun k -> isPressed k runState)
98 | /// Returns wether the left/right keys of the mouse are pressed as of the last update
99 | let isMousePressed (left, right) runState =
100 | let (ml, mr) = runState.mouse.pressed
101 | ((ml && left) || (mr && right))
102 |
103 | type internal Content =
104 | | TextureAsset of Texture2D
105 | | TextureMapAsset of Texture2D * Map
106 | | FontAsset of SpriteFont
107 | | SoundAsset of SoundEffect
108 | | MusicAsset of Song
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/src/GameLoop.fs:
--------------------------------------------------------------------------------
1 | module GameCore.GameLoop
2 |
3 | open GameCore.GameModel
4 | open GameCore.Helpers
5 |
6 | open System
7 | open System.IO
8 | open Microsoft.Xna.Framework
9 | open Microsoft.Xna.Framework.Graphics;
10 | open Microsoft.Xna.Framework.Input;
11 | open Microsoft.Xna.Framework.Audio
12 | open Microsoft.Xna.Framework.Media
13 |
14 | /// Core game loop that implements XNA's game class
15 | /// Not intended for direct use (gameRunner provides hooks that run this)
16 | /// However can be overridden if finer control is needed
17 | type GameLoop<'TModel> (config, updateModel, getView)
18 | as this =
19 | inherit Game()
20 |
21 | let mutable graphics = new GraphicsDeviceManager(this)
22 |
23 | let mutable assets = Map.empty
24 | let mutable whiteTexture: Texture2D = null
25 |
26 | let mutable keyboardInfo = { pressed = []; keysDown = []; keysUp = [] }
27 | let mutable currentModel: 'TModel option = None
28 | let mutable currentView: ViewArtifact list = []
29 | let mutable currentSong: Song option = None
30 | let mutable firstDrawComplete = false
31 |
32 | let mutable fps = 0
33 | let mutable lastFpsUpdate = 0.
34 | let fpsUpdateInterval = 100.
35 |
36 | let mutable spriteBatch = Unchecked.defaultof
37 |
38 | do
39 | match config.resolution with
40 | | FullScreen (w,h) ->
41 | graphics.PreferredBackBufferWidth <- w
42 | graphics.PreferredBackBufferHeight <- h
43 | graphics.IsFullScreen <- true
44 | | Windowed (w,h) ->
45 | graphics.PreferredBackBufferWidth <- w
46 | graphics.PreferredBackBufferHeight <- h
47 |
48 | this.IsMouseVisible <- config.mouseVisible
49 | graphics.SynchronizeWithVerticalRetrace <- true
50 | this.IsFixedTimeStep <- false
51 |
52 | let drawColour (spriteBatch: SpriteBatch) destRect colour =
53 | spriteBatch.Draw(
54 | whiteTexture, asRectangle destRect,
55 | Unchecked.defaultof>, colour, 0.0f, Vector2.Zero,
56 | SpriteEffects.None, 0.0f)
57 |
58 | let drawImage (spriteBatch: SpriteBatch) assetKey destRect colour =
59 | match Map.tryFind assetKey assets with
60 | | Some (TextureAsset texture) ->
61 | spriteBatch.Draw(
62 | texture, asRectangle destRect,
63 | Unchecked.defaultof>, colour, 0.0f, Vector2.Zero,
64 | SpriteEffects.None, 0.0f)
65 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
66 | | _-> sprintf "Asset was not a Texture2D: %s" assetKey |> failwith
67 |
68 | let drawMappedImage (spriteBatch: SpriteBatch) assetKey mapKey destRect colour =
69 | match Map.tryFind assetKey assets with
70 | | Some (TextureMapAsset (texture, map)) when map.ContainsKey mapKey ->
71 | spriteBatch.Draw(
72 | texture, asRectangle destRect,
73 | map.[mapKey] |> Nullable, colour, 0.0f, Vector2.Zero,
74 | SpriteEffects.None, 0.0f)
75 | | Some (TextureMapAsset _) -> sprintf "Missing map key: %s in asset: %s" mapKey assetKey |> failwith
76 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
77 | | _-> sprintf "Asset was not a Texture2D: %s" assetKey |> failwith
78 |
79 | let drawText (spriteBatch: SpriteBatch) assetKey (text: string) position size origin colour =
80 | let font =
81 | match Map.tryFind assetKey assets with
82 | | Some (FontAsset f) -> f
83 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
84 | | _-> sprintf "Asset was not a SpriteFont: %s" assetKey |> failwith
85 |
86 | let rawSize = measureText font text
87 | let scale, fx, fy = getScaleAndPosition rawSize 1 position size origin
88 |
89 | spriteBatch.DrawString(
90 | font, text, new Vector2 (fx, fy), colour,
91 | 0.0f, Vector2.Zero, scale, SpriteEffects.None, 0.5f)
92 |
93 | let drawParagraph (spriteBatch: SpriteBatch) assetKey (lines: string list) position size origin colour =
94 | let font =
95 | match Map.tryFind assetKey assets with
96 | | Some (FontAsset f) -> f
97 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
98 | | _-> sprintf "Asset was not a SpriteFont: %s" assetKey |> failwith
99 |
100 | let sb = stringBuilder lines
101 | let rawSize = measureParagraph font sb
102 | let scale, fx, fy = getScaleAndPosition rawSize lines.Length position size origin
103 |
104 | spriteBatch.DrawString(
105 | font, sb, new Vector2 (fx, fy), colour,
106 | 0.0f, Vector2.Zero, scale, SpriteEffects.None, 0.5f)
107 |
108 | let playSound assetKey =
109 | let sound =
110 | match Map.tryFind assetKey assets with
111 | | Some (SoundAsset s) -> s
112 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
113 | | _ -> sprintf "Asset was not a SoundEffect: %s" assetKey |> failwith
114 | sound.Play () |> ignore
115 |
116 | let playMusic assetKey =
117 | let song =
118 | match Map.tryFind assetKey assets with
119 | | Some (MusicAsset s) -> s
120 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
121 | | _ -> sprintf "Asset was not a Song: %s" assetKey |> failwith
122 | match currentSong with
123 | | Some s when s = song -> ()
124 | | _ ->
125 | currentSong <- Some song
126 | MediaPlayer.Play (song)
127 | MediaPlayer.IsRepeating <- true
128 |
129 | let updateAndPrintFPS (gameTime : GameTime) fontAsset (spriteBatch: SpriteBatch) =
130 | if gameTime.TotalGameTime.TotalMilliseconds - lastFpsUpdate > fpsUpdateInterval then
131 | fps <- int (1. / gameTime.ElapsedGameTime.TotalSeconds)
132 | lastFpsUpdate <- gameTime.TotalGameTime.TotalMilliseconds
133 |
134 | let position = graphics.PreferredBackBufferWidth - 40
135 | drawColour spriteBatch (position, 0, 40, 32) (Color.DarkSlateGray)
136 | drawText spriteBatch fontAsset (sprintf "%i" fps) (position + 20, 16) 18 Centre Color.White
137 |
138 | /// Returns the current model, as of the last call to advanceModel
139 | /// Only exposed when direct control is required, e.g. if a ui generator
140 | /// needs to be derived from the model outside of the advanceModel call
141 | member __.CurrentModel
142 | with get () =
143 | currentModel
144 |
145 | /// Returns a loaded Texture2D with the given asset key.
146 | /// For use with descendent classes that need access to loaded textures
147 | member __.Texture assetKey =
148 | match Map.tryFind assetKey assets with
149 | | Some (TextureAsset texture) -> texture
150 | | None -> sprintf "Missing asset: %s" assetKey |> failwith
151 | | _-> sprintf "Asset was not a Texture2D: %s" assetKey |> failwith
152 |
153 | override __.LoadContent() =
154 | spriteBatch <- new SpriteBatch(this.GraphicsDevice)
155 |
156 | whiteTexture <- new Texture2D(this.GraphicsDevice, 1, 1)
157 | whiteTexture.SetData [|Color.White|]
158 |
159 | assets <-
160 | config.assetsToLoad
161 | |> List.map (
162 | function
163 | | Texture (key, path) ->
164 | use stream = File.OpenRead path
165 | key, Texture2D.FromStream (this.GraphicsDevice, stream) |> TextureAsset
166 | | TextureMap (key, texturePath, keyPath) ->
167 | use stream = File.OpenRead texturePath
168 | let texture = Texture2D.FromStream (this.GraphicsDevice, stream)
169 | let content =
170 | File.ReadAllLines keyPath |> Seq.skip 1
171 | |> Seq.map (fun line -> line.Split(',') |> fun s -> s.[0], new Rectangle(int s.[1], int s.[2], int s.[3], int s.[4]))
172 | |> Map.ofSeq
173 | key, TextureMapAsset (texture, content)
174 | | Font (key, path) ->
175 | key, this.Content.Load path |> FontAsset
176 | | Sound (key, path) ->
177 | use stream = File.OpenRead path
178 | key, SoundEffect.FromStream stream |> SoundAsset
179 | | Song (key, path) ->
180 | let uri = new Uri (path, UriKind.RelativeOrAbsolute)
181 | key, Song.FromUri (key, uri) |> MusicAsset)
182 | |> Map.ofList
183 |
184 | override __.Update(gameTime) =
185 | keyboardInfo <- updateKeyboardInfo (Keyboard.GetState()) keyboardInfo
186 | let mouseInfo = getMouseInfo (Mouse.GetState())
187 | let runState = {
188 | elapsed = gameTime.TotalGameTime.TotalMilliseconds
189 | keyboard = keyboardInfo
190 | mouse = mouseInfo
191 | }
192 |
193 | match currentModel with
194 | | None ->
195 | currentModel <- updateModel runState currentModel
196 | | Some _ when firstDrawComplete ->
197 | currentModel <- updateModel runState currentModel
198 | | _ -> ()
199 |
200 | match currentModel with
201 | | None -> __.Exit()
202 | | Some model ->
203 | currentView <- getView runState model
204 |
205 | override __.Draw(gameTime) =
206 | firstDrawComplete <- true
207 |
208 | match config.clearColour with
209 | | Some c -> this.GraphicsDevice.Clear c
210 | | None -> ()
211 |
212 | spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp)
213 |
214 | currentView
215 | |> Seq.iter (
216 | function
217 | | Colour (d, c) -> drawColour spriteBatch d c
218 | | Image (a, d, c) -> drawImage spriteBatch a d c
219 | | MappedImage (a, m, d, c) -> drawMappedImage spriteBatch a m d c
220 | | Text (a, t, p, s, o, c) -> drawText spriteBatch a t p s o c
221 | | Paragraph (a, t, p, s, o, c) -> drawParagraph spriteBatch a t p s o c
222 | | SoundEffect s -> playSound s
223 | | Music s -> playMusic s)
224 |
225 | match config.fpsFont with
226 | | Some fontAsset -> updateAndPrintFPS gameTime fontAsset spriteBatch
227 | | None -> ()
228 |
229 | spriteBatch.End()
--------------------------------------------------------------------------------