XaiJu
Megan Fox
Megan Fox

patreon


Switch Systems 201

I did a post on Switch systems waaay back in 2017: https://www.patreon.com/posts/switch-systems-11462060

and it's, you know, fine! But it was for Unity, and I feel like I've learned a lot about how these things work since. I've written a version of my Switch system for every single game I've done since then, so I'm on, what, revision 4 of this thing? So yeah! Let's dig in a little deeper.

There's a couple of considerations, now from the perspective of Unreal:

Use Interfaces or Components to contain the logic

This is where I'd really like to say "use Interfaces!", because. Well. That's what I do! And I feel there's a very good reason for this: you simply never know what you're going to want to make interactable. Yes, right now, you might THINK that they're all going to be logical switches based off the same base class (which we'll get to in a second), but later in dev, the ability to slap an arbitrary interaction on anything might save your bacon. So I really like Interfaces for this.

Another nice property of Interfaces is that you don't have to query for them, you simply call a function on something and either it does something or it doesn't. This is handy for, say, when you're setting up a tool for a designer that has a drop-down that lets them target the switch to "whatever the heck it switches", and they can just point it at anything. It isn't hard to make that tool, and the worst that happens if they pick something wrong is, well, nothing.

However!

Interfaces also can't be, say, queried for in an Overlap. Let's say you want to OverlapSphere around the player to query for all interactables, and then pick the one most in-front of them, so they can pick it up or use it. Impossibly basic thing you do in games constantly. And yet: you can't do that with Interfaces. Interface version of that is to query for every class of object that might contain an interactable, and then per-object it returns, query for Interface support, then put those in a second array, and THEN do your sweep. Sucks.

Plan For Online Early

Oh and that doesn't even account for online functionality. Let's say you're making a Switch, as in, a literal light-switch or flip-switch or whatever. Thing that when walked up to, has  Y glyph over it, you press Y, your player flips the switch, something happens. So in practice, what you're doing there is when the player presses Y and you pick the object, they actually (in Unreal anyways) send that call up to the server. Then the server is what actually flips the switch, which flips the SpawnThisGuy object on, which spawns a guy, or whatever.

Now in this case, they probably replicate down some kind of IsSwitchedOn which is then what makes the switch actually look like it's turning on, but point is: Server, Replication, lots of specific fiddly logic. If you're doing this with Interfaces, and you don't have a base class, it means you have to write this fiddly network logic in every single freaking interactable. So you end up making a base class for this anyways. In my case, I've got a BaseSwitch, a BaseSwitchable, a BaseDoor (because doors behave differently enough it seemed worth it), and so on.

If I'd gone Component instead of Interface, would this have been cleaner? I don't know. One thing to remember is that if you go Interfaces, your base classes end up being Actor-type, not Component-type, and that DOES give you substantially more leeway in what code they can contain. Like take the above networking problem, right? I can just enshrine IsSwitchedOn as a Replicated OnRepNotify variable in my Actor, and I'm done. RPCs are easy. Components can also do that stuff, but they're naturally constrained in only being able to replicate/RPC within the context of, you know, the component itself. Would you run into problems with that eventually? No idea! Which is why it's worth planning for early!

You'll Want Entity Messaging Of Some Kind

First of all, I highly recommend using the GameplayMessagingSubsystem. It comes with Lyra (though you can download it directly), it's technically experimental but it works perfectly. It's GameplayTag-based, which is a HUGE boon, and even if you write your own, I highly recommend you emulate it in that regard.

Here's a little explainer of the system: https://github.com/imnazake/gameplay-message-router

Here's the Lyra GitHub repo, from which you can just yank the GameplayMessagingSubsystem from the plugins directory. Remember that you likely can't see this unless your GitHub account has been cleared for Unreal access (which indies can do no sweat it's just a form/etc): https://github.com/EpicGames/UnrealEngine/tree/ue5-main/Samples/Games/Lyra

Now then, why do I recommend this? Gosh, I'm SO glad you asked! It's because:

Why You Want The Entity Messaging

Now you can make "switches" that respond to literally any gameplay verb or outcome.

Let me run you through a compound set of switches and switchables. Let's say you have a set of spawner objects that, when fired in series, create a boss encounter in the form of increasing waves of enemies. Now step one layer back, and we'll use some SwitchableGroups to make it so a single switch can control multiple spawns, probably one switch per wave.

Now let's move one more layer back, and make a Sequence object. Sequence objects are Switchables that contain an array (or Map) of switchables. Each time they get switched, they step one index into the array. Or in the case of a Map, they contain a counter, and each time they switch, they count up one, and check the map to see if there's an entry associated with the current count. If they find an entry, it's a switchable, they switch it. Great, now if you were to step through this Sequence, you'd spawn the first wave, then the second wave, then the big boss, then the treasure chests, and then you'd open the doors allowing the player to escape.

The question here is how do we fire that Sequence. What is it based on? Well, one suggestion comes from our designer: enemy deaths! Make a switch that listens for gameplay messages, in this case enemy deaths, and when it hears one, it flips its target switchable. That target is the Sequence. Now every time you kill a mob in this fight, the Sequence count/index goes up by one. So your encounter goes something like this:

In the middle of the arena is a large trigger that, when stepped into, ticks the Sequence up one. Also, you've routed that trigger through a one-way SwitchableGate, which is basically an intermediate switchable that changes the signal- in this case it only allow SwitchOn through, not SwitchOff. Maybe you even make it go through an OnlyOnce gate, so that it only ever fires once (when the player first steps on), then never again.

Anyways, that Sequence's entry at index 1 is a SwitchableGroup that hits all the doors, closing them, and spawns 3 guys. Now you've got another ListeningSwitch sitting in the scene, listening for enemy deaths, and you've set it up so it will only be triggered by deaths in this arena. Maybe you added an optional bit of range-limit to it, maybe you made the arena enemies be in a specific faction so it's easy to listen to just them. Whatever. Anyways! You kill these 3, each time the Sequence driving this whole thing gets counted up by one by that ListeningSwitch, and now you've killed all 3 guys!

Now at entry 4 in the Sequence is your second wave. Spawns more guys, you kill them, 6 this time. Now at entry 10, that's your Big Boss. Kill him? Encounter over!

There's a couple ways of handling the end-of-fight here. You could technically load that single Sequence up with everything in a group at the end, but it might be simpler to have a second group listening just for BigBadDeath, and when that fires, it kicks off a SwitchableGroup instead of a Sequence, and that one group does everything from open the doors, to spawn the treasure chests, to flip a little invisible switch that causes the YOU BEAT THE BOSS chime audio to play.

This Shit Is Powerful

But the individual parts are quite simple! It's just that if you make the pieces logical and interchangeable, surprisingly complex behavior can fall out. As a random example, the trains in Spartan First are, technically, just very weird looking sliding doors.

https://www.youtube.com/watch?v=YgP15AHQrHw

They're tied to a Sequence logic (one of those arrays that fires the associated switch for an index each time it steps), and the Sequence logic is advanced by time, not gameplay events. But I mean you could just as easily have them tied to a switch, then the player could actually deploy them as weapons mid-melee if they were good. Whatever you want really! Have fun!



More Creators