├── LICENSE ├── README.md ├── Platformer ├── SDL.dll ├── Content │ ├── player.png │ ├── animtest.png │ └── obstacle.png ├── Program.fs ├── App.config ├── PlatformerAnimation.fs ├── PlatformerActor.fs ├── PlatformerInput.fs ├── PlatformerGame.fs ├── PlatformerPhysics.fs └── Platformer.fsproj ├── Platformer.sln └── .gitignore /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FSharpPlatformer 2 | ================ 3 | -------------------------------------------------------------------------------- /Platformer/SDL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruinbrown/FSharpPlatformer/HEAD/Platformer/SDL.dll -------------------------------------------------------------------------------- /Platformer/Content/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruinbrown/FSharpPlatformer/HEAD/Platformer/Content/player.png -------------------------------------------------------------------------------- /Platformer/Content/animtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruinbrown/FSharpPlatformer/HEAD/Platformer/Content/animtest.png -------------------------------------------------------------------------------- /Platformer/Content/obstacle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruinbrown/FSharpPlatformer/HEAD/Platformer/Content/obstacle.png -------------------------------------------------------------------------------- /Platformer/Program.fs: -------------------------------------------------------------------------------- 1 | open PlatformerGame 2 | 3 | [] 4 | let main argv = 5 | use g = new Game1() 6 | g.Run() 7 | 0 8 | -------------------------------------------------------------------------------- /Platformer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Platformer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Platformer", "Platformer\Platformer.fsproj", "{FCE96792-0BBB-4213-9646-562AB981A60F}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {FCE96792-0BBB-4213-9646-562AB981A60F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {FCE96792-0BBB-4213-9646-562AB981A60F}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {FCE96792-0BBB-4213-9646-562AB981A60F}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {FCE96792-0BBB-4213-9646-562AB981A60F}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Platformer/PlatformerAnimation.fs: -------------------------------------------------------------------------------- 1 | module PlatformerAnimation 2 | 3 | open Microsoft.Xna.Framework 4 | open Microsoft.Xna.Framework.Graphics 5 | 6 | let FrameWidth = 32 7 | let FrameHeight = 32 8 | 9 | type Animation = 10 | { 11 | TextureStrip : Texture2D; 12 | FrameCount : int; 13 | CurrentFrame : int; 14 | CurrentTime : int; 15 | TimePerFrame : int; 16 | } 17 | 18 | let CreateAnimation (texture:Texture2D) frameLength = 19 | let frameCount = texture.Width / FrameWidth 20 | { TextureStrip = texture; FrameCount = frameCount; CurrentFrame = 0; CurrentTime = 0; TimePerFrame = frameLength } 21 | 22 | let UpdateAnimation (gameTime:GameTime) animation = 23 | let time = animation.CurrentTime + (int gameTime.ElapsedGameTime.TotalMilliseconds) 24 | let newFrame = if time > animation.TimePerFrame then 25 | let newFrame' = animation.CurrentFrame + 1 26 | if newFrame' >= animation.FrameCount then 27 | 0 28 | else newFrame' 29 | else 30 | animation.CurrentFrame 31 | let counter = if time > animation.TimePerFrame then 0 32 | else time 33 | { animation with CurrentFrame = newFrame; CurrentTime = counter; } 34 | 35 | let DrawAnimation (spriteBatch:SpriteBatch) animation (position:Vector2) = 36 | let rect = System.Nullable(Rectangle(animation.CurrentFrame * FrameWidth, 0, FrameWidth, FrameHeight)) 37 | spriteBatch.Draw(animation.TextureStrip, position, rect, Color.White) -------------------------------------------------------------------------------- /Platformer/PlatformerActor.fs: -------------------------------------------------------------------------------- 1 | module PlatformerActor 2 | 3 | open Microsoft.Xna.Framework 4 | open Microsoft.Xna.Framework.Graphics 5 | open Microsoft.Xna.Framework.Content 6 | open PlatformerAnimation 7 | 8 | type BodyType = 9 | | Static 10 | | Dynamic of Vector2 11 | 12 | type PlayerState = 13 | | Nothing 14 | | Jumping 15 | 16 | type ActorType = 17 | | Player of PlayerState 18 | | Obstacle 19 | 20 | type WorldActor = 21 | { 22 | ActorType : ActorType; 23 | Position : Vector2; 24 | Size : Vector2; 25 | Animation : Animation option; 26 | BodyType : BodyType 27 | } 28 | member this.CurrentBounds 29 | with get () = Rectangle((int this.Position.X),(int this.Position.Y),(int this.Size.X),(int this.Size.Y)) 30 | 31 | member this.DesiredBounds 32 | with get () = let desiredPos = match this.BodyType with 33 | | Dynamic(s) -> this.Position + s 34 | | _-> this.Position 35 | Rectangle((int desiredPos.X), (int desiredPos.Y), (int this.Size.X), (int this.Size.Y)) 36 | 37 | let CreateActor (content:ContentManager) (textureName, actorType, position, size, isStatic) = 38 | let tex = if not (System.String.IsNullOrEmpty textureName) then 39 | let tex = content.Load(textureName) 40 | let anim = CreateAnimation tex 100 41 | Some(anim) 42 | else 43 | None 44 | let bt = if isStatic then 45 | Static 46 | else 47 | Dynamic(Vector2(0.f,0.f)) 48 | { ActorType = actorType; Position = position; Size = size; Animation = tex; BodyType = bt; } 49 | 50 | let UpdateActorAnimation gameTime (actor:WorldActor) = 51 | let animation = if actor.Animation.IsSome then 52 | Some(UpdateAnimation gameTime actor.Animation.Value) 53 | else None 54 | { actor with Animation = animation } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /Platformer/PlatformerInput.fs: -------------------------------------------------------------------------------- 1 | module PlatformerInput 2 | 3 | open Microsoft.Xna.Framework 4 | open Microsoft.Xna.Framework.Input 5 | open PlatformerActor 6 | 7 | let HandleInput (kbState:KeyboardState) actor = 8 | let rec HandleKeys keys (currentVelocity:Vector2,state) = 9 | match keys with 10 | | [] -> currentVelocity 11 | | x :: xs -> match x with 12 | | Keys.Left -> let newSpeed = if (currentVelocity.X - 0.1f) < -1.f then 13 | -1.f 14 | else 15 | currentVelocity.X - 0.1f 16 | let newV = Vector2(newSpeed, currentVelocity.Y) 17 | HandleKeys xs (newV,state) 18 | | Keys.Right -> let newSpeed = if (currentVelocity.X + 0.1f) > 1.f then 19 | 1.f 20 | else 21 | currentVelocity.X + 0.1f 22 | let newV = Vector2(newSpeed, currentVelocity.Y) 23 | HandleKeys xs (newV,state) 24 | | Keys.Space -> match state with 25 | | Nothing -> let newV = Vector2(currentVelocity.X, currentVelocity.Y - 3.f) 26 | HandleKeys xs (newV, Jumping) 27 | | Jumping -> HandleKeys xs (currentVelocity,state) 28 | | _ -> HandleKeys xs (currentVelocity,state) 29 | match actor.ActorType with 30 | | Player(s) -> let initialVelocity = match actor.BodyType with 31 | | Dynamic(v) -> v 32 | | _ -> Vector2() 33 | let velocity = HandleKeys (kbState.GetPressedKeys() |> Array.toList) (initialVelocity, s) 34 | { actor with BodyType = Dynamic(velocity); ActorType = Player(Jumping) } 35 | | _ -> actor 36 | -------------------------------------------------------------------------------- /Platformer/PlatformerGame.fs: -------------------------------------------------------------------------------- 1 | module PlatformerGame 2 | 3 | open Microsoft.Xna.Framework 4 | open Microsoft.Xna.Framework.Graphics 5 | open Microsoft.Xna.Framework.Input 6 | open PlatformerActor 7 | open PlatformerAnimation 8 | open PlatformerInput 9 | open PlatformerPhysics 10 | 11 | type Game1 () as x = 12 | inherit Game() 13 | 14 | do x.Content.RootDirectory <- "Content" 15 | #if INTERACTIVE 16 | do x.Content.RootDirectory <- __SOURCE_DIRECTORY__ + @"bin\Debug\Content" 17 | #endif 18 | let graphics = new GraphicsDeviceManager(x) 19 | let mutable spriteBatch = Unchecked.defaultof 20 | 21 | let CreateActor' = CreateActor x.Content 22 | 23 | let mutable WorldObjects = lazy ([("animtest.png", Player(Nothing), Vector2(10.f,28.f), Vector2(32.f,32.f), false); 24 | ("obstacle.png", Obstacle, Vector2(10.f,60.f), Vector2(32.f,32.f), true); 25 | ("", Obstacle, Vector2(42.f,60.f), Vector2(32.f,32.f), true);] 26 | |> List.map CreateActor') 27 | 28 | let DrawActor (sb:SpriteBatch) actor = 29 | if actor.Animation.IsSome then 30 | do DrawAnimation sb actor.Animation.Value actor.Position 31 | () 32 | 33 | override x.Initialize() = 34 | do spriteBatch <- new SpriteBatch(x.GraphicsDevice) 35 | do base.Initialize() 36 | () 37 | 38 | override x.LoadContent() = 39 | do WorldObjects.Force () |> ignore 40 | () 41 | 42 | override x.Update (gameTime) = 43 | let AddGravity' = AddGravity gameTime 44 | let HandleInput' = HandleInput (Keyboard.GetState ()) 45 | let UpdateActorAnimation' = UpdateActorAnimation gameTime 46 | let current = WorldObjects.Value 47 | do WorldObjects <- lazy (current 48 | |> List.map HandleInput' 49 | |> List.map AddGravity' 50 | |> List.map AddFriction 51 | |> HandleCollisions 52 | |> List.map ResolveVelocities 53 | |> List.map UpdateActorAnimation') 54 | do WorldObjects.Force () |> ignore 55 | () 56 | 57 | override x.Draw (gameTime) = 58 | do x.GraphicsDevice.Clear Color.CornflowerBlue 59 | let DrawActor' = DrawActor spriteBatch 60 | do spriteBatch.Begin () 61 | WorldObjects.Value 62 | |> List.iter DrawActor' 63 | do spriteBatch.End () 64 | () -------------------------------------------------------------------------------- /Platformer/PlatformerPhysics.fs: -------------------------------------------------------------------------------- 1 | module PlatformerPhysics 2 | 3 | open Microsoft.Xna.Framework 4 | open PlatformerActor 5 | 6 | let IsActorStatic actor = 7 | match actor.BodyType with 8 | | Static -> true 9 | | _ -> false 10 | 11 | let PartitionWorldObjects worldObjects = 12 | worldObjects 13 | |> List.partition IsActorStatic 14 | 15 | let HandleCollisions worldObjects = 16 | let stc, dyn = PartitionWorldObjects worldObjects 17 | 18 | let FindNewVelocity rect1 rect2 velocity = 19 | let inter = Rectangle.Intersect(rect1,rect2) 20 | let mutable (newVel:Vector2) = velocity 21 | if inter.Height > inter.Width then 22 | do newVel.X <- 0.f 23 | if inter.Width > inter.Height then 24 | do newVel.Y <- 0.f 25 | newVel 26 | 27 | let FindOptimumCollision a b = 28 | match a.ActorType,b.ActorType with 29 | | Player(h), Obstacle -> match a.BodyType, b.BodyType with 30 | | Dynamic (s), Static -> { a with BodyType = Dynamic((FindNewVelocity a.DesiredBounds b.CurrentBounds s)); ActorType = Player(Nothing) } 31 | | _ -> a 32 | | _ -> a 33 | 34 | let rec FigureCollisions (actor:WorldActor) (sortedActors:WorldActor list) = 35 | match sortedActors with 36 | | [] -> actor 37 | | x :: xs -> let a = if actor.DesiredBounds.Intersects x.DesiredBounds then 38 | FindOptimumCollision actor x 39 | else 40 | actor 41 | FigureCollisions a xs 42 | 43 | let rec FixCollisions (toFix:WorldActor list) (alreadyFixed:WorldActor list) = 44 | match toFix with 45 | | [] -> alreadyFixed 46 | | x :: xs -> let a = FigureCollisions x alreadyFixed 47 | FixCollisions xs (a::alreadyFixed) 48 | 49 | FixCollisions dyn stc 50 | 51 | let AddGravity (gameTime:GameTime) actor = 52 | let ms = gameTime.ElapsedGameTime.TotalMilliseconds 53 | let g = ms * 0.01 54 | match actor.BodyType with 55 | | Dynamic(s) -> let d = Vector2(s.X, s.Y + (float32 g)) 56 | { actor with BodyType = Dynamic(d); } 57 | | _ -> actor 58 | 59 | let AddFriction actor = 60 | match actor.BodyType with 61 | | Dynamic (v) -> let newV = Vector2(v.X*0.95f, v.Y) 62 | { actor with BodyType = Dynamic(newV) } 63 | | _ -> actor 64 | 65 | let ResolveVelocities actor = 66 | match actor.BodyType with 67 | | Dynamic (s) -> { actor with Position = actor.Position + s } 68 | | _ -> actor 69 | 70 | -------------------------------------------------------------------------------- /Platformer/Platformer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | fce96792-0bbb-4213-9646-562ab981a60f 9 | Exe 10 | Platformer 11 | Platformer 12 | v4.5 13 | Platformer 14 | 15 | 16 | true 17 | full 18 | false 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | 3 23 | AnyCPU 24 | bin\Debug\Platformer.XML 25 | true 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | bin\Release\ 32 | TRACE 33 | 3 34 | AnyCPU 35 | bin\Release\Platformer.XML 36 | true 37 | 38 | 39 | 40 | C:\Program Files (x86)\MonoGame\v3.0\Assemblies\WindowsGL\Lidgren.Network.dll 41 | 42 | 43 | C:\Program Files (x86)\MonoGame\v3.0\Assemblies\WindowsGL\MonoGame.Framework.dll 44 | 45 | 46 | 47 | True 48 | 49 | 50 | C:\Program Files (x86)\MonoGame\v3.0\Assemblies\WindowsGL\OpenTK.dll 51 | 52 | 53 | 54 | 55 | 56 | C:\Program Files (x86)\MonoGame\v3.0\Assemblies\WindowsGL\Tao.Sdl.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | PreserveNewest 78 | 79 | 80 | 81 | 11 82 | 83 | 84 | 91 | --------------------------------------------------------------------------------