Blog

Midnight Train to Georgia

Posted

Finally took some time away from tinkering with NPC navigation and editable objects in Stop Shoving! and decided to work on a really minor, nearly inconsequential feature of the simulated world of subway stations: the coming and going of trains.

NPCs can’t board them yet, but they can be hit by them and that’s a start!

Part of the eventual scoring mechanic involves NPCs reaching their intended train in a timely manner (and the gameplay loop for the player involving managing the obstacles and available interactions along the way) and successfully boarding it. This means trains in the station aren’t just random actors showing up at whatever platform(s) exist and letting whatever NPCs that happen to be nearby board and be ferried out of the subterranean gameworld.

The trains belong to specific lines, traveling to specific places, and each and every NPC that inhabits the station cares about those details. How they get there will be the topic of another post as I fill out the station navigation (right now it’s stubbed out to allowing traversal across up to two floors via up to one conveyance (stairs, escalator, etc.)). How and where and when the trains move is the point of this one.

A few requirements:

  • A track can host more than one train.
  • A track can belong to one[1] or two platforms[2].
  • Only one train can be present in the station on a track at any given time.
  • If a train is scheduled for arrival and the platform is occupied, the arriving train must be delayed until the platform clears and then it should arrive promptly.
  • If multiple trains are delayed, they should arrive in the order they were delayed as each prior one clears the track.
  • Upcoming arrival times for at least the next N trains should be known so it can be broadcast through arrival boards.
  • Every train should have its own schedule, and that schedule should vary throughout the day (rush hours, etc.).
  • Trains within the station should also broadcast more granular statuses about the stages of their arrival, boarding, and departure so that all concerned NPCs can react appropriately.

Luckily, part of the infrastructure for this I had already built as part of managing the simulation speed controls: the internal game clock. Godot has a built-in Engine.time_scale property which is ideal for adjusting the overall speed (or paused status, by setting to 0.0) of the simulation, but layering an SSG_GameClock class atop it allows for useful things like making in-game days consume a fraction of a real-world day even at time_scale = 1.0. Even more helpfully, it allows all manner of entities to connect to signals from the game clock for time-based triggers. SSG_GameClock emits signals on every in-game second, minute, hour, and day change that is wholly apart from the need to instance and manage a bunch of Timers[3].

Trains now get to subscribe to a per-minute signal from the game clock, and maintain a simple Array based queue with a configurable size. As long as the array is that minimum length, all is good, and if not then the difference determines how many upcoming train arrivals need to be scheduled. The last-scheduled time is passed along to the Train-specific scheduler which can examine time of day (to adjust arrival rates based on things like increased rush hour service levels or overnight suspensions of service) and pick an increment from the last arrival to use for the next.

In this same trigger, the Train can check whether it should have an arrival in progress. If so, it can make a call to its parent SSG_Track which does the checking of whether a train is already in the station to decide whether to initiate the new train’s arrival immediately, or place it in another queue for delayed trains, which ensures that all the trains which tried to arrive while the platform was still occupied can be dispatched in the correct order as it clears.

And all of this can get hooked into by an arrivals board singleton that can package it up into what a human racing through a busy subway station would have access to should they have a moment to check a display: a fixed-length list of current and upcoming trains and their platforms that lets one know whether you can saunter lazily along the hall and down the stairs for your ride to work, or whether you need to make a mad dash for it to avoid getting yelled at by your arsehole boss Gary for not making it to your shift at the misery factory on time.

Lastly, if there’s a game clock, seems simple enough to hook that up to a world environment to get the sun in the right place and the sky the right colors. I don’t anticipate putting a whole fancy volumetric cloud system into the game, given that almost all of it will take place not only indoors, but underground, but having that little touch of ambient golden hour sunlight coming into the station as the crowds from the rush hour madness filter their way out seems like it would be a nice way to fill out the atmosphere of the game.

I should probably start putting some lights inside the testing station before I put a roof over it, though.


  1. Imagine two platforms, say a South and a North, with two tracks in between them. Each platform serves one of those two tracks, and from a train on either of them a passenger could only alight from or depart to one of the platforms. ↩︎

  2. Imagine a split platform that has only one track running through the middle. A passenger on the arriving train could disembark to either side, and passengers from either platform could board the train in the middle. ↩︎

  3. There are still many things in the simulation’s codebase that are Timer-based, of course. ↩︎

Reading List

Entries are not endorsements of every statement made by writers at those sites, just a suggestion that there may be something interesting, informative, humorous, thought-provoking, and/or challenging.