How do you handle gameplay state "mode" switching?

Let’s say your player has an objective - they get the objective - then you play a “victory” cinematic - the player might have objectives left or this might be the last objective to end the level.

End of the level means another cinematic plays, and switches to a end of level screen showing your stats. If the player still have objectives left you have to return them to the game in progress restart all the UI controls etc. to continue.

I have created the systems that are tracking the objectives, and doing all this already.

I just feel that every time I have do this work I have so many places to make sure are paused or shutdown or like a UI change or control change, and avoiding any sort of race conditions. I write it and then find the edge cases fix those then trying to get a perfect transitions for the different outcomes is a pain - maybe because I suck at coding.

  • Do you all find the same thing happens to you?

  • Do you have a master block of code that handles all the cases or do you it per objective as some have higher levels of complexity not required for simple objectives?

  • Do you wait until the gameplay is locked before you refactor this code to handle all the cases or just add as your needs expand or contract?

1 Like

Probably shouldn’t be showing a victory cinematic or an end of level screen until the gamemode decides the current level is actually done and it’s time to transition to another level or back to lobby. Instead, if you want to let people compete for 2nd/3rd/Nth place just give the winner (and subsequent runner-ups) a spectator pawn until the match is done. Then you don’t have to worry about freezing everyone’s input for 5s to show some fireworks or whatever. Less work for you, and less annoying for players since they won’t have to wait for game to celebrate someone else winning when they may have really only been interested in beating the friend they queued up with.

If you’re wed to the idea of giving the winner instant gratification, at most, I’d say flash a message on the ui that Username has Won, and maybe pop up a small scoreboard so the others know how close they are to 2nd/3rd/etc.. to motivate them (or give them an excuse to ragequit lol). And maybe start a timer announcing the match will end in 30s/2m/whatver in case everyone else just mopes around instead of trying to finish objectives.

Just think of an IRL car race. When the first car crosses the finish line, they don’t make everyone stop, get out, and watch a trophy get handed to first driver before hopping back in their rides to finish that last lap.

it’s a single player game - the game mode already controls this with the game state.

Ah, right, I just assumed multiplayer because you mentioned victory. Oops. In that case, yea, just handle it by objectives. Does gamemode has a counter for all current objectives? If so, when any objective is finished, check if iRemainingObjectives or whatever = 0, if true, call end level logic, and false continues on with standard post-objective logic. Might want to put an option on victory screen to skip straight to next level or continue leftover objectives for bonus points or whatevs tho.

Probably best to nail down the main gameplay loop first rather than waiting until all moment to moment gameplay is locked in. Obviously, some weird edge cases might pop up that need fixing later, but it should be easier to “break the rules” for them later if the core rules are already in place. Just wrap the end of level/victory section of code with comments so it’s easier to find it later.

1 Like

I already have the entire loop built and working for years now, and am very happy with it.

Like I ask > When you get down to the details of doing this work how do you do it? I already have a clean system, but when ever I work on a more complex objective, like I describe above I have to do a lot of steps to shut things down and then turn them on again. Player Input, UI, Sequencers, etc, etc.

None of this is hard to and I have been doing it for years, but I am looking for ways to improve based on other peoples approach to the same thing we all deal with making games.

Same question posted on reddit already harvested some great answers.

If the system you have in place is already working, I think the best way to approach it is to just try to boil down future objectives to an abstract level where they line up with the existing framework. Like, if you’re making a puzzle game and want a level where the abstract goal is to romance a character, try to ignore the intrusive thoughts telling you to invent a new romance minigame, and just frame it as “solve 3 puzzles to impress character X,” or “kill 3 orcs to impress character X” or some such if it’s an action game. It can be hard to ignore the intrusive thoughts sometimes, but doing otherwise can lead to feature creep and bloat.

1 Like

the powerful way is to create UObjects per objective type and the GameState is just a list of UObjects.

each object can bind to its event, ie enemies killed, items picked up. when its conditions are met it can call an event or remove itself from the list. when the list is empty the game is over or however you want to handle it.

1 Like

The tricky part (which I think is what OP is getting at) is future-proofing yet-to-be-designed objectives that might not fit in the original boxes.

Like, on my current project which is an action-rpg sorta like modern Fallout (insofar as it has guns, melee weapons, and npcs who give quests), my quest system uses an enum to define objective types. This enum has options for travel, kill, collect, and interact. Since my dialogue and things like turning on switches are all initiated by my interaction interface, I can finish an interact objective by talking to an npc or flipping a switch based on return from interface message if need be (or bury objective message in a nested dialogue event).

Early on in game, there’s a quest to “search” the forest outside town for undead and kill (re-kill?) them. Since there’s no explicit objective type for “search,” I simply assign the map marker trigger in forest an objective ID and broadcast it on overlap to “travel channel” using the gameplay message subsys plugin. When controller hears it, the quest comp checks if the ID is in CurrentObjectives map, and moves it to completed. Quest system doesn’t need to know if player actually searched every nook and cranny of forest, since the layout pretty much guarantees they’ll fight undead in the area.

1 Like

Not really this - that is a cool system you have created - I am talking about making perfect seamless transitions from taking control away from the player and giving it back because of the amount of items that you need to handle when doing this for the different complexity level of the event.

When see how much or many nodes I am using to do this it got me thinking that my current system is just bad coding. Some of these items were designed early as the original vision but deployed maybe years later. So when I go back to older code to refactored or expand it gets a little daunting.

This might include playing an animation or set of animations and sequencer, triggering effects, playing audio, taking control away - switching the controls for different modes - giving control back in a different state, etc.

When ever I open this can of worms on my project I always wonder if there is an easier way because I am always thinking that even though this is my way and it works others might have better systems.

1 Like

that’s exactly my point, Enums aren’t extensible. adding a new one breaks things, you have to manually edit every switch statement, you have to create new functions handlers.

UObject just works, the class is the variable, you have a virtual execute function and it can handle any logic you think of in the future without touch old classes

True. But constructing arbitrary uObjects from a var or datatable row and actually using them in a blueprint is hit or miss sometimes. Even if you make some base class then create children for future variants and cast to base to access a relevant funct or use a bpi on construct, the bp compiler sometimes has a stroke. c++ is a bit more friendly with things like this, but I dunno if OP is comfy with adding c++ to project.

Another option ( which I’ll probably be using) is to add gameplay tags to actors which are eligible for objectives and have them send tag to objective tracker in a relevant event. Then if a bp_EnemyBase or whatever has tag Objective.Kill and a child of that has tag Objective.Kill.Dragon, and all send objective tag on death, a generic kill 10 enemies could just listen for Objective.Kill and complete after killing 9 basic enemies and 1 dragon (and in reverse a kill 10 dragons objective would ignore other kills).

To add some oddball objective in the future one could make a new tag like Objective.Eat.Potato, and have a new potato item fire the tag to tracker on use so tracker can increment the counter towards an “eat 10 potatoes objective.”

The tracker doesn’t need to worry about how weird the objective is like this. It only needs to know if the tag matches the one in the objective’s datatable or data asset.

nah its not like this at all, casting/interfaces defeats the whole purpose. the UObject is self contained aside from some context that is consistent.

Create a UObject or ActorComponent Base, it has one entry function Initialize/Execute it can take in the context (lets say GameState)

The objects binds to gameplay events (could be on the GameState or MessageBus), it holds it owns state (number of kills, pickups), when its conditions are met it notifies the GameState my job is done.

thats it, you can create any object for any system and nothing else needs to know about it. it also means you can easily save/load the object. You dont even need a DataTable/Asset although you might want some customization like a GameplayTagContainer so it tracks kills/pickups of targets with matching Tags

Ah, right. I didn’t really think to have it automatically bind itself to things. I just had flashbacks to modding some other game where certain things were constructed from a child of a c++ class and you would call a generic function from base class on it which bp children in mod could override. Sometimes the outer would fail to assign tho (maybe replication race condition?) and the uObj would be a zombie lol.