I don’t believe in game engines. I tend to consider software development as the process of using existing bricks together with glue code to obtain the expect result, whereas engines provide the final result with the holes to be filled in. This is not to say that game engines are useless—countless situations can benefit from them. However, my approach to game development has always been to rely on frameworks and collections of helper and utility classes, perhaps due to my beginnings as a DirectDraw programmer.
Objective Caml games do exist, even though they often lack the backing of a full video game studio. Whether used as a prototyping tool or to develop the final product, I think the functional approach can benefit video game development. To that end, I regularly develop small bricks that can be useful for game development. All of the source code presented here is my own work, placed in the public domain.
Vectors and Transforms
Bidimensional vectors with supporting elementary mathematical operators:
type vector = { x : float; y : float } val right : vector val up : vector val zero : vector val equal : vector -> vector -> bool val ( ++ ) : vector -> vector -> vector val ( -- ) : vector -> vector -> vector val ( *+ ) : float -> vector -> vector val sqlen : vector -> float val len : vector -> float exception ZeroVector val normalize : vector -> vector val dot : vector -> vector -> float
The operators behave as expected. Equality and normalization work with a fixed epsilon of 1e-6. The total memory usage for a vector is 20 bytes (16 for the coordinates, 4 for internal usage), and they are manipulated by reference. Intermediary operations on vectors tend to be inlined, and intermediary values are garbage-collected when present.
Bidimensional transforms and corresponding operations:
type transform val identity : transform val rotate : transform -> float -> transform val scale : transform -> float -> transform val translate : transform -> Vector.vector -> transform type transform_description = { angle : float; scale : float; position : Vector.vector } val make : transform_description -> transform val describe : transform -> transform_description val apply : transform -> Vector.vector -> Vector.vector val compose : transform -> transform -> transform
This type represents similarities. The total memory usage is 36 bytes (32 for the coordinates, 4 for internal usage), and they are manipulated by reference.
Downloads: vector.ml vector.mli transform.ml transform.mli
Indexing
When using functional programming, the aliasing problem makes it impossible to have several data structures directly reference a certain value without having to update all data structures when that value changes. A solution to that problem is to store indirect references instead: the value is stored in a single data structure and bound to a key. The other data structures do not reference the value, but rather “whatever the key is bound to”. Meaning that changing the single data structure holding the value also implicitely updates the value for all other data structures.
An indexing data structure provides exactly that: a way of binding a value to a key and retrieving it with that key. If no backtracks happen, then the following implementation performs all relevant operations in amortized constant time.
type 'a t type handle exception UnboundHandle val empty : int -> 'a t val add : 'a -> 'a t -> 'a t * handle val remove : handle -> 'a t -> 'a t val get : handle -> 'a t -> 'a val map : ('a -> 'b) -> 'a t -> 'b t val mapi : (handle -> 'a -> 'b) -> 'a t -> 'b t val iter : ('a -> unit) -> 'a t -> unit val iteri : (handle -> 'a -> unit) -> 'a t -> unit val fold : (handle -> 'a -> 'b -> 'b) -> 'b -> 'a t -> 'b
Note that “UnboundHandle” is not necessarily raised. If it is, then you know what happened. However, attempting to access a removed handle is, in the general case, undefined, and may return another value.
You can also look for persistent arrays for generic data storage.
Hi. I'm Victor Nicollet,
0 Responses to “Video Game Bricks”