Files
2026-07-22 20:57:34 +02:00

204 lines
8.0 KiB
C#

using UnityEngine;
using UnityEngine.Events;
namespace OctoCore.Components
{
public class OctoPlayerDriver : MonoBehaviour
{
[System.Serializable]
public class State
{
public bool ForcePlayerToFollowTarget;
[Range(0, 1)] public float MoveInfluence;
[Range(0, 1)] public float LookInfluence;
[Range(0, 180)] public float YawRange = 180;
[Range(0, 90)] public float PitchRange = 90;
}
[System.Serializable]
public class References
{
public Transform Target;
public Transform LookAt;
}
[System.Serializable]
public class Settings
{
[Min(.1f)] public float DriveStrength = 1;
[Range(0, 1)] public float LookAtStrength = 1;
[Range(.1f, 3)] public float LookAtSmooth = 1;
[Range(0, 1)] public float LookAtPoint = .5f;
public AnimationCurve DriveCurve = AnimationCurve.EaseInOut(0, 0, 1 , 1);
public AnimationCurve LookCurve = AnimationCurve.EaseInOut(0, 0, 1 , 1);
}
[System.Serializable]
public class Events
{
public UnityEvent OnDrive;
public UnityEvent OnFullInfluence;
public UnityEvent OnRelease;
}
[SerializeField, OctoReadOnly] private State _state;
[SerializeField] private References _references;
[SerializeField] private Settings _settings;
[SerializeField] private Events _events;
public References _References => _references;
public State _State => _state;
private bool _fullInfluenceReached;
void OnDrawGizmos()
{
#if UNITY_EDITOR
UnityEditor.Handles.color = Color.cyan;
const float size = .3f;
if (_references.Target)
{
UnityEditor.Handles.matrix = _references.Target.localToWorldMatrix;
UnityEditor.Handles.DrawLine(Vector3.forward * size, Vector3.back * size);
UnityEditor.Handles.DrawLine(Vector3.left * size, Vector3.right * size);
}
if (_references.LookAt)
{
UnityEditor.Handles.matrix = _references.LookAt.localToWorldMatrix;
UnityEditor.Handles.DrawWireCube(Vector3.zero, Vector3.one * size * .75f);
}
#endif
}
void Start()
{
}
void Update()
{
}
public void DrivePlayer()
{
_fullInfluenceReached = false;
_state.YawRange = 180;
_state.PitchRange = 90;
OctoPlayer.Instance.Drive(this);
_events.OnDrive?.Invoke();
}
public void ForcePlayerToFollowTarget(bool force)
{
_state.ForcePlayerToFollowTarget = force;
}
public void ReleasePlayer()
{
OctoPlayer.Instance.ReleaseDrive();
_state.YawRange = 180;
_state.PitchRange = 90;
_events.OnRelease?.Invoke();
}
// Blends the player's move input towards the Target.
// If no Target is assigned, this is a no-op and the original move input passes through untouched.
public void ApplyMoveInfluence(Vector3 inMove, float delta, out Vector3 outMove)
{
if (!OctoPlayer.Instance || !OctoPlayer.Instance._FirstPerson.Camera || !_references.Target)
{
outMove = inMove;
return;
}
_state.MoveInfluence = Mathf.Clamp01(_state.MoveInfluence + delta * (_state.ForcePlayerToFollowTarget ? 1 : inMove.magnitude) * _settings.DriveStrength);
float moveInfluence = _settings.DriveCurve.Evaluate(_state.MoveInfluence);
if (!_fullInfluenceReached && _state.MoveInfluence >= 1)
{
_events.OnFullInfluence?.Invoke();
_fullInfluenceReached = true;
}
Vector3 offset = _references.Target.position - OctoPlayer.Instance.transform.position;
Vector3 offset2D = offset;
offset2D.y = 0;
float driveAtten = Mathf.InverseLerp(1, .1f, offset2D.magnitude);
Vector3 driveMove = offset;
driveMove.y = 0;
driveMove = driveMove.normalized * (_state.ForcePlayerToFollowTarget ? 1 : inMove.magnitude);
driveMove = Vector3.Lerp(driveMove, Vector3.zero, driveAtten);
outMove = Vector3.Lerp(inMove, driveMove, moveInfluence);
}
// Blends the player's look direction towards LookAt.
// Two modes, chosen automatically depending on whether a Target is assigned:
// - Target assigned: original behaviour. The look is CLAMPED to a cone around LookAt
// (YawRange/PitchRange), and that cone tightens as MoveInfluence builds up
// ("crescendo" style). Beyond the cone edge the player is hard-blocked.
// - Target NOT assigned: "look only" mode. No clamp at all - the player can always look anywhere.
// Instead, the look direction is continuously nudged (lerped) towards LookAt
// every frame, like a gentle constant pull/magnet rather than a wall.
public void ApplyLookInfluence(float inYaw, float inPitch, float deltaTime, out float outYaw, out float outPitch)
{
if (!OctoPlayer.Instance || !OctoPlayer.Instance._FirstPerson.Camera || !_references.LookAt)
{
outYaw = inYaw;
outPitch = inPitch;
return;
}
Vector3 lookAt = _references.LookAt.position - OctoPlayer.Instance._FirstPerson.Camera.transform.position;
lookAt = lookAt.normalized;
float lookAtYaw = Mathf.Atan2(lookAt.x, lookAt.z) * Mathf.Rad2Deg;
float lookAtPitch = Mathf.Asin(-lookAt.y) * Mathf.Rad2Deg;
if (_references.Target)
{
// --- Original behaviour: hard clamp cone, tied to how far MoveInfluence has progressed. ---
_state.LookInfluence = Mathf.InverseLerp(_settings.LookAtPoint, 1, _state.MoveInfluence) * _settings.LookAtStrength;
float lookInfluence = _settings.LookCurve.Evaluate(_state.LookInfluence);
// Smoothly interpolate the clamp cone size towards its target value.
float t = deltaTime * _settings.LookAtSmooth;
_state.YawRange = Mathf.Lerp(_state.YawRange, Mathf.Lerp(180, 0, lookInfluence), t);
_state.PitchRange = Mathf.Lerp(_state.PitchRange, Mathf.Lerp(90, 0, lookInfluence), t);
outYaw = lookAtYaw + Mathf.Clamp(Mathf.DeltaAngle(lookAtYaw, inYaw), -_state.YawRange, _state.YawRange);
outPitch = lookAtPitch + Mathf.Clamp(Mathf.DeltaAngle(lookAtPitch, inPitch), -_state.PitchRange, _state.PitchRange);
}
else
{
// --- Look-only mode: no clamp, no cone. The player stays fully free to look anywhere. ---
// Every frame, the look direction is nudged a little towards LookAt (constant pull),
// instead of ever being blocked by a hard limit.
_state.LookInfluence = _settings.LookAtStrength;
float lookInfluence = _settings.LookCurve.Evaluate(_state.LookInfluence);
// Keep the state fields at their "fully free" values since there is no clamp cone here.
_state.YawRange = 180;
_state.PitchRange = 90;
// Pull factor for this frame: how much of the remaining angle gap gets closed now.
// Higher LookAtStrength / LookAtSmooth = stronger pull, but it never becomes a hard limit.
float pull = Mathf.Clamp01(lookInfluence * deltaTime * _settings.LookAtSmooth);
outYaw = Mathf.LerpAngle(inYaw, lookAtYaw, pull);
outPitch = Mathf.LerpAngle(inPitch, lookAtPitch, pull);
}
}
}
}