Game Object Programming Guide
Clone this wiki locally
Editors note: This page is a touch out of date. %PROTO% has been updated. All dynamic components of a Frogatto level are controlled by objects. Objects are defined under `data/objects`. Each type of object has a FrogattoMarkupLanguage (FFL) file defining their behavior. This file may have one or more prototypes, which sets some default behaviour. For example, the ant has a prototype 'hittable', which makes the object respond to having something being thrown at it by default. Prototypes are defined in `data/object_prototypes`.
An object's definition contains several important pieces of data:
- Basic object attributes: Its ID, number of hitpoints, physical behavior such as friction and traction, whether it's solid, etc.
- Event handlers: Attributes beginning with `on_` are event handlers. Objects receive events, and an event handler defines how the object should respond to such an event. If you see `%PROTO%` in an event, have a look at that object's prototype file for additional code. If you see `swallow_event()`, then the object's prototype's code for that event will not be run.
- Object animations: Contained inside `[animation]` tags, these define all the animations an object can enter.
ObjectEvents are received by an object for a large variety of events. An event handler specifies what to do when an event occurs. As a simple example, to make an object bounce whenever it lands on the ground -- when the "collide_feet" event is captured -- we could mirror the y component of its velocity like this:
An object event handler consists of a formula written in FrogattoFormulaLanguage (FFL). FFL is a _pure functional language_. That is to say, a formula does not directly modify any aspects of the game state. Rather, a formula executes, and returns results. These results are typically _commands_ that the game engine then executes on the object that triggered the event.
In the above example, the formula executes and returns a 'set' command which will set the velocity_y of the object.
The formula executed for an event handler has access to all of the ObjectProperties of the object the event is triggered on, and it may use them to calculate its result.
The formula has access to the ObjectFunctions API which contains many functions which can be used to create different kinds of commands that will be executed by the engine.
There are several logic structures that occur in FFL. 1 `if(*boolean expression*, *FFL to do if TRUE*, *FFL to do if FALSE*)` The *boolean expression* uses comparisons like `'<'`, `'!='`, and so on. Expressions may be chained together using boolean logic words, like `'and'` or `'or'`. Example: `x<y and y!=10`. Let's say `x = 5`. `if(x<5, debug('1'), debug('2'))` would print `'2'` to screen. Now, if x was 2, that expression would print `'1'` to screen. 1 `*FFL to do with variable* where *variable* = *FFL*` Executes the first FFL statement using *variable* as the last bit which *variable* equals. For example, `debug(x) where x = 10` would print `'10'` to screen. You can have several `where`s after a statement, like `debug(x,y) where x = 10 where y = 5` would print `'105'` to screen. 1 `map(*list*, *variable*, *FFL to do*)` The *FFL to do* is executed once for each item in the *list*, with that item being called *variable* in the *FFL to do*.
Notes: FFL to do can always be a list of FFL statements. `[debug('1'), debug('2')]` just causes both expressions to be run. Also, FFL is mostly whitespace insensitive. You can put hard returns, tabs, spaces, whatever between things. For example, the pound signs in the following statement can be replaced with whitespace without any effect. `#[#debug#(#'1'#)#,#debug#(#'2'#)#]#``
In the Anura engine, math is safe. For example, a function like 100/sin(x) could crash when x%180=0, but in Frogatto this perfectly OK. This eliminates a class of errors that would only crop up if certain values were assigned to x. Division by zero is equivalent to x*1000000.0. Although a bit odd to the mathematician, this relationship satisfies several basic traits that make math in Frogatto a lot nicer -- it's roughly where we expect the sequence of numbers as x→0 to go. The relationship x/y < 2x/y is always true. It was made this way because it adheres to the principle of least surprise.
In addition to the myriad of errors you will make when programming FFL, you may set up your own errors to make. For example, say you were to make a function that returned a percentage of the width of the current object. If your function expects a percentage between 0.0 and 1.0, you could ensure no one ever passes 50 (for 50%) to the function by mistake like this: def(percent) img_w*percent asserting percent <= 1.0 and percent >= 0 | 'Percent must be a decimal value between 1 and 0.' (Note: img_w is the width of the current object's image.) The asserting keyword is used to throw an error if the following condition fails. The pipe (|) specifies a message that will be outputted when the condition fails. It is good practice to include an explanation of why the error, as a response to "why won't my code run?" Always try to help the future programmer, it might just be you.