204 lines
8.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
} |