(define-by-reference, or: lazy evaluation taken to extremes)
Okay, so I've got a Bomb
entity in my game. It's already got the behavior for flying toward its destination
. What if I want to make a SmartBomb
that tracks a target even if they move? I might do it something like this:
SmartBomb.on_update = function (timestep) {
// other update behaviors go in this function as well
this.destination = target.position
}
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? It's worth noting that what I've thought of here is essentially just functional reactive programming. You can go read wikipedia if you want.
SmartBomb.destination = target.position
Any time something fetches the value of SmartBomb.destination
it'll fetch the value of target.position
. 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 shield
which should float a certain distance between the player
and the enemy
:
shield.position = player.position + shield.distance * unit_vector(enemy.position - player.position)
If and when you do 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:
explosion_location = current_value(bomb.position)
// or maybe the syntax is like this:
explosion_location = bomb.position.current_value
So far, everything's time-independent. For example, the shield
object's position
depends only on the positions of other objects.
Why not include time-dependence? A bullet
has a position
and a velocity
, 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?
bullet.position = bullet.position + time_step * bullet.velocity
// or maybe
bullet.position = bullet.position + bullet.position.time_since_last_calculation * bullet.velocity
Okay, if this seems like impossible magic, I'm sorry. Let's walk through it:
bullet.position
in order to know where it should be drawn.bullet.position
looks at its cached value and sees that it's out of date.bullet.position
subtracts timestamps to find the time step between now and whenever it last got calculated, and remembers this as time_step
bullet.position
, and fetching bullet.velocity
as normal.Hell, why not remember the entire history of a variable's values?Performance. When I fetch the last calculated value for bullet.position
, why not go ahead and make that a graph-crawling reference-calculation just like everything else?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 Achron! And the flaws that make it seem infeasible here wouldn't be a problem in, say, a turn-based time game.
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 no time-dependent complexity - but there are solutions:
shield
's position
, you don't re-fetch the enemy.position
, if you know it hasn't changed.Besides which, we have fast computers, and without running into hard algorithmic complexity problems (collision detection, I'm lookin' at you!) it's not that 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.
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 bullet
.
bullet.position = Wrapper(v(1,2)) offset = v(3,4) endpoint = bullet.position + offset print endpoint
--> WrapperSum(bullet.position, offset)print endpoint.current_value
--> v(4,6)bullet.position.current_value = v(-10,0) print endpoint.current_value
--> v(-7,4)
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...
print bullet.contacts
--> Wrapper(List<Contacts>)contact_acceleration = sum(contact.force_to_resolve for contact in bullet.contacts) bullet.acceleration = contact_acceleration
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?
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:
bullet
's position should just be a vector, with which I should be able to do math as freely and easily as integers.collision component
's position is the same as the bullet
's position! I'd like to do collision_comp.position = bullet.position
bullet.position
then it's not going to change collision_comp.position
because pass-by-value is the dominant paradigm.The solution I've been working with is wrap together sets of related data (as components). The player
object has a PositionComponent
and a CollisionComponent
, and the CollisionComponent
stores a reference to the player
's PositionComponent
so that it can access it at any time. Actually holding those references constant means that there would be problems if the player.position_component
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.
So I don't know. I would love to have the capacity for functional reactive programming in my game engines, at the least! It's so useful for making bulletproof dynamic interactive systems.