Skip to content


Why I switched from component-based game engine architecture to FRP

I was experimenting with component-based game engine architecture for 2 years now and I’d like to share some ideas and issues we had with them in our game projects. To address these issues I eventually stumbled upon functional reactive programming (FRP) and I hope to provide some arguments from an objectoriented programming (OOP) viewpoint to why I think FRP helps to make components more reusable.

The argument for component-based game-engines usually starts like this: Game-objects should be represented like objects in reality. However, class hierarchy becomes more and more complex as the project progress and should be split into small, reusable components. Game developers like abstract and re-usable game-engines. The following diagram illustrates such a class hierarchy (adopted from the book Game Engine Architecture):

Component-based game engines try to solve these issues by reducing game-objects to identifiable containers of components, where each component provides some functionality and automatically communicates with each other. Components could be something like the position, player movement, 3D model, health-points and so on. For communication in our engine we used messages, events or let them directly search for components implementing a specified interface, which is illustrated in the following diagram:

With the component-based architecture we were mostly concerned about the component intercommunication. For example the player movement: Should the movement component manipulate the position directly or send movement messages? Or should the position component listen to movement events? What if we want the movement to behave just a little differently, like being a little random? Do we implement a new random-movement component, or do we need a new component,… again,… just to encapsulate the random procedure into a component? And how does this fit into the automatic position-movement communication?

In the book Component-based Software Engineering (CBSE) they actually define a component as:

A software component is a software element that conforms to a component model and can be independently deployed and composed without modification according to a composition standard. [...] A component model defines specific interaction and composition standards. [...]

In plain English: “In order to allow a new component to be added to an existing environment of components in a game-object, the communication protocol between the components has to predefined, so they can automatically communicate with each other”. And I think the literature about component-based game engines mixes-up “automatic communication with unknown components” with “combining existing functionality to define game-specific logic”. Reusing and combining existing functionality can’t be done automatically, it has to be game-specific (either directly in code, or scripts, or any other data-driven method). Even with components you specifically define a player object for example as: PositionComponent + VisualComponent + InputComponent.

From a FRP perspective in games, everything is based upon time, and thus time should be abstracted away. We can then define small little time-dependent functions, similar to the movement component. It should just produce a translation vector from time- and user-input — that’s it! And if we want it to be a bit random, we just composite it with the random function. There is no need the encapsulate a function in a component, which is then managed by a component container (game-object) and defines a lot of messages and events to get the data to the right point. A pure mathematical function is the most isolated and self-existing component you can get, as it depends (and only depends!) on the input you give it.

I’m arguing that game-objects are always game-specific and all you have to do is connect the existing functionality in the right way. For example the position of an game-object is calculated by: the initial position + some translations over time + a random factor + … and so on. You may then define more complex functions (or complete game-objects if you like) in every way you can imagine: A movable, static image object? MovingImage = translation function + draw image function. A movable, animation? Certainly! MovingAnimation = same translation function + draw animation function. Or just a static animation perhaps? StaticAnimation = position + draw animation function. I’m not going into more detail about FRP for now, you can find more information on my blog or on the internet.

Component-based software engineering can still be applied in this environment, but only on higher level, like the communication between game-objects or the subsystems. Not on the game-object functionality level though!

Tagged with , , , .


FRVR fork of Yampa

I read the paper Dynamic, Interactive Virtual Environments the other day. In section 8.3 Kristopher J. Blom writes about his extension to Yampa called FRVR:

The code structure was improved by:
* separating out the system internals into an AFRPInternals module,
* separating out the switch functionalities into their own module, AFRPSwitches, and
* sorting the remaining code into the proper places.

, advanced developers can explicitly import the internals module when extension of the functionality of Yampa is required.

I don’t know which version of Yampa FRVR uses but maybe this package me to extend reactimate to a full gameloop including resources without extending the Yampa code itself.

The paper and FRVR are part of the DIVE project.

Tagged with .


EclipseFP – Call for feature requests!

JP Moresmau’s, the (new) developer of EclipseFP Haskell IDE plugin, recently releast a new version 1.111 and wrote on EclipseFP blog:

There aren’t too many feature enhancements because I didn’t get any requests for it. I’m not too sure a lot of people are using EclipseFP and are interested in seeing it improved. I suppose now with a Cabalized gtk2hs Leksah becomes more attractive. Anyway, if you have requests for EclipseFP let me know!

There are probably a lot more users like me out there who use EclipseFP and actually do have feature requests. Maybe we should let the developers know and show the project some appreciation :) .

For a user coming from the Windows world I think EclipseFP is the best Haskell IDE. It provides basic features for Haskell development in a familiar editor environment (unlike emacs) and is stable and easy to install (unlike Leksah on Ubuntu 10.04).

Tagged with , .


Dataflow diagram of Yampa reactimate

download diagram (.svg), fonts (cmr10.ttf, cmtt10.ttf)

For me as a Haskell beginner the biggest problem in understanding Yampa reactimate was how the objects are actually passed around and transformed as all the signatures are very, very… very generic. This diagram shows an example scenario staring Pacman (the player), a cherry (enemy trigger) and a ghost (the enemy).

  1. Starting at the upper left corner.
  2. Collect input events in init (which are empty here) and pass them through process, core all the way down to route and killAndSpawn.
  3. core is called with an initial empty object state (which is fed-back in recursively!*) and an initial list of object signal functions. It is very important to separate the logic of the objects (signal functions) and the output they produce (state).
  4. route gets the empty state and no input events, effectively keeping the object collection the same. Note that killAndSpawn doesn’t switch in this step. The object states are passed to output where they are rendered.
  5. In the next step (t=1), still having the same core (core=A), the user produces an input event which is routed to all objects and makes the Pacman move to the cherry. route only checks for collision events in the previous state, thus no collision events are recognized in this step.
  6. In t=2, still having the same core (core=A), route detects a collision between Pacman and the cherry and produces collision events, which are only routed to the objects in charge. This causes killAndSpawn to kill the cherry, spawn the ghost and therefore generate a switching event, which results in the creation of a new core in process.

*) I didn’t really know how to illustrate the recursion of core.

Tagged with , .


Activity diagram of Yampa reactimate

download diagram (.svg), fonts (cmr10.ttf, cmtt10.ttf)

  1. Collect the input events (init and input) in an IO task.
  2. Pass them to process (which is purely functional) and…
  3. core is a Yampa.dpSwitch which consists of route, the object list IL sf and killAndSpawn.
  4. route first reasons about all previous object states to produce logical events (collisions etc.) and…
  5. secondly bundles the input and logical events to the objects (IL (ObjEvents, sf)).
  6. Run all the signal functions which in turn may produce kill or spawn requests of new objects…
  7. which are applied in killAndSpawn to possibly get a new object collection (IL Object) which are then fed-back into core.
  8. Render all objects states and loop.

Tagged with , .


Diagram of Yampa primitives

download diagram (.svg), fonts (cmr10.ttf, cmtt10.ttf)

Tagged with , .


Yampa/SDL program stub

I just completed my first Yampa/SDL program stub. This stub is meant to provide a quickstart for using Yampa with SDL and explains the basic Yampa functions needed for game development in the most minimalistic way I could think of. You can also download the whole source file.

The “game” basically is a player object (black square) which can move around on a 3×3 field and an obstacle object (blue square) which gets killed on collision.

To get an overview of Yampa reactimate have a look at the diagrams of my 2 recent posts Activity diagram of Yampa reactimate and Dataflow diagram of Yampa reactimate.

definitions

At first we are defining some types:

  • Input: non-deterministic events from input devices which have to come from an IO task.
  • Logic: deterministic events from object preprocessor in route
  • ObjEvents: Input and Logic bundled together
  • State: the logical object states (position, velocity etc.) produced after each step which are used for collision detection and rendering
  • ObjOutput: the overall object output consisting of State and the produced kill- and respawn requests.
  • ObjOutput: just an abstract signal function type which takes the events and produces an output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module Main where

import IdentityList

import Maybe
import Control.Monad.Loops

import FRP.Yampa              as Yampa
import FRP.Yampa.Geometry
import Graphics.UI.SDL        as SDL
import Graphics.UI.SDL.Events as SDL.Events
import Graphics.UI.SDL.Keysym as SDL.Keysym

type Position2 = Point2  Double
type Velocity2 = Vector2 Double

type Input = [SDL.Event]    -- non-deterministic events from input devices
type Logic = Yampa.Event () -- deterministic events from object processor

data ObjEvents = ObjEvents
    { oeInput :: Input
    , oeLogic :: Logic
    } deriving (Show)

data State = Rectangle Position2 SDL.Rect SDL.Pixel | Debug String
     deriving (Show)

data ObjOutput = ObjOutput
    { ooState         :: State
    , ooKillRequest   :: Yampa.Event ()       -- NoEvent|Event ()
    , ooSpawnRequests :: Yampa.Event [Object]
    }

defaultObjOutput = ObjOutput
    { ooState         = undefined
    , ooKillRequest   = Yampa.NoEvent
    , ooSpawnRequests = Yampa.NoEvent
    }

type Object = SF ObjEvents ObjOutput

instance (Show a) => Show (Yampa.Event a) where
    show (Yampa.Event a) = "LogicEvent: " ++ (show a)
    show Yampa.NoEvent   = "NoEvent"

instance Show (SF a b) where
    show sf = "SF"

“IdentityList” is taken from the Yampa SpaceInvaders example which you can get via cabal unpack spaceinvaders.

main

Don’t get scared by the long definition, it mostly consists of object bindings. I split main into 2 definitions which can be run seperately by uncommenting them (line 4-5). mainLoop runs the complete game: move via [Arrow] keys and quit with [Esc]. mainSteps runs each step individually and in isolation which should help to understand what is going on and how the types are passed around and transformed. The steps are commented in the source, try to understand them by reading the highlighted lines, the object bindings and the output they produce!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
main :: IO ()
main = do
    -- Uncomment 'mainSteps' or 'mainLoop'!
    --mainLoop  -- Runs the complete reactimate loop.
    --mainSteps -- Tests each reactimate step individually.
  where
    mainLoop :: IO ()
    mainLoop = do
        reactimate initialize input output (process objs)
        SDL.quit
       where
         playerObj   = playerObject (Point2 16 16)
                                    (SDL.Rect (-8) (-8) 8 8)
                                    (SDL.Pixel 0x00000000)
         obstacleObj = staticObject (Point2 48 48)
                                    (SDL.Rect (-8) (-8) 8 8)
                                    (SDL.Pixel 0x000000FF)
         objs = (listToIL [playerObj, obstacleObj])

    mainSteps :: IO ()
    mainSteps = do
        -- initialize :: IO Input
        -- Poll first 'SDL.Event's (should only be 'LostFocus').
        events <- initialize

        -- input :: IO (DTime, Maybe Input)
        -- Poll 'SDL.Event's at each step (probably []).
        events <- input False

        -- hits :: [(ILKey, State)] -> [ILKey]
        -- Testing player over obstacle => collision event.
        putStrLn $ "hits 1: " ++ (show $ hits $ assocsIL $ fmap ooState oos1)

        -- Testing player over enemy => no event.
        putStrLn $ "hits 2: " ++ (show $ hits $ assocsIL $ fmap ooState oos2)

        -- route :: (Input, IL ObjOutput) -> IL sf -> IL (ObjEvents, sf)
        -- Routes 'key' SDL.Event to all 'Object's and
        -- previous object 'State's, if there are any.

        -- First routing step.
        -- No collision events are checked as there are no 'State's yet.
        putStrLn "first route: "
        --mapM putStrLn $ showILObjEvents $ route ([key], emptyIL) objs
        putStrLn $ show $ assocsIL $ route ([key], emptyIL) objs

        -- Intermediate routing step.
        -- Assuming player over obstacle object => create collision event.
        putStrLn "route step: "
        putStrLn $ show $ assocsIL $ route ([key], oos1) objs

        -- killAndSpawn :: (Input, IL ObjOutput)
        --              -> (Yampa.Event (IL Object -> IL Object))
        -- Kill and spawn new objects corresponding to 'ObjOutput' requests.
    -- Note how 'ooObstacle' defined a kill and spawn request
        putStr "objs before kill&Spawn: "
        putStrLn $ show $ keysIL objs
        putStr "objs after kill&Spawn: "
        putStrLn $ show $ keysIL $
            case (killAndSpawn (([], emptyIL), oos1)) of
                (Event d) -> d objs
                _         -> objs

        -- output :: IL ObjOutput -> IO Bool
        -- Just render the 'State's or quit if there is none.
        o1 <- output False oos1
        putStrLn $ show o1
        o2 <- output False oos2
        putStrLn $ show o2
        o3 <- output False emptyIL
        putStrLn $ show o3

        SDL.quit
      where
        key = KeyDown (Keysym
            { symKey = SDL.SDLK_RIGHT
            , symModifiers = []
            , symUnicode = '\0'
            })
        playerObj   = playerObject (Point2 16 16)
                                   (SDL.Rect (-8) (-8) 8 8)
                                   (SDL.Pixel 0x00000000)
        obstacleObj = staticObject (Point2 48 48)
                                   (SDL.Rect (-8) (-8) 8 8)
                                   (SDL.Pixel 0x000000FF)
        objs = (listToIL [playerObj, obstacleObj])

        enemyObj = staticObject (Point2 80 80)
                                (SDL.Rect (-8) (-8) 8 8)
                                (SDL.Pixel 0x00FF0000)
        ooPlayer = defaultObjOutput
            { ooState = Rectangle (Point2 48 48)
                                  (SDL.Rect (-8) (-8) 8 8)
                                  (SDL.Pixel 0x00000000)
            }
        ooObstacle = defaultObjOutput
            { ooState = Rectangle (Point2 48 48)
                                  (SDL.Rect (-8) (-8) 8 8)
                                  (SDL.Pixel 0x000000FF)
            , ooKillRequest   = Event ()
            , ooSpawnRequests = Event [enemyObj]
            }
        ooEnemy = defaultObjOutput
            { ooState = Rectangle (Point2 80 80)
                                  (SDL.Rect (-8) (-8) 8 8)
                                  (SDL.Pixel 0x00FF0000)
            }
        oos1 = listToIL [ooPlayer, ooObstacle]
        oos2 = listToIL [ooPlayer, ooEnemy]

output from mainSteps

…slightly modified for better readability.

0 = playerObject, 1 = obstacleObject, 2 = enemyObject

initialize (sense): [LostFocus [MouseFocus]]
input (sense): []

hits 1: [1,0]
hits 2: []

first route:
[(1, (ObjEvents { oeInput = [KeyDown (Keysym { symKey = SDLK_RIGHT, ... })]
                , oeLogic = NoEvent
                }, SF)),
 (0, (ObjEvents {oeInput = [KeyDown (Keysym { symKey = SDLK_RIGHT, ... })],
                , oeLogic = NoEvent
                }, SF))]

route step:
[(1, (ObjEvents { oeInput = [KeyDown (Keysym { symKey = SDLK_RIGHT, ... })]
                , oeLogic = LogicEvent: ()
                }, SF)),
 (0, (ObjEvents {oeInput = [KeyDown (Keysym { symKey = SDLK_RIGHT, ... })]
                , oeLogic = LogicEvent: ()
                }, SF))]

objs before kill&Spawn: [1,0]
objs after kill&Spawn:  [2,0]

output (actuate) + 500ms delay: False
output (actuate) + 500ms delay: False
output (actuate) + 500ms delay: True

reactimation IO (sense and actuate)

The IO steps are very simple. initialize and inputjust collect the input events (line 10, 22) and output defines the rendering to draw a rectangle or print a debug string and maps over the object output states to draw them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
initialize :: IO Input
initialize = do
    SDL.init [SDL.InitVideo]
    screen <- SDL.setVideoMode windowWidth windowHeight
                               windowDepth [SDL.HWSurface]
    SDL.setCaption windowCaption []

    SDL.fillRect screen Nothing (SDL.Pixel 0x006495ED) -- 0x00RRGGBB
    SDL.flip screen
    events <- unfoldWhileM (/= SDL.NoEvent) SDL.pollEvent

    putStrLn $ "initialize (sense): " ++ show events
    return events
  where
    windowWidth   = 96
    windowHeight  = 96
    windowDepth   = 32
    windowCaption = "Yampa/SDL Stub"

input :: Bool -> IO (DTime, Maybe Input)
input _ = do
    events <- unfoldWhileM (/= SDL.NoEvent) SDL.pollEvent
    putStrLn $ "input (sense): " ++ show events
    return (1.0, Just events)

output :: Bool -> IL ObjOutput -> IO Bool
output _ oos = do
    putStrLn $ "output (actuate) + " ++ (show delayMs) ++ "ms delay: "

    screen <- SDL.getVideoSurface
    SDL.fillRect screen Nothing (SDL.Pixel 0x006495ED) -- Pixel 0x--RRGGBB

    mapM_ (\oo -> render (ooState oo) screen) (elemsIL oos) -- render 'State'!

    SDL.flip screen
    SDL.delay delayMs

    return $ null $ keysIL oos
  where
    delayMs = 500

    render :: State -> SDL.Surface -> IO ()
    render (Rectangle pos rect color) screen = do
        SDL.fillRect screen gRect color
        return ()
      where
        -- center rectangle around position
        x0 = round (point2X pos) + (rectX rect)
        y0 = round (point2Y pos) + (rectY rect)
        x1 = round (point2X pos) + (rectW rect)
        y1 = round (point2Y pos) + (rectH rect)
        gRect = Just (SDL.Rect x0 y0 (x1 - x0) (y1 - y0))
    render (Debug s) screen = putStrLn s

reactimation process (SF)

This is the most important step in reactimate (in -> SF in out -> out) and took me a while to understand. Again, try to get an overview first with the Activity diagram and Dataflow diagram!

process actually just wraps the core to be consistent with the reactimate signature and also feeds the previous output states back into core. The last expression is very interesting as it applies a list of insertIL and deleteIL functions (which are composited together in killAndSpawn) to the object list and switches into the new core. We can say the core is valid as long as the same objects exist.

1
2
3
4
5
6
7
process :: IL Object -> SF Input (IL ObjOutput)
process objs0 = proc input -> do
    rec
        -- 'process' stores the 'State's (note: rec) and
        -- passes them over to core
        oos <- core objs0 -< (input, oos)
    returnA -< oos

Note that core actually takes Input AND the previous object states (IL ObjOutput) as input signals. The dpSwitch is performed on a SF collection (hence parallel and the ‘p’) and the result is observable and applied at the next step (hence delayed and the ‘d’).

1
2
3
4
5
core :: IL Object -> SF (Input, IL ObjOutput) (IL ObjOutput)
core objs = dpSwitch route
                     objs
                     (arr killAndSpawn >>> notYet)
                     (\sfs' f -> core (f sfs'))

The route function actually has 2 tasks:

  1. Reason about the previous object state (if any) and generate logical events like collisions etc.
  2. Distribute input- and logical-events to the corresponding objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
route :: (Input, IL ObjOutput) -> IL sf -> IL (ObjEvents, sf)
route (input, oos) objs = mapIL routeAux objs
  where
    hs = hits (assocsIL (fmap ooState oos)) -- process all object 'State's
    routeAux (k, obj) = (ObjEvents
        { oeInput = input
        -- hit events are only routed to the objects they belong to (hence: routing)
        , oeLogic = if k `elem` hs then Event () else Yampa.NoEvent
        }, obj)

hits :: [(ILKey, State)] -> [ILKey]
hits kooss = concat (hitsAux kooss)
  where
    hitsAux [] = []
    -- Check each object 'State' against each other
    hitsAux ((k,oos):kooss) =
        [ [k, k'] | (k', oos') <- kooss, oos `hit` oos' ]
        ++ hitsAux kooss

    hit :: State -> State -> Bool
    (Rectangle p1 _ _) `hit` (Rectangle p2 _ _) = p1 == p2
    _ `hit` _ = False

killAndSpawn is actually pretty simply once you know what it is doing. It just looks up every object for kill and spawn requests and produces a function composition of deleteIL and insertIL which – in case of a event – is performed on the objects. Remember the expression from core: (\sfs' f -> core (f sfs'))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
killAndSpawn :: ((Input, IL ObjOutput), IL ObjOutput)
             -> Yampa.Event (IL Object -> IL Object)
killAndSpawn ((input, _), oos) =
    if any checkEscKey input
        then Event (\_ -> emptyIL) -- kill all 'State' on [Esc] => quit
        else foldl (mergeBy (.)) noEvent events
  where
    events :: [Yampa.Event (IL Object -> IL Object)]
    events = [ mergeBy (.)
                      (ooKillRequest oo `tag` (deleteIL k))
                      (fmap  (foldl (.) id . map insertIL_)
                             (ooSpawnRequests oo))
             | (k, oo) <- assocsIL oos ]
    checkEscKey (SDL.KeyDown (SDL.Keysym SDL.SDLK_ESCAPE  _ _)) = True
    checkEscKey _ = False

objects

The interesting parts here are that a Object can take parameters just like any other function to produce signal functions. Here it is used to specify the initial position for example. The actual position is calculated by a simple integrator based on the user input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
playerObject :: Position2 -> SDL.Rect -> SDL.Pixel -> Object
playerObject p0 rect color = proc objEvents -> do
    -- .+^ is Point-Vector-addition
    -- ^+^ is Vector-Vector addition
    -- here we sum up all vectors based on the possibly multiple
    -- user inputs, thus allowing diagonal moves
    p <- (p0 .+^) ^<< integral -<
        foldl (^+^) (vector2 0 0) $ mapMaybe checkKey (oeInput objEvents)
    returnA -< defaultObjOutput { ooState = Rectangle p rect color }
    where
        checkKey (SDL.KeyUp (SDL.Keysym SDL.SDLK_UP    _ _)) =
            Just $ vector2    0 (-32)
        checkKey (SDL.KeyUp (SDL.Keysym SDL.SDLK_LEFT  _ _)) =
            Just $ vector2 (-32)   0
        checkKey (SDL.KeyUp (SDL.Keysym SDL.SDLK_DOWN  _ _)) =
            Just $ vector2    0   32
        checkKey (SDL.KeyUp (SDL.Keysym SDL.SDLK_RIGHT _ _)) =
            Just $ vector2   32    0
        checkKey _ = Nothing

staticObject :: Position2 -> SDL.Rect -> SDL.Pixel -> Object
staticObject p0 rect color = proc objEvents -> do
    returnA -< defaultObjOutput { ooState         = Rectangle p0 rect color
                                , ooKillRequest   = (oeLogic objEvents)
                                , ooSpawnRequests = (debugIfKilled objEvents)
                                }
  where
    debugIfKilled objEvents =
        case (oeLogic objEvents) of
            Yampa.Event () -> Event [debugObject "hit"]
            _              -> Event []

debugObject :: String -> Object
debugObject s = proc objEvents -> do
    returnA -< defaultObjOutput { ooState       = Debug s
                                , ooKillRequest = Event ()
                                }

Download the whole source file! (.hs)

Please let me know if the tutorial was helpful or if you didn’t understand something!

Tagged with , , , , , .


Learning Yampa and Functional Reactive Programming

Help us, help you! If you are just starting to learn Yampa, please share your experiences! This post just covers my personal opinion on how to tackle Yampa, maybe you have additional or different recommendations.

Recommendations for learning

I recommend reading the following papers/presentations to learn Yampa and FRP in general:

  1. A Brief Introduction to Functional Reactive Programming and Yampa (slides)
  2. Arrows, FRP, and Functional Reactive Programming (PPT)
  3. Arrows, Robots and Functional Programming: covers Yampa basics in detail (Section 3 is very domain specific and may be omitted)
  4. Functional Reactive Programming, Continued: more Yampa basics
  5. The Yampa Arcade: standard paper for games
  6. Dynamic, Interactive Virtual Environments: read chapter 3 – Time and appendix A – Functional Reactive Programming
  7. Functional Programming and 3D Games: Yampa basics in games, not very detailed though
  8. Functional Reactive Programming from First Principles: Yampa implementation details
  9. Dynamic Optimization for Functional Reactive Programming: Yampa optimization details

Understanding FRP

I think to learn FRP (for games) you have to especially understand the following aspects:

  • Signals make time omnipresent
  • Systems are built with Signal Functions (SF a b)
  • FRP is implemented in standard Haskell
  • Arrow notation makes using FRP convenient and more readable
  • Signal functions diagrams look just mirrored to the actual arrow notation code :)
  • The signal function systems need to be updated somehow – usually via reactimate
  • reactimate divides the programm into input IO (sense), the signal function (SF) and output IO (actuate)
  • Switches allow dynamic changes of the reactive system. Note that in Yampa signal functions are continuation-based so they “switch into” a new signal function.
  • To handle dynamic game object collections use the delayed parallel switch (dpSwitch) signal function
  • Input events are propagated to the objects via route
  • routealso reasons about the whole object collection to produce logical events (f.e. hit detection)
  • killOrSpawn collects all kill and spawn events into one big function composition (insertion/deletion) which is applied to the object collection
  • In the Space Invaders example gameCore :: IL Object -> SF (GameInput, IL ObjOutput) (IL ObjOutput) is actually embedded in the function core :: ... -> SF GameInput (IL ObjOutput) which acts as the intermediate between sense and actuate (this is not mentioned in the Yampa Arcade paper)

Complete list of recommended papers

Covering FRP in general and FRP in games:

List of discarded papers

The reason for discarding was mostly because they are too old, too theoretical or off-topic from games:

  • A Functional Reactive Animation Of A Lift Using Fran
  • A Language for Declarative Robotic Programming
  • Crafting Game-Models Using Reactive System Design
  • Event-Driven FRP
  • FrTime – A Language for Reactive Programs
  • Functional Reactive Animation
  • Functional Reactive Programming for Real-Time Reactive Systems
  • Genuinely Functional User Interfaces
  • Interactive Functional Objects in Clean
  • Modelling Reactive Multimedia – Events and Behaviours
  • Modular Domain Specific Languages and Tools
  • Prototyping Real-Time Vision Systems
  • Reactive Multimedia Documents in a Functional Framework
  • Real-Time FRP

Tagged with , , .


Running primitive signal functions in Yampa

To test signal functions in Yampa, use the embed function. Enter the following commands in the Haskell command-line to show the header definition:

> :type FRP.Yampa.embed

FRP.Yampa.embed :: FRP.Yampa.SF a b -> (a, [(FRP.Yampa.DTime, Maybe a)]) -> [b]

So the parameters are:

  1. the signal function to run
  2. a tuple of…
    1. the first input value at time=0
    2. and a list of…
      1. (time, Nothing|Just nextValue)

and return a list of values produced by the signal function.

Primitive signal functions include: time, identity and constant

main :: IO ()
main = do
    putStrLn $ show $ embed time (Nothing, [(1.0, Nothing), (0.2, Nothing), (0.03, Nothing)])
    putStrLn $ show $ embed time (123, [(1.0, Just 234), (0.2, Just 345), (0.03, Just 456)])
    -- [0.0,1.0,1.2,1.23]
    -- [0.0,1.0,1.2,1.23]

    putStrLn $ show $ embed identity (123, [(1.0, Just 234), (0.2, Just 345), (0.03, Just 456)])
    putStrLn $ show $ embed identity (537, [(1.0, Nothing), (0.2, Nothing), (0.03, Just 123)])
    -- [123,234,345,456]
    -- [537,537,537,537]

    putStrLn $ show $ embed (constant 537) (Nothing, [(1.0, Nothing), (0.2, Nothing), (0.03, Nothing)])
    putStrLn $ show $ embed (constant 537) (123, [(1.0, Just 234), (0.2, Just 345), (0.03, Just 456)])
    -- putStrLn $ show (embed constant (123, [(1.0, Just 234), (0.2, Just 345), (0.03, Just 456)])) -- ERROR
    -- [537,537,537,537]
    -- [537,537,537,537]

Tagged with , , .


Concept of my game engine architecture

this is the concept of my game engine architecture i just scribbled together (please don’t mind the bad design)

Further descriptions will follow.

Tagged with , , , .