beast/usage/index.html @ 15305ac3db17
adopt: Update site.
| author | Steve Losh <steve@stevelosh.com> |
|---|---|
| date | Thu, 22 Nov 2018 00:39:35 -0500 |
| parents | 19233527280d |
| children | 4fd427fcd0a2 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Usage / beast</title> <link rel="stylesheet" href="../_dmedia/pygments-clean.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">></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="http://bitbucket.org/sjl/beast/src/">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>