From 36fd1709c1e4a0841183a335b31757771e3c5abd Mon Sep 17 00:00:00 2001 From: Hazim Bin Ijaz Date: Fri, 26 Sep 2025 02:55:06 +0500 Subject: [PATCH] Converted to third person --- .idea/.idea.RizzeBattleRoyale/.idea/vcs.xml | 6 + .../Player/Agent/PlayerAgentChillGuy.prefab | 16 +- Assets/Scripts/Player/PlayerAgent.cs | 296 +++++++++++------- 3 files changed, 197 insertions(+), 121 deletions(-) create mode 100644 .idea/.idea.RizzeBattleRoyale/.idea/vcs.xml 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); + } + } }