Skip to content

Instantly share code, notes, and snippets.

@bsimser
Last active February 20, 2026 14:10
Show Gist options
  • Select an option

  • Save bsimser/471dad7e26f317b776ccb1015c5b81d0 to your computer and use it in GitHub Desktop.

Select an option

Save bsimser/471dad7e26f317b776ccb1015c5b81d0 to your computer and use it in GitHub Desktop.
///////////////////////////////////////////////////////////////////////////////
//
// This code is licensed under MIT license.
//
// Copyright © 2026 Bil Simser, https://www.simstools.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Playables;
using Unity.Cinemachine;
using Newtonsoft.Json.Linq;
using UHFPS.Tools;
namespace UHFPS.Runtime
{
public class CutsceneTrigger : MonoBehaviour, IInteractStart, ISaveable
{
public enum TriggerTypeEnum { Trigger, Interact, Event }
public enum CutsceneTypeEnum { CameraCutscene, PlayerCutscene }
public TriggerTypeEnum TriggerType;
public CutsceneTypeEnum CutsceneType;
public PlayableDirector Cutscene;
public CutscenePlayer CutscenePlayer;
public CinemachineCamera CutsceneCamera;
public float CutsceneFadeSpeed;
public CinemachineBlendDefinition BlendDefinition;
[Tooltip("This is the asset that contains custom settings for blends between specific virtual cameras in your scene.")]
public CinemachineBlenderSettings CustomBlendAsset;
[Tooltip("Wait for the dialogue to finish before starting the cutscene.")]
public bool WaitForDialogue = true;
[Tooltip("Wait for the camera to blend into cutscene camera before starting the cutscene.")]
public bool WaitForBlendIn = true;
[Tooltip("The time offset at which the cutscene starts during the camera blend.")]
[Range(0f, 1f)] public float BlendInOffset = 1f;
[Tooltip("The time at which the cutscene camera blends in to player camera.")]
public float BlendOutTime = 1f;
public Transform CutEndTransform;
public float CutFadeInSpeed = 3f;
public float CutFadeOutSpeed = 3f;
public bool DrawCutEndGizmos;
[Tooltip("Enable this to allow the cutscene to be played multiple times.")]
public bool IsRepeatable = false;
public UnityEvent OnCutsceneStart;
public UnityEvent OnCutsceneEnd;
private DialogueSystem dialogueSystem;
private CutsceneModule cutscene;
private bool isPlayed;
private void Awake()
{
cutscene = GameManager.Module<CutsceneModule>();
//dialogueSystem = DialogueSystem.Instance;
}
private void Start()
{
// rebuild playable graph to ensure seamless transition
if (Cutscene != null) Cutscene.RebuildGraph();
}
private void OnTriggerEnter(Collider other)
{
if (TriggerType != TriggerTypeEnum.Trigger)
return;
if (other.CompareTag("Player"))
TriggerCutscene();
}
public void InteractStart()
{
if (TriggerType != TriggerTypeEnum.Interact)
return;
TriggerCutscene();
}
public void TriggerCutscene()
{
if (Cutscene == null || (!IsRepeatable && isPlayed) || (WaitForDialogue && dialogueSystem.IsPlaying))
return;
cutscene.PlayCutscene(this);
OnCutsceneStart?.Invoke();
isPlayed = true;
}
private void OnDrawGizmos()
{
if (!DrawCutEndGizmos || BlendDefinition.Style != CinemachineBlendDefinition.Styles.Cut || CutEndTransform == null || !PlayerPresenceManager.HasReference)
return;
CharacterController controller = PlayerPresenceManager.Instance.StateMachine.PlayerCollider;
float offset = 0.6f;
float height = (controller.height + offset) / 2f;
float radius = controller.radius;
Vector3 origin = CutEndTransform.position + Vector3.up * (offset / 2f);
Vector3 p2 = origin + Vector3.up * height;
Vector3 p1 = origin;
Gizmos.color = Color.red;
GizmosE.DrawWireCapsule(p1, p2, radius);
Gizmos.color = Color.green;
GizmosE.DrawGizmosArrow(CutEndTransform.position, CutEndTransform.forward * 0.5f);
}
public StorableCollection OnSave()
{
return new StorableCollection()
{
{ nameof(isPlayed), isPlayed }
};
}
public void OnLoad(JToken data)
{
isPlayed = (bool)data[nameof(isPlayed)];
}
}
}
///////////////////////////////////////////////////////////////////////////////
//
// This code is licensed under MIT license.
//
// Copyright © 2026 Bil Simser, https://www.simstools.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using UnityEditor;
using UHFPS.Runtime;
using ThunderWire.Editors;
using Unity.Cinemachine;
namespace UHFPS.Editors
{
[CustomEditor(typeof(CutsceneTrigger))]
public class CutsceneTriggerEditor : InspectorEditor<CutsceneTrigger>
{
public override void OnInspectorGUI()
{
EditorDrawing.DrawInspectorHeader(new GUIContent("Cutscene Trigger"), Target);
EditorGUILayout.Space();
CutsceneTrigger.CutsceneTypeEnum cutsceneType = (CutsceneTrigger.CutsceneTypeEnum)Properties["CutsceneType"].enumValueIndex;
serializedObject.Update();
{
EditorGUILayout.BeginVertical(GUI.skin.box);
Properties.Draw("TriggerType");
Properties.Draw("CutsceneType");
Properties.Draw("Cutscene");
// Draw the new IsRepeatable bool
Properties.Draw("IsRepeatable");
EditorGUILayout.EndVertical();
if (cutsceneType == CutsceneTrigger.CutsceneTypeEnum.CameraCutscene)
{
EditorGUILayout.Space();
using (new EditorDrawing.BorderBoxScope())
{
Properties.Draw("WaitForDialogue");
}
EditorGUILayout.Space();
using (new EditorDrawing.BorderBoxScope())
{
EditorGUILayout.LabelField("Blend Settings", EditorStyles.miniBoldLabel);
Properties.Draw("CutsceneCamera");
Properties.Draw("CutsceneFadeSpeed");
}
}
else
{
EditorGUILayout.Space();
using (new EditorDrawing.BorderBoxScope())
{
Properties.Draw("CutscenePlayer");
Properties.Draw("WaitForDialogue");
}
EditorGUILayout.Space();
SerializedProperty blendDefinition = Properties["BlendDefinition"];
SerializedProperty style = blendDefinition.FindPropertyRelative("Style");
SerializedProperty time = blendDefinition.FindPropertyRelative("Time");
SerializedProperty curve = blendDefinition.FindPropertyRelative("CustomCurve");
CinemachineBlendDefinition.Styles blendStyle = (CinemachineBlendDefinition.Styles)style.enumValueIndex;
using (new EditorDrawing.BorderBoxScope())
{
EditorGUILayout.LabelField("Blend Definition", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(style);
EditorGUILayout.PropertyField(time);
if (blendStyle == CinemachineBlendDefinition.Styles.Custom)
EditorGUILayout.PropertyField(curve);
EditorGUILayout.Space();
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.LabelField("Custom Blending", EditorStyles.miniBoldLabel);
Properties.Draw("CustomBlendAsset");
EditorGUILayout.EndVertical();
}
EditorGUILayout.Space();
using (new EditorDrawing.BorderBoxScope())
{
EditorGUILayout.LabelField("Blend Settings", EditorStyles.miniBoldLabel);
if (blendStyle != CinemachineBlendDefinition.Styles.Cut)
{
Properties.Draw("WaitForBlendIn");
Properties.Draw("BlendInOffset");
Properties.Draw("BlendOutTime");
}
else
{
Properties.Draw("CutEndTransform");
Properties.Draw("CutFadeInSpeed");
Properties.Draw("CutFadeOutSpeed");
Properties.Draw("DrawCutEndGizmos");
}
}
}
EditorGUILayout.Space();
if (EditorDrawing.BeginFoldoutBorderLayout(Properties["OnCutsceneStart"], new GUIContent("Events")))
{
Properties.Draw("OnCutsceneStart");
Properties.Draw("OnCutsceneEnd");
EditorDrawing.EndBorderHeaderLayout();
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment