Making a platformer in F# with MonoGame

Introduction

In this guide, I’ll walk through how we can make a game with very simple platformer physics using F# and MonoGame. This will be a Windows desktop game which runs on OpenGL but you can easily port it to any other platform that MonoGame supports. The code will be available every step and the Github repository will be tagged so you can pick up from any step. Repo.

Setting up

There’s 2 key things I’ll be using throughout this. Visual studio 2012. I’ll be using Ultimate but any IDE that supports F# will work (even Xamarin studio). We’ll also need Monogame. MonoGame is an open source implementation of the Microsoft XNA framework to the extent that it’s essentially a drop in replacement. It’ll also run on pretty much any platform ranging from Windows Desktop to Windows Store to iOS and PS Vita (unfortunately the PS Vita doesn’t technically support F# though). So all you need to do here is to install the MonoGame framework available at monogame.net/downloads

Part 1 – Creating the project

We’ll need to first create an F# Windows application, so open Visual Studio and go File –> New Project and choose an F# Windows Application. Call it whatever you like but I called mine Platformer. You should now have a file called Program.fs which when you run will print the command line arguments and close.

Now let’s add the references. Right click the references folder and add reference. Now navigate to where you installed MonoGame and choose the following binaries to add from the WindowsGL folder (this is probably C:\Program Files (x86)\MonoGame\v3.0\Assemblies\WindowsGL if you followed the default installation):

  • Monogame.Framework.dll
  • Lidgren.Network.dll
  • OpenTK.dll
  • Tao.SDL.dll

Now right click on the project and add an existing item and choose the SDL.dll from the same directory. You’ll need to set the build action to none but copy to output directory in the properties.

Part 2 – Point of entry

We’ll now need to actually create a game which we’ll run. As the file order is important in the F# compiler, we’ll need to add a new file called PlatformerGame.fs above Program.fs. Once we’ve got that add the following code.

module PlatformerGame

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics

type Game1 () as x =
    inherit Game()

    do x.Content.RootDirectory <- "Content"
    let graphics = new GraphicsDeviceManager(x)
    let mutable spriteBatch = Unchecked.defaultof

    override x.Initialize() =
        do spriteBatch         do base.Initialize()
        ()

    override x.LoadContent() =
        ()

    override x.Update (gameTime) =
        ()

    override x.Draw (gameTime) =
        do x.GraphicsDevice.Clear Color.CornflowerBlue
        ()

This is the very base of what we’ll be using. We essentially just inherit from the base Game class and then initialise a few variables to handle all of the drawing. We also clear the screen to a solid blue in the Draw method. To use it, in the main entry point in Program.fs we’ll need to run our game as follows.

open PlatformerGame

[]
let main argv =
    use g = new Game1()
    g.Run()
    0

Here we’re just basically saying run the game. Now when that’s done you should end up with something that looks like the following.

Blank

Part 3 – Representing objects in the world

Our world will be made up of lots of 1 thing, we’ll call it an actor. An actor can be a wall in the level, it can be a player or even an enemy. We’ll then add properties onto this that help us identify it. We’ll be making a few assumptions though. First of all, we’ll be making a tiled platformer, so all collisions will be based on rectangles. Second, we’ll have only 2 types of physics bodies (static and dynamic). Finally for now, we’ll only have 2 possible player states; jumping and nothing. We’ll call our world objects actors, so that we don’t cause any naming conflicts with the keyword object.

So create a new file above PlatformerGame.fs called PlatformerActors.fs. This is where we’ll store our code relating to representing our players. So now add the following code.

module PlatformerActor

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Content

type BodyType =
    | Static
    | Dynamic of Vector2

type PlayerState =
    | Nothing
    | Jumping

type ActorType =
    | Player of PlayerState
    | Obstacle

type WorldActor =
    {
        ActorType : ActorType;
        Position : Vector2;
        Size : Vector2;
        Texture : Texture2D option;
        BodyType : BodyType
    }
    member this.CurrentBounds
        with get () = Rectangle((int this.Position.X),(int this.Position.Y),(int this.Size.X),(int this.Size.Y))

    member this.DesiredBounds
        with get () = let desiredPos = match this.BodyType with
                                       | Dynamic(s) -> this.Position + s
                                       | _-> this.Position

Here we’re creating a couple of discriminated unions which are our properties and then a record type for our actor. You’ll see why a record type is useful later on. As we’re representing all our world objects with this one type, we need a way to tell if something is invisible or not, for this we use an option type. We also add a couple of member variables called Bounds and DesiredBounds which are later used to calculate whether or not it is touching something else.

Part 4 – Creating and drawing actors

We’ll create a simple function which is used to create our actors as follows. It essentially wraps our constructors into a function. So add the following to PlatformerActors.fs.

let CreateActor (content:ContentManager) (textureName, actorType, position, size, isStatic) =
    let tex = if not (System.String.IsNullOrEmpty textureName) then
                  Some(content.Load textureName)
              else
                  None
    let bt = if isStatic then
                Static
             else
                Dynamic(Vector2(0.f,0.f))
    { ActorType = actorType; Position = position; Size = size; Texture = tex; BodyType = bt; }

We’ll create a list of arguments which we’ll pass to the LoadActor function later. Then we can create a mutable list of actors which we’ll use our list of arguments to map from lazily. By doing it lazily, it avoids creating lots of mutable states and potential null references which are only filled later in the LoadContent method. Instead, we’ll simply force the result in the LoadContent method. In the repository on GitHub, there are a couple of content files called player.png and obstacle.png, you can either use these or create your own. So add the following to the Game.

let CreateActor' = CreateActor x.Content

let WorldObjects = lazy ([("player.png", Player(Nothing), Vector2(10.f,28.f), Vector2(32.f,32.f), false);
                          ("obstacle.png", Obstacle, Vector2(10.f,60.f), Vector2(32.f,32.f), true);
                          ("", Obstacle, Vector2(42.f,60.f), Vector2(32.f,32.f), true);]
                         |> List.map CreateActor')

Then in our LoadContent override we can force the lazy expression to evaluate.

override x.LoadContent() =
        do WorldObjects.Force () |> ignore
        ()

We’ll also need to add a function which can draw our objects. So let’s add the following to our Game file.

let DrawActor (sb:SpriteBatch) actor =
    if actor.Texture.IsSome then
        do sb.Draw(actor.Texture.Value, actor.Position, Color.White)
    ()

This will essentially check to see if we have a Texture as noted by IsSome. If there is then we can draw it, otherwise we’ll skip it. Now when you run it you should see the following.

Sprites

Part 5 – Adding gravity

The game’s a bit dull at the minute. Not much happens at all. Let’s add some gravity. We’ll add a file called PlatformerPhysics.fs below PlatformerActors. Here we pass in an actor and if it’s dynamic then we add a downward acceleration. We also need to then resolve all of our velocities by adding any new velocity to the current position. So add the following to that file.

module PlatformerPhysics

open Microsoft.Xna.Framework
open PlatformerActor

let AddGravity (gameTime:GameTime) actor =
    let ms = gameTime.ElapsedGameTime.TotalMilliseconds
    let g = ms * 0.005
    match actor.BodyType with
    | Dynamic(s) -> let d = Vector2(s.X, s.Y + (float32 g))
                    { actor with BodyType = Dynamic(d); }
    | _ -> actor

let ResolveVelocities actor =
    match actor.BodyType with
    | Dynamic (s) -> { actor with Position = actor.Position + s }
    | _ -> actor

We need to pass in a gametime so that we can work out how far to move. Whilst we could say just move forward 5 each time, if somebody was running at 60 frames per second they would move twice as fast as someone running the game at 30 frames per second. The one key thing to note in here is that we need to take a reference to the current list of world objects. If we were to try and use map on the lazy value, then it would throw an exception. We now need to call this function in our update as follows.

override x.Update (gameTime) =
        let AddGravity' = AddGravity gameTime
        let current = WorldObjects.Value
        do WorldObjects  List.map AddGravity'
                                 |> List.map ResolveVelocities)
        do WorldObjects.Force () |> ignore
        ()

You should now see your objects fall if they were set to be dynamic, but they fall through our static objects. Next we’ll be adding a way to check for collisions.

Part 6 – Detecting collisions

Here comes one of the trickier parts of this. We need a way to stop our player from walking through walls. We’ll do this by passing all of our world actors and then splitting them based on type. Static objects can intersect static objects because they can’t move. A dynamic actor can touch a dynamic actor as they may need to handle the likes of enemy collisions. Then for every dynamic object, we take it’s desired position, which is where it would be if there wasn’t anything in the way and check to see whether it touches anything. If it does then we don’t move it completely otherwise it’s free to go. I’ve added it to the PlatformerPhysics file.

let IsActorStatic actor =
    match actor.BodyType with
    | Static -> true
    | _ -> false

let PartitionWorldObjects worldObjects =
    worldObjects
    |> List.partition IsActorStatic

let HandleCollisions worldObjects =
    let stc, dyn = PartitionWorldObjects worldObjects

    let FindNewVelocity rect1 rect2 velocity =
        let inter = Rectangle.Intersect(rect1,rect2)
        let mutable (newVel:Vector2) = velocity
        if inter.Height > inter.Width then
            do newVel.X  inter.Height then
            do newVel.Y  match a.BodyType, b.BodyType with
                                    | Dynamic (s), Static -> { a with BodyType = Dynamic((FindNewVelocity a.DesiredBounds b.CurrentBounds s)) }
                                    | _ -> a
        | _ -> a

    let rec FigureCollisions (actor:WorldActor) (sortedActors:WorldActor list) =
        match sortedActors with
        | [] -> actor
        | x :: xs -> let a = if actor.DesiredBounds.Intersects x.DesiredBounds then
                                 FindOptimumCollision actor x
                             else
                                 actor
                     FigureCollisions a xs

    let rec FixCollisions (toFix:WorldActor list) (alreadyFixed:WorldActor list) =
        match toFix with
        | [] -> alreadyFixed
        | x :: xs -> let a = FigureCollisions x alreadyFixed
                     FixCollisions xs (a::alreadyFixed)

    FixCollisions dyn stc

As you can see we take the world objects and split them into static actors and dynamic actors. Then for every dynamic actor, we fix it’s collisions and then add it to the list of fixed collisions. We repeat this until all actors are fixed and then we return our list. To check our collision we get the intersect of our 2 objects and then fix in the correct direction. Let’s also add the call to this in our Update which should now look something like this.

 override x.Update (gameTime) =
        let AddGravity' = AddGravity gameTime
        let current = WorldObjects.Value
        do WorldObjects  List.map AddGravity'
                                 |> HandleCollisions
                                 |> List.map ResolveVelocities)
        do WorldObjects.Force () |> ignore
        ()

Part 7 – Handling input

To control our game, we’ll just be using our keyboard. We’ll write a function which takes in all the pressed keys and then calculates whether or not we need to do anything with them. Let’s create a new file called PlatformerInput.fs. Add the following.

module PlatformerInput

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Input
open PlatformerActor

let HandleInput (kbState:KeyboardState) actor =
    let rec HandleKeys keys (currentVelocity:Vector2,state) =
        match keys with
        | [] -> currentVelocity
        | x :: xs -> match x with
                     | Keys.Left -> let newSpeed = if (currentVelocity.X - 0.1f) < -1.f then                                                        -1.f                                                    else                                                        currentVelocity.X - 0.1f                                     let newV = Vector2(newSpeed, currentVelocity.Y)                                     HandleKeys xs (newV,state)                      | Keys.Right -> let newSpeed = if (currentVelocity.X + 0.1f) > 1.f then
                                                       1.f
                                                    else
                                                       currentVelocity.X + 0.1f
                                     let newV = Vector2(newSpeed, currentVelocity.Y)
                                     HandleKeys xs (newV,state)
                     | Keys.Space -> match state with
                                     | Nothing -> let newV = Vector2(currentVelocity.X, currentVelocity.Y - 3.f)
                                                  HandleKeys xs (newV, Jumping)
                                     | Jumping -> HandleKeys xs (currentVelocity,state)
                     | _ -> HandleKeys xs (currentVelocity,state)
    match actor.ActorType with
    | Player(s) -> let initialVelocity = match actor.BodyType with
                                         | Dynamic(v) -> v
                                         | _ -> Vector2()
                   let velocity = HandleKeys (kbState.GetPressedKeys() |> Array.toList) (initialVelocity, s)
                   { actor with BodyType = Dynamic(velocity); ActorType = Player(Jumping) }
    | _ -> actor

Here we take in a game actor and a GameTime, then if the actor is a Player, we can operate on it. We also need to ensure that if the player wants to jump then they can’t already be jumping. This then needs to be called in our update. We need to make sure that we call it before the collision checking though so that we can’t walk through walls.

override x.Update (gameTime) =
        let AddGravity' = AddGravity gameTime
        let HandleInput' = HandleInput (Keyboard.GetState ())
        let current = WorldObjects.Value
        do WorldObjects  List.map HandleInput'
                                 |> List.map AddGravity'
                                 |> HandleCollisions
                                 |> List.map ResolveVelocities)
        do WorldObjects.Force () |> ignore
        ()

If you run that now though, we’ll move even if we aren’t pressing any buttons. So we need to add some friction. Once again in PlatformerPhysics, let’s add a function called AddFriction which takes our actor and reduces our speed by 5%. You can tweak this to whatever you like though. You should end up with something like this, then make sure you call this in the Update with a map.

let AddFriction actor =
    match actor.BodyType with
    | Dynamic (v) -> let newV = Vector2(v.X*0.95f, v.Y)
                     { actor with BodyType = Dynamic(newV) }
    | _ -> actor

Then also don’t forget to call this in update as follows.

override x.Update (gameTime) =
        let AddGravity' = AddGravity gameTime
        let HandleInput' = HandleInput (Keyboard.GetState ())
        let current = WorldObjects.Value
        do WorldObjects  List.map HandleInput'
                                 |> List.map AddGravity'
                                 |> List.map AddFriction
                                 |> HandleCollisions
                                 |> List.map ResolveVelocities)
        do WorldObjects.Force () |> ignore
        ()

Final thoughts

Hopefully this has given you some idea of how you can create a simple game with F#. There’s a lot that’s not been implemented but I’ll leave that as a challenge to you. You could add enemies with AI. You could create a simple level editor to be able to export your maps, or you could use something that already exists like Tiled. Also as it stands, we’re working in a very small map. Having a camera to move around which follows the player allows larger maps.

All the code in this tutorial is available on GitHub here. The tags are available for the end of each step as well, so you can always pick up wherever.

Advertisements

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s