Getting started with 3D XNA in F#

XNA Game Studio is one of the main pure managed 3D data visualization tools. It is widely used for game development and operates not only on PC but on Xbox and Windows Phones too.

As you probably know, The F# Software Foundation has a “Game And Visualization Stacks” page with description of options available from F#. On this page you can find a link to the “F# With XNA Game Studio” post by AzerDark (@azer89). In the post you can find the detailed guide of how to create a new F# project, reference all required assemblies and create minimal XNA application that shows up an empty window.

I have tried to create something a bit more interesting than an empty window. It is a rotating cube :). A full description of how it works you can find in the post “Getting started with 3D XNA” by David Conrad (with source code in C#). In this post you can see F# code that create XNA game object and model of cube, initialize basic effect (turn on light and configure light, projection and view), create an animation(rotating) for cube and render it in XNA window. As a result, you will see something like that (but with animation 😉 ):

XNA

open System
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input

type Game1() as this =
    inherit Game()
    let graphics = new GraphicsDeviceManager(this)

    let cube =
        let texCoords = new Vector2(0.0f, 0.0f);
        let face = [|Vector3(-1.0f, 1.0f, 0.0f); Vector3(-1.0f, -1.0f, 0.0f);
                     Vector3(1.0f, 1.0f, 0.0f);   //TopLeft-BottomLeft-TopRight
                     Vector3(-1.0f, -1.0f, 0.0f); Vector3(1.0f, -1.0f, 0.0f);
                     Vector3(1.0f, 1.0f, 0.0f);|] //BottomLeft-BottomRight-TopRight
        let faceNormals = [|Vector3.UnitZ; -Vector3.UnitZ;   //Front & Back faces
                            Vector3.UnitX; -Vector3.UnitX;   //Left & Right faces
                            Vector3.UnitY; -Vector3.UnitY|]; //Top & Bottom faces
        let ang90 = (float32)Math.PI / 2.0f;
        let faceRotations = [|Matrix.CreateRotationY(2.0f*ang90); Matrix.CreateRotationY(0.0f);
                              Matrix.CreateRotationY(-ang90); Matrix.CreateRotationY(ang90);
                              Matrix.CreateRotationX(ang90); Matrix.CreateRotationX(-ang90)|];
        Array.init 36 (fun x ->
            let i,j = x%6, x/6
            VertexPositionNormalTexture(
                Vector3.Transform(face.[i], faceRotations.[j])
                    + faceNormals.[j], faceNormals.[j], texCoords))

    let mutable effect = null
    let angle = ref 0.0f;

    override Game.Initialize() =
        effect <- new BasicEffect(graphics.GraphicsDevice,
                    AmbientLightColor = Vector3(0.0f, 1.0f, 0.0f),
                    LightingEnabled = true,
                    View = Matrix.CreateTranslation(0.0f,0.0f,-10.0f),
                    Projection =
                        Matrix.CreatePerspectiveFieldOfView(
                            (float32)Math.PI / 4.0f,
                            (float32)this.Window.ClientBounds.Width
                                / (float32)this.Window.ClientBounds.Height,
                            1.0f, 10.0f));
        effect.DirectionalLight0.Enabled <- true;
        effect.DirectionalLight0.DiffuseColor <- Vector3.One;
        effect.DirectionalLight0.Direction <- Vector3.Normalize(Vector3.One);
        base.Initialize()

    override Game.Update gameTime =
        angle := !angle + 0.005f
        if (!angle > 2.0f * (float32)Math.PI) then angle := 0.0f;
        let R = Matrix.CreateRotationY(!angle) * Matrix.CreateRotationX(0.4f);
        let T = Matrix.CreateTranslation(0.0f, 0.0f, 5.0f);
        effect.World <- R * T;
        base.Update gameTime

    override Game.Draw gameTime =
        this.GraphicsDevice.Clear(Color.CornflowerBlue)
        graphics.GraphicsDevice.RasterizerState <- new RasterizerState();
        effect.CurrentTechnique.Passes |> Seq.iter (fun pass ->
            pass.Apply()
            graphics.GraphicsDevice.DrawUserPrimitives(
                PrimitiveType.TriangleList, cube, 0, 12))
        base.Draw gameTime

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