diff --git a/.idea/.idea.RizzeBattleRoyale/.idea/vcs.xml b/.idea/.idea.RizzeBattleRoyale/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.RizzeBattleRoyale/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/Prefabs/Player/Agent/PlayerAgentChillGuy.prefab b/Assets/Prefabs/Player/Agent/PlayerAgentChillGuy.prefab
index be2aa6d..d0d1971 100644
--- a/Assets/Prefabs/Player/Agent/PlayerAgentChillGuy.prefab
+++ b/Assets/Prefabs/Player/Agent/PlayerAgentChillGuy.prefab
@@ -87,15 +87,27 @@ MonoBehaviour:
_Owner: {fileID: 0}
_cameraPivot: {fileID: 5401675310905547977}
_cameraHandle: {fileID: 2117859135794692440}
+ _minPitch: -40
+ _maxPitch: 70
+ _yawRotateSpeed: 120
+ _cameraDistance: 2.26
+ _cameraDistanceMin: 1
+ _cameraDistanceMax: 5
+ _shoulderOffset: {x: -1.19, y: 0.67, z: 0.27}
+ _cameraCollisionMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ _cameraCollisionRadius: 0.15
_moveSpeed: 6
_upGravity: -18
_downGravity: -25
- _maxCameraAngle: 75
_jumpImpulse: 6
_groundAcceleration: 55
_groundDeceleration: 25
_airAcceleration: 25
_airDeceleration: 1.3
+ _turnSpeed: 1
+ _faceMoveDirection: 1
--- !u!114 &8517683973242102361
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -366,7 +378,7 @@ Transform:
m_GameObject: {fileID: 1315163349561248969}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -0.9, y: 2.16, z: -2.06}
+ m_LocalPosition: {x: 0, y: 1.38, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
diff --git a/Assets/Scripts/Player/PlayerAgent.cs b/Assets/Scripts/Player/PlayerAgent.cs
index 6fe0ebb..26743f4 100644
--- a/Assets/Scripts/Player/PlayerAgent.cs
+++ b/Assets/Scripts/Player/PlayerAgent.cs
@@ -4,152 +4,210 @@ using Fusion.Addons.SimpleKCC;
namespace Projectiles
{
- ///
- /// Main script handling player agent. It provides access to common components and handles movement input processing and camera.
- ///
- [DefaultExecutionOrder(-5)]
- [RequireComponent(typeof(Weapons), typeof(Health), typeof(SimpleKCC))]
- public class PlayerAgent : ContextBehaviour
- {
- // PUBLIC MEMBERS
+ ///
+ /// Third-person PlayerAgent for Photon Fusion + SimpleKCC.
+ /// - Camera-relative movement
+ /// - Character rotation toward move/aim direction
+ /// - Shoulder camera with wall-collision
+ ///
+ [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; }
+ [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;
+ public bool InputBlocked => Health.IsAlive == false;
- // PRIVATE MEMBERS
+ // --- Camera rig (reuse your existing references) ---------------------------
- [SerializeField]
- private Transform _cameraPivot;
- [SerializeField]
- private Transform _cameraHandle;
+ [SerializeField] private Transform _cameraPivot; // yaw/pitch anchor above character
+ [SerializeField] private Transform _cameraHandle; // optional visual/weapon anchor
- [Header("Movement")]
- [SerializeField]
- private float _moveSpeed = 6f;
- [SerializeField]
- public float _upGravity = 15f;
- [SerializeField]
- public float _downGravity = 25f;
- [SerializeField]
- private float _maxCameraAngle = 75f;
- [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;
+ [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
+ [SerializeField] private float _cameraCollisionRadius = 0.15f;
- [Networked]
- private Vector3 _moveVelocity { get; set; }
+ // --- Movement --------------------------------------------------------------
- private Vector2 _lastFUNLookRotation;
+ [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;
- // NetworkBehaviour INTERFACE
+ [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)
- public override void Spawned()
- {
- name = Object.InputAuthority.ToString();
+ [Networked] private Vector3 _moveVelocity { get; set; }
- // Only local player needs networked properties (move velocity).
- // This saves network traffic by not synchronizing networked properties to other clients except local player.
- ReplicateToAll(false);
- ReplicateTo(Object.InputAuthority, true);
- }
+ // Local cache of most recent FUN (FixedUpdateNetwork) look to build on in Render/LateUpdate
+ private Vector2 _lastFUNLookRotation; // (pitch,yaw) from KCC
- public override void Despawned(NetworkRunner runner, bool hasState)
- {
- Owner = null;
- }
+ // --- Fusion lifecycle ------------------------------------------------------
- public override void FixedUpdateNetwork()
- {
- if (Owner != null && Health.IsAlive == true)
- {
- ProcessMovementInput();
- }
+ public override void Spawned()
+ {
+ name = Object.InputAuthority.ToString();
- // Setting camera pivot rotation
- var pitchRotation = KCC.GetLookRotation(true, false);
- _cameraPivot.localRotation = Quaternion.Euler(pitchRotation);
+ // Only local player needs networked props replicated (bandwidth saver)
+ ReplicateToAll(false);
+ ReplicateTo(Object.InputAuthority, true);
+ }
- _lastFUNLookRotation = KCC.GetLookRotation();
- }
+ public override void Despawned(NetworkRunner runner, bool hasState)
+ {
+ Owner = null;
+ }
- // MONOBEHAVIOUR
+ public override void FixedUpdateNetwork()
+ {
+ if (Owner != null && Health.IsAlive)
+ {
+ ProcessMovementInput();
+ }
- protected void Awake()
- {
- KCC = GetComponent();
- Weapons = GetComponent();
- Health = GetComponent();
- Input = GetComponent();
- }
+ // Keep pivot pitch synced to KCC look (we set it in LateUpdate on local)
+ var pitchYaw = KCC.GetLookRotation(true, true); // (x=pitch, y=yaw)
+ _cameraPivot.localRotation = Quaternion.Euler(pitchYaw.x, 0f, 0f);
- protected void LateUpdate()
- {
- if (HasInputAuthority == true && Owner != null && Health.IsAlive == true)
- {
- // For responsive look experience we use last FUN look + accumulated look rotation delta
- KCC.SetLookRotation(_lastFUNLookRotation + Input.AccumulatedLook, -_maxCameraAngle, _maxCameraAngle);
- }
+ _lastFUNLookRotation = KCC.GetLookRotation();
+ }
- // Update camera pitch
- // Camera pivot influences also weapon rotation so it needs to be set on proxies as well
- var pitchRotation = KCC.GetLookRotation(true, false);
- _cameraPivot.localRotation = Quaternion.Euler(pitchRotation);
+ // --- Unity lifecycle -------------------------------------------------------
- if (HasInputAuthority == true)
- {
- var cameraTransform = Context.Camera.transform;
+ private void Awake()
+ {
+ KCC = GetComponent();
+ Weapons = GetComponent();
+ Health = GetComponent();
+ Input = GetComponent();
+ }
- // Setting base camera transform based on handle
- cameraTransform.position = _cameraHandle.position;
- cameraTransform.rotation = _cameraHandle.rotation;
- }
- }
+ 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);
+ }
- // PRIVATE METHODS
+ // 2) Apply current pitch to pivot for everyone (affects remote weapon aim too)
+ var pitchOnly = KCC.GetLookRotation(true, false);
+ _cameraPivot.localRotation = Quaternion.Euler(pitchOnly);
- private void ProcessMovementInput()
- {
- if (GetInput(out GameplayInput input) == false)
- return;
+ // 3) Position the actual camera (LOCAL ONLY) with shoulder offset + collision
+ if (HasInputAuthority)
+ {
+ var cam = Context.Camera.transform;
- KCC.AddLookRotation(input.LookRotationDelta, -_maxCameraAngle, _maxCameraAngle);
+ // Desired camera target position (pivot in character space + shoulder offset)
+ Vector3 pivotWorld = _cameraPivot.TransformPoint(_shoulderOffset);
- // It feels better when player falls quicker
- KCC.SetGravity(KCC.RealVelocity.y >= 0f ? _upGravity : _downGravity);
+ // Camera wants to sit behind pivot along its backward vector
+ Quaternion yawWorld = Quaternion.Euler(0f, KCC.GetLookRotation(false, true).y, 0f);
+ Vector3 desiredOffset = yawWorld * new Vector3(0f, 0f, -_cameraDistance);
+ Vector3 desiredPos = pivotWorld + desiredOffset;
- // Calculate input direction based on recently updated look rotation (the change propagates internally also to KCC.TransformRotation)
- var inputDirection = KCC.TransformRotation * new Vector3(input.MoveDirection.x, 0f, input.MoveDirection.y);
+ // Simple collision test: spherecast from pivot toward desired
+ Vector3 dir = (desiredPos - pivotWorld);
+ float len = dir.magnitude;
+ Vector3 finalPos = desiredPos;
- var desiredMoveVelocity = inputDirection * _moveSpeed;
- float acceleration = 1f;
+ if (len > 0.0001f)
+ {
+ dir /= len;
+ if (Physics.SphereCast(pivotWorld, _cameraCollisionRadius, dir, out RaycastHit hit, len, _cameraCollisionMask, QueryTriggerInteraction.Ignore))
+ {
+ finalPos = pivotWorld + dir * Mathf.Max(hit.distance - 0.05f, _cameraDistanceMin);
+ }
+ }
- if (desiredMoveVelocity == Vector3.zero)
- {
- // No desired move velocity - we are stopping.
- acceleration = KCC.IsGrounded == true ? _groundDeceleration : _airDeceleration;
- }
- else
- {
- acceleration = KCC.IsGrounded == true ? _groundAcceleration : _airAcceleration;
- }
+ // Clamp within min/max boom
+ float dist = Mathf.Clamp(Vector3.Distance(pivotWorld, finalPos), _cameraDistanceMin, _cameraDistanceMax);
+ finalPos = pivotWorld + dir * dist;
- _moveVelocity = Vector3.Lerp(_moveVelocity, desiredMoveVelocity, acceleration * Runner.DeltaTime);
+ cam.position = finalPos;
+ cam.rotation = Quaternion.Euler(pitchOnly.x, KCC.GetLookRotation(false, true).y, 0f);
- float jumpImpulse = input.Buttons.WasPressed(Input.PreviousButtons, EInputButton.Jump) && KCC.IsGrounded ? _jumpImpulse : 0f;
- KCC.Move(_moveVelocity, jumpImpulse);
- }
- }
+ // Keep your optional handle aligned to the camera (muzzle/crosshair visuals)
+ _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();
+
+ // 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);
+ // Rotate the KCC transform smoothly (turnSpeed deg/sec)
+ Quaternion newRot = Quaternion.RotateTowards(KCC.TransformRotation, target, _turnSpeed * Runner.DeltaTime);
+ KCC.SetLookRotation(newRot);
+ }
+
+ // 5) Jump
+ float jumpImpulse = (input.Buttons.WasPressed(Input.PreviousButtons, EInputButton.Jump) && KCC.IsGrounded)
+ ? _jumpImpulse : 0f;
+
+ // 6) Move
+ KCC.Move(_moveVelocity, jumpImpulse);
+ }
+ }
}