239 lines
9.9 KiB
C#
239 lines
9.9 KiB
C#
using Fusion;
|
|
using UnityEngine;
|
|
using Fusion.Addons.SimpleKCC;
|
|
|
|
namespace Projectiles
|
|
{
|
|
/// <summary>
|
|
/// Third-person PlayerAgent for Photon Fusion + SimpleKCC.
|
|
/// - Camera-relative movement
|
|
/// - Character rotation toward move/aim direction
|
|
/// - Shoulder camera with wall-collision
|
|
/// </summary>
|
|
[DefaultExecutionOrder(-5)]
|
|
[RequireComponent(typeof(Weapons), typeof(Health), typeof(SimpleKCC))]
|
|
public class PlayerAgent : ContextBehaviour
|
|
{
|
|
// --- Networked / components ------------------------------------------------
|
|
|
|
[Networked] public Player Owner { get; set; }
|
|
public Weapons Weapons { get; private set; }
|
|
public Health Health { get; private set; }
|
|
public SimpleKCC KCC { get; private set; }
|
|
public PlayerInput Input { get; private set; }
|
|
|
|
public bool InputBlocked => Health.IsAlive == false;
|
|
|
|
// --- Camera rig (reuse your existing references) ---------------------------
|
|
|
|
[SerializeField] private Transform _cameraPivot; // yaw/pitch anchor above character
|
|
[SerializeField] private Transform _cameraHandle; // optional visual/weapon anchor
|
|
|
|
[Header("Third-Person Camera")]
|
|
[SerializeField] private float _minPitch = -40f;
|
|
[SerializeField] private float _maxPitch = 70f;
|
|
[SerializeField] private float _yawRotateSpeed = 720f; // deg/sec when snapping to aim
|
|
[SerializeField] private float _cameraDistance = 3.5f; // default boom length
|
|
[SerializeField] private float _cameraDistanceMin = 1.0f;
|
|
[SerializeField] private float _cameraDistanceMax = 5.0f;
|
|
[SerializeField] private Vector3 _shoulderOffset = new Vector3(0.4f, 1.6f, 0f);
|
|
[SerializeField] private LayerMask _cameraCollisionMask = ~0; // collide with world (exclude Player layer)
|
|
[SerializeField] private float _cameraCollisionRadius = 0.15f;
|
|
[SerializeField] private float _cameraDistanceSmoothing = 10f; // smooth camera distance changes
|
|
|
|
// --- Movement --------------------------------------------------------------
|
|
|
|
[Header("Movement")]
|
|
[SerializeField] private float _moveSpeed = 6f;
|
|
[SerializeField] public float _upGravity = 15f;
|
|
[SerializeField] public float _downGravity = 25f;
|
|
[SerializeField] private float _jumpImpulse = 6f;
|
|
[SerializeField] public float _groundAcceleration = 55f;
|
|
[SerializeField] public float _groundDeceleration = 25f;
|
|
[SerializeField] public float _airAcceleration = 25f;
|
|
[SerializeField] public float _airDeceleration = 1.3f;
|
|
private Animator _anim;
|
|
private NetworkMecanimAnimator _netAnim;
|
|
private static readonly int HashMoveX = Animator.StringToHash("InputX"); // or "MoveX"
|
|
private static readonly int HashMoveY = Animator.StringToHash("InputY"); // or "MoveY"
|
|
[Header("Character Facing")]
|
|
[SerializeField] private float _turnSpeed = 720f; // deg/sec turning toward desired facing
|
|
[SerializeField] private bool _faceMoveDirection = true; // if false, faces camera forward (aiming)
|
|
|
|
[Networked] private Vector3 _moveVelocity { get; set; }
|
|
|
|
// Smooth camera distance for collision response
|
|
private float _currentCameraDistance;
|
|
|
|
// Local cache of most recent FUN (FixedUpdateNetwork) look to build on in Render/LateUpdate
|
|
private Vector2 _lastFUNLookRotation; // (pitch,yaw) from KCC
|
|
|
|
// --- Fusion lifecycle ------------------------------------------------------
|
|
|
|
public override void Spawned()
|
|
{
|
|
name = Object.InputAuthority.ToString();
|
|
|
|
// Only local player needs networked props replicated (bandwidth saver)
|
|
ReplicateToAll(false);
|
|
ReplicateTo(Object.InputAuthority, true);
|
|
_anim = GetComponentInChildren<Animator>(); // your mesh/visual Animator
|
|
_netAnim = GetComponent<NetworkMecanimAnimator>();
|
|
}
|
|
|
|
public override void Despawned(NetworkRunner runner, bool hasState)
|
|
{
|
|
Owner = null;
|
|
}
|
|
|
|
public override void FixedUpdateNetwork()
|
|
{
|
|
if (Owner != null && Health.IsAlive)
|
|
{
|
|
ProcessMovementInput();
|
|
}
|
|
|
|
// REMOVED: Don't set camera pivot here - it conflicts with LateUpdate
|
|
// Store the look rotation for LateUpdate to use
|
|
_lastFUNLookRotation = KCC.GetLookRotation();
|
|
}
|
|
|
|
// --- Unity lifecycle -------------------------------------------------------
|
|
|
|
private void Awake()
|
|
{
|
|
KCC = GetComponent<SimpleKCC>();
|
|
Weapons = GetComponent<Weapons>();
|
|
Health = GetComponent<Health>();
|
|
Input = GetComponent<PlayerInput>();
|
|
|
|
// Initialize camera distance
|
|
_currentCameraDistance = _cameraDistance;
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
// 1) UPDATE LOOK ROTATION (LOCAL ONLY): accumulate mouse/controller deltas
|
|
if (HasInputAuthority && Owner != null && Health.IsAlive)
|
|
{
|
|
// Accumulate pitch/yaw with clamp on pitch only; yaw is free
|
|
var look = _lastFUNLookRotation + Input.AccumulatedLook;
|
|
look.x = Mathf.Clamp(look.x, _minPitch, _maxPitch);
|
|
KCC.SetLookRotation(look, _minPitch, _maxPitch);
|
|
}
|
|
|
|
// 2) Apply current pitch to pivot for everyone (affects remote weapon aim too)
|
|
var currentLook = KCC.GetLookRotation(true, true); // Get both pitch and yaw
|
|
_cameraPivot.localRotation = Quaternion.Euler(currentLook.x, 0f, 0f);
|
|
|
|
// 3) Position the actual camera (LOCAL ONLY) with shoulder offset + collision
|
|
if (HasInputAuthority)
|
|
{
|
|
var cam = Context.Camera.transform;
|
|
|
|
// Desired camera target position (pivot in character space + shoulder offset)
|
|
Vector3 pivotWorld = _cameraPivot.TransformPoint(_shoulderOffset);
|
|
|
|
// Camera wants to sit behind pivot along its backward vector
|
|
Quaternion yawWorld = Quaternion.Euler(0f, currentLook.y, 0f);
|
|
Vector3 desiredOffset = yawWorld * new Vector3(0f, 0f, -_cameraDistance);
|
|
Vector3 desiredPos = pivotWorld + desiredOffset;
|
|
|
|
// Simple collision test: spherecast from pivot toward desired
|
|
Vector3 dir = (desiredPos - pivotWorld);
|
|
float len = dir.magnitude;
|
|
Vector3 finalPos = desiredPos;
|
|
float targetDistance = _cameraDistance;
|
|
|
|
if (len > 0.0001f)
|
|
{
|
|
dir /= len;
|
|
if (Physics.SphereCast(pivotWorld, _cameraCollisionRadius, dir, out RaycastHit hit, len, _cameraCollisionMask, QueryTriggerInteraction.Ignore))
|
|
{
|
|
targetDistance = Mathf.Max(hit.distance - 0.05f, _cameraDistanceMin);
|
|
}
|
|
}
|
|
|
|
// Smooth the camera distance changes for less jarring collision response
|
|
_currentCameraDistance = Mathf.Lerp(_currentCameraDistance, targetDistance, _cameraDistanceSmoothing * Time.deltaTime);
|
|
|
|
// Clamp within min/max boom and apply smoothed distance
|
|
_currentCameraDistance = Mathf.Clamp(_currentCameraDistance, _cameraDistanceMin, _cameraDistanceMax);
|
|
finalPos = pivotWorld + dir * _currentCameraDistance;
|
|
|
|
cam.position = finalPos;
|
|
cam.rotation = Quaternion.Euler(currentLook.x, currentLook.y, 0f);
|
|
|
|
// Keep your optional handle aligned to the camera (muzzle/crosshair visuals)
|
|
if (_cameraHandle != null)
|
|
{
|
|
_cameraHandle.position = cam.position;
|
|
_cameraHandle.rotation = cam.rotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Input → Movement / Facing --------------------------------------------
|
|
|
|
private void ProcessMovementInput()
|
|
{
|
|
if (!GetInput(out GameplayInput input))
|
|
return;
|
|
|
|
// 1) Update look from input deltas (affects yaw for camera-relative movement)
|
|
KCC.AddLookRotation(input.LookRotationDelta, _minPitch, _maxPitch);
|
|
|
|
// 2) Gravity preference: faster fall feels better
|
|
KCC.SetGravity(KCC.RealVelocity.y >= 0f ? _upGravity : _downGravity);
|
|
|
|
// 3) CAMERA-RELATIVE MOVE on XZ plane
|
|
// Build forward/right from current yaw only (ignore pitch)
|
|
float yaw = KCC.GetLookRotation(false, true).y;
|
|
Quaternion yawRot = Quaternion.Euler(0f, yaw, 0f);
|
|
Vector3 camForward = yawRot * Vector3.forward;
|
|
Vector3 camRight = yawRot * Vector3.right;
|
|
Vector3 inputDir = (camForward * input.MoveDirection.y + camRight * input.MoveDirection.x);
|
|
inputDir.y = 0f;
|
|
if (inputDir.sqrMagnitude > 1f) inputDir.Normalize();
|
|
float moveX = Vector3.Dot(inputDir, camRight);
|
|
float moveY = Vector3.Dot(inputDir, camForward);
|
|
|
|
const float damp = 0.1f; // seconds to smooth toward target
|
|
_anim.SetFloat(HashMoveX, moveX, damp, Time.deltaTime);
|
|
_anim.SetFloat(HashMoveY, moveY, damp, Time.deltaTime);
|
|
|
|
// Desired velocity and acceleration/deceleration
|
|
Vector3 desiredVel = inputDir * _moveSpeed;
|
|
float accel = 1f;
|
|
if (desiredVel == Vector3.zero)
|
|
accel = KCC.IsGrounded ? _groundDeceleration : _airDeceleration;
|
|
else
|
|
accel = KCC.IsGrounded ? _groundAcceleration : _airAcceleration;
|
|
|
|
_moveVelocity = Vector3.Lerp(_moveVelocity, desiredVel, accel * Runner.DeltaTime);
|
|
|
|
// 4) Character facing: toward move dir OR toward camera forward (aim)
|
|
Vector3 faceDir = _faceMoveDirection
|
|
? (_moveVelocity.sqrMagnitude > 0.01f ? _moveVelocity : (yawRot * Vector3.forward))
|
|
: (yawRot * Vector3.forward);
|
|
faceDir.y = 0f;
|
|
if (faceDir.sqrMagnitude > 0.0001f)
|
|
{
|
|
Quaternion target = Quaternion.LookRotation(faceDir, Vector3.up);
|
|
// Use KCC for body yaw rotation, but only set the Y (yaw) component
|
|
// This keeps the body rotation networked via KCC
|
|
Vector2 currentLook = KCC.GetLookRotation();
|
|
float targetYaw = target.eulerAngles.y;
|
|
float newYaw = Mathf.MoveTowardsAngle(currentLook.y, targetYaw, _turnSpeed * Runner.DeltaTime);
|
|
KCC.SetLookRotation(new Vector2(currentLook.x, newYaw), _minPitch, _maxPitch);
|
|
}
|
|
|
|
// 5) Jump
|
|
float jumpImpulse = (input.Buttons.WasPressed(Input.PreviousButtons, EInputButton.Jump) && KCC.IsGrounded)
|
|
? _jumpImpulse : 0f;
|
|
|
|
// 6) Move
|
|
KCC.Move(_moveVelocity, jumpImpulse);
|
|
}
|
|
}
|
|
} |