Initial commit of refactored StepTimeline package
This commit is contained in:
commit
2189cfc787
|
@ -0,0 +1,53 @@
|
|||
# Visual Studio Cache
|
||||
.vs/
|
||||
.vscode/
|
||||
|
||||
# Autogenerated VS/MD/Consulo solution and project files
|
||||
ExportedObj/
|
||||
.consulo/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
*.opendb
|
||||
*.sublime-*
|
||||
|
||||
# Unity3D generated meta files
|
||||
*.pidb.meta
|
||||
*.pdb.meta
|
||||
*.unitypackage.meta
|
||||
|
||||
# Unity3D Generated File On Crash Reports
|
||||
sysinfo.txt
|
||||
|
||||
# Builds
|
||||
*.apk
|
||||
*.unitypackage
|
||||
*.ipa
|
||||
|
||||
# Documentation
|
||||
Documentation/api/
|
||||
Documentation/scripting/
|
||||
_site/
|
||||
/**/obj/
|
||||
/**/obj.meta
|
||||
Documentation/obj*
|
||||
UserSettings
|
||||
|
||||
# OS junk
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
Thumbs.db.meta
|
||||
.vsconfig
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "R0bbie.Timeline",
|
||||
"rootNamespace": "R0bbie.Timeline",
|
||||
"references": [
|
||||
"Unity.TextMeshPro",
|
||||
"Unity.InputSystem",
|
||||
"TriInspector",
|
||||
"NaughtyAttributes.Core"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 683a8bf2a8db6154b9177e122c046565
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,9 @@
|
|||
# Timeline System
|
||||
|
||||
Package to allow setup of a linear timeline of steps with attached commands.
|
||||
|
||||
# Dependencies
|
||||
|
||||
- TriInspector
|
||||
|
||||
These plugins/packages must be in the project prior to import, or they will throw some errors in the console.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0e45445281b15f746a15206faf2e745a
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e5271432087219a46bb979a961ec809c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f92b03cc00774d3a960183ae6aba004b
|
||||
timeCreated: 1647379457
|
|
@ -0,0 +1,78 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Animation StepCmd type, used to control an animation
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/Animation (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class AnimationCmd : StepCmd
|
||||
{
|
||||
[Title("Animation Options")]
|
||||
[SerializeField] protected Animator animator;
|
||||
[SerializeField] bool forceAnimation;
|
||||
[InfoBox("If forcing the animation, the animationTrigger string should be the name on the AnimatorState(not the trigger parameter) to be able to force it")]
|
||||
[SerializeField] protected string animationTrigger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise command (called before Activate)
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Check the necessary command values are set before continuing
|
||||
if (animator == null || string.IsNullOrEmpty(animationTrigger))
|
||||
{
|
||||
Debug.LogWarning("Tried to activate an AnimationCmd with a null animator or trigger. Couldn't continue.");
|
||||
DeactivateEarly();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure movement resumed to normal on animator (in case paused before)
|
||||
// The "Speed" float of the animator is used just to set a character idle at the start of an anim so it needs to be setup on the editor
|
||||
// For the resume function we just need to resume the speed on the animator itself
|
||||
animator.speed = 1f;
|
||||
|
||||
// Play animation
|
||||
if (forceAnimation)
|
||||
animator.Play(animationTrigger);
|
||||
else
|
||||
animator.SetTrigger(animationTrigger);
|
||||
|
||||
// Ensure GO attached to animator is enabled
|
||||
if (!animator.isActiveAndEnabled)
|
||||
animator.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public Animator GetAnimator() { return animator; }
|
||||
public string GetAnimatorString() { return animationTrigger; }
|
||||
public bool GetForceAnimation() { return forceAnimation; }
|
||||
|
||||
public void SetAnimatorEditor(Animator editorAnimator)
|
||||
{
|
||||
animator = editorAnimator;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bf3c00707389af459ed4de79649f977
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,49 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// DeactivateCommands StepCmd type, used to deactivate a list of commands which may have been left activated from a previous step
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/Deactivate Commands (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class DeactivateCommandsCmd : StepCmd
|
||||
{
|
||||
|
||||
[SerializeField] private List<StepCmd> commandsToDeactivate = new List<StepCmd>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise command (called before Activate)
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Loop through and deactivate all desired commands
|
||||
foreach (StepCmd command in commandsToDeactivate)
|
||||
{
|
||||
command.DeactivateEarly();
|
||||
}
|
||||
|
||||
// Then job of this command is done, complete it and deactivate
|
||||
Complete();
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8be5dd5a090c8d4d94bfd7d55003569
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,46 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
[AddComponentMenu("Timeline/Commands/Destroy GameObjects (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class DestroyObjectsCmd : StepCmd
|
||||
{
|
||||
|
||||
[SerializeField] private List<GameObject> objectsToDestroy = new List<GameObject>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise command (called before Activate)
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Loop through and destroy all objects
|
||||
foreach (GameObject go in objectsToDestroy)
|
||||
{
|
||||
if (go != null)
|
||||
Destroy(go);
|
||||
}
|
||||
|
||||
// Then job of this command is done, complete it and deactivate
|
||||
Complete();
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f8c50fae92c281498c7ded14e874c3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,54 @@
|
|||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Activate GameObjects from a list
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/GameObjects Set Active (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class GameObjectsSetActiveCmd : StepCmd
|
||||
{
|
||||
[Title("Set Objects Status")]
|
||||
[SerializeField] List<GameObject> objectsToActivate = new List<GameObject>();
|
||||
[SerializeField] List<GameObject> objectsToDeactivate = new List<GameObject>();
|
||||
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Activate all requested GameObjects
|
||||
foreach (var objectToActivate in objectsToActivate)
|
||||
{
|
||||
objectToActivate.SetActive(true);
|
||||
}
|
||||
|
||||
// Deactivate all requested GameObjects
|
||||
foreach (var objectToDeactivate in objectsToDeactivate)
|
||||
{
|
||||
objectToDeactivate.SetActive(false);
|
||||
}
|
||||
|
||||
// Then job of this command is done, complete it and deactivate
|
||||
Complete();
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23f22643cffb7374cbece05dc0dce17b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to use on the commands that needs specific commands if the timeline is paused
|
||||
/// </summary>
|
||||
public interface IPausable
|
||||
{
|
||||
public void Pause();
|
||||
|
||||
public void Resume(float _elapsedTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8ddbd29540db3046a25c59e5834e204
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,7 @@
|
|||
namespace R0bbie.Timeline
|
||||
{
|
||||
public interface ISingleInstance
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cb9d7a8a31607740b23f763821cdb07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,55 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Command for simply invoking a Unity event
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/Invoke Unity Event (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class InvokeEventCmd : StepCmd
|
||||
{
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Event")]
|
||||
[SerializeField] UnityEvent commandEvent;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the command
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Do whatever this command is designed to do
|
||||
/// </summary>
|
||||
/// <param name="_parentStep"></param>
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Play the event
|
||||
commandEvent?.Invoke();
|
||||
|
||||
// Immediately complete command now event invoked
|
||||
Complete();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Do any cleanup stuff here, resetting command state, clearing garbage, etc. Called by parent StepCmd after Complete, should never be called within child command itself
|
||||
/// </summary>
|
||||
protected override void Cleanup()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 49d6743615421ce4f89ecc78205d3836
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,140 @@
|
|||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class inherited by all StepCmd types
|
||||
/// </summary>
|
||||
public abstract class StepCmd : MonoBehaviour
|
||||
{
|
||||
[ShowIf("Inspector_ImplementsPausable")]
|
||||
[SerializeField] protected bool dontRepeatOnPauseResume;
|
||||
|
||||
public event Action onCompleteCommand;
|
||||
|
||||
protected Step parentStep;
|
||||
|
||||
public bool init { get; protected set; }
|
||||
public bool active { get; protected set; }
|
||||
public bool completed { get; private set; }
|
||||
|
||||
|
||||
protected abstract void Init();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Base Activate function, called by inheriting command before its own custom activation command
|
||||
/// </summary>
|
||||
/// <param name="_parentStep">The step this command is attached to</param>
|
||||
public virtual void Activate(Step _parentStep)
|
||||
{
|
||||
parentStep = _parentStep;
|
||||
|
||||
if (!init)
|
||||
Init();
|
||||
|
||||
active = true;
|
||||
|
||||
// If this is a pausable command, add it to activePausableCommands list
|
||||
if (this is IPausable)
|
||||
{
|
||||
if (parentStep.attachedStepTimeline)
|
||||
parentStep.attachedStepTimeline.AddActivePausableCommand(this);
|
||||
else if (parentStep.attachedController)
|
||||
parentStep.attachedController.AddActivePausableCommand(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called by any commands which have a limited number of components on a particular step, to see if max has been reached
|
||||
/// </summary>
|
||||
/// <param name="_currentActivatingStep">The Step we want to check for max commands (the one currently being activated)</param>
|
||||
/// <param name="_maxCommandsOfTypeOnStep">How many the max no. of commands of that type on this step there should be</param>
|
||||
/// <param name="_activeCommandsOfTypeOnStep">All instances of this command type on the step</param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool CheckMaxCommandsOnStep(Step _currentActivatingStep, int _maxCommandsOfTypeOnStep, List<StepCmd> _activeCommandsOfTypeOnStep)
|
||||
{
|
||||
// Check if too many commands of this type have already been activated on this step?
|
||||
if (_currentActivatingStep != null && _currentActivatingStep == parentStep)
|
||||
{
|
||||
// If so, return a warning, and return without ever activating this command
|
||||
if (_activeCommandsOfTypeOnStep.Count >= _maxCommandsOfTypeOnStep)
|
||||
{
|
||||
Debug.LogWarning("Tried to activate a command when the maximum of " + _maxCommandsOfTypeOnStep.ToString() + " of this command type per step have already been activated. Make sure there are only " + _maxCommandsOfTypeOnStep.ToString() + " of this type of command type on each Step.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// First command of this type activated on this step. Clear our list from previous step (if there was one)
|
||||
_currentActivatingStep = null;
|
||||
_activeCommandsOfTypeOnStep.Clear();
|
||||
}
|
||||
// Otherwise, we're fine, keep a log of the step this command was activated on, then proceed to activate it
|
||||
_currentActivatingStep = parentStep;
|
||||
_activeCommandsOfTypeOnStep.Add(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called by the command once whatever it does has been completed, and the command is to deactivate. Performs generic completion stuff, then calls back into the command itself to Cleanup
|
||||
/// </summary>
|
||||
protected virtual void Complete()
|
||||
{
|
||||
completed = true;
|
||||
active = false;
|
||||
Cleanup();
|
||||
|
||||
// If this is a pausable command, ensure it's removed from activePausableCommands list
|
||||
if (this is IPausable)
|
||||
{
|
||||
if (parentStep.attachedStepTimeline)
|
||||
parentStep.attachedStepTimeline.RemoveActivePausableCommand(this);
|
||||
else if (parentStep.attachedController)
|
||||
parentStep.attachedController.RemoveActivePausableCommand(this);
|
||||
}
|
||||
|
||||
onCompleteCommand?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called by an external component (such as DeactivateCommandsCmd) to deactivate a command before it's actually completing - i.e. stop whatever it's doing then clean it up
|
||||
/// </summary>
|
||||
public void DeactivateEarly()
|
||||
{
|
||||
// TODO: Look at actually deactivating whatever the command behaviour is, rather than just setting in inactive and tidying up?
|
||||
|
||||
active = false;
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Implemented be command to tidy up any specific stuff in that command that needs tidied up when the command has been deactivated
|
||||
/// </summary>
|
||||
protected abstract void Cleanup();
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#region Editor Inspector functions
|
||||
|
||||
bool Inspector_ImplementsPausable()
|
||||
{
|
||||
return (this is IPausable);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0c07706edea72534daeb8c9db1e9b651
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,125 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Video StepCmd type, used to control a video
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/Video (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class VideoCmd : StepCmd, IPausable
|
||||
{
|
||||
// Exposed variables
|
||||
|
||||
[Title("Video Settings")]
|
||||
[SerializeField] protected VideoPlayer videoPlayer;
|
||||
[SerializeField] protected VideoClip clip;
|
||||
|
||||
bool eventSubscribed_VideoEndReached;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise command (called before Activate)
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// TODO: Consider what the command itself should control, or pass to an external controller
|
||||
// What about looping videos etc? Would you want the command to start a video looping and leave it looping once the command was complete?
|
||||
|
||||
// Complete conditions - On video end, on number of loops?
|
||||
|
||||
// Ensure video player component is active, and set clip to the one we want to play
|
||||
videoPlayer.gameObject.SetActive(true);
|
||||
videoPlayer.clip = clip;
|
||||
|
||||
// Listen for event when video reaches end
|
||||
videoPlayer.loopPointReached += VideoEndReached;
|
||||
eventSubscribed_VideoEndReached = true;
|
||||
|
||||
// Play video
|
||||
videoPlayer.Play();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void VideoEndReached(VideoPlayer _videoPlayer)
|
||||
{
|
||||
// Unsubscribe from event
|
||||
videoPlayer.loopPointReached -= VideoEndReached;
|
||||
eventSubscribed_VideoEndReached = false;
|
||||
|
||||
// Ensure video stopped
|
||||
videoPlayer.Stop();
|
||||
|
||||
// Clear clip
|
||||
videoPlayer.clip = null;
|
||||
|
||||
// Complete command
|
||||
Complete();
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// Pause video
|
||||
videoPlayer.Pause();
|
||||
}
|
||||
|
||||
|
||||
public void Resume(float _elapsedTime)
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// If the time paused is longer than RepeatOnResumeDelay const, then we'll repeat the animation from the start
|
||||
if (_elapsedTime >= StepTimeline.RepeatOnResumeDelay && !dontRepeatOnPauseResume)
|
||||
{
|
||||
RepeatFromStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resume video (if currently paused)
|
||||
if (videoPlayer.isPaused)
|
||||
{
|
||||
videoPlayer.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RepeatFromStart()
|
||||
{
|
||||
// Ensure we're still subscribed to video end event
|
||||
if (!eventSubscribed_VideoEndReached)
|
||||
{
|
||||
videoPlayer.loopPointReached += VideoEndReached;
|
||||
eventSubscribed_VideoEndReached = true;
|
||||
}
|
||||
|
||||
// Reset video player to start then play
|
||||
videoPlayer.frame = 0;
|
||||
videoPlayer.Play();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 44bbcdad2ef168240a8f9d7dcc79f291
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,71 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using R0bbie.Timeline.Events;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Just watches for custom GameEvent being raised, then auto-completes
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Commands/Watch for Event (Step Command)")]
|
||||
[RequireComponent(typeof(Step))]
|
||||
public class WatchForEventCmd : StepCmd, IGameEventListener
|
||||
{
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Event Reference")]
|
||||
[SerializeField] GameEvent gameEventToWatchFor;
|
||||
|
||||
bool registered;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise command (called before Activate)
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Activate(Step _parentStep)
|
||||
{
|
||||
base.Activate(_parentStep);
|
||||
|
||||
// Start watching for GameEvent...
|
||||
if (!registered)
|
||||
Register();
|
||||
}
|
||||
|
||||
|
||||
void Register()
|
||||
{
|
||||
gameEventToWatchFor.RegisterListener(this);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
|
||||
void Unregister()
|
||||
{
|
||||
gameEventToWatchFor.UnregisterListener(this);
|
||||
registered = false;
|
||||
}
|
||||
|
||||
|
||||
public void OnEventRaised()
|
||||
{
|
||||
Unregister();
|
||||
|
||||
Complete();
|
||||
}
|
||||
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4df40ed0726adf34b8051b16b021d598
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ed442ae6eba152b4b82258f39a41222c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "R0bbie.Timeline.Editor",
|
||||
"rootNamespace": "R0bbie.Timeline.Editor",
|
||||
"references": [
|
||||
"GUID:683a8bf2a8db6154b9177e122c046565",
|
||||
"GUID:e851236b9ac2b9b4eaaa99506366edea"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af285bff4afff234c98e8dc81deeef5f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,69 @@
|
|||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using TriInspector.Editors;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor class to update the GameObject name based on the shortName of the step and child position
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Step),true)]
|
||||
public class StepEditor : TriEditor
|
||||
{
|
||||
private TriEditorCore _core;
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnEnable function - called here when object is selected to be viewed in inspector
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
{
|
||||
Rename();
|
||||
|
||||
// Re-implement base TriEditor.OnEnable logic (not overridable sadly as its private)
|
||||
_core = new TriEditorCore(this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnDisable function - called here when object selection in inspector view is cleared
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
Rename();
|
||||
|
||||
// Re-implement base TriEditor.OnDisable logic
|
||||
_core.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rename the Step GameObject to reflect latest settings
|
||||
/// </summary>
|
||||
void Rename()
|
||||
{
|
||||
// If the object has been destroyed avoid changing the name
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// Get target as Step
|
||||
Step step = target as Step;
|
||||
if (!step)
|
||||
return;
|
||||
|
||||
step.UpdateStepName();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Function from base TriEditor class which frustratingly we have to re-implement here due to accessibility issues in the base class
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
return _core.CreateVisualElement();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: efbff33e8d18109479bfa8a74e42ce2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e49844d1f2e94213bd017f9ad63b2039
|
||||
timeCreated: 1661478048
|
|
@ -0,0 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using TriInspector;
|
||||
#endif
|
||||
|
||||
namespace R0bbie.Timeline.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Scriptable Object to handle in game events without the need of direct references
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "GameEvent", menuName = "Timeline/Game Event")]
|
||||
public class GameEvent : ScriptableObject
|
||||
{
|
||||
// Private Variables
|
||||
|
||||
List<IGameEventListener> listeners = new List<IGameEventListener>();
|
||||
|
||||
/// <summary>
|
||||
/// Call all listeners registered under this event
|
||||
/// </summary>
|
||||
public void Raise()
|
||||
{
|
||||
for (int i = listeners.Count -1; i >= 0; i--)
|
||||
listeners[i].OnEventRaised();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Register listener to be called when event raised
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
public void RegisterListener(IGameEventListener listener)
|
||||
{
|
||||
// Check that the listener is not on the list before adding it to avoid duplicates
|
||||
if(!listeners.Contains(listener))
|
||||
listeners.Add(listener);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unregister listener to not be called anymore
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
public void UnregisterListener(IGameEventListener listener)
|
||||
{
|
||||
// Check that the listener is part of the list before trying to remove it
|
||||
if(listeners.Contains(listener))
|
||||
listeners.Remove(listener);
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Button("Test Raise")]
|
||||
void Test()
|
||||
{
|
||||
Raise();
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33416bcf642caac4998573878bb99f91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,89 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace R0bbie.Timeline.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Register to the linked GameEvent scriptable object, and trigger any function assign to the unity event
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/Game Events/Event Listener")]
|
||||
public class GameEventListener : MonoBehaviour, IGameEventListener
|
||||
{
|
||||
// Exposed Variables
|
||||
|
||||
[Header("Event Listener")]
|
||||
public bool registerAutomatically = true;
|
||||
public bool triggerOnce;
|
||||
|
||||
[Space(10)]
|
||||
[FormerlySerializedAs("animationStateEvent")]
|
||||
public GameEvent gameEvent;
|
||||
public UnityEvent response = new UnityEvent();
|
||||
|
||||
// Private Variables
|
||||
|
||||
bool registered;
|
||||
bool triggered;
|
||||
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (!gameEvent)
|
||||
return;
|
||||
|
||||
// If is not set to registered automatically, and haven't been registered yet, do nothing
|
||||
if (!registerAutomatically && !registered)
|
||||
return;
|
||||
|
||||
// If already triggered, do nothing
|
||||
if (triggerOnce && triggered)
|
||||
return;
|
||||
|
||||
Register();
|
||||
}
|
||||
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
// If there is no game event attached at the moment the object is disabled, do nothing
|
||||
if (!gameEvent)
|
||||
return;
|
||||
|
||||
// Unregister listener but remember status
|
||||
gameEvent.UnregisterListener(this);
|
||||
}
|
||||
|
||||
|
||||
public void OnEventRaised()
|
||||
{
|
||||
// If for some reason the event is still registered when it shouldn't, do nothing
|
||||
if (triggerOnce && triggered)
|
||||
return;
|
||||
|
||||
triggered = true;
|
||||
|
||||
response?.Invoke();
|
||||
|
||||
// Unregister and disable to avoid calling onDisable when unloading the level
|
||||
if (triggerOnce)
|
||||
Unregister();
|
||||
}
|
||||
|
||||
|
||||
public void Register()
|
||||
{
|
||||
gameEvent.RegisterListener(this);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
|
||||
public void Unregister()
|
||||
{
|
||||
gameEvent.UnregisterListener(this);
|
||||
registered = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 192fcff1e69520e49b2bf437f3079b1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline.Events
|
||||
{
|
||||
public class GameEventMultipleTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("Linked Event")]
|
||||
public GameEvent[] gameEvent;
|
||||
|
||||
|
||||
public void Trigger(int index)
|
||||
{
|
||||
// Only trigger if there is an event at that index
|
||||
if (index < gameEvent.Length)
|
||||
gameEvent[index].Raise();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 96bf9479be478e747898fbce37e42829
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,13 @@
|
|||
namespace R0bbie.Timeline.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to create custom event listeners inside another classes
|
||||
/// </summary>
|
||||
public interface IGameEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Action to do when the event is raised
|
||||
/// </summary>
|
||||
public void OnEventRaised();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f66c81ba53a2e824ca292c53bdcbb0ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,388 @@
|
|||
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
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3baa379cd674430998fe97d77e1f5c2e
|
||||
timeCreated: 1659436582
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a554e01126d3416ebb3404d8dac980c9
|
||||
timeCreated: 1648757375
|
|
@ -0,0 +1,151 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Wait for an action to be completed, allowing to trigger an incorrect step
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/StepSwitches/Correct Action (Step Switch)")]
|
||||
public class CorrectActionStepSwitch : StepSwitch
|
||||
{
|
||||
[System.Serializable]
|
||||
struct ActionSwitch
|
||||
{
|
||||
public TimelineTrigger trigger;
|
||||
public Step stepOption;
|
||||
}
|
||||
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Correct Action Step Switch")]
|
||||
[SerializeField] ActionSwitch incorrectStep;
|
||||
[SerializeField] bool keepPlaying;
|
||||
[Space(10)]
|
||||
[SerializeField] ActionSwitch correctStep;
|
||||
|
||||
|
||||
// Private variables
|
||||
|
||||
bool incorrectPlayed;
|
||||
bool correctPlayed;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init by Timeline
|
||||
/// </summary>
|
||||
/// <param name="_stepTimeline"></param>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
if (incorrectStep.stepOption)
|
||||
incorrectStep.stepOption.Init(attachedStepTimeline, this);
|
||||
incorrectStep.trigger.AddStep(this);
|
||||
|
||||
if (correctStep.stepOption)
|
||||
correctStep.stepOption.Init(attachedStepTimeline, this);
|
||||
correctStep.trigger.AddStep(this);
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init switch by StepController
|
||||
/// </summary>
|
||||
/// <param name="_controller"></param>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
if (incorrectStep.stepOption)
|
||||
incorrectStep.stepOption.Init(attachedController, this);
|
||||
incorrectStep.trigger.AddStep(this);
|
||||
|
||||
if (correctStep.stepOption)
|
||||
correctStep.stepOption.Init(attachedController, this);
|
||||
correctStep.trigger.AddStep(this);
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
waitingForExternalTrigger = true;
|
||||
|
||||
// Wait for event from the player
|
||||
activeMode = Mode.WaitingForEvent;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Trigger command received from external TimelineTrigger
|
||||
/// </summary>
|
||||
public override void TriggerContinue(TimelineTrigger _trigger)
|
||||
{
|
||||
if (!isActive || activeMode != Mode.WaitingForEvent)
|
||||
return;
|
||||
|
||||
if (_trigger == incorrectStep.trigger)
|
||||
{
|
||||
if (!incorrectPlayed || keepPlaying)
|
||||
PlaySelectedStepOption(incorrectStep.stepOption);
|
||||
|
||||
incorrectPlayed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change the status only if the correct action have been completed
|
||||
// If is not completed we still want to be able to receive input from triggers
|
||||
activeMode = Mode.PlayingChildStep;
|
||||
|
||||
correctPlayed = true;
|
||||
PlaySelectedStepOption(correctStep.stepOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PlaySelectedStepOption(Step _option)
|
||||
{
|
||||
activeStepOption = _option;
|
||||
|
||||
// Play the selected option, unless it's null (in which case just play next step in timeline)
|
||||
if (activeStepOption)
|
||||
{
|
||||
activeStepOption.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("The option selected by the step switch was null, so just proceeding to next step in timeline.");
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Continue()
|
||||
{
|
||||
if (!correctPlayed)
|
||||
return;
|
||||
|
||||
// If the correct step have been played, then continue to the next step
|
||||
base.Continue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 10abc55aef1f11d4ea461c302df84e31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,138 @@
|
|||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
[AddComponentMenu("Timeline/StepSwitches/Counter (Step Switch)")]
|
||||
public class CounterStepSwitch : StepSwitch
|
||||
{
|
||||
// Assign the step, and how long should the timeline wait before trigger the next step
|
||||
[System.Serializable]
|
||||
struct CountSwitch
|
||||
{
|
||||
[SerializeField] public Step stepOption;
|
||||
[SerializeField] public int triggerAfter;
|
||||
}
|
||||
|
||||
// Private Variables
|
||||
|
||||
[Title("Switch Options")]
|
||||
[SerializeField] List<CountSwitch> countSwitches = new List<CountSwitch>();
|
||||
[SerializeField] TimelineTrigger externalTrigger;
|
||||
|
||||
[SerializeField] bool repeatStepsInSwitch;
|
||||
|
||||
int count;
|
||||
int activeStepIndex = -1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init switch with Timeline
|
||||
/// </summary>
|
||||
/// <param name="_stepTimeline"></param>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (CountSwitch countSwitch in countSwitches)
|
||||
{
|
||||
if (countSwitch.stepOption)
|
||||
countSwitch.stepOption.Init(attachedStepTimeline, this);
|
||||
}
|
||||
|
||||
externalTrigger.AddStep(this);
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init switch by StepController
|
||||
/// </summary>
|
||||
/// <param name="_controller"></param>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (CountSwitch countSwitch in countSwitches)
|
||||
{
|
||||
if (countSwitch.stepOption)
|
||||
countSwitch.stepOption.Init(attachedController, this);
|
||||
}
|
||||
|
||||
externalTrigger.AddStep(this);
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepSwitch, in turn playing whichever step its conditions point to
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
waitingForExternalTrigger = true;
|
||||
|
||||
activeMode = Mode.WaitingForEvent;
|
||||
}
|
||||
|
||||
|
||||
public void IncreaseCount()
|
||||
{
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
count++;
|
||||
|
||||
// Reset step index, or exit this method if all steps in the switch have already been performed
|
||||
if (repeatStepsInSwitch)
|
||||
{
|
||||
if (countSwitches.Count <= activeStepIndex + 1)
|
||||
activeStepIndex = -1;
|
||||
else return;
|
||||
}
|
||||
|
||||
if (count >= countSwitches[activeStepIndex + 1].triggerAfter && (activeStepIndex + 1) <= countSwitches.Count)
|
||||
{
|
||||
activeStepIndex++;
|
||||
countSwitches[activeStepIndex].stepOption.Play();
|
||||
|
||||
// If it's the last one, disable the counter for now
|
||||
if (activeStepIndex >= countSwitches.Count)
|
||||
isActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Continue()
|
||||
{
|
||||
// Leave empty as we want to wait until the player completes the actual task
|
||||
// And the steps are triggered through increase of the count tries
|
||||
}
|
||||
|
||||
|
||||
public override void TriggerContinue(TimelineTrigger _trigger)
|
||||
{
|
||||
if (!isActive || activeMode != Mode.WaitingForEvent)
|
||||
return;
|
||||
|
||||
base.Continue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 736f7f84c60d0d248a617b0cf796dd6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,188 @@
|
|||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggle between step option based on what external TimelineTrigger is received
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/StepSwitches/External Trigger (Step Switch)")]
|
||||
public class ExternalTriggerStepSwitch : StepSwitch
|
||||
{
|
||||
enum IfStepAlreadyPlayedAction
|
||||
{
|
||||
PlayAgain,
|
||||
DoNothing,
|
||||
SkipSwitch
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
struct TriggerSwitch
|
||||
{
|
||||
[SerializeField] public TimelineTrigger trigger;
|
||||
[SerializeField] public Step stepOption;
|
||||
[SerializeField] public IfStepAlreadyPlayedAction ifStepPrevPlayed;
|
||||
}
|
||||
|
||||
[Title("Switch Options")]
|
||||
[SerializeField] List<TriggerSwitch> triggerSwitches = new List<TriggerSwitch>();
|
||||
|
||||
[Title("Adjust Trigger Behaviour")]
|
||||
[SerializeField, Tooltip("If a trigger attached to this switch was triggered previously, set the corresponding step option as active immediately, rather than waiting for a live trigger.")] private bool switchImmediatelyIfPrevTriggered;
|
||||
|
||||
[ShowIf(nameof(switchImmediatelyIfPrevTriggered))]
|
||||
[SerializeField, Tooltip("If no triggers attached to this switch have been triggered previously, just skip this switch altogether.")] private bool skipImmediatelyIfNotPrevTriggered;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch by Timeline
|
||||
/// </summary>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (TriggerSwitch triggerSwitch in triggerSwitches)
|
||||
{
|
||||
triggerSwitch.stepOption.Init(attachedStepTimeline, this);
|
||||
triggerSwitch.trigger.AddStep(this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch by StepController
|
||||
/// </summary>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (TriggerSwitch triggerSwitch in triggerSwitches)
|
||||
{
|
||||
triggerSwitch.stepOption.Init(attachedController, this);
|
||||
triggerSwitch.trigger.AddStep(this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepSwitch, in turn playing whichever step its conditions point to
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
|
||||
activeMode = Mode.WaitingForEvent;
|
||||
|
||||
// Option to play a child option immediately if the paired trigger was triggered previously
|
||||
if (switchImmediatelyIfPrevTriggered)
|
||||
{
|
||||
// Loop through all triggers and check if any were triggered before
|
||||
foreach (TriggerSwitch triggerSwitch in triggerSwitches)
|
||||
{
|
||||
// If so we've got our step option to play
|
||||
if (triggerSwitch.trigger.triggered)
|
||||
{
|
||||
activeStepOption = triggerSwitch.stepOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no triggers were previously triggered, option to just skip over this switch altogether, and inform the timeline to play the next step
|
||||
if (skipImmediatelyIfNotPrevTriggered)
|
||||
{
|
||||
SkipSwitch();
|
||||
return;
|
||||
}
|
||||
|
||||
// If active step is now setup, play it!
|
||||
if (activeStepOption)
|
||||
{
|
||||
activeMode = Mode.PlayingChildStep;
|
||||
activeStepOption.Play();
|
||||
}
|
||||
}
|
||||
|
||||
// If we've got here then we want to wait for an external trigger to send instruction to this switch..
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SkipSwitch()
|
||||
{
|
||||
activeMode = Mode.Inactive;
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Trigger command received from external TimelineTrigger
|
||||
/// </summary>
|
||||
public override void TriggerContinue(TimelineTrigger _trigger)
|
||||
{
|
||||
if (!isActive || activeMode != Mode.WaitingForEvent)
|
||||
return;
|
||||
|
||||
// Find this trigger in the list, and set the corresponding child step as active
|
||||
int switchIndexInList = triggerSwitches.FindIndex(x => x.trigger == _trigger);
|
||||
|
||||
// If this trigger wasn't found in the list, we can't play any child step. Don't do anything
|
||||
if (switchIndexInList == -1)
|
||||
{
|
||||
Debug.LogWarning("ExternalTriggerStepSwitch received a command from a trigger not in the switch list, can't do anything.");
|
||||
return;
|
||||
}
|
||||
|
||||
// With the trigger found in the list, set active step option to corresponding step
|
||||
activeStepOption = triggerSwitches[switchIndexInList].stepOption;
|
||||
|
||||
// Check if step was previously played, and if we want to execute any custom action here if so
|
||||
if (activeStepOption.wasCompleted)
|
||||
{
|
||||
if (triggerSwitches[switchIndexInList].ifStepPrevPlayed == IfStepAlreadyPlayedAction.DoNothing)
|
||||
{
|
||||
activeStepOption = null;
|
||||
return;
|
||||
}
|
||||
else if (triggerSwitches[switchIndexInList].ifStepPrevPlayed == IfStepAlreadyPlayedAction.SkipSwitch)
|
||||
{
|
||||
SkipSwitch();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Target step option to play is now definitely set..
|
||||
|
||||
// Play the selected option, unless it's null (in which case just play next step in timeline)
|
||||
if (activeStepOption)
|
||||
{
|
||||
activeMode = Mode.PlayingChildStep;
|
||||
activeStepOption.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("The option selected by the step switch was null, so just proceeding to next step in timeline.");
|
||||
SkipSwitch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 834a12c2ec5a4e6ea419e14e319d809f
|
||||
timeCreated: 1648758186
|
|
@ -0,0 +1,121 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Plays one of its sub steps at random
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/StepSwitches/Randomised (Step Switch)")]
|
||||
public class RandomisedStepSwitch : StepSwitch
|
||||
{
|
||||
// Add ability to avoid repeating the same option twice
|
||||
[SerializeField, Tooltip("Try to avoid playing the same step twice in a row (only applicable if a StepLooper is repeating this switch!)")]
|
||||
bool avoidRepeats;
|
||||
|
||||
List<Step> childSteps = new List<Step>();
|
||||
|
||||
int lastSelectedStepIndex = -1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch
|
||||
/// </summary>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Get refs to all child steps (only 1 level deep)
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
if (t.GetComponent<Step>())
|
||||
{
|
||||
childSteps.Add(t.GetComponent<Step>());
|
||||
}
|
||||
}
|
||||
|
||||
// Init all child steps
|
||||
foreach (var step in childSteps)
|
||||
{
|
||||
step.Init(attachedStepTimeline, this);
|
||||
}
|
||||
|
||||
// Reset stored values
|
||||
lastSelectedStepIndex = -1;
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch by StepController
|
||||
/// </summary>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Get refs to all child steps (only 1 level deep)
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
if (t.GetComponent<Step>())
|
||||
{
|
||||
childSteps.Add(t.GetComponent<Step>());
|
||||
}
|
||||
}
|
||||
|
||||
// Init all child steps
|
||||
foreach (var step in childSteps)
|
||||
{
|
||||
step.Init(attachedController, this);
|
||||
}
|
||||
|
||||
// Reset stored values
|
||||
lastSelectedStepIndex = -1;
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepSwitch, in turn playing one randomly selected child step
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
isActive = true;
|
||||
|
||||
// If no child step options exist, do nothing, and just skip onto the next step in the timeline
|
||||
if (childSteps.Count < 1)
|
||||
Continue();
|
||||
|
||||
// Select a child step at random
|
||||
int randomStepIndex = Random.Range(0, (childSteps.Count - 1));
|
||||
|
||||
// If selected step is same as last one, try selecting avoid
|
||||
if (avoidRepeats && randomStepIndex == lastSelectedStepIndex)
|
||||
randomStepIndex = Random.Range(0, (childSteps.Count - 1));
|
||||
|
||||
// Set the active step option to the one selected
|
||||
activeStepOption = childSteps[randomStepIndex];
|
||||
|
||||
// Play the selected option, unless it's null (in which case just play next step in timeline)
|
||||
if (activeStepOption)
|
||||
{
|
||||
activeMode = Mode.PlayingChildStep;
|
||||
activeStepOption.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("The option selected by the step switch was null, so just proceeding to next step in timeline.");
|
||||
Continue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb482d05f9754bd7ac2c629b36b1eeef
|
||||
timeCreated: 1648765606
|
|
@ -0,0 +1,170 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Trigger the next step on the switch after a delay if the player doesn't realize the desired action
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/StepSwitches/Reminder (Step Switch)")]
|
||||
public class ReminderStepSwitch : StepSwitch
|
||||
{
|
||||
|
||||
// Assign the step, and how long should the timeline wait before trigger the next step
|
||||
[System.Serializable]
|
||||
struct ReminderSwitch
|
||||
{
|
||||
[SerializeField] public Step stepOption;
|
||||
[SerializeField] public float secondsToTrigger;
|
||||
}
|
||||
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Switch Options")]
|
||||
[SerializeField] List<ReminderSwitch> reminderSwitches = new List<ReminderSwitch>();
|
||||
[SerializeField] bool keepTriggeringSteps;
|
||||
|
||||
// Private Variables
|
||||
|
||||
int stepIndex;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init switch by Timeline
|
||||
/// </summary>
|
||||
/// <param name="_stepTimeline"></param>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (ReminderSwitch reminderSwitch in reminderSwitches)
|
||||
{
|
||||
reminderSwitch.stepOption.Init(attachedStepTimeline, this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Init switch by StepController
|
||||
/// </summary>
|
||||
/// <param name="_controller"></param>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all step switch options
|
||||
foreach (ReminderSwitch reminderSwitch in reminderSwitches)
|
||||
{
|
||||
reminderSwitch.stepOption.Init(attachedController, this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
waitingForExternalTrigger = true;
|
||||
|
||||
activeMode = Mode.WaitingForEvent;
|
||||
|
||||
// Trigger the first step
|
||||
reminderSwitches[stepIndex].stepOption.Play();
|
||||
|
||||
// Start the coroutine in case the player doesn't press the button before the first reminder
|
||||
StartCoroutine(TriggerReminder(reminderSwitches[stepIndex].secondsToTrigger));
|
||||
}
|
||||
|
||||
|
||||
IEnumerator TriggerReminder(float delayTime)
|
||||
{
|
||||
yield return new WaitForSeconds(delayTime);
|
||||
|
||||
// If by the time this part is triggered, the step has skipped through external Step Controller then avoid continue
|
||||
if (wasSkipped || !isActive)
|
||||
yield break;
|
||||
|
||||
// If the step haven't been completed yet, skip it and trigger the next reminder on the list
|
||||
if (!wasCompleted)
|
||||
{
|
||||
reminderSwitches[stepIndex].stepOption.Skip();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Continue()
|
||||
{
|
||||
// If the active step was skipped because of the delay, trigger the next one on the switch
|
||||
if (reminderSwitches[stepIndex].stepOption.wasSkipped && !reminderSwitches[stepIndex].stepOption.wasCompleted)
|
||||
{
|
||||
stepIndex++;
|
||||
|
||||
// If we want to loop again through the steps
|
||||
if (keepTriggeringSteps)
|
||||
{
|
||||
// Reset the index to 0
|
||||
if (stepIndex >= reminderSwitches.Count)
|
||||
stepIndex = 0;
|
||||
}
|
||||
|
||||
// Play the next one if it's inside the length of the list
|
||||
if (stepIndex < reminderSwitches.Count)
|
||||
{
|
||||
// Trigger the next step
|
||||
reminderSwitches[stepIndex].stepOption.Play();
|
||||
|
||||
// If the actual step is not the last one, or we want to loop through all the steps, start the coroutine to trigger the reminder
|
||||
if (stepIndex < reminderSwitches.Count - 1 || keepTriggeringSteps)
|
||||
StartCoroutine(TriggerReminder(reminderSwitches[stepIndex].secondsToTrigger));
|
||||
}
|
||||
}
|
||||
else // Triggered through the timeline trigger
|
||||
{
|
||||
// If the step switch was already completed and is called through the same timeline trigger twice, only register one
|
||||
if (wasCompleted)
|
||||
return;
|
||||
|
||||
Debug.Log(name + " - Completed");
|
||||
|
||||
// Complete the switch
|
||||
isActive = false;
|
||||
activeMode = Mode.Inactive;
|
||||
wasCompleted = true;
|
||||
|
||||
// Tell the timeline to play the next step (or step group, or controller)
|
||||
if (parentStepGroup)
|
||||
{
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
else
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
|
||||
// TODO Complete the current switch and steps inside
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 075dbe4732565d84d8559bedda870952
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,99 @@
|
|||
using System.Collections.Generic;
|
||||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Trigger the child steps in order, increasing index each time is played
|
||||
/// </summary>
|
||||
public class StepPoolSwitch : StepSwitch
|
||||
{
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Pool Settings")]
|
||||
[InfoBox("Only compatible with steps inside a StepController", TriMessageType.Warning)]
|
||||
[SerializeField] bool storeIndexOnDevice;
|
||||
|
||||
|
||||
// Private Variables
|
||||
|
||||
List<Step> childSteps = new List<Step>();
|
||||
|
||||
int activeIndex;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise StepPool via controller
|
||||
/// </summary>
|
||||
/// <param name="_controller"></param>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
attachedController = _controller;
|
||||
|
||||
InitPool();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise StepPool via controller, if child of StepGroup
|
||||
/// </summary>
|
||||
/// <param name="_controller"></param>
|
||||
public override void Init(StepController _controller, StepGroup _stepGroup)
|
||||
{
|
||||
parentStepGroup = _stepGroup;
|
||||
|
||||
Init(_controller);
|
||||
}
|
||||
|
||||
|
||||
void InitPool()
|
||||
{
|
||||
// If getting the store from memory, use the step name as the key, otherwise start at value 0
|
||||
if (storeIndexOnDevice)
|
||||
activeIndex = PlayerPrefs.GetInt(shortName);
|
||||
|
||||
// Setup necessary refs to parent timeline, and child steps (only 1 level deep to avoid getting children of StepSwitches etc)
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
Step childStep = t.GetComponent<Step>();
|
||||
|
||||
if (childStep)
|
||||
{
|
||||
childSteps.Add(childStep);
|
||||
childStep.Init(attachedController, this);
|
||||
}
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepGroup with the current active step index
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
// Set the index to start at the first step
|
||||
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
|
||||
// Only play the active step
|
||||
Step activeChildStep = childSteps[activeIndex];
|
||||
activeChildStep.Play();
|
||||
|
||||
activeIndex++;
|
||||
|
||||
// If index out of range, reset it
|
||||
if (activeIndex >= childSteps.Count)
|
||||
activeIndex = 0;
|
||||
|
||||
if (storeIndexOnDevice)
|
||||
PlayerPrefs.SetInt(shortName, activeIndex);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e7d75fa3fe456914f89d063bba8a2d8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,114 @@
|
|||
using UnityEngine;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow the selection of the next step based on a player action
|
||||
/// </summary>
|
||||
public abstract class StepSwitch : Step
|
||||
{
|
||||
protected enum Mode
|
||||
{
|
||||
Inactive,
|
||||
WaitingForEvent,
|
||||
PlayingChildStep
|
||||
}
|
||||
|
||||
protected Mode activeMode;
|
||||
|
||||
protected Step activeStepOption;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tell timeline to continue once step option associated with this Switch has completed
|
||||
/// </summary>
|
||||
public override void Continue()
|
||||
{
|
||||
Debug.Log(name + " - Completed");
|
||||
|
||||
isActive = false;
|
||||
activeMode = Mode.Inactive;
|
||||
wasCompleted = true;
|
||||
|
||||
// Tell the timeline to play the next step (or step group, or controller)
|
||||
if (parentStepGroup)
|
||||
{
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else if (parentStepSwitch)
|
||||
{
|
||||
parentStepSwitch.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
else
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// ODIN INSPECTOR BUTTONS
|
||||
|
||||
[Title("Add Option to Switch")]
|
||||
[Button("Add Step")]
|
||||
private void Editor_AddStepOption()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the StepSwitch parent object
|
||||
GameObject stepGo = new GameObject();
|
||||
stepGo.transform.parent = transform;
|
||||
|
||||
// Add Step component to this new GameObject
|
||||
Step step = stepGo.AddComponent<Step>();
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.GetSiblingIndex() + 1;
|
||||
int childStepNo = step.transform.GetSiblingIndex() + 1;
|
||||
step.gameObject.name = "#" + parentStepNo.ToString("00") + "." + childStepNo.ToString("00") + " - Switch Option - *ADD STEP NAME*";
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Button("Add GoTo Step")]
|
||||
private void Editor_AddGoToStepOption()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the StepSwitch parent object
|
||||
GameObject stepGo = new GameObject();
|
||||
stepGo.transform.parent = transform;
|
||||
|
||||
// Add GoToStep component to this new GameObject
|
||||
GoToStep step = stepGo.AddComponent<GoToStep>();
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.GetSiblingIndex() + 1;
|
||||
int childStepNo = step.transform.GetSiblingIndex() + 1;
|
||||
step.gameObject.name = "#" + parentStepNo.ToString("00") + "." + childStepNo.ToString("00") + " - GOTO - Switch Option - *ADD STEP NAME*";
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Title("Editor Functions")]
|
||||
[Button("Rename Child Steps")]
|
||||
private void Editor_RenameChildren()
|
||||
{
|
||||
RenameChildren();
|
||||
}
|
||||
|
||||
// OTHER EDITOR HELPER FUNCTIONS
|
||||
public void RenameChildren()
|
||||
{
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
if (child.GetComponent<Step>())
|
||||
child.GetComponent<Step>().UpdateStepName();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f1388074bc9495291bc0198c990fb7e
|
||||
timeCreated: 1647378284
|
|
@ -0,0 +1,195 @@
|
|||
using UnityEngine;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Watch for an event to happen, and if it doesn't after some delay, trigger a step to assist the player
|
||||
/// </summary>
|
||||
[AddComponentMenu("Timeline/StepSwitches/Watch and Assist (Step Switch)")]
|
||||
public class WatchAndAssistStepSwitch : StepSwitch
|
||||
{
|
||||
public enum WatchForEvent
|
||||
{
|
||||
OnCommandComplete,
|
||||
OnExternalTrigger
|
||||
}
|
||||
|
||||
[Title("Switch Setup")]
|
||||
[SerializeField] WatchForEvent watchForEvent;
|
||||
|
||||
[ShowIf("watchForEvent", WatchForEvent.OnCommandComplete)]
|
||||
[SerializeField] StepCmd watchOnCommandComplete;
|
||||
|
||||
[ShowIf("watchForEvent", WatchForEvent.OnExternalTrigger)]
|
||||
[SerializeField] TimelineTrigger watchTrigger;
|
||||
|
||||
[SerializeField, Tooltip("If watching for OnCommandComplete will skip if command previously completed, if OnExternalTrigger then if prev triggered")] bool skipIfPreviouslyTriggered;
|
||||
|
||||
[SerializeField] float waitForSeconds;
|
||||
|
||||
[Title("Switch Options")]
|
||||
|
||||
[SerializeField, Tooltip("Step switched to if event isn't detected before waitForSeconds runs out")]
|
||||
Step assistAfterDelayStep;
|
||||
|
||||
[SerializeField, Tooltip("Step switched to if event is detected before waitForSeconds is up. If left null will just continue to next step in timeline")]
|
||||
Step watchedEventPerformedStep;
|
||||
|
||||
|
||||
float timer;
|
||||
bool timedOut;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch with Timeline
|
||||
/// </summary>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all child steps
|
||||
if (assistAfterDelayStep)
|
||||
assistAfterDelayStep.Init(attachedStepTimeline, this);
|
||||
|
||||
if (watchedEventPerformedStep)
|
||||
watchedEventPerformedStep.Init(attachedStepTimeline, this);
|
||||
|
||||
ResetTimer();
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepSwitch with StepController
|
||||
/// </summary>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs such as parent controller
|
||||
attachedController = _controller;
|
||||
|
||||
// Inactive by default
|
||||
activeMode = Mode.Inactive;
|
||||
|
||||
// Init all child steps
|
||||
if (assistAfterDelayStep)
|
||||
assistAfterDelayStep.Init(attachedController, this);
|
||||
|
||||
if (watchedEventPerformedStep)
|
||||
watchedEventPerformedStep.Init(attachedController, this);
|
||||
|
||||
ResetTimer();
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
void ResetTimer()
|
||||
{
|
||||
timer = 0;
|
||||
timedOut = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepSwitch
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
isActive = true;
|
||||
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
// Set mode to waiting for event
|
||||
activeMode = Mode.WaitingForEvent;
|
||||
|
||||
// Continue immediately if action we're watching for was previously performed?
|
||||
if (skipIfPreviouslyTriggered)
|
||||
{
|
||||
if (watchForEvent == WatchForEvent.OnCommandComplete && watchOnCommandComplete)
|
||||
{
|
||||
if (watchOnCommandComplete.completed)
|
||||
{
|
||||
activeStepOption = watchedEventPerformedStep;
|
||||
PlaySetStepOption();
|
||||
}
|
||||
}
|
||||
else if (watchForEvent == WatchForEvent.OnExternalTrigger && watchTrigger)
|
||||
{
|
||||
waitingForExternalTrigger = true;
|
||||
|
||||
if (watchTrigger.triggered)
|
||||
{
|
||||
activeStepOption = watchedEventPerformedStep;
|
||||
PlaySetStepOption();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PlaySetStepOption()
|
||||
{
|
||||
// Play the selected option, unless it's null (in which case just play next step in timeline)
|
||||
if (activeStepOption)
|
||||
{
|
||||
activeMode = Mode.PlayingChildStep;
|
||||
activeStepOption.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("The option selected by the step switch was null, so just proceeding to next step in timeline.");
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
// If step switch isn't active, is paused, has already timed out, or isn't currently waiting for an event to happen (i.e. may be playing a child step already), then don't handle timer
|
||||
if (!isActive || isPaused || timedOut || activeMode != Mode.WaitingForEvent)
|
||||
return;
|
||||
|
||||
// Add to timer
|
||||
timer += Time.deltaTime;
|
||||
|
||||
// Time out once we've reached a set target seconds
|
||||
if (timer > waitForSeconds)
|
||||
timedOut = true;
|
||||
|
||||
if (timedOut)
|
||||
{
|
||||
activeStepOption = assistAfterDelayStep;
|
||||
|
||||
PlaySetStepOption();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for delay / action to be performed
|
||||
if (watchForEvent == WatchForEvent.OnCommandComplete && watchOnCommandComplete)
|
||||
{
|
||||
if (watchOnCommandComplete.completed)
|
||||
{
|
||||
activeStepOption = watchedEventPerformedStep;
|
||||
|
||||
PlaySetStepOption();
|
||||
}
|
||||
}
|
||||
else if (watchForEvent == WatchForEvent.OnExternalTrigger && watchTrigger)
|
||||
{
|
||||
if (watchTrigger.triggered)
|
||||
{
|
||||
activeStepOption = watchedEventPerformedStep;
|
||||
|
||||
PlaySetStepOption();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 06537e8bbb6340099bfdcf33ffabb298
|
||||
timeCreated: 1659446099
|
|
@ -0,0 +1,479 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Plays through timeline steps in order, in turn triggering commands attached to each step
|
||||
/// </summary>
|
||||
public class StepTimeline : MonoBehaviour
|
||||
{
|
||||
#region Inspector exposed vars
|
||||
[Title("Timeline")]
|
||||
[SerializeField] bool playOnStart;
|
||||
|
||||
[Title("Events")]
|
||||
[SerializeField] UnityEvent onTimelinePlayEvent;
|
||||
[SerializeField] UnityEvent onTimelineCompleteEvent;
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
List<Step> steps = new List<Step>();
|
||||
|
||||
List<StepCmd> activePausableCommands = new List<StepCmd>();
|
||||
|
||||
int activeStepIndex;
|
||||
|
||||
bool init;
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
public const float RepeatOnResumeDelay = 30.0f;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public Step activeStep { get; private set; }
|
||||
public bool activeStepIsGroup { get; private set; }
|
||||
public bool activeStepHasVariants { get; private set; }
|
||||
|
||||
public bool forwarding { get; private set; }
|
||||
public bool isPaused { get; private set; }
|
||||
public bool isPlaying { get; private set; }
|
||||
public bool isComplete { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Public subscribable events
|
||||
public event Action onStepContinued;
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity default Start function, called the first time the script is activated. Used here to check if timeline should auto-play or just be initialised ready for play
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
// If playOnStart then start playing first step in timeline, otherwise just initialise everything ready for play, and wait for Play to be called externally
|
||||
if (playOnStart)
|
||||
Play();
|
||||
else
|
||||
Init();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise timeline ready for play
|
||||
/// </summary>
|
||||
void Init()
|
||||
{
|
||||
// Avoid duplicated initialisation
|
||||
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 initialise 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 the timeline
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
if (!init)
|
||||
Init();
|
||||
|
||||
// Don't try and start playing the timeline if it's already playing
|
||||
if (isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying = true;
|
||||
isComplete = false;
|
||||
|
||||
// Invoke timeline play event
|
||||
onTimelinePlayEvent?.Invoke();
|
||||
|
||||
StepTimelineManager.activeStepTimeline = this;
|
||||
|
||||
// Set initial step index to zero by default
|
||||
activeStepIndex = 0;
|
||||
|
||||
// Play the first step
|
||||
SetActiveStep(activeStepIndex);
|
||||
activeStep.Play();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play the timeline from a set step
|
||||
/// </summary>
|
||||
public void Play(Step _playFromStep, bool _skipToIfPlaying = true)
|
||||
{
|
||||
if (!init)
|
||||
Init();
|
||||
|
||||
// Check if timeline is already playing, and if we need to take any action first
|
||||
if (isPlaying)
|
||||
{
|
||||
if (!_skipToIfPlaying)
|
||||
{
|
||||
// Don't try and start playing the timeline if it's already playing
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, before we play from this step, end the current step, if one is active
|
||||
if (activeStep)
|
||||
activeStep.ForceEnd();
|
||||
|
||||
// TODO: Possibly don't allow if step requested is earlier than the active step?
|
||||
}
|
||||
}
|
||||
|
||||
isPlaying = true;
|
||||
isComplete = false;
|
||||
|
||||
StepTimelineManager.activeStepTimeline = this;
|
||||
|
||||
// Invoke timeline play event
|
||||
onTimelinePlayEvent?.Invoke();
|
||||
|
||||
// Play the requested step
|
||||
GoToStepAndPlay(_playFromStep);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play the next step in timeline. Called by Continue function on Step, which should ensure the previously active step is tidied up and set inactive before playing asking the timeline to play the next one
|
||||
/// </summary>
|
||||
public void PlayNextStep()
|
||||
{
|
||||
// If the controller is playing force the step to end as the timeline has been resumed and has priority on the level
|
||||
if (StepTimelineManager.activeController)
|
||||
{
|
||||
StepTimelineManager.activeController.ForceEnd();
|
||||
}
|
||||
|
||||
activeStepIndex++;
|
||||
|
||||
// Check we've not reached the end of the timeline, before playing the next step (if there is one)
|
||||
if (activeStepIndex >= steps.Count)
|
||||
{
|
||||
TimelineCompleted();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetActiveStep(activeStepIndex);
|
||||
activeStep.Play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal only function to set activeStep and associated vars
|
||||
/// </summary>
|
||||
/// <param name="_newActiveStepIndex">New active step index on the main timeline</param>
|
||||
void SetActiveStep(int _newActiveStepIndex)
|
||||
{
|
||||
activeStep = steps[_newActiveStepIndex];
|
||||
activeStepIsGroup = (activeStep is StepGroup);
|
||||
activeStepHasVariants = (activeStep is StepSwitch);
|
||||
}
|
||||
|
||||
|
||||
public void StepContinuedEvent()
|
||||
{
|
||||
// Invoke step continued event
|
||||
onStepContinued?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play a specific step which may not be the "next" one (may mean replaying an already played step or jumping to a future one), and update activeStepIndex to the desired one
|
||||
/// </summary>
|
||||
/// <param name="_step">Reference to the Step the timeline should now play</param>
|
||||
public void GoToStepAndPlay(Step _step)
|
||||
{
|
||||
// Make absolutely sure previously active step has been set inactive before setting the new active one and playing
|
||||
if (activeStep && activeStep.isActive)
|
||||
activeStep.ForceEnd();
|
||||
|
||||
// Make sure requested step isn't a child of a step switch - only step switches should play a sub-option
|
||||
if (_step.parentStepSwitch)
|
||||
{
|
||||
Debug.LogError("Can't 'GoTo' a step which is a child of a step switch. Use the step switch to switch between options, or move the step outwith the switch!");
|
||||
return;
|
||||
}
|
||||
|
||||
// If requested step has a parent step group, we want to activate the step group first, then jump to it
|
||||
if (_step.parentStepGroup)
|
||||
{
|
||||
// Tell the direct parent step group to jump to the step and play it (will handle any other intermediary step groups between the timeline root and the step there)
|
||||
_step.parentStepGroup.GoToStepAndPlay(_step);
|
||||
|
||||
// GoToStepAndPlay will in turn recurse upwards and find the highest level group before the root timeline, and call back into Timeline via BLAAA to set it active
|
||||
}
|
||||
else // Otherwise this is just a normal step with no parent step group, play it directly
|
||||
{
|
||||
// Set the index to the newly requested step
|
||||
activeStepIndex = steps.IndexOf(_step);
|
||||
// Set new active step, then play it
|
||||
SetActiveStep(activeStepIndex);
|
||||
activeStep.Play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called via a child step group when a deeper child step group has been activated via GoToStep
|
||||
/// </summary>
|
||||
public void SetSpecifiedHighestStepGroupActive(StepGroup _group)
|
||||
{
|
||||
// Set the index in the timeline to that of new step's highest parent group
|
||||
activeStepIndex = steps.IndexOf(_group);
|
||||
SetActiveStep(activeStepIndex);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the timeline is completed
|
||||
/// </summary>
|
||||
public void TimelineCompleted()
|
||||
{
|
||||
isComplete = true;
|
||||
isPlaying = false;
|
||||
onTimelineCompleteEvent?.Invoke();
|
||||
|
||||
// If GameTimeline thinks this is the currently active timeline, set null now timeline is completed
|
||||
if (StepTimelineManager.activeStepTimeline == this)
|
||||
StepTimelineManager.activeStepTimeline = 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 Timeline")]
|
||||
[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*";
|
||||
}
|
||||
|
||||
|
||||
[Button("Add Step Looper")]
|
||||
private void Editor_AddStepLooper()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
||||
GameObject stepLooperGo = new GameObject();
|
||||
stepLooperGo.transform.parent = transform;
|
||||
|
||||
// Add StepLoop component to this new GameObject
|
||||
StepLooper step = stepLooperGo.AddComponent<StepLooper>();
|
||||
|
||||
// Rename object
|
||||
stepLooperGo.gameObject.name = "#" + (step.transform.GetSiblingIndex() + 1).ToString("00") + " - LOOP - *ADD STEP NAME*";
|
||||
}
|
||||
|
||||
|
||||
[Button("Add GoTo Step")]
|
||||
private void Editor_AddGoToStep()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
||||
GameObject gotoStepGo = new GameObject();
|
||||
gotoStepGo.transform.parent = transform;
|
||||
|
||||
// Add GoToStep component to this new GameObject
|
||||
GoToStep step = gotoStepGo.AddComponent<GoToStep>();
|
||||
|
||||
// Rename object
|
||||
gotoStepGo.gameObject.name = "#" + (step.transform.GetSiblingIndex() + 1).ToString("00") + " - GOTO - *ADD STEP NAME*";
|
||||
}
|
||||
|
||||
|
||||
[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
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bbf728ffa6714547ba853641784e5b7f
|
||||
timeCreated: 1647378107
|
|
@ -0,0 +1,66 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
public class StepTimelineManager : MonoBehaviour
|
||||
{
|
||||
public static StepTimeline activeStepTimeline;
|
||||
public static StepController activeController;
|
||||
|
||||
public static Step lastPlayedStep;
|
||||
|
||||
static Step pausedOnStep;
|
||||
|
||||
|
||||
public static void ClearState()
|
||||
{
|
||||
activeStepTimeline = null;
|
||||
activeController = null;
|
||||
lastPlayedStep = null;
|
||||
pausedOnStep = null;
|
||||
}
|
||||
|
||||
|
||||
public static bool WasLastStepOnTimeline()
|
||||
{
|
||||
if (lastPlayedStep && lastPlayedStep.attachedStepTimeline)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Pause all commands on the currently active timeline or controller (if one is active)
|
||||
/// </summary>
|
||||
public static void PauseCommands()
|
||||
{
|
||||
if (lastPlayedStep && lastPlayedStep.isActive)
|
||||
{
|
||||
if (lastPlayedStep.attachedStepTimeline)
|
||||
lastPlayedStep.attachedStepTimeline.Pause();
|
||||
else if (lastPlayedStep.attachedController)
|
||||
lastPlayedStep.attachedController.Pause();
|
||||
|
||||
pausedOnStep = lastPlayedStep;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resume all commands on the currently active timeline or controller (if one was active when game was paused)
|
||||
/// </summary>
|
||||
public static void ResumeCommands(float _elapsedTime)
|
||||
{
|
||||
// Resume
|
||||
if (pausedOnStep)
|
||||
{
|
||||
if (pausedOnStep.attachedStepTimeline)
|
||||
pausedOnStep.attachedStepTimeline.Resume(_elapsedTime);
|
||||
else if (pausedOnStep.attachedController)
|
||||
pausedOnStep.attachedController.Resume(_elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c2d2b5b3411b479e8bdc69e012664290
|
||||
timeCreated: 1669563502
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9cdcf7fa370f4df383a1da6982849f37
|
||||
timeCreated: 1702505969
|
|
@ -0,0 +1,68 @@
|
|||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Step type which simply tells the timeline to immediately play another step (anywhere in the timeline). Should generally be paired with a step switch (with the GoToStep as a child option), in order to jump the timeline back or forward to a particular step based on a set condition
|
||||
/// </summary>
|
||||
public class GoToStep : Step
|
||||
{
|
||||
[Title("Next Step")]
|
||||
[SerializeField] Step stepToJumpTo;
|
||||
|
||||
[Tooltip("Passes true to _overrideIfAlreadyPlaying on StepController, making it force end the current step, then play this one.")]
|
||||
[SerializeField] bool forceStepPlayIfOnController;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this step, which here means telling the timeline to immediately play another step
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Complete this step by simply telling the timeline to play the set step
|
||||
/// </summary>
|
||||
public override void Continue()
|
||||
{
|
||||
Debug.Log(name + " - Completed");
|
||||
|
||||
isActive = false;
|
||||
wasCompleted = true;
|
||||
|
||||
// If no step to "go to" defined, just go to next step in timeline
|
||||
if (stepToJumpTo == null)
|
||||
{
|
||||
Debug.Log("stepToJumpTo was null on GoToStep, just proceeding to next step in the timeline.");
|
||||
base.Continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Instruct timeline to play the requested step
|
||||
if (attachedStepTimeline != null)
|
||||
{
|
||||
attachedStepTimeline.GoToStepAndPlay(stepToJumpTo);
|
||||
}
|
||||
else if (attachedController)
|
||||
{
|
||||
if (forceStepPlayIfOnController)
|
||||
attachedController.Play(stepToJumpTo, true);
|
||||
else
|
||||
attachedController.Play(stepToJumpTo);
|
||||
}
|
||||
else
|
||||
Debug.LogError("Tried to play a GoToStep without a Timeline or Controller attached, which isn't currently supported.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f5311fef721b4c1192ec1af3f02074e2
|
||||
timeCreated: 1650379654
|
|
@ -0,0 +1,666 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using TriInspector;
|
||||
using R0bbie.Timeline.Events;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual Step in the timeline, with associated commands attached to it. May also be inherited and command overridden by StepGroup or StepSwitch
|
||||
/// </summary>
|
||||
public class Step : MonoBehaviour, IGameEventListener, IPausable
|
||||
{
|
||||
public enum ContinueCondition
|
||||
{
|
||||
Immediate,
|
||||
Timed,
|
||||
OnCommandComplete,
|
||||
OnExternalTrigger,
|
||||
OnGameEventRaise
|
||||
}
|
||||
|
||||
[Title("Step")]
|
||||
|
||||
[SerializeField, Tooltip("Short descriptive name for this ")] public string shortName;
|
||||
|
||||
[Title("Continue Condition")]
|
||||
|
||||
//[HideIf("@this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep")]
|
||||
[ShowIf("Inspector_ShowContinueCondition")]
|
||||
[SerializeField] ContinueCondition continueCondition;
|
||||
|
||||
[ShowIf(nameof(continueCondition), ContinueCondition.Timed)]
|
||||
[SerializeField] float stepContinueDelay;
|
||||
|
||||
[ShowIf(nameof(continueCondition), ContinueCondition.OnCommandComplete)]
|
||||
[SerializeField] StepCmd continueOnCommandComplete;
|
||||
|
||||
[ShowIf(nameof(continueCondition), ContinueCondition.OnExternalTrigger)]
|
||||
[SerializeField] TimelineTrigger externalContinueTrigger;
|
||||
|
||||
[ShowIf("Inspector_HasContinueTrigger")]
|
||||
[SerializeField] bool continueImmediatelyIfPrevTriggered;
|
||||
|
||||
[ShowIf(nameof(continueCondition), ContinueCondition.OnGameEventRaise)]
|
||||
[SerializeField] GameEvent continueOnEventRaised;
|
||||
|
||||
|
||||
[Title("Time Limits")]
|
||||
|
||||
//[HideIf("@this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep || continueCondition == ContinueCondition.Immediate || continueCondition == ContinueCondition.Timed")]
|
||||
[ShowIf("Inspector_AllowTimeLimits")]
|
||||
[Tooltip("Force Step to not continue for a minimum time duration, even once continue condition met")]
|
||||
[SerializeField] float minimumTimeLimit;
|
||||
|
||||
//[HideIf("@this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep || continueCondition == ContinueCondition.Immediate || continueCondition == ContinueCondition.Timed")]
|
||||
[ShowIf("Inspector_AllowTimeLimits")]
|
||||
[Tooltip("Force Step to continue once this time limit is up")]
|
||||
[SerializeField] bool setMaxTimeLimit;
|
||||
|
||||
//[HideIf("@this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep || continueCondition == ContinueCondition.Immediate || continueCondition == ContinueCondition.Timed || !setMaxTimeLimit")]
|
||||
[ShowIf("Inspector_ShowMaxTimeLimit")]
|
||||
[Tooltip("Force Step to continue once this time limit is up")]
|
||||
[SerializeField] float maximumTimeLimit;
|
||||
|
||||
|
||||
[Title("Interrupt")]
|
||||
|
||||
[HideIf(nameof(continueCondition), ContinueCondition.Immediate)]
|
||||
[Tooltip("If this step can be interrupted by a step outside the timeline")]
|
||||
[SerializeField] bool canBeInterrupted;
|
||||
|
||||
[HideIf(nameof(continueCondition), ContinueCondition.Immediate)]
|
||||
[Tooltip("The step that should skip to if interrupted")]
|
||||
[SerializeField, ShowIf("canBeInterrupted")] Step skipToStep;
|
||||
|
||||
// Refs to associated objects
|
||||
public StepTimeline attachedStepTimeline { get; protected set; }
|
||||
public StepController attachedController { get; protected set; }
|
||||
public StepGroup parentStepGroup { get; protected set; }
|
||||
public StepSwitch parentStepSwitch { get; protected set; }
|
||||
|
||||
// Refs to all attached commands
|
||||
private List<StepCmd> commands = new List<StepCmd>();
|
||||
|
||||
bool timerActive;
|
||||
|
||||
float timeSinceStepStarted;
|
||||
bool minimumTimeMet;
|
||||
bool conditionCompleted;
|
||||
|
||||
// Step status vars
|
||||
public bool init { get; protected set; }
|
||||
public bool isActive { get; protected set; }
|
||||
public bool isPaused { get; protected set; }
|
||||
public bool wasSkipped { get; protected set; }
|
||||
public bool wasCompleted { get; protected set; }
|
||||
public bool fastForwarding { get; protected set; }
|
||||
public bool waitingForExternalTrigger { get; protected set; }
|
||||
public bool registeredToGameEvent { get; protected set; }
|
||||
|
||||
|
||||
// Properties
|
||||
public bool CanBeInterrupted { get => canBeInterrupted; }
|
||||
public Step SkipToStep { get => skipToStep; }
|
||||
|
||||
// Events
|
||||
public event Action onStepPlayed;
|
||||
public event Action onStepContinued;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Standard Init function, called by the parent timeline
|
||||
/// </summary>
|
||||
public virtual void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
if (init)
|
||||
return;
|
||||
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
|
||||
// Get refs to all commands attached to this step
|
||||
commands = GetComponents<StepCmd>().ToList();
|
||||
|
||||
InitializeContinueConditions();
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Step Init function called by a parent StepGroup
|
||||
/// </summary>
|
||||
public virtual void Init(StepTimeline _stepTimeline, StepGroup _stepGroup)
|
||||
{
|
||||
// Setup necessary refs parent StepGroup
|
||||
parentStepGroup = _stepGroup;
|
||||
|
||||
// Init base shared step functionality
|
||||
Init(_stepTimeline);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Step Init function called by a parent StepSwitch
|
||||
/// </summary>
|
||||
public virtual void Init(StepTimeline _stepTimeline, StepSwitch _stepSwitch)
|
||||
{
|
||||
// Setup necessary refs parent StepSwitch
|
||||
parentStepSwitch = _stepSwitch;
|
||||
|
||||
// Init base shared step functionality
|
||||
Init(_stepTimeline);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Override Init function, called by a parent StepController if this step isn't part of a level timeline
|
||||
/// </summary>
|
||||
public virtual void Init(StepController _controller)
|
||||
{
|
||||
if (init)
|
||||
return;
|
||||
|
||||
// Setup necessary refs such as parent timeline
|
||||
attachedController = _controller;
|
||||
|
||||
// Get refs to all commands attached to this step
|
||||
commands = GetComponents<StepCmd>().ToList();
|
||||
|
||||
InitializeContinueConditions();
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Step Init function called by a parent StepGroup, if a StepController is in control
|
||||
/// </summary>
|
||||
public virtual void Init(StepController _controller, StepGroup _stepGroup)
|
||||
{
|
||||
// Setup necessary refs parent StepGroup
|
||||
parentStepGroup = _stepGroup;
|
||||
|
||||
// Init base shared step functionality
|
||||
Init(_controller);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Step Init function called by a parent StepSwitch, if a StepController is in control
|
||||
/// </summary>
|
||||
public virtual void Init(StepController _controller, StepSwitch _stepSwitch)
|
||||
{
|
||||
// Setup necessary refs parent StepSwitch
|
||||
parentStepSwitch = _stepSwitch;
|
||||
|
||||
// Init base shared step functionality
|
||||
Init(_controller);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize any reference needed for the continue condition
|
||||
/// </summary>
|
||||
void InitializeContinueConditions()
|
||||
{
|
||||
// Only need to initialize the external trigger
|
||||
if (continueCondition == ContinueCondition.OnExternalTrigger)
|
||||
{
|
||||
// Assign this step to the trigger
|
||||
if (externalContinueTrigger != null)
|
||||
externalContinueTrigger.AddStep(this);
|
||||
else
|
||||
Debug.LogWarning("No external trigger assigned to step: " + name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update loop used for tracking timers
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
// If step not active return
|
||||
if (!init || !isActive || wasCompleted)
|
||||
return;
|
||||
|
||||
// If no timer active, return
|
||||
if (!timerActive)
|
||||
return;
|
||||
|
||||
// If game / step paused, return
|
||||
if (isPaused)
|
||||
return;
|
||||
|
||||
// Update timer
|
||||
timeSinceStepStarted += Time.deltaTime;
|
||||
|
||||
// Check timer against time limit (if Timed continue condition) or minimum time limit (if set)
|
||||
if (continueCondition == ContinueCondition.Timed)
|
||||
{
|
||||
if (timeSinceStepStarted > stepContinueDelay)
|
||||
TimedContinueGoalMet();
|
||||
}
|
||||
else if (minimumTimeLimit > 0)
|
||||
{
|
||||
if (timeSinceStepStarted > minimumTimeLimit)
|
||||
{
|
||||
minimumTimeMet = true;
|
||||
|
||||
// Keep it separated in case the condition completes later than the min time
|
||||
if(conditionCompleted)
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
else if (setMaxTimeLimit && maximumTimeLimit > minimumTimeLimit)
|
||||
{
|
||||
if (timeSinceStepStarted > maximumTimeLimit)
|
||||
{
|
||||
// Reached maximum time on step, auto-continue
|
||||
TimedContinueGoalMet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this step, triggering all commands attached to it
|
||||
/// </summary>
|
||||
public virtual void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
|
||||
StepTimelineManager.lastPlayedStep = this;
|
||||
|
||||
// Invoke event
|
||||
onStepPlayed?.Invoke();
|
||||
|
||||
// ACTIVATE ATTACHED COMMANDS (some command types can have unlimited instances on a step, others can only have one instance on a step)
|
||||
foreach (StepCmd command in commands)
|
||||
{
|
||||
command.Activate(this);
|
||||
}
|
||||
|
||||
// SETUP CONTINUE CONDITION TO LOOK OUT FOR
|
||||
switch (continueCondition)
|
||||
{
|
||||
case ContinueCondition.Immediate:
|
||||
// Continue immediately, and end this step
|
||||
Debug.Log(name + " - Continue Immediate");
|
||||
|
||||
Continue();
|
||||
break;
|
||||
|
||||
case ContinueCondition.Timed:
|
||||
// Continue after the defined time delay
|
||||
timerActive = true;
|
||||
break;
|
||||
|
||||
// Player input is also based on command complete, but we set the command from the accessibility settings of the game
|
||||
case ContinueCondition.OnCommandComplete:
|
||||
// If the step can be completed if the command is already completed then skip register the listener
|
||||
if (continueImmediatelyIfPrevTriggered && continueOnCommandComplete.completed)
|
||||
{
|
||||
Debug.Log(name + " - Command already completed");
|
||||
|
||||
Continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe to event when continueOnCommandComplete command completes, and call ContinueConditionCommandComplete
|
||||
continueOnCommandComplete.onCompleteCommand += ContinueConditionCommandComplete;
|
||||
break;
|
||||
|
||||
case ContinueCondition.OnExternalTrigger:
|
||||
if (externalContinueTrigger != null)
|
||||
{
|
||||
// Option to continue immediately if trigger was previously called (for example, if player performed a trigger action early)
|
||||
if (continueImmediatelyIfPrevTriggered && externalContinueTrigger.triggered)
|
||||
{
|
||||
Debug.Log(name + " - External trigger already triggered");
|
||||
|
||||
Continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we'll wait for the external trigger to tell the step to continue..
|
||||
waitingForExternalTrigger = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Step continue condition set to external trigger, but no trigger ref was set.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ContinueCondition.OnGameEventRaise:
|
||||
// Register listener
|
||||
registeredToGameEvent = true;
|
||||
continueOnEventRaised.RegisterListener(this);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if we need to start a timer for minimumTimeLimit
|
||||
if (continueCondition is ContinueCondition.OnCommandComplete or ContinueCondition.OnExternalTrigger or ContinueCondition.OnGameEventRaise)
|
||||
{
|
||||
if (minimumTimeLimit > 0 || (setMaxTimeLimit && maximumTimeLimit > minimumTimeLimit))
|
||||
timerActive = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called to Skip over this step, mark it done without triggering any attached commands, and move onto the next step
|
||||
/// </summary>
|
||||
public virtual void Skip()
|
||||
{
|
||||
wasSkipped = true;
|
||||
isActive = false;
|
||||
|
||||
Debug.Log(name + " - Skipped");
|
||||
|
||||
if (parentStepGroup)
|
||||
{
|
||||
if (canBeInterrupted)
|
||||
parentStepGroup.GoToStepAndPlay(skipToStep);
|
||||
else
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else if (parentStepSwitch)
|
||||
{
|
||||
parentStepSwitch.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline)
|
||||
{
|
||||
if (canBeInterrupted)
|
||||
attachedStepTimeline.GoToStepAndPlay(skipToStep);
|
||||
else
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
}
|
||||
else
|
||||
{
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called externally to force this step to end, without automatically continuing
|
||||
/// </summary>
|
||||
public virtual void ForceEnd()
|
||||
{
|
||||
Debug.Log(name + " - Forced End");
|
||||
|
||||
isActive = false;
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
command.DeactivateEarly();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called if continue condition is Timed and timer reaches goal. Will trigger the step to end and timeline to continue after a set time delay.
|
||||
/// </summary>
|
||||
void TimedContinueGoalMet()
|
||||
{
|
||||
timerActive = false;
|
||||
Debug.Log(name + " - Continue after delay");
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If continue condition is OnCommandComplete, this function will be called once the reference command has completed its function
|
||||
/// </summary>
|
||||
public void ContinueConditionCommandComplete()
|
||||
{
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
Debug.Log(name + " - Command Completed");
|
||||
|
||||
continueOnCommandComplete.onCompleteCommand -= ContinueConditionCommandComplete;
|
||||
|
||||
// Continue immediately, unless a minimum time limit was set that we've not met yet
|
||||
if (minimumTimeLimit > 0 && !minimumTimeMet)
|
||||
conditionCompleted = true;
|
||||
else
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If continue condition is OnExternalTrigger, this function will be called by the external trigger
|
||||
/// </summary>
|
||||
public virtual void TriggerContinue(TimelineTrigger _trigger)
|
||||
{
|
||||
if (!isActive || !waitingForExternalTrigger)
|
||||
return;
|
||||
|
||||
// Only accept continue command if it was received from the TimelineTrigger specified
|
||||
if (externalContinueTrigger != _trigger)
|
||||
return;
|
||||
|
||||
Debug.Log(name + " - Continue from trigger");
|
||||
|
||||
waitingForExternalTrigger = false;
|
||||
|
||||
// Continue immediately, unless a minimum time limit was set that we've not met yet
|
||||
if (minimumTimeLimit > 0 && !minimumTimeMet)
|
||||
conditionCompleted = true;
|
||||
else
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called if a GameEvent is raised that this step is registered to
|
||||
/// </summary>
|
||||
public void OnEventRaised()
|
||||
{
|
||||
|
||||
// Unregister from event
|
||||
continueOnEventRaised.UnregisterListener(this);
|
||||
registeredToGameEvent = false;
|
||||
|
||||
// If step is now inactive, then don't do anything
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
Debug.Log(name + " - Continue on event Raised");
|
||||
|
||||
// Continue condition met..
|
||||
|
||||
// Continue immediately, unless a minimum time limit was set that we've not met yet
|
||||
if (minimumTimeLimit > 0 && !minimumTimeMet)
|
||||
conditionCompleted = true;
|
||||
else
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to complete Step until minimum time limit has been met
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Coroutines are stopped when the object is disabled, blocking progress if going to the fold while running")]
|
||||
IEnumerator WaitForMinimumTimeLimit()
|
||||
{
|
||||
// Wait until minimum time limit met
|
||||
while (!minimumTimeMet)
|
||||
yield return null;
|
||||
|
||||
// Now continue
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End this step, and inform timeline to continue playing
|
||||
/// </summary>
|
||||
public virtual void Continue()
|
||||
{
|
||||
isActive = false;
|
||||
wasCompleted = true;
|
||||
|
||||
// Invoke step continued event
|
||||
onStepContinued?.Invoke();
|
||||
|
||||
if (attachedStepTimeline)
|
||||
attachedStepTimeline.StepContinuedEvent();
|
||||
|
||||
if (parentStepGroup)
|
||||
{
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else if (parentStepSwitch)
|
||||
{
|
||||
parentStepSwitch.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
else // Playing through a StepController
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used on game pause, pauses active timers etc
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
if (!isActive || wasCompleted)
|
||||
return;
|
||||
|
||||
// Set paused flag
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when game unpaused
|
||||
/// </summary>
|
||||
/// <param name="_elapsedTime">How long step was paused for</param>
|
||||
public void Resume(float _elapsedTime)
|
||||
{
|
||||
if (!isActive || wasCompleted || !isPaused)
|
||||
return;
|
||||
|
||||
// Unset paused flag
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#region Editor Inspector functions
|
||||
|
||||
public void UpdateStepName()
|
||||
{
|
||||
// Get corresponding string for the type of step this is
|
||||
string stepType = this switch
|
||||
{
|
||||
StepGroup => "GROUP",
|
||||
StepSwitch => "SWITCH",
|
||||
StepLooper => "LOOP",
|
||||
GoToStep => "GOTO",
|
||||
_ => "Step"
|
||||
};
|
||||
|
||||
string stepNameToDisplay = "*ADD STEP NAME*";
|
||||
if (!String.IsNullOrEmpty(shortName))
|
||||
stepNameToDisplay = shortName;
|
||||
|
||||
// Check if this step is a child of a step group or switch
|
||||
if (transform.parent.GetComponent<StepGroup>())
|
||||
{
|
||||
// Get step no. of parent step
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
|
||||
// Format = #XX.xx - Step name
|
||||
|
||||
if (stepType == "Step")
|
||||
name = "#" + parentStepNo.ToString("00") + "." + (transform.GetSiblingIndex() + 1).ToString("00") + " - " + stepNameToDisplay;
|
||||
else
|
||||
name = "#" + parentStepNo.ToString("00") + "." + (transform.GetSiblingIndex() + 1).ToString("00") + " - " + stepType + " - " + stepNameToDisplay;
|
||||
|
||||
return;
|
||||
}
|
||||
else if (transform.parent.GetComponent<StepSwitch>())
|
||||
{
|
||||
// Get step no. of parent step
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
|
||||
// Format = #XX.xx - Switch Option - Step name
|
||||
|
||||
if (stepType == "Step")
|
||||
name = "#" + parentStepNo.ToString("00") + "." + (transform.GetSiblingIndex() + 1).ToString("00") + " - Switch Option - " + stepNameToDisplay;
|
||||
else
|
||||
name = "#" + parentStepNo.ToString("00") + "." + (transform.GetSiblingIndex() + 1).ToString("00") + " - Switch Option - " + stepType + " - " + stepNameToDisplay;
|
||||
|
||||
return;
|
||||
}
|
||||
else if (transform.parent.GetComponent<StepLooper>())
|
||||
{
|
||||
Debug.LogWarning("A StepLooper should not have any child steps - these won't be played!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Rename (including string type in name)
|
||||
// Format = #XX - TYPE - Step name
|
||||
if (stepType == "Step")
|
||||
name = "#" + (transform.GetSiblingIndex() + 1).ToString("00") + " - " + stepNameToDisplay;
|
||||
else
|
||||
name = "#" + (transform.GetSiblingIndex() + 1).ToString("00") + " - " + stepType + " - " + stepNameToDisplay;
|
||||
}
|
||||
|
||||
|
||||
bool Inspector_ShowContinueCondition()
|
||||
{
|
||||
return !(this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep);
|
||||
}
|
||||
|
||||
|
||||
bool Inspector_HasContinueTrigger()
|
||||
{
|
||||
return (continueCondition is ContinueCondition.OnExternalTrigger or ContinueCondition.OnCommandComplete);
|
||||
}
|
||||
|
||||
|
||||
bool Inspector_AllowTimeLimits()
|
||||
{
|
||||
return !(this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep ||
|
||||
continueCondition == ContinueCondition.Immediate || continueCondition == ContinueCondition.Timed);
|
||||
}
|
||||
|
||||
|
||||
bool Inspector_ShowMaxTimeLimit()
|
||||
{
|
||||
return !(this is StepGroup || this is StepSwitch || this is StepLooper || this is GoToStep ||
|
||||
continueCondition == ContinueCondition.Immediate || continueCondition == ContinueCondition.Timed ||
|
||||
!setMaxTimeLimit);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c6f45d2c3c9d79459e201c582d8bb67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,353 @@
|
|||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Events;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
public class StepGroup : Step
|
||||
{
|
||||
|
||||
// Exposed Variables
|
||||
|
||||
[Title("Events")]
|
||||
public UnityEvent onGroupPlayEvent;
|
||||
public UnityEvent onGroupCompleteEvent;
|
||||
|
||||
|
||||
// Private Variables
|
||||
|
||||
List<Step> childSteps = new List<Step>();
|
||||
|
||||
int activeChildStepIndex;
|
||||
|
||||
|
||||
// Properties
|
||||
|
||||
public Step activeChildStep { get; private set; }
|
||||
|
||||
public bool activeChildStepIsSwitch { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepGroup, setting up required external refs, and getting refs to all child steps
|
||||
/// </summary>
|
||||
public override void Init(StepTimeline _stepTimeline)
|
||||
{
|
||||
// Setup necessary refs to parent timeline, and child steps (only 1 level deep to avoid getting children of StepSwitches etc)
|
||||
attachedStepTimeline = _stepTimeline;
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
if (t.GetComponent<Step>())
|
||||
{
|
||||
childSteps.Add(t.GetComponent<Step>());
|
||||
}
|
||||
}
|
||||
|
||||
// Init all child steps
|
||||
foreach (var step in childSteps)
|
||||
{
|
||||
step.Init(attachedStepTimeline, this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the StepGroup if StepController initialising
|
||||
/// </summary>
|
||||
public override void Init(StepController _controller)
|
||||
{
|
||||
// Setup necessary refs to parent Controller, and child steps (only 1 level deep to avoid getting children of StepSwitches etc)
|
||||
attachedController = _controller;
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
if (t.GetComponent<Step>())
|
||||
{
|
||||
childSteps.Add(t.GetComponent<Step>());
|
||||
}
|
||||
}
|
||||
|
||||
// Init all child steps
|
||||
foreach (var step in childSteps)
|
||||
{
|
||||
step.Init(attachedController, this);
|
||||
}
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this StepGroup. Starting with the first step in it.
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
// Set the index to start at the first step
|
||||
activeChildStepIndex = 0;
|
||||
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
wasCompleted = false;
|
||||
isActive = true;
|
||||
|
||||
onGroupPlayEvent?.Invoke();
|
||||
|
||||
activeChildStep = childSteps[activeChildStepIndex];
|
||||
activeChildStep.Play();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Continue and play the next step in the group, unless we're at the end of the group
|
||||
/// </summary>
|
||||
public override void Continue()
|
||||
{
|
||||
activeChildStepIndex++;
|
||||
|
||||
// Check we've not reached the end of the step group, before playing the next step (if there is one)
|
||||
if (activeChildStepIndex >= childSteps.Count)
|
||||
{
|
||||
GroupCompleted();
|
||||
}
|
||||
else
|
||||
{
|
||||
activeChildStep = childSteps[activeChildStepIndex];
|
||||
if (activeChildStep is StepSwitch)
|
||||
activeChildStepIsSwitch = true;
|
||||
|
||||
activeChildStep.Play();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skip this entire step group
|
||||
/// </summary>
|
||||
public override void Skip()
|
||||
{
|
||||
wasSkipped = true;
|
||||
isActive = false;
|
||||
|
||||
// TODO: Check if currently active child step needs tidied up before skipping?
|
||||
// TODO: Mark all steps within the step group not played before Skip was called as skipped also?
|
||||
|
||||
if (parentStepGroup)
|
||||
{
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else if (parentStepSwitch)
|
||||
{
|
||||
parentStepSwitch.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
else
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play a specific step which may not be the "next" one (may mean replaying an already played step or jumping to a future one), and update activeStepIndex to the desired one
|
||||
/// </summary>
|
||||
/// <param name="_step">Reference to the Step the timeline should now play, as part of the group</param>
|
||||
public void GoToStepAndPlay(Step _step)
|
||||
{
|
||||
// If group wasn't already active, we'll set it active now
|
||||
if (!isActive)
|
||||
{
|
||||
isActive = true;
|
||||
onGroupPlayEvent?.Invoke();
|
||||
}
|
||||
|
||||
// If requested step has parent step groups above this one, make sure their status is set to active. Otherwise we're at the highest group in the hierarchy, so make sure this is active in the timeline
|
||||
if (parentStepGroup)
|
||||
parentStepGroup.SetActiveOnGoToChild(this);
|
||||
else
|
||||
attachedStepTimeline.SetSpecifiedHighestStepGroupActive(this);
|
||||
|
||||
// Set the index to the newly requested step
|
||||
activeChildStepIndex = childSteps.IndexOf(_step);
|
||||
|
||||
// Set new active step, then play it
|
||||
activeChildStep = childSteps[activeChildStepIndex];
|
||||
activeChildStep.Play();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When a child step is played using GoToStep, if that step was nested within multiple step groups, this function may be called by a child step group to tell this step group that it's now active
|
||||
/// </summary>
|
||||
private void SetActiveOnGoToChild(StepGroup _childStepGroup)
|
||||
{
|
||||
// If group wasn't already active, we'll set it active now
|
||||
if (!isActive)
|
||||
{
|
||||
isActive = true;
|
||||
onGroupPlayEvent?.Invoke();
|
||||
}
|
||||
|
||||
// Set the index and active step on this group
|
||||
activeChildStepIndex = childSteps.IndexOf(_childStepGroup);
|
||||
activeChildStep = childSteps[activeChildStepIndex];
|
||||
|
||||
// Then set any other parent step groups active too (all the way up the hierarchy, till we make it to the timeline, then till timeline to mark highest level group as active)
|
||||
if (parentStepGroup)
|
||||
parentStepGroup.SetActiveOnGoToChild(this);
|
||||
else
|
||||
attachedStepTimeline.SetSpecifiedHighestStepGroupActive(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called externally to force this step to end, without automatically continuing
|
||||
/// </summary>
|
||||
public override void ForceEnd()
|
||||
{
|
||||
isActive = false;
|
||||
|
||||
if (activeChildStep)
|
||||
activeChildStep.ForceEnd();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the last step in the group has completed
|
||||
/// </summary>
|
||||
private void GroupCompleted()
|
||||
{
|
||||
if (wasCompleted)
|
||||
return;
|
||||
|
||||
wasCompleted = true;
|
||||
isActive = false;
|
||||
|
||||
// We are currently having just one depth child, so it's likely that parentStepGroup & parentStepSwitch here won't ever play on a GroupCompleted call
|
||||
|
||||
// Tell the timeline to play the next step (or step group, or controller)
|
||||
if (parentStepGroup)
|
||||
{
|
||||
parentStepGroup.Continue();
|
||||
}
|
||||
else if (parentStepSwitch)
|
||||
{
|
||||
parentStepSwitch.Continue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
else
|
||||
attachedController.StepCompleted();
|
||||
}
|
||||
|
||||
// Play the event at the end to make sure all the functionality and bools have been disabled
|
||||
onGroupCompleteEvent?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// ODIN INSPECTOR BUTTONS
|
||||
|
||||
[Title("Add to Group")]
|
||||
[Button("Add Step")]
|
||||
private void Editor_AddStep()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the StepGroup parent object
|
||||
GameObject stepGo = new GameObject();
|
||||
stepGo.transform.parent = transform;
|
||||
|
||||
// Add Step component to this new GameObject
|
||||
Step step = stepGo.AddComponent<Step>();
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
int childStepNo = step.transform.GetSiblingIndex() + 1;
|
||||
step.gameObject.name = "#" + parentStepNo + "." + childStepNo + " - *ADD STEP NAME*";
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Button("Add Step Switch")]
|
||||
private void Editor_AddStepSwitch()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the StepGroup 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 as child of StepGroup, now remember to add the specific StepSwitch type you want to it.");
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
int childStepNo = stepSwitchGo.transform.GetSiblingIndex() + 1;
|
||||
stepSwitchGo.gameObject.name = "#" + parentStepNo + "." + childStepNo + " - SWITCH - *ADD DESIRED SWITCH COMPONENT*";
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Button("Add Step Looper")]
|
||||
private void Editor_AddStepLooper()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
||||
GameObject stepLooperGo = new GameObject();
|
||||
stepLooperGo.transform.parent = transform;
|
||||
|
||||
// Add StepLoop component to this new GameObject
|
||||
StepLooper step = stepLooperGo.AddComponent<StepLooper>();
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
int childStepNo = step.transform.GetSiblingIndex() + 1;
|
||||
step.gameObject.name = "#" + parentStepNo + "." + childStepNo + " - LOOP - *ADD STEP NAME*";
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Button("Add GoTo Step")]
|
||||
private void Editor_AddGoToStep()
|
||||
{
|
||||
// Create new GameObject, and reparent it as a child of the Timeline parent object
|
||||
GameObject gotoStepGo = new GameObject();
|
||||
gotoStepGo.transform.parent = transform;
|
||||
|
||||
// Add GoToStep component to this new GameObject
|
||||
GoToStep step = gotoStepGo.AddComponent<GoToStep>();
|
||||
|
||||
// Rename object
|
||||
int parentStepNo = transform.parent.GetSiblingIndex() + 1;
|
||||
int childStepNo = step.transform.GetSiblingIndex() + 1;
|
||||
gotoStepGo.gameObject.name = "#" + parentStepNo + "." + childStepNo + " - GOTO - *ADD STEP NAME*";
|
||||
}
|
||||
|
||||
|
||||
[Title("Editor Functions")]
|
||||
[Button("Rename Child Steps")]
|
||||
private void Editor_RenameChildren()
|
||||
{
|
||||
RenameChildren();
|
||||
}
|
||||
|
||||
|
||||
// OTHER EDITOR HELPER FUNCTIONS
|
||||
public void RenameChildren()
|
||||
{
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
if (child.GetComponent<Step>())
|
||||
child.GetComponent<Step>().UpdateStepName();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2b7cdfaad11143a4ae986711f8923e15
|
||||
timeCreated: 1647378293
|
|
@ -0,0 +1,124 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using TriInspector;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple step type which loops back to a previous step, until a set condition is met
|
||||
/// </summary>
|
||||
public class StepLooper : Step
|
||||
{
|
||||
[Title("Loop Setup")]
|
||||
[SerializeField] Step loopBackToStep;
|
||||
|
||||
enum LoopCondition
|
||||
{
|
||||
NumberLoops,
|
||||
IfStepPlayed,
|
||||
IfCommandCompleted,
|
||||
IfTriggerTriggered
|
||||
}
|
||||
|
||||
[SerializeField] LoopCondition loopUntil;
|
||||
|
||||
[ShowIf(nameof(loopUntil), LoopCondition.NumberLoops)]
|
||||
[SerializeField] int loopForIterations;
|
||||
|
||||
[ShowIf(nameof(loopUntil), LoopCondition.IfStepPlayed)]
|
||||
[SerializeField] Step stepToCheck;
|
||||
|
||||
[ShowIf(nameof(loopUntil), LoopCondition.IfCommandCompleted)]
|
||||
[SerializeField] StepCmd commandToCheck;
|
||||
|
||||
[ShowIf(nameof(loopUntil), LoopCondition.IfTriggerTriggered)]
|
||||
[SerializeField] TimelineTrigger triggerToCheck;
|
||||
|
||||
|
||||
int loopCounter = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Play this step, which here means triggering the loop back to a previous step
|
||||
/// </summary>
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Log(name + " - Started");
|
||||
|
||||
isActive = true;
|
||||
|
||||
Continue();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Continue (either looping back to a previous step, or playing ahead to next in timeline if loop has been broken)
|
||||
/// </summary>
|
||||
public override void Continue()
|
||||
{
|
||||
bool breakConditionReached = false;
|
||||
|
||||
// Increase loop counter
|
||||
loopCounter++;
|
||||
|
||||
// First check if this loop has reached its break condition
|
||||
switch (loopUntil)
|
||||
{
|
||||
case LoopCondition.NumberLoops:
|
||||
if (loopCounter >= loopForIterations)
|
||||
breakConditionReached = true;
|
||||
|
||||
break;
|
||||
|
||||
case LoopCondition.IfStepPlayed:
|
||||
if (stepToCheck.wasCompleted)
|
||||
breakConditionReached = true;
|
||||
|
||||
break;
|
||||
|
||||
case LoopCondition.IfCommandCompleted:
|
||||
if (commandToCheck.completed)
|
||||
breakConditionReached = true;
|
||||
|
||||
break;
|
||||
|
||||
case LoopCondition.IfTriggerTriggered:
|
||||
if (triggerToCheck.triggered)
|
||||
breakConditionReached = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (breakConditionReached)
|
||||
{
|
||||
Debug.Log(name + " - Completed");
|
||||
|
||||
// Play the next step after this looper in the timeline
|
||||
if (parentStepGroup != null)
|
||||
parentStepGroup.Continue();
|
||||
else
|
||||
attachedStepTimeline.PlayNextStep();
|
||||
|
||||
isActive = false;
|
||||
wasCompleted = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, loop again!
|
||||
|
||||
// Play the step we're looping back to
|
||||
if (attachedStepTimeline != null)
|
||||
attachedStepTimeline.GoToStepAndPlay(loopBackToStep);
|
||||
else
|
||||
Debug.LogError("Tried to play a StepLooper with a StepController, which isn't currently compatible. Must be a child of a Timeline to function.");
|
||||
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bd2a69d7f1f4b6eba035850624bc3b5
|
||||
timeCreated: 1648763328
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 24897a938f0446319ef84b7c2243edfa
|
||||
timeCreated: 1647379464
|
|
@ -0,0 +1,48 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace R0bbie.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic TimelineTrigger which simply calls TriggerContinue on all attached steps
|
||||
/// </summary>
|
||||
public class TimelineTrigger : MonoBehaviour
|
||||
{
|
||||
[SerializeField] string description;
|
||||
|
||||
// Private variables
|
||||
|
||||
List<Step> triggerContinueOnSteps = new List<Step>();
|
||||
|
||||
// Properties
|
||||
|
||||
public bool triggered { get; protected set; }
|
||||
|
||||
|
||||
|
||||
public void AddStep(Step _stepToTrigger)
|
||||
{
|
||||
// Only add step to the list if it's not already in it..
|
||||
if (triggerContinueOnSteps.Contains(_stepToTrigger))
|
||||
return;
|
||||
|
||||
// Add step to list
|
||||
triggerContinueOnSteps.Add(_stepToTrigger);
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
public void Trigger()
|
||||
{
|
||||
triggered = true;
|
||||
|
||||
// Add message to trigger continue on all attached steps
|
||||
foreach (Step triggerContinueOnStep in triggerContinueOnSteps)
|
||||
{
|
||||
triggerContinueOnStep.TriggerContinue(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f2d0bd9c3ad049298ed7c9d7f5b6de22
|
||||
timeCreated: 1647378303
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "com.r0bbie.timeline",
|
||||
"displayName": "Step Timeline",
|
||||
"version": "0.2.0",
|
||||
"unity": "2021.3",
|
||||
"author": {
|
||||
"name" : "Robbie Cargill",
|
||||
"email" : "contact@robbiecargill.com",
|
||||
"url" : "https://r0bbie.com"
|
||||
},
|
||||
"description": "Timeline utility, allows a serious of steps to be setup in a scene, each with attached commands.",
|
||||
"keywords": [
|
||||
"timeline",
|
||||
"steps",
|
||||
"step",
|
||||
"commands"
|
||||
],
|
||||
"dependencies": {
|
||||
"com.codewriter.triinspector": "1.13.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c4b8106b70c204846bcdbd9b66ed06ce
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue