// Copyright (c) Pixel Crushers. All rights reserved. using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace PixelCrushers.DialogueSystem { /// /// A static utility class for Sequencer. /// public static class SequencerTools { private static Dictionary registeredSubjects = new Dictionary(); private static bool hasHookedIntoSceneLoaded = false; #if UNITY_2019_3_OR_NEWER && UNITY_EDITOR [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void InitStaticVariables() { registeredSubjects = new Dictionary(); hasHookedIntoSceneLoaded = false; } #endif public static void HookIntoSceneLoaded() { if (!hasHookedIntoSceneLoaded) { hasHookedIntoSceneLoaded = true; SceneManager.sceneLoaded += OnSceneLoaded; } } private static void OnSceneLoaded(Scene arg0, LoadSceneMode arg1) { CleanNullSubjects(); } /// /// Registers a GameObject by name for faster lookup. /// public static void RegisterSubject(Transform subject) { if (subject == null) return; registeredSubjects[subject.name] = subject; HookIntoSceneLoaded(); } /// /// Unregisters a registered GameObject. /// public static void UnregisterSubject(Transform subject) { if (subject == null || !registeredSubjects.ContainsKey(subject.name)) return; registeredSubjects.Remove(subject.name); } /// /// Clears null entries from registeredSubjects. /// public static void CleanNullSubjects() { registeredSubjects.RemoveAll(x => x == null); } /// /// Sequencer commands usually specify a subject to which the command applies (e.g., where to /// aim the camera). This utility function that returns the specified subject. /// /// /// The transform of the specified subject, or null if the specifier names a game /// object that isn't in the scene. /// /// /// "speaker", "listener", or the name of a game object in the scene. /// /// /// Speaker. /// /// /// Listener. /// /// /// Default subject. /// public static Transform GetSubject(string specifier, Transform speaker, Transform listener, Transform defaultSubject = null) { if (string.IsNullOrEmpty(specifier)) { return defaultSubject ?? speaker; } else if (string.Compare(specifier, SequencerKeywords.Speaker, System.StringComparison.OrdinalIgnoreCase) == 0) { return speaker; } else if (string.Compare(specifier, SequencerKeywords.Listener, System.StringComparison.OrdinalIgnoreCase) == 0) { return listener; } else if (string.Compare(specifier, SequencerKeywords.SpeakerPortrait, System.StringComparison.OrdinalIgnoreCase) == 0) { return GetPortraitImage(speaker); } else if (string.Compare(specifier, SequencerKeywords.ListenerPortrait, System.StringComparison.OrdinalIgnoreCase) == 0) { return GetPortraitImage(listener); } else if (specifier.StartsWith(SequencerKeywords.ActorPrefix)) { return CharacterInfo.GetRegisteredActorTransform(specifier.Substring(SequencerKeywords.ActorPrefix.Length)); } else { GameObject go = FindSpecifier(specifier); return (go != null) ? go.transform : defaultSubject; } } /// /// Returns true if specifier specifies a tag ('tag=foo'). /// public static bool SpecifierSpecifiesTag(string specifier) { return !string.IsNullOrEmpty(specifier) && specifier.StartsWith("tag=", System.StringComparison.OrdinalIgnoreCase); } /// /// Assumes specifier specifies a tag ('tag=foo'). Returns the tag. /// public static string GetSpecifiedTag(string specifier) { return specifier.Substring("tag=".Length); } /// /// Finds a game object with the specified name, returning any match in scene before /// trying to return any non-scene matches in the project. Checks GameObjects registered /// with the specifier as its actor name first. /// /// /// The specified game object. /// /// /// The name to search for. /// /// Only search active objects in the scene. public static GameObject FindSpecifier(string specifier, bool onlyActiveInScene = false) { if (string.IsNullOrEmpty(specifier)) return null; // Check for 'tag=' keyword: if (SpecifierSpecifiesTag(specifier)) { var tag = GetSpecifiedTag(specifier); var taggedGO = GameObject.FindGameObjectWithTag(tag); if (taggedGO != null) return taggedGO; var results = Tools.FindGameObjectsWithTagHard(tag); return (results.Length > 0) ? results[0] : null; } // Search registered actors: var t = CharacterInfo.GetRegisteredActorTransform(specifier); if (t != null) return t.gameObject; // Check registered subjects: if (registeredSubjects.TryGetValue(specifier, out t) && t != null) return t.gameObject; // Search for active objects in scene: var match = GameObject.Find(specifier); if (match != null) return match; if (onlyActiveInScene) return null; // Search for all objects in scene, including inactive as long as it's a child of an active object: match = Tools.GameObjectHardFind(specifier); if (match != null) return match; // Search for all objects, including loaded-but-not-instantiated (i.e., prefabs): foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[]) { if (string.Compare(specifier, go.name, System.StringComparison.OrdinalIgnoreCase) == 0) { return go; } } return null; } /// /// The transform of the Portrait Image GameObject, or null if not found. /// Must be using Standard Dialogue UI. Not for use with simultaneous conversations. /// public static Transform GetPortraitImage(Transform subject) { if (DialogueManager.standardDialogueUI == null) return null; if (subject == null) return null; var subtitleControls = DialogueManager.standardDialogueUI.conversationUIElements.standardSubtitleControls; DialogueActor dialogueActor; StandardUISubtitlePanel panel = null; if (DialogueManager.isConversationActive && DialogueManager.currentConversationState != null) { var subtitle = DialogueManager.currentConversationState.subtitle; if (subtitle.speakerInfo != null && subtitle.speakerInfo.transform == subject) { panel = subtitleControls.GetPanel(subtitle, out dialogueActor); } } if (panel == null) { StandardUISubtitlePanel defaultPanel = subtitleControls.defaultNPCPanel; dialogueActor = DialogueActor.GetDialogueActorComponent(subject); if (dialogueActor != null) { var actor = DialogueManager.masterDatabase.GetActor(dialogueActor.actor); if (actor != null) defaultPanel = actor.IsPlayer ? subtitleControls.defaultPCPanel : subtitleControls.defaultNPCPanel; } panel = subtitleControls.GetActorTransformPanel(subject, defaultPanel, out dialogueActor); } return (panel != null && panel.portraitImage != null) ? panel.portraitImage.transform : null; } /// /// Gets the default camera angle for a subject. /// /// The default camera angle. If the subject doesn't have a DefaultCameraAngle /// component, returns "Closeup". /// Subject. public static string GetDefaultCameraAngle(Transform subject) { var defaultCameraAngle = (subject != null) ? subject.GetComponentInChildren() : null; return (defaultCameraAngle != null) ? defaultCameraAngle.cameraAngle : "Closeup"; } /// /// Gets parameters[i]. /// /// /// parameters[i], or the specified default value if i is out of range. /// /// /// An array of parameters. /// /// /// The index into parameters[] /// /// /// The default value to return if i is out of range. /// public static string GetParameter(string[] parameters, int i, string defaultValue = null) { return ((parameters != null) && (i < parameters.Length)) ? parameters[i] : defaultValue; } /// /// Gets parameters[i] as the specified type. Culture invariant (i.e., floats use '.' for /// decimal point). /// /// /// parameters[i] as type T, or the specified default value if i is out of range /// or parameters[i] can't be converted to type T. /// /// /// An array of parameters. /// /// /// The index into parameters[] /// /// /// The default value to return if i is out of range or the parameter can't be converted /// to type T. /// /// /// The type to convert the parameter to. /// /// /// // Get parameters[1] as a float, defaulting to 5f: /// float duration = GetParameterAs(parameters, 1, 5f); /// public static T GetParameterAs(string[] parameters, int i, T defaultValue) { try { return ((parameters != null) && (i < parameters.Length)) ? (T)System.Convert.ChangeType(parameters[i], typeof(T), System.Globalization.CultureInfo.InvariantCulture) : defaultValue; } catch (System.Exception) { return defaultValue; } } /// /// Gets the i-th parameter as a float. /// /// /// The parameter as float, or defaultValue if out of range. /// /// /// The array of parameters. /// /// /// The zero-based index of the parameter. /// /// /// The default value to use if the parameter doesn't exist or isn't valid for the type. /// public static float GetParameterAsFloat(string[] parameters, int i, float defaultValue = 0) { return GetParameterAs(parameters, i, defaultValue); } /// /// Gets the i-th parameter as an int. /// /// /// The parameter as an int, or defaultValue if out of range. /// /// /// The array of parameters. /// /// /// The zero-based index of the parameter. /// /// /// The default value to use if the parameter doesn't exist or isn't valid for the type. /// public static int GetParameterAsInt(string[] parameters, int i, int defaultValue = 0) { return GetParameterAs(parameters, i, defaultValue); } /// /// Gets the i-th parameter as a bool. /// /// /// The parameter as bool, or defaultValue if out of range. /// /// /// The array of parameters. /// /// /// The zero-based index of the parameter. /// /// /// The default value to use if the parameter doesn't exist or isn't valid for the type. /// public static bool GetParameterAsBool(string[] parameters, int i, bool defaultValue = false) { return GetParameterAs(parameters, i, defaultValue); } /// /// Gets the audio source on a subject, using the Dialogue Manager as the subject if the /// specified subject is null. If no audio source exists on the subject, this /// method adds one. /// /// The audio source. /// Subject. public static AudioSource GetAudioSource(Transform subject) { GameObject go = (subject != null) ? subject.gameObject : DialogueManager.instance.gameObject; AudioSource audio = go.GetComponentInChildren(); if (audio == null) { audio = go.AddComponent(); audio.playOnAwake = false; audio.loop = false; } return audio; } /// /// Checks if a Lua variable "Mute" is true. /// /// true if audio is muted; otherwise, false. public static bool IsAudioMuted() { return DialogueLua.GetVariable("Mute").asBool; } } }