Entities

Before starting with more Haskell game programming it might be a good idea to have a look at some basic concepts of the HGamer3D API and its structure. The most basic building blocks we will need are entities and events. Entities are explained in this section, events in the next one.

Overview

The Entity API is constructed in a way, wich intends to give you an easy to use interface for object creation and property manipulation. Let’s have a look at some interesting topics on entities:

  • they are composable from components
  • they are references to at least one object, combined with properties
  • the CRUD operations are in the IO Monad (most basic interface)
  • their components are build from regular algebraic data types
  • reading and writing to them is fully thread-safe
  • creation is done with newE and a component list
  • one or more of the components of an entity are objects the others are properties
  • attributes modify all objects in one entity

Example

Let’s have a closer view on them by looking at an example. Below you will find the code to create a cube in HGamer3D. It is composed of an IO call to newE and an argument list of key value pairs. Each key denotes a specific type and each value instantiates this type with a corresponding setting.

    -- create the cube geometry entity
    eGeo <- newE hg3d [
        ctGeometry #: ShapeGeometry Cube,
        ctMaterial #: matBlue,
        ctScale #: Vec3 10.0 10.0 10.0,
        ctPosition #: Vec3 0.0 0.0 0.0,
        ctOrientation #: unitU
        ]

In this specific case, the object created is the eGeo one, a geometry object. The other components are actually properties, which are set on the one object. This is automatically done, combining different properties with objects in an entity works the way that each property which is available in the object is set, once the property is modified or initialized. Properties which are not present in the list are set to a default value. Properties which are not available in the object do no harm and are ignored. In the case above the geometry gets a material, a position, a scale and an orientation property.

Functions on Entities

Reading and writing to components can be done with the following methods:

  • readC - reads a component
  • setC - sets a component to a specific value
  • updateC - updates a component with a function from value to value

Those functions are living in the IO Monad and they are fully thread safe, they can be called from any thread concurrently even on one entity, the last set operation wins.

Now let’s see this in action:

    -- rotate the cube around 2 axes
    let rotateCube = do
            forever $ do 
                updateC eGeo ctOrientation (\u -> (rotU vec3Z 0.02) .*. u)
                updateC eGeo ctOrientation (\u -> (rotU vec3X 0.015) .*. u)
                sleepFor (msecT 12)

    forkIO rotateCube

Here a rotation is programmed by updating one component of the entity - namely the rotation - with two calls to updateC. This function takes as a first argument the entity (remember behind the scenes it is a reference) to be modified, as a second argument the component which value is going to be updated and as a third argument a function which takes a value and transforms it into a new value. In this case a rotation around the z-axis by 0.02 and around the x-axis by 0.015 radians. This is done cyclic with a pause of 12 msec.

Components

The “C” in updateC stand for “component” and correspondingly the “ct” in ctOrientation means “component type”.

The data values behind the components are ordinary Haskell algebraic data types. And effects to the game world are caused by setting or updating those data values in the entity conglomerate. This means to use the HGamer3D API you only need to know two things: the first is how entities work, the second are the details of the types which make up the components. Below you will find two examples, which show how easy this can.

data Material = ResourceMaterial Text
    deriving (Eq, Read, Show)

instance ComponentClass Material where
    toObj (ResourceMaterial v1) = ObjectArray [ObjectInt 0, ObjectArray [(toObj v1)]]
    fromObj (ObjectArray [ObjectInt 0, ObjectArray [v1]]) = ResourceMaterial (fromObj v1)

ctMaterial :: ComponentType Material
ctMaterial = ComponentType 0xb4bae8b0d0d8c162

-- some materials from the material folder
matOrangeCrossMetal = ResourceMaterial "Materials/Pattern_01.xml"
matCrossMetal = ResourceMaterial "Materials/Pattern_03.xml"
matCrossMetalBlue = ResourceMaterial "Materials/Pattern_04.xml"
matMetal = ResourceMaterial "Materials/Pattern_12.xml"
matMetalZigZag = ResourceMaterial "Materials/Pattern_13.xml"
matMetalBumps = ResourceMaterial "Materials/Pattern_14.xml"
matFishEye = ResourceMaterial "Materials/Pattern_15.xml"
matMetalOrnament = ResourceMaterial "Materials/Pattern_29.xml"
matMetalScratch = ResourceMaterial "Materials/Pattern_30.xml"
matMetalLine = ResourceMaterial "Materials/Pattern_31.xml"
matGreenGrass = ResourceMaterial "Materials/Pattern_42.xml"
matBrownGrass = ResourceMaterial "Materials/Pattern_43.xml"
matGreyGrass = ResourceMaterial "Materials/Pattern_44.xml"
matSand = ResourceMaterial "Materials/Pattern_45.xml"
matRedRock = ResourceMaterial "Materials/Pattern_46.xml"
matBlackRock = ResourceMaterial "Materials/Pattern_124.xml"
matBrownStone = ResourceMaterial "Materials/Pattern_125.xml"
matStoneMetalWall = ResourceMaterial "Materials/Pattern_126.xml"
matCoalWall = ResourceMaterial "Materials/Pattern_127.xml"
matBrickWallGray = ResourceMaterial "Materials/Pattern_100.xml"
matBrickWallRed = ResourceMaterial "Materials/Pattern_102.xml"
matTilesOrange = ResourceMaterial "Materials/Pattern_270.xml"
matWoodTiles = ResourceMaterial "Materials/Pattern_271.xml"
matColourTiles = ResourceMaterial "Materials/Pattern_272.xml"
matBlackTiles = ResourceMaterial "Materials/Pattern_273.xml"

matWhite = ResourceMaterial "Materials/ColourWhite.xml"
matSilver  = ResourceMaterial "Materials/ColourSilver.xml"
matGrey = ResourceMaterial "Materials/ColourGrey.xml" 
matDarkGrey = ResourceMaterial "Materials/ColourDarkGrey.xml"
matBlack = ResourceMaterial "Materials/ColourBlack.xml" 
matRed = ResourceMaterial "Materials/ColourRed.xml"
matMaroon = ResourceMaterial "Materials/ColourMaroon.xml"
matYellow = ResourceMaterial "Materials/ColourYellow.xml"
matOlive = ResourceMaterial "Materials/ColourOlive.xml" 
matLime = ResourceMaterial "Materials/ColourLime.xml" 
matGreen = ResourceMaterial "Materials/ColourGreen.xml"
matAqua = ResourceMaterial "Materials/ColourAqua.xml" 
matTeal = ResourceMaterial "Materials/ColourTeal.xml" 
matBlue = ResourceMaterial "Materials/ColourBlue.xml"  
matNavy = ResourceMaterial "Materials/ColourNavy.xml"
matFuchsia = ResourceMaterial "Materials/ColourFuchsia.xml"
matPurple = ResourceMaterial "Materials/ColourPurple.xml"

So a Material can be a ResourceMaterial and that’s it. You need to supply a Text value. Of course this is the name of the material resource in the resources. For convenience there are a couple of predefined resource materials available.

data LightType =    PointLight              -- ^ casting light in all directions, from position 
                    | DirectionalLight      -- ^ like a very far light source (the Sun)
                    | SpotLight Angle Float -- ^ a light with a field of view (Angle) and an aspect-ratio (Float)
                
instance ComponentClass LightType where
    toObj PointLight = ObjectArray [ObjectInt 0]
    toObj DirectionalLight =  ObjectArray [ObjectInt 1]
    toObj (SpotLight fov ar) = ObjectArray [ObjectInt 2, toObj fov, ObjectFloat ar]

    fromObj (ObjectArray [ObjectInt 0]) = PointLight
    fromObj (ObjectArray [ObjectInt 1]) = DirectionalLight 
    fromObj (ObjectArray [ObjectInt 2, fov_o, ObjectFloat ar]) = SpotLight (fromObj fov_o) ar

data Light = Light 
                LightType 
                Float     
                Float     
                Float     -- ^ floats: brightness, range, specular intensity

instance ComponentClass Light where
    toObj (Light lt b r s) = ObjectArray [toObj lt, ObjectFloat b, ObjectFloat r, ObjectFloat s]
    fromObj (ObjectArray [lt_o, ObjectFloat b, ObjectFloat r, ObjectFloat s]) = Light (fromObj lt_o) b r s
    
ctLight :: ComponentType Light
ctLight = ComponentType 0x981e80e50d994ea9

A Light is more complex, it can be of type PointLight, SpotLight or DirectionalLight and also has brightness, range and specular intensity.

You can lookup the haddock documentation of all interesting types on hackage, package HGamer3D.

Two additional remarks: the instance ComponentClass is a serializer kind of type, which transforms the ADT into a messagepack object, which is sent to the binding layer. The ctMaterial and ctLight types are used to tag each component with a 64 bit id, which are also used in the binding and additionally to make the components type safe