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