@node Scripting @section Scripting @menu * Overview:: Scripting basics. * Actors:: Scriptable game objects. * Actions:: Action combinators. @end menu @node Overview @subsection Overview @example (use-modules (sly actor)) @end example An actor associates an object within the game world with a ``script'' to control that object. For example, one can control a non-player character's movements in a role-playing game with an actor script. Or, a shoot-em-up game could make the game world itself an actor whose script adds enemies to the world at designated times. Like all high-level interfaces in Sly, it is purely functional. An actor is a wrapper around another game object. To create an actor, use the @code{make-actor} constructor: @example (make-actor (make-enemy) script) @end example Each script is composed of ``actions'', which are procedures that perform a user-defined transformation on a game object. Action procedures take three arguments: The ``world'', the ``effects'' list, and the game object itself. The world is an object that the action can query as part of its logic. For example, an enemy action that shot a bullet aimed at the player could query the world to find out the current location of the player. An effect is a procedure generated by an action that alters the game world rather than the actor itself. Going back to the previous example, in order for the enemy to actually shoot a bullet it would have to add to the effects list, because adding a bullet alters the world, not the actor. An action must return three values: the next action (or @code{#f} if there is nothing left to do), the new effects lists, and the new game object. Let's take a look at the simplest possible action, @code{idle}, which does not change the game object, nor add an effect, nor do anything else afterwards: @example (define (idle world effects object) (values #f effects object)) @end example Now for something a bit more complicated. Here's a higher-order procedure that returns an action that moves an enemy object horizontally: @example (define (move-horizontally x) (lambda (world effects object) (values #f effects (move-enemy object (vector2 x 0))))) ;; Create a new action that moves the enemy by 3 units. (move-horizontally 3) @end example But what if one wants to wait a turn and then move horizontally? Enter action combinators, which allow multiple, simpler actions to be @emph{composed} into a complex action. To perform actions one after another, use the @code{sequence} combinator: @example (sequence idle (move-horizontally 3) idle (move-horizontally 6)) @end example To perform actions at the same time, use the @code{together} combinator: @example (together (move-horizontally 3) shoot-at-player) @end example To perform an action in an unbounded loop, use the @code{forever} combinator: @example (forever (move-horizontally 3)) @end example To perform an action a fixed number of times, use the @code{repeat} combinator: @example (repeat 10 (move-horizontally 3)) @end example To perform one of two actions depending upon a condition, like @code{if} in regular Scheme code, use the @code{ifa} combinator: @example (ifa enemy-close-to-player? shoot-at-player (move-horizontally 3)) @end example To take a single step in the actor script, use @code{update-actor}: @example (update-actor game-world effects-list actor) @end example The following sections provide a detailed reference for all of the procedures in the scripting interface. @node Actors @subsection Actors @deffn {Scheme Procedure} make-actor @var{object} @var{action} Create a new actor that wraps @var{object}, an arbitrary game object, and associates it with @var{action}, an action procedure. @end deffn @deffn {Scheme Procedure} actor? @var{object} Return @code{#t} if @var{object} is an actor. @end deffn @deffn {Scheme Procedure} actor-ref @var{actor} Return the object stored within @var{actor}. @end deffn @deffn {Scheme Procedure} actor-action @var{actor} Return the action for @var{actor}. @end deffn @deffn {Scheme Procedure} update-actor @var{world} @var{effects} @var{actor} Apply the action for @var{actor} with the given @var{world}, the game world object, and @var{effects}, the effects list. @end deffn @deffn {Scheme Procedure} actor-filter-update @var{predicate} @var{world} @var{actors} Update each actor in the list @var{actors} with respect to @var{world} and return a new list of actors whose objects satisfy @var{predicate}. @end deffn @deffn {Scheme Procedure} call-with-actor @var{actor} @var{proc} Apply @var{proc} with the object stored in @var{actor} and return a new actor containing the value returned from @var{proc}. The actor's action remains unchanged. @end deffn @node Actions @subsection Actions @deffn {Scheme Procedure} idle @var{world} @var{effects} @var{object} Do nothing. Do not change @var{object} nor add anything to @var{effects}. @end deffn @deffn {Scheme Procedure} wait @var{duration} Create an action that does nothing @var{duration} times. @end deffn @deffn {Scheme Procedure} forever @var{action} Create an action that performs @var{action} in an infinite loop, once per tick. @end deffn @deffn {Scheme Procedure} sequence @var{actions} @dots{} Create an action that sequentially performs each action in the order specified in the arguments list. @end deffn @deffn {Scheme Procedure} together @var{actions} @dots{} Create an action that concurrently performs each action in the order specified in the arguments list. @end deffn @deffn {Scheme Procedure} ifa @var{predicate} @var{consequent} @var{alternate} Create an action that performs @var{consequent} if @var{predicate} is satisfied, or @var{alternate} otherwise. @var{predicate} is a procedure that accepts a single argument: The game object stored within the actor that is performing the action. @end deffn @deffn {Scheme Procedure} whena @var{predicate} @var{consequent} Create an action that performs @var{consequent} when @var{predicate} is satisfied, otherwise nothing is done. @end deffn @deffn {Scheme Procedure} unlessa @var{predicate} @var{alternate} Create an action that performs @var{alternate} unless @var{predicate} is satisfied, otherwise nothing is done. @end deffn @deffn {Scheme Procedure} repeat @var{times} @var{action} Create an action that performs @var{action} @var{times} in a row. @end deffn @deffn {Scheme Procedure} action-lift Create an action constructor from @var{proc}, a procedure of any number of arguments whose first argument is the game object being transformed. @example (define (move-player player offset) ...) (define move-player* (action-lift move-player)) ;; Create a new action that moves the player horizontally. (move-player* (vector2 10 0)) @end example @end deffn @deffn {Scheme Procedure} both @var{a} @var{b} Peform action @var{a} immediately followed by action @var{b}. When the action is run, the remainder of both @var{a} and @var{b} are returned as the next action to perform. This is a low-level combinator. Use @code{together} instead. @end deffn @deffn {Scheme Procedure} then @var{a} @var{b} Perform action @var{a} followed by action @var{b}. Unlike @code{both}, action @var{b} is not performed immediately after @var{a} finishes, but rather requires another tick. This is a low-level combinator. Use @code{sequence} instead. @end deffn