A 2D game engine.
Connecting the state
Games can be written by defining a GameState
and the EngineState
event handlers for it.
Example of a possible GameState you might define:
data GameState = GameState
{ gsSize :: (Double, Double) -- size of the window
, time :: Millisecond -- current time of your game's state
, player :: Player -- your definition for a player
, world :: World -- your definition for the world
}
The EngineState
which has to be implemented for your GameState
:
data EngineState a = EngineState
{ click :: PosX -> PosY -> a -> a -- how your game should change when clicked
, hover :: PosX -> PosY -> a -> a -- how your game should change when hovered
, drag :: PosX -> PosY -> a -> a -- how your game should change when dragged
, resize :: (Width, Height) -> a -> a -- how to resize your game
, getSize :: a -> (Width, Height) -- how to get the size of your game
, moveTime :: Millisecond -> a -> a -- how your game should change over time
, getTime :: a -> Millisecond -- how to get the current time of your game
, setTime :: Millisecond -> a -> a -- how to set the time of your game
, getTitle :: a -> String -- how to get the title of your game
, toGlInstr :: a -> RenderInstruction -- how to receive a render instruction to display your game
}
Rendering
Rendering is done by implementing GlInstructable
for your GameState
, which requires you to define a RenderInstruction
for your state. This type is very composable and can be used to define any combination of basic shapes:
--- The class you have to implement
class GlInstructable a where
toGlInstruction :: a -> RenderInstruction
--- The RenderInstruction type and its constructors
data RenderInstruction = RenderNothing
| RenderWithCamera GlPosX GlPosY GlScaleX GlScaleY RenderInstruction
| RenderText String
| RenderLineStrip GlShape GL.GLfloat
| RenderTriangle GlShape
| RenderLineLoop GlShape GL.GLfloat
| RenderScale GlScaleX GlScaleY
| RenderTranslate GlPosX GlPosY
| RenderRotate Double
| RenderColorize GlColorRGB
| RenderColorizeAlpha GlColorRGBA
| RenderPreserve RenderInstruction
| RenderMany [RenderInstruction]
--- Some example shapes included within HGE2D which can be used to define the RenderInstruction of your GameState
rectangle :: GlWidth -> GlHeight -> RenderInstruction
rectangle w h = RenderTriangle [ll, lr, ur, ur, ul, ll]
where
ll = point2 xMin yMin
lr = point2 xMax yMin
ur = point2 xMax yMax
ul = point2 xMin yMax
xMin = - (w/2)
xMax = (w/2)
yMin = - (h/2)
yMax = (h/2)
borderedRectangle :: GlWidth -> GlHeight -> GlThickness -> GlColorRGB -> GlColorRGBA -> RenderInstruction
borderedRectangle w h t colorInner colorBorder = RenderMany
[ RenderColorize colorInner
, rectangle w h
, RenderColorizeAlpha colorBorder
, wireFrame w h t
]
--- In a game this might look like this
instance GlInstructable Tower where
toGlInstruction t = RenderMany
[ baseRender (base t)
, centerRender (center t)
, RenderPreserve $ RenderMany
[ RenderTranslate (rtf centerX) (rtf centerY)
, RenderRotate (radGun $ gun t)
, gunSkinRender (gunSkin $ gun t)
, radarRender (radarSkin $ radar t)
, rangeRenders
]
]
where
centerX = getX t
centerY = getY t
rangeRenders | not (towerHighlighted t) = RenderNothing
| otherwise = RenderMany [shotRangeRender (shotRange $ shot $ gun t), radarRangeRender (scanRange $ radar t)]
baseRender :: Base -> RenderInstruction
...
centerRender :: Center -> RenderInstruction
...
Some more types, classes and functions
-- | For types which are positioned in space
class Positioned a where
getPos :: a -> RealPosition
getX :: a -> Double
getY :: a -> Double
-- | For types which are affected by time
class Dynamic a where
moveInTime :: Millisecond -> a -> a
-- | Tests whether two objects collide (overlap in any way)
doCollide :: (HasBoundingBox a, HasBoundingBox b) => a -> b -> Bool
-- | A bounding box tree for fast collision detection
data (HasBoundingBox a) => AABBTree a = AABBTreeEmpty
| AABBTreeLeaf [a] BoundingBox
| AABBTreeBranch (AABBTree a) (AABBTree a) BoundingBox