388 lines
14 KiB
C#
388 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using TriInspector;
|
|
|
|
namespace R0bbie.Timeline
|
|
{
|
|
/// <summary>
|
|
/// Parent component for a collection of Steps and StepGroups, which are triggered externally (i.e. by level script) and play individually, rather than as part of a full timeline
|
|
/// </summary>
|
|
public class StepController : MonoBehaviour
|
|
{
|
|
[Title("Controller")]
|
|
[SerializeField] bool autoInit = true;
|
|
|
|
[Title("Events")]
|
|
[SerializeField] UnityEvent onParentStepPlayed;
|
|
[SerializeField] UnityEvent onActiveStepComplete;
|
|
|
|
|
|
// Private Variables
|
|
List<Step> steps = new List<Step>();
|
|
|
|
List<StepCmd> activePausableCommands = new List<StepCmd>();
|
|
|
|
int activeStepIndex;
|
|
|
|
bool init;
|
|
|
|
|
|
// Events
|
|
public event Action onStepCompleted;
|
|
|
|
|
|
// Properties
|
|
public Step activeStep { get; private set; }
|
|
public bool activeStepIsGroup { get; private set; }
|
|
public bool activeStepHasVariants { get; private set; }
|
|
|
|
public bool isPaused { get; private set; }
|
|
public bool isPlaying { get; private set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Initialise steps ready to play on Start
|
|
/// </summary>
|
|
void Start()
|
|
{
|
|
if (autoInit)
|
|
Init();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Initialise step controller ready for play
|
|
/// </summary>
|
|
public void Init()
|
|
{
|
|
// Avoid duplicated initialization
|
|
if (init)
|
|
return;
|
|
|
|
// Get refs to all child steps (only 1 level deep to avoid getting children of StepGroups and StepSwitches etc, as these will init their own children)
|
|
foreach (Transform t in transform)
|
|
{
|
|
if (t.GetComponent<Step>())
|
|
{
|
|
steps.Add(t.GetComponent<Step>());
|
|
}
|
|
}
|
|
|
|
// Initialise Steps (and StepGroups and StepSwitches)
|
|
foreach (var step in steps)
|
|
{
|
|
step.Init(this);
|
|
}
|
|
|
|
init = true;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Play a set parent step
|
|
/// </summary>
|
|
/// /// <param name="_playStep">Step to play</param>
|
|
/// /// <param name="_overrideIfAlreadyPlaying">If another step is already playing, should it be cancelled and this one play instead? Defaults to false.</param>
|
|
public void Play(Step _playStep, bool _overrideIfAlreadyPlaying = false)
|
|
{
|
|
if (!init)
|
|
Init();
|
|
|
|
Step stepPlaying = null;
|
|
|
|
// TODO: Check the logic on this bit. All seems a bit odd to not distinguish between a step playing on a timeline as compared to the current controller (or another controller) when simply skipping
|
|
// TODO Lock for a step playing on a different controller?
|
|
|
|
if (StepTimelineManager.activeStepTimeline)
|
|
{
|
|
if (StepTimelineManager.activeStepTimeline.activeStepIsGroup)
|
|
{
|
|
StepGroup activeGroup = StepTimelineManager.activeStepTimeline.activeStep as StepGroup;
|
|
stepPlaying = activeGroup.activeChildStep;
|
|
}
|
|
else
|
|
{
|
|
stepPlaying = StepTimelineManager.activeStepTimeline.activeStep;
|
|
}
|
|
}
|
|
|
|
|
|
if (stepPlaying)
|
|
{
|
|
// TODO: Check this with Alejandro - if the stepPlaying is in a timeline, simply skipping to the next step in the timeline, at the same time as we're about to play a potentially conflicting step on the controller, is surely unintended?
|
|
// ! If the step playing is on a timeline (rather than controller) should it not simply be interrupted (timeline left halted where it is until we return to it) rather skipping to the next step in the timeline.. at the same time we're playing a specific step in a controller?
|
|
if (stepPlaying.CanBeInterrupted)
|
|
stepPlaying.Skip();
|
|
else if (stepPlaying.isActive && !stepPlaying.waitingForExternalTrigger)
|
|
// If the current step can't be interrupted and is still playing then do nothing
|
|
return;
|
|
}
|
|
|
|
// If we're already playing another step, should we cancel and jump to this one instead?
|
|
if (isPlaying)
|
|
{
|
|
if (!_overrideIfAlreadyPlaying)
|
|
{
|
|
// Don't try and start playing the timeline if it's already playing
|
|
Debug.Log("StepController was asked to play a step, but returned as another step was already playing.");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, before we play from this step, end the current step, if one is active
|
|
if (activeStep)
|
|
activeStep.ForceEnd();
|
|
}
|
|
}
|
|
|
|
isPlaying = true;
|
|
|
|
StepTimelineManager.activeController = this;
|
|
|
|
// Set initial step index
|
|
activeStepIndex = steps.FindIndex(a => a == _playStep);
|
|
|
|
// Play the requested step
|
|
SetActiveStep(activeStepIndex);
|
|
activeStep.Play();
|
|
|
|
// Invoke event
|
|
onParentStepPlayed.Invoke();
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Internal only function to set activeStep and associated vars
|
|
/// </summary>
|
|
/// <param name="_newActiveStepIndex"></param>
|
|
void SetActiveStep(int _newActiveStepIndex)
|
|
{
|
|
activeStep = steps[_newActiveStepIndex];
|
|
activeStepIsGroup = (activeStep is StepGroup);
|
|
activeStepHasVariants = (activeStep is StepSwitch);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called when a child step has completed, stop operation and wait for a new instruction to play a specific step
|
|
/// </summary>
|
|
public void StepCompleted()
|
|
{
|
|
isPlaying = false;
|
|
activeStep = null;
|
|
activeStepIndex = 0;
|
|
activeStepIsGroup = false;
|
|
activeStepHasVariants = false;
|
|
onActiveStepComplete.Invoke();
|
|
|
|
// Invoke step completed event
|
|
onStepCompleted?.Invoke();
|
|
|
|
// If GameTimeline thinks this is the currently active timeline, set null now controller finished the requested step
|
|
if (StepTimelineManager.activeController == this)
|
|
StepTimelineManager.activeController = null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called when we want the timeline to resume actions and avoid any switch or reminder from continue playing in the background
|
|
/// </summary>
|
|
public void ForceEnd()
|
|
{
|
|
if (activeStep)
|
|
activeStep.ForceEnd();
|
|
|
|
isPlaying = false;
|
|
activeStep = null;
|
|
activeStepIndex = 0;
|
|
activeStepIsGroup = false;
|
|
activeStepHasVariants = false;
|
|
|
|
// If GameTimeline thinks this is the currently active timeline, set null now controller finished the requested step
|
|
if (StepTimelineManager.activeController == this)
|
|
StepTimelineManager.activeController = null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Pause currently active commands
|
|
/// </summary>
|
|
public void Pause()
|
|
{
|
|
isPaused = true;
|
|
|
|
// Tell active step to pause (pauses timers)
|
|
if (activeStep)
|
|
activeStep.Pause();
|
|
|
|
// Pause all currently active commands which are pausable
|
|
foreach (StepCmd commandToPause in activePausableCommands)
|
|
{
|
|
// On the (theoretically impossible) chance a non-pausable command made it onto the list, don't try and pause it, and remove it from list
|
|
if (commandToPause is not IPausable)
|
|
{
|
|
RemoveActivePausableCommand(commandToPause);
|
|
continue;
|
|
}
|
|
|
|
// Pause command
|
|
(commandToPause as IPausable).Pause();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Resume paused commands
|
|
/// </summary>
|
|
/// <param name="_elapsedTime">Amount of time in seconds the game was paused i.e. player was in The Fold</param>
|
|
public void Resume(float _elapsedTime)
|
|
{
|
|
isPaused = false;
|
|
|
|
// Tell active step to resume (resumes any active timers)
|
|
if (activeStep)
|
|
activeStep.Resume(_elapsedTime);
|
|
|
|
// Resume all currently active commands which are pausable
|
|
foreach (StepCmd commandToPause in activePausableCommands)
|
|
{
|
|
// On the (theoretically impossible) chance a non-pausable command made it onto the list, don't try and pause it, and remove it from list
|
|
if (commandToPause is not IPausable)
|
|
{
|
|
RemoveActivePausableCommand(commandToPause);
|
|
continue;
|
|
}
|
|
|
|
// Resume command
|
|
(commandToPause as IPausable).Resume(_elapsedTime);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// When a command is activated which is pausable, add it to the list
|
|
/// </summary>
|
|
/// <param name="_commandToAddToList"></param>
|
|
public void AddActivePausableCommand(StepCmd _commandToAddToList)
|
|
{
|
|
// Ensure IPausable interface is present
|
|
if (_commandToAddToList is not IPausable)
|
|
return;
|
|
|
|
// Make sure command isn't already in list, if so don't add again
|
|
if (activePausableCommands.FirstOrDefault(a => a == _commandToAddToList) != null)
|
|
return;
|
|
|
|
// If this command implements ISingleInstance make sure there are no existing ISingleInstances in the active list (there can only be one active at a time..)
|
|
if (_commandToAddToList is ISingleInstance)
|
|
{
|
|
// remove all existing ISingleInstance from list before adding another. iterate through all current pausable commands backwards to check
|
|
for (int i = activePausableCommands.Count - 1; i >= 0; i--)
|
|
{
|
|
if (activePausableCommands[i] is ISingleInstance)
|
|
activePausableCommands.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
// Add to list
|
|
activePausableCommands.Add(_commandToAddToList);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// When a pausable command is deactivated, remove it from list
|
|
/// </summary>
|
|
/// <param name="_commandToRemoveToList"></param>
|
|
public void RemoveActivePausableCommand(StepCmd _commandToRemoveToList)
|
|
{
|
|
// Check command is actually in the list before doing anything
|
|
if (activePausableCommands.FirstOrDefault(i => i == _commandToRemoveToList) == null)
|
|
return;
|
|
|
|
// Remove from list
|
|
activePausableCommands.RemoveAll(r => r == _commandToRemoveToList);
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
/// EDITOR FUNCTIONS
|
|
|
|
[Title("Add to Controller")]
|
|
[Button("Add Step")]
|
|
private void Editor_AddStep()
|
|
{
|
|
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
|
GameObject stepGo = new GameObject();
|
|
stepGo.transform.parent = transform;
|
|
|
|
// Add Step component to this new GameObject
|
|
Step step = stepGo.AddComponent<Step>();
|
|
|
|
// Rename object
|
|
step.gameObject.name = "#" + (step.transform.GetSiblingIndex() + 1).ToString("00") + " - *ADD STEP NAME*";
|
|
|
|
}
|
|
|
|
|
|
[Button("Add Step Group")]
|
|
private void Editor_AddStepGroup()
|
|
{
|
|
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
|
GameObject stepGroupGo = new GameObject();
|
|
stepGroupGo.transform.parent = transform;
|
|
|
|
// Add StepGroup component to this new GameObject
|
|
StepGroup step = stepGroupGo.AddComponent<StepGroup>();
|
|
|
|
// Rename object
|
|
step.gameObject.name = "#" + (step.transform.GetSiblingIndex() + 1).ToString("00") + " - GROUP - *ADD STEP NAME*";
|
|
|
|
}
|
|
|
|
|
|
[Button("Add Step Switch")]
|
|
private void Editor_AddStepSwitch()
|
|
{
|
|
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
|
GameObject stepSwitchGo = new GameObject();
|
|
stepSwitchGo.transform.parent = transform;
|
|
|
|
// Player must add the specific StepSwitch type they like, remind them
|
|
Debug.Log("Empty StepSwitch object created, now remember to add the specific StepSwitch type you want to it.");
|
|
|
|
// Rename object
|
|
stepSwitchGo.gameObject.name = "#" + (stepSwitchGo.transform.GetSiblingIndex() + 1).ToString("00") + " - SWITCH - *ADD DESIRED SWITCH COMPONENT*";
|
|
}
|
|
|
|
|
|
[Title("Editor Functions")]
|
|
[Button("Rename All")]
|
|
private void Editor_RenameAll()
|
|
{
|
|
foreach (Transform child in transform)
|
|
{
|
|
Step childStepComponent = child.GetComponent<Step>();
|
|
|
|
if (childStepComponent)
|
|
childStepComponent.UpdateStepName();
|
|
|
|
// If group / switch also rename children
|
|
if (childStepComponent is StepGroup)
|
|
(childStepComponent as StepGroup).RenameChildren();
|
|
else if (childStepComponent is StepSwitch)
|
|
(childStepComponent as StepSwitch).RenameChildren();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
}
|
|
} |