From 9972e5c26da25c29939e5a75f59db5d58c14a31c Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 18 Apr 2016 20:39:39 -0400 Subject: Add actor module. * sly/actor.scm: New file. * Makefile.am (SOURCES): Add it. * doc/api/scripting.texi: New file. * doc/Makefile.am (sly_TEXINFOS): Add it. * doc/sly.texi: Include scripting section. * .dir-locals.el: Add identing rule for call-with-actor. --- doc/Makefile.am | 1 + doc/api/scripting.texi | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ doc/sly.texi | 2 + 3 files changed, 241 insertions(+) create mode 100644 doc/api/scripting.texi (limited to 'doc') diff --git a/doc/Makefile.am b/doc/Makefile.am index 4f7d4a2..abf5170 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -9,6 +9,7 @@ sly_TEXINFOS = \ api/time.texi \ api/input.texi \ api/rendering.texi \ + api/scripting.texi \ api/utils.texi dvi: # Don't build dvi docs diff --git a/doc/api/scripting.texi b/doc/api/scripting.texi new file mode 100644 index 0000000..ca2cfd4 --- /dev/null +++ b/doc/api/scripting.texi @@ -0,0 +1,238 @@ +@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 diff --git a/doc/sly.texi b/doc/sly.texi index e0c081d..127f52b 100644 --- a/doc/sly.texi +++ b/doc/sly.texi @@ -167,6 +167,7 @@ See their respective home pages for installation and usage instructions. * Booting:: Opening a window and running the game loop. * Math:: Vectors, quaternions, matrices, etc. * Time:: Tick-tock. +* Scripting:: Functional game object scripting. * Input:: Keyboard, mouse, and joystick input. * Rendering:: Drawing to the screen. * Utilities:: Miscellaneous conveniences. @@ -175,6 +176,7 @@ See their respective home pages for installation and usage instructions. @include api/init.texi @include api/math.texi @include api/time.texi +@include api/scripting.texi @include api/input.texi @include api/rendering.texi @include api/utils.texi -- cgit v1.2.3