beast/usage/index.html @ 4fd427fcd0a2

beast: Update site.
author Steve Losh <steve@stevelosh.com>
date Mon, 23 Dec 2019 16:22:20 -0500
parents 19233527280d
children (none)
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Usage / beast</title>
        <link rel="stylesheet" href="../_dmedia/tango.css"/>
        <link rel="stylesheet/less" type="text/css" href="../_dmedia/style.less"/>
        <script src="../_dmedia/less.js" type="text/javascript">
        </script>
    </head>
    <body class="content">
        <div class="wrap">
            <header><h1><a href="..">beast</a></h1></header>
                <div class="markdown">
<h1 id="usage"><a href="">Usage</a></h1><p>Beast aims to be a thin layer over CLOS, and so has a fairly small user-facing
API.</p>
<div class="toc">
<ul>
<li><a href="#aspects">Aspects</a><ul>
<li><a href="#defining-aspects">Defining Aspects</a></li>
<li><a href="#aspect-slot-options">Aspect Slot Options</a></li>
<li><a href="#aspect-type-predicates">Aspect Type Predicates</a></li>
</ul>
</li>
<li><a href="#entities">Entities</a><ul>
<li><a href="#defining-entities">Defining Entities</a></li>
<li><a href="#entity-slot-definitions">Entity Slot Definitions</a></li>
<li><a href="#creating-and-destroying-entities">Creating and Destroying Entities</a></li>
<li><a href="#callbacks">Callbacks</a></li>
</ul>
</li>
<li><a href="#systems">Systems</a></li>
<li><a href="#next-steps">Next Steps</a></li>
</ul></div>
<h2 id="aspects">Aspects</h2>
<p>Aspects are facets/traits of your game objects that you want to model.  Some
examples could be things like: location, moveable, visible, edible, sentient.</p>
<h3 id="defining-aspects">Defining Aspects</h3>
<p>To define an aspect you use <code>define-aspect</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">location</span> <span class="nv">x</span> <span class="nv">y</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">edible</span> <span class="nv">nutrition-value</span><span class="p">)</span>
</pre></div>


<p><code>define-aspect</code> takes the name of the aspect and zero or more slot definitions.</p>
<p>The names of aspect slots will have the aspect name prepended to them with
a slash to avoid clashing between aspects, and the <code>initargs</code> and <code>accessors</code>
will be added for you.  So for example, this:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">inspectable</span> <span class="nv">text</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">readable</span> <span class="nv">text</span><span class="p">)</span>
</pre></div>


<p>Would macroexpand into something roughly like:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nb">defclass</span> <span class="nv">inspectable</span> <span class="p">()</span>
  <span class="p">((</span><span class="nv">inspectable/text</span> <span class="ss">:initarg</span> <span class="ss">:inspectable/text</span>
                     <span class="ss">:accessor</span> <span class="nv">inspectable/text</span><span class="p">)))</span>

<span class="p">(</span><span class="nb">defclass</span> <span class="nv">readable</span> <span class="p">()</span>
  <span class="p">((</span><span class="nv">readable/text</span> <span class="ss">:initarg</span> <span class="ss">:readable/text</span>
                  <span class="ss">:accessor</span> <span class="nv">readable/text</span><span class="p">)))</span>
</pre></div>


<h3 id="aspect-slot-options">Aspect Slot Options</h3>
<p>You can include extra slot options when defining an aspect's slots, and they'll
be passed along to the <code>defclass</code>.  This is especially handy for <code>:initform</code>
and <code>:type</code>.</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">container</span>
  <span class="p">(</span><span class="nv">contents</span> <span class="ss">:initform</span> <span class="no">nil</span><span class="p">))</span>

<span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">throwable</span>
  <span class="p">(</span><span class="nv">accuracy</span> <span class="ss">:type</span> <span class="kt">single-float</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">damage</span> <span class="ss">:type</span> <span class="nc">integer</span><span class="p">))</span>
</pre></div>


<p>In the end it's just CLOS though, so if you want to add some <code>:documentation</code> or
use <code>:allocation :class</code> then go nuts!</p>
<h3 id="aspect-type-predicates">Aspect Type Predicates</h3>
<p>When you define an aspect named <code>foo</code> Beast also defines a <code>foo?</code> predicate that
returns <code>(typep object 'foo)</code>, which comes in handy when using higher-order
functions:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nb">defun</span> <span class="nv">whats-for-dinner?</span> <span class="p">()</span>
  <span class="p">(</span><span class="nb">remove-if-not</span> <span class="nf">#'</span><span class="nv">edible?</span> <span class="p">(</span><span class="nv">container/contents</span> <span class="vg">*fridge*</span><span class="p">)))</span>
</pre></div>


<h2 id="entities">Entities</h2>
<p>Once you've got some aspects you'll want to define some entity classes that mix
them together.</p>
<h3 id="defining-entities">Defining Entities</h3>
<p>You define entity classes using <code>define-entity</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-entity</span> <span class="nv">dart</span> <span class="p">(</span><span class="nv">throwable</span><span class="p">))</span>
<span class="p">(</span><span class="nv">define-entity</span> <span class="nv">bread</span> <span class="p">(</span><span class="nv">edible</span><span class="p">))</span>
<span class="p">(</span><span class="nv">define-entity</span> <span class="nv">pie</span> <span class="p">(</span><span class="nv">edible</span> <span class="nv">throwable</span><span class="p">))</span>
<span class="p">(</span><span class="nv">define-entity</span> <span class="nv">icebox</span> <span class="p">(</span><span class="nv">container</span><span class="p">))</span>
</pre></div>


<p>The resulting classes will inherit from <code>entity</code> and each of the aspects (in
order).  This example would expand (roughly) into:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nb">defclass</span> <span class="nv">dart</span> <span class="p">(</span><span class="nv">entity</span> <span class="nv">throwable</span><span class="p">)</span> <span class="p">())</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">dart?</span> <span class="p">(</span><span class="nv">object</span><span class="p">)</span> <span class="p">(</span><span class="nb">typep</span> <span class="nv">object</span> <span class="ss">'dart</span><span class="p">))</span>

<span class="p">(</span><span class="nb">defclass</span> <span class="nv">bread</span> <span class="p">(</span><span class="nv">entity</span> <span class="nv">edible</span><span class="p">)</span> <span class="p">())</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">bread?</span> <span class="p">(</span><span class="nv">object</span><span class="p">)</span> <span class="p">(</span><span class="nb">typep</span> <span class="nv">object</span> <span class="ss">'bread</span><span class="p">))</span>

<span class="p">(</span><span class="nb">defclass</span> <span class="nv">pie</span> <span class="p">(</span><span class="nv">entity</span> <span class="nv">edible</span> <span class="nv">throwable</span><span class="p">)</span> <span class="p">())</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">pie?</span> <span class="p">(</span><span class="nv">object</span><span class="p">)</span> <span class="p">(</span><span class="nb">typep</span> <span class="nv">object</span> <span class="ss">'pie</span><span class="p">))</span>

<span class="p">(</span><span class="nb">defclass</span> <span class="nv">icebox</span> <span class="p">(</span><span class="nv">entity</span> <span class="nv">container</span><span class="p">)</span> <span class="p">())</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">icebox?</span> <span class="p">(</span><span class="nv">object</span><span class="p">)</span> <span class="p">(</span><span class="nb">typep</span> <span class="nv">object</span> <span class="ss">'icebox</span><span class="p">))</span>
</pre></div>


<h3 id="entity-slot-definitions">Entity Slot Definitions</h3>
<p>You can also specify slot definitions at the entity level, and they'll be passed
along to <code>defclass</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-entity</span> <span class="nv">cheese</span> <span class="p">(</span><span class="nv">edible</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">variety</span> <span class="ss">:type</span> <span class="p">(</span><span class="nb">member</span> <span class="ss">:swiss</span> <span class="ss">:cheddar</span> <span class="ss">:feta</span><span class="p">)</span>
           <span class="ss">:initarg</span> <span class="ss">:variety</span>
           <span class="ss">:reader</span> <span class="ss">:cheese-variety</span><span class="p">))</span>
</pre></div>


<p>Note that slot definitions on entities are passed along <strong>raw</strong>, without the
name-mangling or default-slot-option-adding that's done for aspects.  This may
change in the future.</p>
<h3 id="creating-and-destroying-entities">Creating and Destroying Entities</h3>
<p>After you've defined your entity classes you can can create some entities using
<code>create-entity</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nb">defparameter</span> <span class="vg">*my-fridge*</span> <span class="p">(</span><span class="nv">create-entity</span> <span class="ss">'icebox</span><span class="p">))</span>

<span class="p">(</span><span class="nb">dotimes</span> <span class="p">(</span><span class="nv">i</span> <span class="mi">30</span><span class="p">)</span>
  <span class="p">(</span><span class="nb">push</span> <span class="p">(</span><span class="nv">create-entity</span> <span class="ss">'cheese</span>
          <span class="ss">:edible/nutrition-value</span> <span class="mi">10</span>
          <span class="ss">:variety</span> <span class="p">(</span><span class="nb">nth</span> <span class="p">(</span><span class="nb">random</span> <span class="mi">3</span><span class="p">)</span> <span class="o">'</span><span class="p">(</span><span class="ss">:swiss</span> <span class="ss">:cheddar</span> <span class="ss">:feta</span><span class="p">)))</span>
        <span class="p">(</span><span class="nv">container/contents</span> <span class="vg">*my-fridge*</span><span class="p">)))</span>
</pre></div>


<p><code>create-entity</code> is a thin wrapper around <code>make-instance</code> that handles some extra
bookkeeping.  When you create an entity, Beast will keep track of it in a global
index.  We'll see the reason for this in the next section.</p>
<p>To destroy an entity (i.e. remove it from Beast's index) you can use
<code>(destroy-entity the-entity)</code>.  You can wipe the slate clean and remove all
entities at once with <code>(clear-entities)</code>.</p>
<h3 id="callbacks">Callbacks</h3>
<p>Beast also defines two generic functions called <code>entity-created</code> and
<code>entity-destroyed</code> which don't do anything by default, but are there for you to
add methods on if you want.  For example:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-aspect</span> <span class="nv">location</span> <span class="nv">x</span> <span class="nv">y</span><span class="p">)</span>

<span class="p">(</span><span class="nb">defvar</span> <span class="vg">*world*</span> <span class="p">(</span><span class="nb">make-array</span> <span class="p">(</span><span class="mi">100</span> <span class="mi">100</span><span class="p">)</span> <span class="ss">:initial-element</span> <span class="no">nil</span><span class="p">))</span>

<span class="p">(</span><span class="nb">defmethod</span> <span class="nv">entity-created</span> <span class="ss">:after</span> <span class="p">((</span><span class="nv">e</span> <span class="nv">location</span><span class="p">))</span>
  <span class="p">(</span><span class="nb">push</span> <span class="nv">e</span> <span class="p">(</span><span class="nb">aref</span> <span class="vg">*world*</span> <span class="p">(</span><span class="nv">location/x</span> <span class="nv">e</span><span class="p">)</span> <span class="p">(</span><span class="nv">location/y</span> <span class="nv">e</span><span class="p">))))</span>

<span class="p">(</span><span class="nb">defmethod</span> <span class="nv">entity-destroyed</span> <span class="ss">:after</span> <span class="p">((</span><span class="nv">e</span> <span class="nv">location</span><span class="p">))</span>
  <span class="p">(</span><span class="nb">with-slots</span> <span class="p">((</span><span class="nv">x</span> <span class="nv">location/x</span><span class="p">)</span> <span class="p">(</span><span class="nv">y</span> <span class="nv">location/y</span><span class="p">))</span> <span class="nv">e</span>
    <span class="p">(</span><span class="nb">setf</span> <span class="p">(</span><span class="nb">aref</span> <span class="vg">*world*</span> <span class="nv">x</span> <span class="nv">y</span><span class="p">)</span> <span class="p">(</span><span class="nb">delete</span> <span class="nv">e</span> <span class="p">(</span><span class="nb">aref</span> <span class="vg">*world*</span> <span class="nv">x</span> <span class="nv">y</span><span class="p">)))))</span>
</pre></div>


<h2 id="systems">Systems</h2>
<p>Beast's aspects and entities are just <em>very</em> thin sugar over CLOS, but systems
provide extra functionality that comes in handy when writing games.</p>
<p>A system is essentially a function that takes an entity as an argument with
zero or more aspects as type specifiers.  When you run a system the function
will be run on every entity that meet the requirements.  For example:</p>
<div class="codehilite"><pre><span/><span class="c1">; No specifiers, this just runs on every entity.</span>
<span class="p">(</span><span class="nv">define-system</span> <span class="nv">log-all-entities</span> <span class="p">(</span><span class="nv">entity</span><span class="p">)</span>
  <span class="p">(</span><span class="nb">print</span> <span class="nv">entity</span><span class="p">))</span>

<span class="c1">; Runs on entities with the lifetime aspect.</span>
<span class="p">(</span><span class="nv">define-system</span> <span class="nv">age</span> <span class="p">((</span><span class="nv">entity</span> <span class="nv">lifetime</span><span class="p">))</span>
  <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nb">&gt;</span> <span class="p">(</span><span class="nb">incf</span> <span class="p">(</span><span class="nv">lifetime/age</span> <span class="nv">entity</span><span class="p">))</span>
           <span class="p">(</span><span class="nv">lifetime/lifespan</span> <span class="nv">entity</span><span class="p">))</span>
    <span class="p">(</span><span class="nv">destroy-entity</span> <span class="nv">entity</span><span class="p">)))</span>

<span class="c1">; Run on entities with both the visible and location aspects.</span>
<span class="p">(</span><span class="nv">define-system</span> <span class="nv">render</span> <span class="p">((</span><span class="nv">entity</span> <span class="nv">visible</span> <span class="nv">location</span><span class="p">))</span>
  <span class="p">(</span><span class="nv">draw</span> <span class="nv">entity</span> <span class="p">(</span><span class="nv">location/x</span> <span class="nv">entity</span><span class="p">)</span>
               <span class="p">(</span><span class="nv">location/y</span> <span class="nv">entity</span><span class="p">)</span>
               <span class="p">(</span><span class="nv">visible/color</span> <span class="nv">entity</span><span class="p">)))</span>
</pre></div>


<p>Systems with more than one argument are currently supported, but should be
considered experimental.  The API may change in the future.</p>
<div class="codehilite"><pre><span/><span class="c1">; Run on all PAIRS of entities that have the appropriate aspects.</span>
<span class="p">(</span><span class="nv">define-system</span> <span class="nv">detect-collisions</span> <span class="p">((</span><span class="nv">e1</span> <span class="nv">location</span> <span class="nv">collidable</span><span class="p">)</span>
                                  <span class="p">(</span><span class="nv">e2</span> <span class="nv">location</span> <span class="nv">collidable</span><span class="p">))</span>
  <span class="c1">; ...</span>
  <span class="p">)</span>
</pre></div>


<p><code>define-system</code> defines a function with the same name as the system, and
a <code>run-...</code> function that will do the actual running for you:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-system</span> <span class="nv">log-all-entities</span> <span class="p">(</span><span class="nv">entity</span><span class="p">)</span>
  <span class="p">(</span><span class="nb">print</span> <span class="nv">entity</span><span class="p">))</span>

<span class="p">(</span><span class="nv">run-log-all-entities</span><span class="p">)</span>
</pre></div>


<p>You should always use the <code>run-...</code> function, but the other one can be handy to
have around for tracing/debugging/disassembling purposes.</p>
<h2 id="next-steps">Next Steps</h2>
<p>That's most of Beast in a nutshell.  If you've gotten this far you can dive in
and make something, or take a look at the <a href="../reference/">API Reference</a>.</p>
<p>Beast also does some stuff not discussed here like caching entities by
aspect/system and type-hinting system functions.  If you're curious about how it
works you can <a href="https://hg.sr.ht/~sjl/beast/">read the source</a>.</p>
                </div>
            <footer><p><i>Made with Lisp and love by <a href="http://stevelosh.com/">Steve Losh</a> in Reykjavík, Iceland.</i></p>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-15328874-3', 'auto');
  ga('send', 'pageview');

</script></footer>
        </div>
    </body>
</html>