// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System; using System.Collections; using Doozy.Runtime.Common.Extensions; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.UIManager.Input; using Doozy.Runtime.UIManager.ScriptableObjects; using UnityEngine; using UnityEngine.Events; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable PartialTypeWithSinglePart namespace Doozy.Runtime.Nody { /// /// The Flow Controller is responsible of managing a Flow Graph. /// It can control the graph either as a local graph (instance) or a global graph. /// [AddComponentMenu("Doozy/Nody/Flow Controller")] public partial class FlowController : MonoBehaviour, IUseMultiplayerInfo { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/Nody/Flow Controller", false, 8)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene(false, true); } #endif /// Reference to the UIManager Input Settings public static UIManagerInputSettings inputSettings => UIManagerInputSettings.instance; /// True if Multiplayer Mode is enabled public static bool multiplayerMode => inputSettings.multiplayerMode; [SerializeField] private bool DontDestroyOnSceneChange; [SerializeField] private FlowGraph Flow; [SerializeField] private FlowType FlowType = FlowType.Global; [SerializeField] private ControllerBehaviour OnEnableBehaviour = ControllerBehaviour.StartFlow; [SerializeField] private ControllerBehaviour OnDisableBehaviour = ControllerBehaviour.StopFlow; [SerializeField] private UnityEvent OnStart = new UnityEvent(); [SerializeField] private UnityEvent OnStop = new UnityEvent(); [SerializeField] private UnityEvent OnPause = new UnityEvent(); [SerializeField] private UnityEvent OnResume = new UnityEvent(); [SerializeField] private UnityEvent OnBackFlow = new UnityEvent(); /// Flag that makes sure that the Flow Controller is not destroyed when a new scene is loaded public bool dontDestroyOnSceneChange { get => DontDestroyOnSceneChange; set => DontDestroyOnSceneChange = value; } /// Flow graph managed by this controller public FlowGraph flow => Flow; /// Type of flow public FlowType flowType => FlowType; /// Behaviour of the controller OnEnable public ControllerBehaviour onEnableBehaviour { get => OnEnableBehaviour; set => OnEnableBehaviour = value; } /// Behaviour of the controller OnDisable public ControllerBehaviour onDisableBehaviour { get => OnDisableBehaviour; set => OnDisableBehaviour = value; } /// Called when the flow graph starts or restarts public UnityEvent onStart => OnStart ?? (OnStart = new UnityEvent()); /// Called when the flow graph is stopped public UnityEvent onStop => OnStop ?? (OnStop = new UnityEvent()); /// Called when the flow graph is paused public UnityEvent onPause => OnPause ?? (OnPause = new UnityEvent()); /// Called when the flow graph is resumed public UnityEvent onResume => OnResume ?? (OnResume = new UnityEvent()); /// Called when the 'Back' flow is triggered in the flow graph public UnityEvent onBackFlow => OnBackFlow ?? (OnBackFlow = new UnityEvent()); #region Player Index [SerializeField] private MultiplayerInfo MultiplayerInfo; public MultiplayerInfo multiplayerInfo => MultiplayerInfo; public bool hasMultiplayerInfo => multiplayerInfo != null; public int playerIndex => multiplayerMode & hasMultiplayerInfo ? multiplayerInfo.playerIndex : inputSettings.defaultPlayerIndex; public void SetMultiplayerInfo(MultiplayerInfo reference) => MultiplayerInfo = reference; #endregion /// Flag used to keep track for when this FlowController has been initialized public bool initialized { get; private set; } /// Flag used to determine if this controller has been initialized and has a valid flow graph assigned to it public bool isValid => initialized && Flow != null && Flow.controller == this; protected virtual void Awake() { if (!Application.isPlaying) return; if (dontDestroyOnSceneChange & transform.parent != null) { Debug.LogWarning ( $"[{nameof(FlowController)}] - {name} is set to 'Don't destroy controller on scene change' but it has a parent. " + $"For this to work, the controller must be a root object in the scene hierarchy (it must not have a parent)." ); } if (dontDestroyOnSceneChange) { DontDestroyOnLoad(gameObject); } BackButton.Initialize(); initialized = false; } protected virtual IEnumerator Start() { if (!Application.isPlaying) yield break; yield return null; SetFlowGraph(Flow); } protected virtual void OnEnable() { if (!Application.isPlaying) return; //do not execute this code in the editor RunBehavior(onEnableBehaviour); //run the set behaviour when the controller is enabled } protected virtual void OnDisable() { if (!Application.isPlaying) return; //do not execute this code in the editor RunBehavior(onDisableBehaviour); //run the set behaviour when the controller is disabled } protected virtual void RunBehavior(ControllerBehaviour behaviour) { // Debug.Log($"[FlowController] RunBehavior -> {behaviour}"); switch (behaviour) { case ControllerBehaviour.Disabled: //do nothing break; case ControllerBehaviour.StartFlow: StartFlow(); break; case ControllerBehaviour.RestartFlow: RestartFlow(); break; case ControllerBehaviour.StopFlow: StopFlow(); break; case ControllerBehaviour.PauseFlow: PauseFlow(); break; case ControllerBehaviour.ResumeFlow: ResumeFlow(); break; default: throw new ArgumentOutOfRangeException(nameof(behaviour), behaviour, null); } } private void Update() { if (!isValid) return; //do not execute this code if the controller has not been initialized or if the flow graph is not valid Flow.Update(); //tick the Update method of the flow graph } private void FixedUpdate() { if (!isValid) return; //do not execute this code if the controller has not been initialized or if the flow graph is not valid Flow.FixedUpdate(); //tick the FixedUpdate method of the flow graph } private void LateUpdate() { if (!isValid) return; //do not execute this code if the controller has not been initialized or if the flow graph is not valid Flow.LateUpdate(); //tick the LateUpdate method of the flow graph } /// Set a new flow graph to this controller /// Target flow graph public void SetFlowGraph(FlowGraph graph) { if (isValid) { StopFlow(); Flow.OnStart.RemoveListener(OnStartFlow); Flow.OnStop.RemoveListener(OnStopFlow); Flow.OnPause.RemoveListener(OnPauseFlow); Flow.OnResume.RemoveListener(OnResumeFlow); Flow.OnBackFlow.RemoveListener(OnBackFlowTriggered); Flow.controller = null; Flow = null; } enabled = graph != null; if (graph == null) return; Flow = flowType == FlowType.Local ? graph.Clone() : graph; Flow.OnStart.AddListener(OnStartFlow); Flow.OnStop.AddListener(OnStopFlow); Flow.OnPause.AddListener(OnPauseFlow); Flow.OnResume.AddListener(OnResumeFlow); Flow.OnBackFlow.AddListener(OnBackFlowTriggered); StartCoroutine(InitializeFlow()); } /// Activate the given node (if it exists in the flow graph) /// Note to search for in the loaded flow graph /// Pass a port as the source port public void SetActiveNode(FlowNode node, FlowPort fromPort = null) { if (!isValid) return; if (node == null) return; if (!Flow.ContainsNode(node)) return; Flow.SetActiveNode(node, fromPort); } /// Activate the node with the given id (if it exists in the flow graph) /// Node id to search for in the loaded flow graph public void SetActiveNodeById(string nodeId) { if (!isValid) return; if (nodeId.IsNullOrEmpty()) return; if (!Flow.ContainsNodeById(nodeId)) return; Flow.SetActiveNodeByNodeId(nodeId); } /// Activate the first node with the given node name (if it exists in the flow graph) /// Node name to search for in the loaded flow graph public void SetActiveNodeByName(string nodeName) { if (!isValid) return; if (nodeName.IsNullOrEmpty()) return; if (!Flow.ContainsNodeByName(nodeName)) return; Flow.SetActiveNodeByNodeName(nodeName); } /// /// Reset the flow graph and set its state to Idle. /// This means that the graph has not been started yet, thus there is no active node. /// public void ResetFlow() { if (!isValid) return; Flow.ResetGraph(); } /// /// Reset the flow graph. This will reset the graph and activate the first node. /// Even if the graph is paused, it will reset and start from the beginning. /// public void RestartFlow() { if (!isValid) return; Flow.Restart(); } /// /// Start the flow graph. This will reset the graph and activate the first node. /// If the graph is paused, it will resume from the last active node instead of the first node. /// public void StartFlow() { if (!isValid) return; Flow.Start(); } /// /// Stop the flow graph. /// public void StopFlow() { if (!isValid) return; Flow.Stop(); } /// /// Pause the flow graph. /// If the graph state is not Running, it will do nothing. /// public void PauseFlow() { if (!isValid) return; Flow.Pause(); } /// /// Resume the flow graph. /// If the graph state is Idle (stopped), it will start the graph instead. /// public void ResumeFlow() { if (!isValid) return; Flow.Resume(); } protected virtual void OnStartFlow() => onStart?.Invoke(); protected virtual void OnStopFlow() => onStop?.Invoke(); protected virtual void OnPauseFlow() => onPause?.Invoke(); protected virtual void OnResumeFlow() => onResume?.Invoke(); protected virtual void OnBackFlowTriggered() => onBackFlow?.Invoke(); /// Initialize the flow graph and start it at the end of the second frame private IEnumerator InitializeFlow() { yield return null; //wait for the second frame yield return new WaitForEndOfFrame(); //wait for the end of the second frame if (Flow == null) yield break; //if the flow graph is null, exit initialized = true; //set the initialized flag to true Flow.controller = this; //set the flow graph controller to this StartFlow(); //start the flow graph } } }