I finished this course not too long ago and I’m now trying to set up a nice opening scene for a new game. I start by triggering a dialogue (code below) with a MonoBehaviour I call ConditionallyExecuteTriggerOnce. It uses the strategy pattern and conditions. If the condition is true then it calls a TriggeringStrategy (scriptable object strategy pattern) which can execute arbitrary code. All well and good. I also use TriggeringStrategy’s in the DialogueNode’s onExit/onEnterAction to trigger arbitrary code at different points in the dialogue (i.e. do a cut scene, grant the initial quest, change music, etc)
But here’s an example of the kids of things that trip me up.
After the first quest is done, I need to change the way music works. Outside of the opening sequence, I have a monobehavior (MusicPlayer) on the player gameobject with an update loop that checks what scene we’re in, if there are monsters, etc and picks the appropriate track. But that update loop shouldn’t run unless the first quest is complete because it a specific set of musical tracks triggered by either starting the game or by the initial dialogue. So now the music player also needs a condition (e.g. if first quest not completed, don’t do the normal update loop).
I’m realizing I might need to do a lot of these conditions in other parts of the code now because this opening sequence changes how the normal course of the game is played. Here are other examples:
- Disable the portal in first scene unless the initial quest is complete
- Add tutorial arrows (e.g. “click here”, “notice this UI element here”, disable certain elements of the UI) the first time a certain piece of the UI is used.
The pattern seems to be that some objects have “a first time through” state and there is some arbitrary condition that breaks that object out of that state.
EDIT (adding the question): It feels like it can get out of hand quickly so is adding condition(s) for each class that has some sort of initial behavior a good way to do it?
ConditionallyExecuteTriggerOnce.cs
using GameDevTV.Saving;
using GameDevTV.Utils;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace RPG.Triggers
{
public class ConditionallyExecuteTriggerOnce : MonoBehaviour, IJsonSavable
{
[SerializeField] private TriggeringStrategy[] triggerArray;
[SerializeField] private Condition condition;
// State
private bool previouslyTriggered;
// cached references
private GameObject player;
private IPredicateEvaluator[] evaluators;
private void Awake()
{
player = GameObject.FindWithTag("Player");
}
private void Start()
{
evaluators = player.GetComponents<IPredicateEvaluator>();
}
private void Update()
{
if (condition.Check(evaluators))
{
foreach (var trigger in triggerArray)
{
trigger.Trigger();
}
enabled = false;
previouslyTriggered = true;
}
}
public JToken CaptureAsJToken()
{
return JToken.FromObject(previouslyTriggered);
}
public void RestoreFromJToken(JToken state)
{
previouslyTriggered = state.ToObject<bool>();
if (previouslyTriggered) enabled = false;
}
}
}
TriggeringStrategy.cs
using RPG.Core;
using UnityEngine;
namespace RPG.Triggers
{
public abstract class TriggeringStrategy : ScriptableObject
{
public abstract void Trigger();
}
}