Very late response to Will Vale's question!
Thanks for your earlier description of the event tree - I think what I wasn't getting was whether it was fully discrete, or whether events could come in at any point in time - so you jump -10s, but you can start from any time rather than at 10 second intervals.
The latter would make it possible for the tree to grow very large, but since you have a limiting factor in that players only make so many decisions per second, it's probably not a huge deal?
How do you store player movement in the event tree?
Cheers,
Will
So: This is
NOT IMPORTANT TO PLAYING THE GAME, but here's a bunch of internal information about how the game works.
There are:
- Objects
- Events
There are two [main] kinds of objects:
- Bullets
- Players
There are two kinds of events:
- Events (single events, at single points in time)
- Lifetimes (an event plus a "duration"-- the duration can change if it needs to)
Objects have:
- A lifetime-event associated with them
- Arrays recording their position and velocity at each point in time that they exist
Events have:
- A list of "children"-- events with the property "if I happened, so did this".
Lifetimes have, in addition to their duration:
- A list of "children", BUT each child may also have an "after" time-- meaning with lifetimes, dependence is more like "if this lifetime survived AT LEAST to time T, then this other event happened".
Events have a "set_canon" function, which can be used to say "you didn't happen anymore" or "you happened, but you now cease to exist at time T". When this gets called, it goes over its list of children and (if appropriate) updates their canonicity-- it says, you depended on me, but I didn't happen anymore, so you didn't happen either. The children then dutifully call their children and so on...
Aside from this, there is a "forever" array. For each moment in time, this has a list of the events which occurred at that moment and a list of the lifetimes that began at that moment. Remember, time wraps, so there is a finite number of moments.
So, one more object I need to introduce: I also have a sort of a game-runner object I call a "space". It has:
- A Chipmunk physics space.
- A list of "currently alive" lifetimes.
When I simulate the game, I tell the space object to step through the "forever" array, moment by moment, one moment for each drawn frame. At the start of each moment, I trigger the events which are registered at that moment. The events do different things depending on their type. Some of those events are lifetimes, and they insert themselves into the "alive" list for the stepping space. Some of the lifetimes are OBJECT lifetimes, and these ALSO insert the appropriate bodies/shapes for that object into the physics space. After triggering the events, I go over the list of "living" lifetimes, and give each one an option to do more or less whatever it feels like. The object lifetimes in particular take this moment to interact with the physics objects they manage: If the object is "present" (i.e.: it has not filled up yet its position/velocity arrays) then it lets Chipmunk calculate its next position and velocity and then it records them in its record arrays. If an object is "past" (i.e. the position/velocity arrays are already set), it overrides what Chipmunk thinks should happen next with the historical recorded values. (This means that there may be lots of objects on screen at once, but only a few are ever being updated at any one time, the others are just marching through their replays). Eventually each lifetime object reaches its "maximum duration" and then it is removed from the alive array.
Objects and lifetimes cannot "time travel" in any fashion. Sometimes an object lives to the "end of time", the wrap moment. If this happens I actually "destroy" (i.e. set the maximum lifetime of) the object and create a new object at time 0 at the appropriate place. The new object's lifetime event depends on the old object's lifetime event surviving all the way to the end of time.
So, let's consider a very simple thing that could happen in gameplay: Let's say 60 is the "end of time". Let's say at time 50 player 1 fires a bullet. Time passes and we reach time 60; the bullet is still in flight. The player and the bullet are both destroyed. A new player object is born at time 0; it depends on the old player object living to time 60 (though this is irrelevant because if a player ever died the game would end). A new bullet object is also born at time 0; it depends on the old bullet object living to time 60. More time passes; at time 10 new-bullet hits a wall. The bullet is destroyed, and a "kash" event is born at time 10. The "kash" event depends on new-bullet surviving to time 10; what it does is play a sound effect ("KASH!") and draw a little explosion animation. Okay, so this is all well and good. Time keeps passing. Eventually, we get BACK to time 50, again. At this point player 2, who we've been ignoring all this time, comes flying in and, at time 55, accidentally walks into the path of the bullet (the "old" bullet, because we haven't reached the wrap yet). Several things happen at once. A new "kash" event is born at time 55, dependent on old-bullet surviving to time 55; and a new "health loss" event for player 2 is born at time 55 since she got hit by a bullet, dependent on old-bullet surviving to time 55 (health quantities have their own system for tracking I won't get into except to say it's done via the event tree). Meanwhile, old-bullet is destroyed: Its "maximum duration" changes such that it now only survives to time 55. Since the bullet-lifetime's duration has changed, we check the event tree to see if there are any children who are no longer canon. There's one: the "new-bullet" birth event, at time 0, depends on the old-bullet living to time 60. It doesn't anymore, so new-bullet is no longer canon. New-bullet informs its own children (the "kash" event); they are no longer canon either. So time keeps marching and this time, at time 0 new-bullet is NOT born and at time 10 the kash effect does NOT play, because although they're still in the forever array, they're marked as non-canon.
... there's actually a lot of complicated crap happening just to track a bullet here!!! The system's very expressive, though, which is why I think I can do the things I want it to do if I ever make a Snap 2.0 (four players,
players dying and coming back to life, etc).
One final thing: The scenario I gave above describes just stuff happening in a single "space", a single thread of time. However if you perform a snap you can jump around in time. The way this works is that I actually am always running several spaces at a time, scattered throughout the timeline. So if "forever" is 20 seconds and the "snap size" is 5 seconds, I create 4 space objects, one initially "positioned" at 0 seconds, the others at 5, 10 and 15 seconds. On each drawn frame, all four spaces load their respective moments and do the right thing. Each player has a space they're "looking at", and when you snap all you're doing is changing which of the four spaces (four in this example) is drawn on your half of the screen.