Frisbee is a simplified games engine. It is written in a different language to the rest of fluxus, and requires no knowledge or use of any of the other fluxus commands.
The language it uses is called 'Father Time' (FrTime), which is a functional reactive programming language available as part of PLT Scheme. Functional reactive programming (frp) is a way of programming which emphasises behaviours and events, and makes them a central part of the language.
Programming a graphics environment like a game is all about describing a scene and behaviours which modify it over time. Using normal programming languages (like the base fluxus one) you generally need to do these things separately, build the scene, then animate it. Using FRP, we can describe the scene with the behaviours built in. The idea is that this makes programs smaller and simpler to modify, thus making the process of programming more creative.
A simple frisbee scene
This is the simplest frisbee scene:
(require fluxus-017/frisbee) (scene (object))
(scene) is the main frisbee command - it is used to define a list of objects and their behaviours.
(object) creates a solid object, by default a cube (of course! :)
We can modify our object by using optional 'keyword parameters', they work like this:
(scene (object #:shape 'sphere))
This sets the shape of the object - there are some built in shapes:
(object #:shape 'cube) (object #:shape 'sphere) (object #:shape 'torus) (object #:shape 'cylinder)
Or, you can also load in .obj files to make your own shapes:
(object #:shape "mushroom.obj")
These object files are relative to where you launch fluxus, or they can also live somewhere in the searchpaths for fluxus (which you can set up in your
.fluxus.scm script using
If we want to change the colour of our cube we can add a new parameter:
(object #:colour (vec3 1 0 0))
The vec3 specifies the rgb colour, so this makes a red cube. Note that frisbee uses (vec3) to make it's vectors, rather than (vector).
Here are the other parameters you can set on an object:
(object #:translate (vec3 0 1 0) #:scale (vec3 0.1 1 0.1) #:rotate (vec3 0 45 0) #:texture "test.png" #:hints '(unlit wire))
It doesn't matter what order you specify parameters in, the results will be the same. The transform order is always translate first, then rotate, then scale.
FrTime makes specifying movement very simple:
(object #:rotate (vec3 0 (integral 10) 0))
Makes a cube which rotates 10 degrees every second. Rather than setting the angles explicitly, integral specifies the amount the rotation changes every second. We can also do this:
(object #:rotate (vec3-integral 0 10 0))
Which is easier in some situations.
Making things reactive
What we have made with the integral command is what is called a behaviour - it's value depends on time. This is a core feature of FrTime, and there are many ways to create and manipulate behaviours. Frisbee also gives you some default behaviours which represent changing information coming from the outside world:
(object #:rotate (vec3 mouse-x mouse-y 0))
This rotates the cube according to the mouse position.
(object #:colour (key-press-b #\c (vec3 1 0 0) (vec3 0 1 0)))
This changes the colour of the cube when you press the 'c' key.
(object #:translate (vec3 0 (key-control-b #\q #\a 0.01) 0))
This moves the cube up and down as you press the 'q' and 'a' keys, by 0.01 units.
So far all the objects we have created have stayed active for the duration of the program running. Sometimes we want to control the lifetime of an object, or create new ones. This is obviously important for many games! To do this, we need to introduce events. Events are another basic part of FrTime and therefore Frisbee, and behaviours can be turned into events and vice versa. Events are things which happen at a specific time, rather than behaviours which can always be asked for their current value. For this reason events can't be directly used for driving objects in Frisbee in the same way as behaviours can - but they are used for triggering new objects into, or out of existence.
Here is a script which creates a continuous stream of cubes:
(scene (factory (lambda (e) (object #:translate (vec3-integral 0.1 0 0))) (metro 1) 5))
There are several new things happening here. Firstly the metro command is short for metronome, which creates a stream of events happening at the rate specified (1 per second in this case). (factory) is a command that listens to a stream of events - taken as it's second argument, and runs a procedure passed as it's first argument on each one (passing the event in as an argument to the supplied function).
So in this case, each time an event occurs, the anonymous function is run, which creates an object moving away from the origin. Left like this frisbee would eventually slow down to a crawl, as more and more cubes are created. So the factory also takes a third parameter, which is the maximum number of things it can have alive at any time. Once 5 objects have been created it will recycle them, and remove the oldest objects.
Frisbee come with some built in events which we can visualise with this script:
(scene (factory (lambda (e) (object #:translate (vec3-integral 0.1 0 0))) keyboard 5))
Which spawns a cube each time a key is pressed on the keyboard.
So far we have been ignoring the event which gets passed into our little cube making function, but as the events the keyboard spits out are the keys which have been pressed, we can make use of them thusly:
(scene (factory (lambda (e) (if (char=? e #\a) ; make a bigger cube if 'a' is pressed (object #:translate (vec3-integral 0.1 0 0) #:scale (vec3 2 2 2)) (object #:translate (vec3-integral 0.1 0 0)))) keyboard 5))
Converting behaviours to events
You can create events when a behaviour changes:
(scene (factory (lambda (e) (object #:translate (vec3-integral 0.1 0 0))) (when-e (> mouse-x 200)) 5))
Frisbee comes with it's own particle system primitive - which makes it easy to make different particle effects.
It is created in a similar way to the solid objects:
And comes with a set of parameters you can control explicitly or via behaviours:
(scene (particles #:colour (vec3 1 1 1) #:translate (vector 0 0 0) #:scale (vector 0.1 0.1 0.1) #:rotate (vector 0 0 0) #:texture "test.png" #:rate 1 #:speed 0.1 #:spread 360 #:reverse #f))