How to Code Scenario-Based Progression in Games?

How can you code a scenario-based progression in games where dialogues and player choices shape the game, resulting in different situations? It’s not just dialogues; actions taken within the game, like moving to different locations, can also affect the scenario. What kind of architecture would be used for this? If we were to use a state machine, would we have to write 100 states for 100 different scenarios? This doesn’t seem efficient.

I wouldn’t use an FSM. If I had to do it it would be something like this:

  • ActionBase abstract class that contains an Execute() function that’s either a UniTask or IEnumerable (or Awaitable in Unity 6)
  • Any kind of action inherits from ActionBase and implements whatever you need such as StartDialogueAction, Move action, Delay x seconds action, etc. Each action can have its own fields as necessary. The only common thing is that they can all be awaited via Execute() method.
  • An ActionManager class takes a List and runs a foreach loop on it and then waits for the Execute() to finish inside the loop before continuing onto the next action. My preference is for awaiting UniTask pre-Unity 6, otherwise Awaitable is the new preferred way. But coroutines should also work just fine.
  • Place action lists inside scriptable objects for a scene free workflow, so multiple people can work on several scenarios in parallel.

That’s it, pretty simple and quick to do. State tracking can be separate thing, some StateTracking manager class with a List where you add choices made Ids, or a Dictionary<string, bool> but those are harder to serialize in Unity.

This approach assumes you’ve massaged Unity inspector to display Abstract class and/or interface data properly. Many a way to do it like Odin Inspector, and several options for free on Github.

   public class DelayAction: ActionBase
    {
        [SerializeField]
        private float _delayTime;

        public override async UniTask Execute()
        {
            await UniTask.Delay(TimeSpan.FromSeconds(_delayTime));
        }
    }
public class ActionManager
{
    private List<ActionBase> _actions;

    public async UniTask RunScenario()
    {
        foreach (ActionBase action in _actions)
        {
            await action.Execute();
        }
    }
}
2 Likes

I believe the easiest way for you to get it would be to investigate RenPy engine, which is made for writing novels. Or twine engine and interactive fiction genre.

Branching is handled like this:

  • The game tracks some variables. Money earned, distance walked, enemies killed, whatever.
  • Those variables can be accessed in game diaogue, cutscenes and so on as a conditional, which determines whether cutscene will be shown, or diaogue presented.
  • The dialogue can also alter variables and set some of their own.

So. In your game, the payer at starting location can meet a squirrel, that is hidden. Encountering the squirrel sets a variable, named “met_important_squirrel”. At later date, near the end, the game checks for this flag. And either plalys a cutscene, or adds a diaogue option if the flag is set.

That’s it.

This is still a state machine, but state machine where nodes are reused, and a node can set flag that affects the behavior of a state machine.

There are generally no 100 unique paths in a video game. There’s one main path, which can branch off for a moment but then returns back to the original story. So, if you want the world a react, that for every action in the world, in every part of the world, where this action would affect anything, you impement a check. Which is a lot of work.

Games generally do not do that. I only saw this done only in “Way of Samurai” titles and nowhere else.

1 Like

Funny timing. :stuck_out_tongue:

https://discussions.unity.com/t/948612

2 Likes

My suggestion would be, just code things locally at first, and then extract the core functionality afterward to create the Incredible Fantastic MultiFunctional Yet Elegant System that will drive it.

Unless you know exactly what you want (which is hardly ever the case in gamedev), you might find you actually need something you never expected. Like it might seem to fit into a state machine, and then you realize some aspect doesn’t fit. Often you end up with a series of systems that solve different parts of the problem, patched together somewhat crudely, that resolutely defy any further abstraction.

3 Likes