using System; using UnityEngine; using Framework.Services; namespace Framework.Actors { /// Combines multiple head controllers to a single point of access public class HeadControllerCombiner : ScriptComponent, IHeadController { /// Fired to pass on head rotation that was clamped public event Action ExcessHeadRotation; /// Head controllers the combiner will combine /// /// If this is null, the list of head controllers will be queried /// from the component hosting the /// at runtime. /// public IHeadController[] CombinedHeadControllers; /// Head controller the combiner is currently forwarding to public IHeadController ActiveHeadController { get { if(this.activeHeadControllerIndex == -1) { return null; } else { return this.CombinedHeadControllers[this.activeHeadControllerIndex]; } } } /// Called once when the component is loaded into a game object protected override void Awake() { base.Awake(); this.onExcessHeadRotationDelegate = new Action(OnExcessHeadRotation); // Create a list of head controllers present in the hierarchy excluding this one. { IHeadController[] headControllers = GetComponentsInChildren(); int headControllerCount = headControllers.Length; // We make the assumption that 'this' is always present in the list returned // by GetComponentsInChildren(), to the point were we'd crash if it wasn't. this.CombinedHeadControllers = new IHeadController[headControllerCount - 1]; // Copy all head controllers that are not 'this' into 'CombinedHeadControllers' int targetIndex = 0; for(int index = 0; index < headControllerCount; ++index) { if(!ReferenceEquals(this, headControllers[index])) { this.CombinedHeadControllers[targetIndex] = headControllers[index]; ++targetIndex; } } } this.activeHeadControllerIndex = -1; } /// Handles clamped-off head rotation from the current controller /// Head rotation the controller has clamped off protected virtual void OnExcessHeadRotation(Quaternion excessRotation) { if(this.ExcessHeadRotation != null) { this.ExcessHeadRotation(excessRotation); } } /// Called when the component is removed from its game object protected virtual void OnDestroy() { IHeadController activeHeadController = ActiveHeadController; if(activeHeadController != null) { activeHeadController.ExcessHeadRotation -= this.onExcessHeadRotationDelegate; } } /// Called once per visual update (before a rendered frame) protected virtual void Update() { // If there is no current active head controller, look for one that became active if(this.activeHeadControllerIndex == -1) { this.activeHeadControllerIndex = findActiveHeadController(); if(this.activeHeadControllerIndex != -1) { switchHeadController( null, this.CombinedHeadControllers[this.activeHeadControllerIndex] ); } } else { // Otherwise, check if the active one is still active bool isActive = ( this.CombinedHeadControllers[this.activeHeadControllerIndex].IsActive ); if(!isActive) { int previousIndex = this.activeHeadControllerIndex; this.activeHeadControllerIndex = findActiveHeadController(); if(this.activeHeadControllerIndex == -1) { switchHeadController(this.CombinedHeadControllers[previousIndex], null); } else { switchHeadController( this.CombinedHeadControllers[previousIndex], this.CombinedHeadControllers[this.activeHeadControllerIndex] ); } } } } /// Switches delegation to another head controller /// /// Head controller the component delegated to previously /// /// /// Head controller the component will delegate to from now on /// private void switchHeadController( IHeadController previousController, IHeadController currentController ) { #if UNITY_EDITOR DebugLogControllerChange(previousController, currentController); #endif // If there was a previous head controller, unsubscribe from its events if(previousController != null) { previousController.ExcessHeadRotation -= this.onExcessHeadRotationDelegate; } // If a new head controller became active, subscribe to its events if(currentController != null) { currentController.ExcessHeadRotation += this.onExcessHeadRotationDelegate; } // If a different head controller become active, apply the previous // head orientation to the new controller if((previousController != null) && (currentController != null)) { currentController.SetGlobalLookDirection( previousController.GetGlobalLookDirection() ); } } #if UNITY_EDITOR /// Does debug logging when the active head controller is changed /// Head controller that was active previously /// Head controller that has now become active private void DebugLogControllerChange( IHeadController previousController, IHeadController currentController ) { // Figure out a descriptive name for the previous head controller string previousControllerName; if(previousController == null) { previousControllerName = ""; } else { previousControllerName = previousController.GetType().Name; } // Figure out a descriptive name for the new head controller string currentControllerName; if(currentController == null) { currentControllerName = ""; } else { currentControllerName = currentController.GetType().Name; } Debug.Log( gameObject.name + " switched head controller from '" + previousControllerName + "'" + " to '" + currentControllerName + "'" ); } #endif // UNITY_EDITOR /// Looks for the currently active head controller /// The index of the active head controller, -1 if none are active private int findActiveHeadController() { int headControllerCount = this.CombinedHeadControllers.Length; for(int index = 0; index < headControllerCount; ++index) { if(this.CombinedHeadControllers[index].IsActive) { return index; } } return -1; } /// Transform to which the virtual reality camera is parented Transform IHeadController.VirtualRealityCameraRoot { get { IHeadController activeHeadController = ActiveHeadController; if(activeHeadController == null) { return null; } else { return activeHeadController.VirtualRealityCameraRoot; } } } /// Base transform in which the player controls the look direction Quaternion IHeadController.HeadBaseOrientation { get { IHeadController activeHeadController = ActiveHeadController; if(activeHeadController == null) { return Quaternion.identity; } else { return activeHeadController.HeadBaseOrientation; } } } /// Where the player is looking within the head's coordinate space Quaternion IHeadController.LocalHeadOrientation { get { IHeadController activeHeadController = ActiveHeadController; if(activeHeadController == null) { return Quaternion.identity; } else { return activeHeadController.LocalHeadOrientation; } } set { IHeadController activeHeadController = ActiveHeadController; if(activeHeadController != null) { activeHeadController.LocalHeadOrientation = value; } } } /// Whether this look handler is part of the active input profile bool IHeadController.IsActive { get { return true; } // This one is always active } /// Index of the currently active head controller private int activeHeadControllerIndex; /// Delegate for the method private Action onExcessHeadRotationDelegate; } } // namespace Framework.Actors