Temporal Objects and Temporal Object Accessories


The core of this game is about learning the routines of the world, who'll be where at exactly what point in time, given you've put the world in a particular state. Conceptually, it's easy to implement this; in fact it was one of the first things that I did for this project. However, this system slowly grew a shell of additional parts that makes it the one that I've spent by far the most time on. I think it's developed into something really great, so I'll walk you through a bit of its history and my thought processes.

To build a TemporalObject - one that follows a predefined schedule / path - Just hold a collection of objects like this, and you're 90% of the way there:

TemporalPoint
{
    public WorldTime time;
    public Vector3 pos; }

The Temporal Object simply checks which Temporal Point it visited last, and then sets its position to a point on the line between it and the next Temporal Point. As long as you keep your Temporal Points sorted by time, then you'll have smooth movement along a path. Real simple.

The first - and most obvious - snag comes from the fact that our game is laid out in scenes. Many areas in the game technically exist at the same coordinates in 3D space, but are separated by scenes, which we could consider as another dimension. This is a pretty easy fix on the data structure side, we just shift from using a Vector3 to an object which holds both a 3D point and which scene it belongs to - a PosWithScene.

Ok, that problem is solved, but it brings us to a much more critical one - how do we manage the loading and unloading of these objects? Checking each object every frame to see if it needs to load in or disappear into the digital aether is heck of expensive, and we can do better. Because Temporal Points are discrete, it's very easy to figure out which windows of time a Temporal Object should exist in a given scene. So, we build a Temporal Object manager class, and one of its jobs is to lay out exactly what time a Temporal Object should start existing in a given scene, and build a dictionary of all objects' entry times. Whenever we move to a new scene, we rebuild this dictionary, and every frame, we check the dictionary for whether there are any incoming objects, and if there are, load 'em all in. We're down to essentially just one call to a dictionary per frame, which is much cheaper than the naive solution.

Now that all the structure is in place, lets work through actually building a Temporal Object that works. By hand. Oh no.

Only a few dozen lines of this kind of code should be enough to drive home the point that this sucks.

public static TemporalPoint[] NPCTestSchedule1 ()
{
    PosWithScene pws1 = (12.4f, 0.5f, 4f, "TestScene"); //Not shown: All the tabbing in and out of the scene view to get these coordinates
    WorldTime t1 = (52, 25, 4, 0);
    TemporalPoint p1 = new TemporalPoint (pws1, t1); //All this for ONE point.
    PosWithScene pws2 = (18f, 0.5f, 4f, "TestScene");
    WorldTime t2 = (11, 31, 4, 0);
    TemporalPoint p2 = new TemporalPoint (pws2, t2);
    ...
    ...
    //Oh god why did I think this was a good way to do this
}

So, that brings us to the meat of today's devlog, Unity's editor tools. They are absolutely amazing, but pretty obtuse in a lot of ways. the first, pretty obvious tool would be something that we can generate 'PosWithScene's with directly in the editor. Just click on a point in space, and bam, we have an easy reference to it.


All you need to do is click somewhere in the scene view, and it populates the position fields based on whatever position was clicked, as well as the scene name. All that's needed beyond that is to give the point a distinctive name, and to save it as a new point. Super simple, super convenient!

Now, what about building something similar for schedules (the collection of temporal points)? It's a much more complex task, but it's super doable! The first pass at this... wasn't great. It managed adding and removing Temporal Points, and would allow you to select a PosWithScene from the ones created using the PWS tool, so the only additional info to input was the time. The tool would manage the rest, sorting the points by time automatically. Not the easiest to use, but still much, much easier than typing it all out by hand.

Pain point number one with the schedule tool came from not having an easy way to reuse common elements. If an NPC walks in a circle n times, you would have to manually input the data (n * number of points) times. Very tedious. So, how do we best solve this? The answer I landed at was to make 'Schedule Subunits', which could either be an individual Temporal Point, or an array of other Schedule Subunits. Instead of a Temporal Schedule being made up of Temporal Points directly, they would be made up of Schedule Subunits.

Skipping over the fun mess of implementing editor UI, 'Subschedules', or 'an array of Schedule Subunits' could now be built by selecting two Temporal Points in a schedule, and all the points between them would be swept up into a Subschedule, which could be inserted just like any Temporal Point. Fantastic!

One of the things I've kind of glossed over has been the speed at which Temporal Objects move. Their speed isn't explicitly defined, it's just a result of how we're defining Temporal Points. So far, because we've been setting the times of each Temporal Point by hand, the speed at which an object travels is far from consistent; it's involved lots of noticing an NPC is going way faster or slower than expected, and tweaking Temporal Point times until it looks right. This unfortunately brings me to math: to travel from point A to B in time T in a straight line at a constant speed means we can derive that speed from the information given. It's just distance / time. That being the case, we can build in a field that displays the speed between two temporal points directly in the Schedule tool, so we can roughly keep speeds consistent. It's mostly the same issue as before, but the iteration time is much quicker.

Not entirely satisfied with that as a 'solution', we can take this one step further: setting the speed directly in the scheduler, and deriving time from that. It's not the most complicated math, but wow it's a much nicer solution in practice. Keeping speed roughly constant, and tweaking slightly if we want a specific arrival time at certain points is so much easier to work with.

Without further ado, here's the current iteration of the Scheduler tool in all its glory:


This set of tools had worked really well for a long time, but after a while another issue started creeping into focus: Turns out that you need a LOT of PWSes, and just letting you arbitrarily connect any two in the scheduler means that you're scrolling through lists that are so long that they may as well be unreadable. Not to mention that there's not a quick visual representation of where they exist in space. So, one final system to top things off for the day: additions to the PWS system. First of all, a visualization tool:


Fantastic, we can see PWS points next to their names, one less thing to have to keep track of in your head. Now what are those lines? The next enhancement to the PWS system - each point now contains a list of all points it connects to, as well as whether or not it's a 'scene edge' node. Only scene edge nodes can connect to PWSes from other scenes (and only they get that lovely shade of deep blue in the visualizer!). From here it's just a matter of making the scheduler tool only allow PWS nodes that are connected to form adjacent Temporal Points.

With that, we're up to speed on where the system stands currently. It's incredibly easy to build new schedules now, and everything is far more robust and flexible than when we started. Thanks for reading!

Get Lepidoptera

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.