I’m going to be taking a bottom-up design approach to this project. Yes, I have an overarching general design, but I fully expect it to change. Change is acceptable and a constant. I’m going to be doing bottom-up design since I am unfamiliar with it, so another chance to learn. I’m not obsessed with keeping my original game design pure as some great mechanics could arise from unforeseen interactions.
Unforeseen interactions is sure to occur since as much of the game as possible will be procedurally generated (another hint, are you keeping track?). Therefore there will be a lot of math involved, not hard math, just a lot of it. So I’m going to start off with some clear functionality that I know will be required. Also, this is a chance to start becoming accustomed to the F# environment.
Here is where I begin. I’m opening Visual Studio 2013 and I’m creating a new solution titled “Morgemil” with one project “Core”.Inside this Core project will be a namespace “Math”. The contents of Math will be stuff such as “Vector2”. Yes the core will be re-inventing the wheel but it is a chance for me to sharpen my teeth on F#. So without further ado, Vector2:
module Math.Vector2 type Vector2f = struct val public X : float val public Y : float new(x, y) = { X = x Y = y } end |
Let’s go through this. On the first line you see “Math.Vector2”. The first part “Math” implicitly defines a namespace and “Vector2” is the module. By defining “Vector2f” as a struct, this is a value-type and will be useful “primarily for small aggregates of data that are accessed and copied frequently.” Both fields are immutable by default. I’ll see if there is a reason to change that later.
This is certainly not all the functionality needed, so I’m going to go through and do some basic operations. And because I can, I’m going to do some F# Operator Overloading to hopefully make my life easier down the road. This will be so that I can include Vector2 in mathematical notation easily. Not only will Vector2 have to be add and subtract against itself, but scalar types too.
Did you notice that I explicitly named “float” as the member type? Could I make this generic? Sure. But then I would have to deal with making the generic constraints for each mathematical operation (+) and (-) and so on for the type. I’d much rather specialize the struct from the outset and provide some unique behavior. So now we have
module Math.Vector2 type Vector2f = struct val public X : float val public Y : float new(x, y) = { X = x Y = y } //########## Operator overloads ##################################### //Addition static member (+) (vec1 : Vector2f, vec2 : Vector2f) = Vector2f(vec1.X + vec2.X, vec1.Y + vec2.Y) static member (+) (vec1 : Vector2f, scalar) = Vector2f(vec1.X + scalar, vec1.Y + scalar) static member (+) (scalar : float, vec1 : Vector2f) = vec1 + scalar //Subtraction static member (-) (vec1 : Vector2f, vec2 : Vector2f) = Vector2f(vec1.X - vec2.X, vec1.Y - vec2.Y) static member (-) (vec1 : Vector2f, scalar) = Vector2f(vec1.X - scalar, vec1.Y - scalar) static member (-) (scalar, vec1 : Vector2f) = Vector2f(scalar - vec1.X, scalar - vec1.Y) //Multiplication static member (*) (vec1 : Vector2f, vec2 : Vector2f) = Vector2f(vec1.X * vec2.X, vec1.Y * vec2.Y) static member (*) (vec1 : Vector2f, scalar) = Vector2f(vec1.X * scalar, vec1.Y * scalar) static member (*) (scalar : float, vec1 : Vector2f) = vec1 * scalar //Division (Does not guard against divide by zero) static member (/) (vec1 : Vector2f, vec2 : Vector2f) = Vector2f(vec1.X / vec2.X, vec1.Y / vec2.Y) static member (/) (vec1 : Vector2f, scalar) = Vector2f(vec1.X / scalar, vec1.Y * scalar) //########## Member methods ######################################### //Distance member this.LengthSquared() = (this.X * this.X) + (this.Y + this.Y) member this.Length() = System.Math.Sqrt(this.LengthSquared()) end |
This is a good start. Is it perfect? No. But it allows some complex statements such as
let one = Vector2f() let two = Vector2f(5.0, 4.0) let three = one * two * 5.0 / 3.0 + 5.0 + one |
That statement has a meaningless evaluation. I just threw some numbers together. But it allows for complex mathematical statements to be expressed concisely. It should be noted here that any later work in C# will not inherit these operators as cleanly, they would have to be called in method form with names similar to “op_Multiply” F# Operator Overloading
I’m sure there will be more methods to add to this type but I’ll add them when a clear need is shown. This is where I leave you for now. I’m going to go ahead and fill out “Vector2i” for integers before the next post.