using System; using UnityEngine; using Framework.Services; using Framework.State; namespace Framework.Actors { /// Lets the player to control a character's head via the mouse [RequireComponent(typeof(HeadPoser))] public class AiHeadHeadController : ScriptComponent, IHeadController { /// Fired to pass on head rotation that was clamped /// /// This is not used by the AI head controller. If the AI can't look at /// something (without turning its body), it won't. This is debatable, /// but turning an AI's body would introduce a lot of complexity into /// the steering code. /// event Action IHeadController.ExcessHeadRotation { add { } remove { } } /// How fast the actor's head will rotate on the yaw axis public float YawDegreesPerSecond = 120.0f; /// How fast the actor's head will rotate on the pitch axis public float PitchDegreesPerSecond = 120.0f; /// Transform the actor's head will be looking at public Transform TrackedTransform; /// Called when the component has been added to a game object protected override void Awake() { base.Awake(); // Look up the head poser by which the mesh's head bones are controlled this.headPoser = GetComponent(); if(this.headPoser == null) { Debug.LogError( "AI head controller assigned to '" + gameObject.name + "' found " + "no head poser component and will not work." ); } // Use the control framework, if present, to disable AI head control // whenever a human player takes control of the actor this.controllable = GetComponentInParent(); } // Do AI head movement in FixedUpdate(), too? // PRO: If visibility checks (sneak game) are needed, this is fps independent // CONTRA: Lots of pointless processing if only visuals are used #if false /// Called once per physics frame to step the simulation protected void FixedUpdate() { // If the control framework isn't being used, assume the AI head controller // is on the actor because it's purely an AI actor. if(this.controllable != null) { // Otherwise, if a player is controlling the actor, do not do AI head movements. if(this.controllable.ControllingPlayerIndex >= 0) { return; } } // TODO: Head movement logic here } #endif /// Called once per physics frame protected virtual void LateUpdate() { // If the control framework isn't being used, assume the AI head controller // is on the actor because it's purely an AI actor. if(this.controllable != null) { // Otherwise, if a player is controlling the actor, do not do AI head movements. if(this.controllable.ControllingPlayerIndex >= 0) { return; } } // TODO: Interpolate towards target rotation rather than flicking towards it this.headPoser.Orient(getTargetTrackingOrientation()); } /// /// Determines the orientation the head target to look at the tracked transform /// /// The orientation for the head to look at the tracked transform private Quaternion getTargetTrackingOrientation() { if(this.TrackedTransform == null) { return this.headPoser.GetBaseOrientation(); } // Find the orientation the head has to point towards to look at the target Quaternion globalTargetOrientation; { Vector3 forward = Vector3.Normalize( this.TrackedTransform.position - (this.headPoser.Head.position + this.headPoser.EyeOffset) ); globalTargetOrientation = Quaternion.LookRotation( forward, this.headPoser.GetBaseOrientation() * Vector3.up ); } return globalTargetOrientation; } #if false // Trick from Unity's MouseLook script. Extendable to X + Y axes? Quaternion ClampRotationAroundXAxis(Quaternion q) { q.x /= q.w; q.y /= q.w; q.z /= q.w; q.w = 1.0f; float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x); angleX = Mathf.Clamp(angleX, MinimumX, MaximumX); q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX); return q; } #endif /// Transform to which the virtual reality camera is parented Transform IHeadController.VirtualRealityCameraRoot { get { return null; } // We're not a virtual reality head controller } /// Base transform in which the player controls the look direction Quaternion IHeadController.HeadBaseOrientation { get { if(this.headPoser == null) { return Quaternion.identity; } else { return this.headPoser.GetBaseOrientation(); } } } /// Where the player is looking within the head's coordinate space /// /// Append this rotation to the to /// get the direction the player is looking at in the global coordinate frame. /// Quaternion IHeadController.LocalHeadOrientation { get { return this.lookOrientation; } set { this.lookOrientation = value; } } /// Whether this look handler is part of the active input profile /// /// It is not unusual for actors to have multiple head controllers. For example, /// there might be a mouse look head controller and a virtual reality head controller /// that are switched between depending on the input profile the game runs with. /// This property allows the actor controller to figure out which one to talk to. /// bool IHeadController.IsActive { get { return (this.controllable.ControllingPlayerIndex == -1); } } /// Tracks, if present, whether the player is controlling this actor private IControllable controllable; /// Head poser used to rotate the head bones private HeadPoser headPoser; /// Direction the head is currently pointing at private Quaternion lookOrientation; } } // namespace Framework.Actors