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