64 lines
8.4 KiB
HTML
64 lines
8.4 KiB
HTML
<article>
|
|
<header>
|
|
<h1>Snorlax Evaluation</h1>
|
|
<p>(define-by-reference, or: lazy evaluation taken to extremes)</p>
|
|
</header>
|
|
<p>Okay, so I've got a <code>Bomb</code> entity in my game. It's already got the behavior for flying toward its <code>destination</code>. What if I want to make a <code>SmartBomb</code> that tracks a target even if they move? I might do it something like this:</p>
|
|
<pre><code>SmartBomb.on_update = function (timestep) {
|
|
// other update behaviors go in this function as well
|
|
this.destination = target.position
|
|
}</code></pre>
|
|
<p>I have to explicitly tell my engine to execute this every frame. I think in terms of this kind of thing already, because I'm a math and physics person. Why can't I define relationships like this more simply? What if, in fact, this was the default way we referred to values? <span class="sidenote span4">It's worth noting that what I've thought of here is essentially just <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">functional reactive programming</a>. You can go read wikipedia if you want.</span></p>
|
|
<pre><code>SmartBomb.destination = target.position</code></pre>
|
|
<p>Any time something fetches the value of <code>SmartBomb.destination</code> it'll fetch the value of <code>target.position</code>. Writing it this way, instead of explicitly specifying which variables need to be kept up-to-date with other variables (and when they need to be updated!), means it's easier to give variables interesting dependencies on other values - like this <code>shield</code> which should float a certain distance between the <code>player</code> and the <code>enemy</code>:</p>
|
|
<pre><code>shield.position = player.position + shield.distance * unit_vector(enemy.position - player.position)</code></pre>
|
|
<p>If and when you <em>do</em> need to store something's value at a specific time, you can do that if you want - for example, this code when a bomb explodes:</p>
|
|
<pre><code>explosion_location = current_value(bomb.position)
|
|
// or maybe the syntax is like this:
|
|
explosion_location = bomb.position.current_value</code></pre>
|
|
<p>So far, everything's time-independent. For example, the <code>shield</code> object's <code>position</code> depends only on the positions of other objects.</p>
|
|
<p>Why not include time-dependence? A <code>bullet</code> has a <code>position</code> and a <code>velocity</code>, and these are related in a way that doesn't change at any point during gameplay. Why not codify that relationship in a similar way?</p>
|
|
<pre><code>bullet.position = bullet.position + time_step * bullet.velocity
|
|
// or maybe
|
|
bullet.position = bullet.position + bullet.position.time_since_last_calculation * bullet.velocity</code></pre>
|
|
<p>Okay, if this seems like impossible magic, I'm sorry. Let's walk through it:</p>
|
|
<ol>
|
|
<li>The global loop asks for <code>bullet.position</code> in order to know where it should be drawn.</li>
|
|
<li><code>bullet.position</code> looks at its cached value and sees that it's out of date.</li>
|
|
<li><code>bullet.position</code> subtracts timestamps to find the time step between now and whenever it last got calculated, and remembers this as <code>time_step</code></li>
|
|
<li>The time-dependence expression is now evaluated, using the last calculated value for <code>bullet.position</code>, and fetching <code>bullet.velocity</code> as normal.</li>
|
|
</ol>
|
|
<p>Hell, why not remember the entire history of a variable's values?<span class="sidenote span4">Performance.</span> When I fetch the last calculated value for <code>bullet.position</code>, why not go ahead and make that a graph-crawling reference-calculation just like everything else?<span class="sidenote span4">Reactive time-dependence is something to think about for an interesting mechanic, if nothing else - keeping track of dependencies on history means you could change a value at any point throughout the game's history and see how it changes things... Just like <a href="http://achrongame.com">Achron</a>! And the flaws that make it seem infeasible here wouldn't be a problem in, say, a turn-based time game.</span></p>
|
|
<p>Performance is a legitimate worry at that point. If you include the full-on time-dependence, then depending on implementation you might end up following a reference back through every frame the game's been through. This approach would obviously fall apart because every value in your world is going to be at least O(t) to calculate, which sucks - there should be <em>no</em> time-dependent complexity - but there are solutions:</p>
|
|
<ul>
|
|
<li>Careful design can ensure that your objects aren't too densely interlinked.</li>
|
|
<li>If you know you can do things more efficiently in some strenuous case, you can always hard-code it to extract current values and explicitly pass them around whenever you want.</li>
|
|
<li>Variables should carry metadata about when they last changed, and when their predecessors and descendents in the graph have changed - so that, in calculating the <code>shield</code>'s <code>position</code>, you don't re-fetch the <code>enemy.position</code>, if you know it hasn't changed.</li>
|
|
</ul>
|
|
<p>Besides which, we have fast computers, and without running into hard algorithmic complexity problems (collision detection, I'm lookin' at you!) it's not <em>that</em> easy to overload a modern gaming machine. And hey, maybe having declarative time-dependent values is a pipe dream - I don't care. The core idea is still awesome.</p>
|
|
<p>Here's an example (with more attention paid to data types) of how I'd come up with a vector that always points to a set offset from the position of <code>bullet</code>.</p>
|
|
<pre><code>bullet.position = Wrapper(v(1,2))
|
|
offset = v(3,4)
|
|
endpoint = bullet.position + offset
|
|
print endpoint</code>
|
|
<samp>--> WrapperSum(bullet.position, offset)</samp>
|
|
<code>print endpoint.current_value</code>
|
|
<samp>--> v(4,6)</samp>
|
|
<code>bullet.position.current_value = v(-10,0)
|
|
print endpoint.current_value</code>
|
|
<samp>--> v(-7,4)</samp></pre>
|
|
<p>Once I've started thinking and talking about this, I start wondering how much we need imperative game loops at all, for anything other than those troublesome time dependencies and feedback loops. I wonder...</p>
|
|
<pre><code>print bullet.contacts</code>
|
|
<samp>--> Wrapper(List<Contacts>)</samp>
|
|
<code>contact_acceleration = sum(contact.force_to_resolve for contact in bullet.contacts)
|
|
bullet.acceleration = contact_acceleration</code></pre>
|
|
<p>Assuming that force due to contacts is the only thing present in the world, then the bullet now feels enough acceleration to resolve all the conflicts. I mean, assuming contacts are simple to work with, and they always are. Right? Right?</p>
|
|
<p>I'm thinking about this a lot because this kind of referencing is very natural for things like defining interlinks between components in my game engine - I've got conflicting interests:</p>
|
|
<ul>
|
|
<li>I want to keep my vectors and properties lightweight, so that a <code>bullet</code>'s position should just be a vector, with which I should be able to do math as freely and easily as integers.</li>
|
|
<li>But I also want to ensure that the <code>collision component</code>'s position is the same as the <code>bullet</code>'s position! I'd like to do <code>collision_comp.position = bullet.position</code></li>
|
|
<li>And then I run into the problem that since I want my vectors as primitive as possible, if I change <code>bullet.position</code> then it's not going to change <code>collision_comp.position</code> because pass-by-value is the dominant paradigm.</li>
|
|
</ul>
|
|
<p>The solution I've been working with is wrap together <em>sets</em> of related data (as <i>components</i>). The <code>player</code> object has a <code>PositionComponent</code> and a <code>CollisionComponent</code>, and the <code>CollisionComponent</code> stores a reference to the <code>player</code>'s <code>PositionComponent</code> so that it can access it at any time. Actually holding those references constant means that there would be problems if the <code>player.position_component</code> suddenly changed to a different object - but that should never happen. I'm satisfied with this for the reasons I implemented it in the first place, but it doesn't get me the dynamic interlinking I'd like from something more like functional reactive programming.</p>
|
|
<p>So I don't know. I would love to have the <em>capacity</em> for functional reactive programming in my game engines, at the least! It's so useful for making bulletproof dynamic interactive systems.</p>
|
|
</article> |