Sunday, November 12, 2017

Hub and Spoke

Part twenty in a weekly(🏛) devlog.

Standard development disclaimers apply. This is pre-Alpha content, everything is subject to change, features may not be present in the final version, there's a strong chance none of this will ever be released, etc. etc.

There shouldn't be any plot spoilers in these posts, but there will be occasional discussions related to characters, locations, mechanics, and other aspects of my potential upcoming Shadowrun campaign (tentatively titled "CalFree in Chains"). You may wish to skip them if you'd like to be completely surprised.

Now that last week’s boring post is out of the way, we can finally move on to the topic everyone ACTUALLY cares about: spreadsheets!

I’m close to wrapping up work on the hub, the location to which you and your team return between runs. This has been a long process, and also a fairly complex one. I have a variety of goals for the hub which overlap and sometimes contradict one another.

The hub should feel dynamic. It’s boring if everything looks exactly the same every time you come back. It adds a ton if the physical space changes and if characters move around from one visit to the next; it adds to the sense that you’re occupying a real place and interacting with people who have their own desires and agency, and aren’t merely there for your bidding.

The hub should be easy to navigate. It’s frustrating if you can’t find something you need or have to waste a lot of time running around.

The hub needs to support ongoing quests, which in turn will often require certain people or objects being in a certain location at a certain time.

The hub should reflect and support the overall narrative mood at a given point in time. If something upsetting has recently happened, it would feel jarring for everyone in the hub to be jokey and silly. Conversely, if your team has recently accomplished something great, I don’t want to bring down the mood with doom and gloom.

I realized fairly early on that there was way too much going on for me to keep everything in my head at once, and so I outsourced record keeping to the cloud: in particular, a Google Sheet that I threw together. Here’s what a portion of it looks like, heavily redacted.

Everything is ultimately driven by the global (story) variable “NumMissions”. It increments by 1 every time you return to the hub, and in turn it drives everything else about the scene.

There’s some mechanical bookkeeping driven by this. Part of that is the CharacterScaleAmount, which in turn will determine when you can select upgrades for your crew and how difficult enemies will become.

The more complex thing in this shot, though, was working out where various crew members would be located. Pretty much everything here is redacted, sorry, but the idea is to move the spawned actor into the correct location for a given visit to the hub. I try to avoid clustering them all close to one another. I also try to put them in a location that makes sense for the character: some more martially-inclined crew members will often by found in the target shooting range, while a bon vivant would more likely be found in a restaurant, and a decker will spend a lot of time by a computer.

But this gets especially tricky when a character is involved in a side quest. If I want the character to walk to a certain location, then they need to be located in a place from where they can path to their destination. So, in addition to the default positions, I may override their location based on the current state of a given side-quest. There aren’t any examples of that in this screenshot, but I flagged those cells with a teal background.

Speaking of teal, let’s more on to a later section of the sheet.

You’ll note that I froze the two left columns - this is a long sheet! The number of missions is most important for scripting purposes, but by itself the number doesn’t mean anything to me. The previous mission tells me what went down immediately before this hub visit, which in turn informs what conversations would be tonally appropriate. In some cases this is variable, like [Act 2], where the player may have gone on any one of a variety of runs. The more important story missions are individually flagged and usually have bigger thematic implications for what’s happening. Anyways, freezing the columns helps me keep my place regarding the overall state of the story during a given visit.

The next three columns here show when various conversations get unlocked for different NPCs. When possible, I try to spread these around so there’s a relatively consistent amount of new content, instead of overwhelming the player early on and leaving them with too little near the end (which was one of the slightly annoying things about Heoi in SRHK). Some conversations are just for fluff or flavor and can happen any time. Others are tied to specific events; here, the ones at NumMissions 5 are specific reactions to a story mission that the player has completed. Some conversations might be interjections that take priority over everything else, and others could be tied to ongoing missions or sidequests, which are respectively flagged in yellow or teal.

The PC0 Spawn column acts similar to the companion spawn ones from before. You’ll typically start out in your bedroom, unless there’s a team meeting or similar gathering. Occasionally, an event might happen right when you spawn, which is noted here.

The remaining columns are related to the atmosphere of the hub: little bits of character that will change from one visit to the next. I actually kind of hope that players don’t notice this: ideally, it’ll be an almost subliminal effect, adding to the impression that you’re in a dynamically evolving environment without calling too much attention to itself. But folks who are very observant will find these things, which will hopefully add another fun thing to look out for on return visits.

While building out a lot of these things (conversations, romances, side-quests), I did almost all of my work in a test scene instead of the actual hub scene. I’d just activate whichever specific NPC spawners I needed, set up the global variables correctly, and then run through it. This does add a little overhead, since I later needed to re-test things in the actual hub, but more importantly, each time I need to test a scene it takes only about 5 seconds to load as opposed to the 30+ for the real hub. When I’m testing things dozens or hundreds of times, that really adds up! So it’s good to do as many of my iterations as I can on the simple test scene, and just do my final verification on the real thing.

Over the past weekend I was able to run through a streamlined test of the hub: basically starting a character at NumMissions 1, talking to and interacting with everything in the hub, advancing to NumMissions 2, and repeating it through the whole game. This let me verify that multi-stage quests were advancing properly, see how all of the different states were interacting, and so on. As expected, I caught quite a few things here that I’d overlooked during my more targeted unit testing early on. This included one really amusing unexpected interaction, when I realized that one crew member who had joined you for a side quest might be present during a particularly intimate scene. Even my vaunted spreadsheet hadn’t anticipated that interaction. But, hey, at least now I know about it!

A couple of random technical comments:

There's a fair amount of ambiguity about whether you should interact with spawners during the "On Map Setup" or "On Map Start" event. Ideally, you would activate your spawners during "On Map Setup", and then move / animate / whatever them during "On Map Start". But, from what I can tell, spawners that were activated during setup may not have finished by the time "Start" runs, so attempting to teleport a recently-spawned actor during "Start" will fail. Because of this, I generally keep "Spawn At Map Start" set on the map spawner, and then kill off unwanted actors during Map Start. This is unfortunate, since it leads to longer map loading times - I incur all the time to create the actors even though I'm tossing them - but it's the only solution I've found yet.

On a related note: this isn't very consistent, but sometimes the scene will freeze/crash during loading if you attempt to kill actors during Setup. It's safest to do this during Start instead. Which, again, is annoying - it leads to a longer gap between when the player clicks "Continue" on the loading screen and when they can actually start playing - but a longer delay is far preferable to a crashed game.

There's a longstanding bug where actors lose their animation state when reloading a saved game, so it looks like people are just awkwardly standing around and staring into space instead of doing what they should be. I addressed this back in Caldecott by setting all animations on the Curtains Up event instead of the Map Start event, since the curtains will re-fire when reloading a saved game in addition to when first starting a map. I built on that for CFiC, coming up with a system that I think works pretty well for dynamic-feeling characters.

So: my overall goal is to have individual crew members doing something on a given hub visit. They might be repairing some equipment, or talking to nearby NPCs, or dancing in a club, whatever. When you start talking with them, they stop whatever they were doing and turn their attention to you. After the conversation is over, they resume their previous action.

Initially, I implemented this with a ton of individual triggers, enabling and disabling them as conversations started and ended. But I ended up with a pretty nice and unified approach. It looks a bit like this. First of all, we place our characters in their initial position while the map initializes.

Next, start their animations. This will happen when the curtains are raised or an event fires. I do one of these for each mission, and each character's animation makes sense for what they're doing. ("Sacrifice" and "Doomventing" may sound very dramatic, but they aren't really. "Sacrifice" is just the character looking slightly upward, at some object taller than themself. "Doomventing" involves fiddling with something on the floor.)

In each conversation, set the actor to the "idle" animation. Most of them will also automatically face the PC when the convo starts; if not, you can manually trigger that as well. As a result, they'll stop dancing or whatever and focus on you for the remainder of the conversation.

Then, we wire up this one trigger to restart all animations whenever any conversation ends.

That will re-run the same trigger up above that we ran when the curtains raised. So, after every conversation, everyone will go back to what they were doing when we first started the map: now that your chat is over, they can pick up where they left off.

In a handful of cases, I might not want a crew member to resume their original animation: for example, if they are following you or have moved to another spot on the map. In this situation, I just create another trigger to handle that specific case, going back to the "Idle" animation or whatever is most appropriate. Remember that triggers always run in sequence, from top to bottom, so triggers defined later in your list will execute after ones above it. In this example, I'm checking whether one particular actor is in a specific area, and if so, have him continue standing instead of his standard animation.

That's pretty much it.

So, yeah. There’s still a ton left to do, but I’m feeling like the hub itself is in pretty good shape now: the crew conversations, romances, merchants, side quests, paydata, rewards, and lots of other stuff is now present. I still need to work on crew advancement and a couple of other mechanical-related stuff, but for the most part the overall framework is in place. Running through that end-to-end test felt awesome: I love this feeling, when the project starts to actually seem like a game, with interlocking systems and coherent progress. Excelsior!

No comments:

Post a Comment