2025-05-07 14:43:43 +01:00
|
|
|
using UnityEngine;
|
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
2025-05-03 14:35:35 +01:00
|
|
|
using UnityEngine.InputSystem;
|
|
|
|
|
#endif
|
2025-05-07 14:43:43 +01:00
|
|
|
using Cinemachine;
|
|
|
|
|
using TMPro;
|
2025-05-03 14:35:35 +01:00
|
|
|
|
|
|
|
|
/* Note: animations are called via the controller for both the character and capsule using animator null checks
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace StarterAssets
|
|
|
|
|
{
|
|
|
|
|
[RequireComponent(typeof(CharacterController))]
|
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
|
|
|
[RequireComponent(typeof(PlayerInput))]
|
|
|
|
|
#endif
|
|
|
|
|
public class ThirdPersonController : MonoBehaviour
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
[Header("Popup")]
|
|
|
|
|
[SerializeField] private TMP_Text _popUpText;
|
|
|
|
|
private Transform _currentTarget;
|
|
|
|
|
private Transform _textTransform;
|
2025-05-06 14:38:02 +01:00
|
|
|
|
|
|
|
|
[Header("Animation")]
|
|
|
|
|
[Tooltip("Animator component reference")]
|
|
|
|
|
public Animator Animator;
|
|
|
|
|
|
2025-05-03 14:35:35 +01:00
|
|
|
[Header("Player")]
|
|
|
|
|
[Tooltip("Move speed of the character in m/s")]
|
|
|
|
|
public float MoveSpeed = 2.0f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("Sprint speed of the character in m/s")]
|
|
|
|
|
public float SprintSpeed = 5.335f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("How fast the character turns to face movement direction")]
|
|
|
|
|
[Range(0.0f, 0.3f)]
|
|
|
|
|
public float RotationSmoothTime = 0.12f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("Acceleration and deceleration")]
|
|
|
|
|
public float SpeedChangeRate = 10.0f;
|
|
|
|
|
|
|
|
|
|
public AudioClip LandingAudioClip;
|
|
|
|
|
public AudioClip[] FootstepAudioClips;
|
|
|
|
|
[Range(0, 1)] public float FootstepAudioVolume = 0.5f;
|
|
|
|
|
|
|
|
|
|
[Space(10)]
|
|
|
|
|
[Tooltip("The height the player can jump")]
|
|
|
|
|
public float JumpHeight = 1.2f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
|
|
|
|
|
public float Gravity = -15.0f;
|
|
|
|
|
|
|
|
|
|
[Space(10)]
|
|
|
|
|
[Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
|
|
|
|
|
public float JumpTimeout = 0.50f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
|
|
|
|
|
public float FallTimeout = 0.15f;
|
|
|
|
|
|
|
|
|
|
[Header("Player Grounded")]
|
|
|
|
|
[Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
|
|
|
|
|
public bool Grounded = true;
|
|
|
|
|
|
|
|
|
|
[Tooltip("Useful for rough ground")]
|
|
|
|
|
public float GroundedOffset = -0.14f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
|
|
|
|
|
public float GroundedRadius = 0.28f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("What layers the character uses as ground")]
|
|
|
|
|
public LayerMask GroundLayers;
|
|
|
|
|
|
|
|
|
|
[Header("Cinemachine")]
|
|
|
|
|
[Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
|
|
|
|
|
public GameObject CinemachineCameraTarget;
|
|
|
|
|
|
|
|
|
|
[Tooltip("How far in degrees can you move the camera up")]
|
|
|
|
|
public float TopClamp = 70.0f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("How far in degrees can you move the camera down")]
|
|
|
|
|
public float BottomClamp = -30.0f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
|
|
|
|
|
public float CameraAngleOverride = 0.0f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("For locking the camera position on all axis")]
|
|
|
|
|
public bool LockCameraPosition = false;
|
|
|
|
|
|
|
|
|
|
// cinemachine
|
|
|
|
|
private float _cinemachineTargetYaw;
|
|
|
|
|
private float _cinemachineTargetPitch;
|
|
|
|
|
|
|
|
|
|
// player
|
|
|
|
|
private float _speed;
|
|
|
|
|
private float _animationBlend;
|
|
|
|
|
private float _targetRotation = 0.0f;
|
|
|
|
|
private float _rotationVelocity;
|
|
|
|
|
private float _verticalVelocity;
|
|
|
|
|
private float _terminalVelocity = 53.0f;
|
|
|
|
|
|
|
|
|
|
// timeout deltatime
|
|
|
|
|
private float _jumpTimeoutDelta;
|
|
|
|
|
private float _fallTimeoutDelta;
|
|
|
|
|
|
2025-05-07 14:43:43 +01:00
|
|
|
// character height
|
|
|
|
|
private float _characterHeight;
|
|
|
|
|
|
|
|
|
|
// camera rotation
|
|
|
|
|
private float CurrentTargetRotation;
|
|
|
|
|
private float CurrentCameraRotation; // Added declaration for CurrentCameraRotation
|
|
|
|
|
|
2025-05-03 14:35:35 +01:00
|
|
|
// animation IDs
|
|
|
|
|
private int _animIDSpeed;
|
|
|
|
|
private int _animIDGrounded;
|
|
|
|
|
private int _animIDJump;
|
|
|
|
|
private int _animIDFreeFall;
|
|
|
|
|
private int _animIDMotionSpeed;
|
|
|
|
|
|
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
|
|
|
private PlayerInput _playerInput;
|
|
|
|
|
#endif
|
|
|
|
|
private Animator _animator;
|
|
|
|
|
private CharacterController _controller;
|
|
|
|
|
private StarterAssetsInputs _input;
|
|
|
|
|
private GameObject _mainCamera;
|
|
|
|
|
|
|
|
|
|
private const float _threshold = 0.01f;
|
|
|
|
|
|
|
|
|
|
private bool _hasAnimator;
|
|
|
|
|
|
|
|
|
|
private bool IsCurrentDeviceMouse
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
|
|
|
return _playerInput.currentControlScheme == "KeyboardMouse";
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
_controller = GetComponent<CharacterController>();
|
|
|
|
|
|
|
|
|
|
// Get the main camera reference
|
|
|
|
|
_mainCamera = Camera.main?.gameObject;
|
2025-05-03 14:35:35 +01:00
|
|
|
if (_mainCamera == null)
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
Debug.LogError("ThirdPersonController: Main camera not found!");
|
|
|
|
|
return;
|
2025-05-03 14:35:35 +01:00
|
|
|
}
|
2025-05-07 14:43:43 +01:00
|
|
|
|
|
|
|
|
// Initialize camera rotation values
|
|
|
|
|
_cinemachineTargetYaw = _mainCamera.transform.rotation.eulerAngles.y;
|
|
|
|
|
_cinemachineTargetPitch = _mainCamera.transform.localRotation.eulerAngles.x;
|
|
|
|
|
_cinemachineTargetYaw = _cinemachineTargetYaw % 360f;
|
|
|
|
|
_cinemachineTargetPitch = _cinemachineTargetPitch % 360f;
|
|
|
|
|
_cinemachineTargetYaw = (_cinemachineTargetYaw > 180f) ? _cinemachineTargetYaw - 360f : _cinemachineTargetYaw;
|
|
|
|
|
_cinemachineTargetPitch = (_cinemachineTargetPitch > 180f) ? _cinemachineTargetPitch - 360f : _cinemachineTargetPitch;
|
|
|
|
|
_cinemachineTargetYaw = Mathf.Clamp(_cinemachineTargetYaw, BottomClamp, TopClamp);
|
|
|
|
|
_cinemachineTargetPitch = Mathf.Clamp(_cinemachineTargetPitch, BottomClamp, TopClamp);
|
|
|
|
|
_cinemachineTargetYaw = Mathf.Abs(_cinemachineTargetYaw) > 90f ? 90f : _cinemachineTargetYaw;
|
|
|
|
|
_cinemachineTargetPitch = Mathf.Abs(_cinemachineTargetPitch) > 90f ? 90f : _cinemachineTargetPitch;
|
|
|
|
|
|
|
|
|
|
// Initialize current rotation values
|
|
|
|
|
CurrentTargetRotation = _cinemachineTargetYaw;
|
|
|
|
|
CurrentCameraRotation = _cinemachineTargetPitch;
|
|
|
|
|
|
|
|
|
|
// Set initial target rotation
|
|
|
|
|
_targetRotation = transform.eulerAngles.y;
|
|
|
|
|
|
|
|
|
|
// Initialize animator if present
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator = GetComponent<Animator>();
|
|
|
|
|
}
|
|
|
|
|
_targetRotation = transform.eulerAngles.y;
|
|
|
|
|
|
|
|
|
|
// Initialize character state
|
|
|
|
|
_animationBlend = 0f;
|
|
|
|
|
_verticalVelocity = 0f;
|
|
|
|
|
_characterHeight = _controller.height;
|
|
|
|
|
|
|
|
|
|
// Initialize popup fields
|
|
|
|
|
if (_popUpText == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("ThirdPersonController: TMP_Text component not assigned!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_textTransform = _popUpText.transform;
|
2025-05-03 14:35:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Start()
|
|
|
|
|
{
|
|
|
|
|
_cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y;
|
|
|
|
|
|
|
|
|
|
_hasAnimator = TryGetComponent(out _animator);
|
|
|
|
|
_input = GetComponent<StarterAssetsInputs>();
|
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
|
|
|
_playerInput = GetComponent<PlayerInput>();
|
|
|
|
|
#else
|
|
|
|
|
Debug.LogError( "Starter Assets package is missing dependencies. Please use Tools/Starter Assets/Reinstall Dependencies to fix it");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
AssignAnimationIDs();
|
|
|
|
|
|
|
|
|
|
// reset our timeouts on start
|
|
|
|
|
_jumpTimeoutDelta = JumpTimeout;
|
|
|
|
|
_fallTimeoutDelta = FallTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
|
|
|
|
_hasAnimator = TryGetComponent(out _animator);
|
|
|
|
|
|
|
|
|
|
JumpAndGravity();
|
|
|
|
|
GroundedCheck();
|
|
|
|
|
Move();
|
2025-05-06 14:38:02 +01:00
|
|
|
|
|
|
|
|
CheckForObjectInformation(); // Call the new Raycast function
|
|
|
|
|
|
|
|
|
|
if (Input.GetMouseButtonUp(0))
|
|
|
|
|
{
|
|
|
|
|
PlayerAttack(); // Call the attack function when the left mouse button is pressed
|
|
|
|
|
} // Left mouse button
|
|
|
|
|
|
|
|
|
|
// Block when the right mouse button is pressed
|
|
|
|
|
if (Input.GetMouseButton(1))
|
|
|
|
|
{
|
|
|
|
|
PlayerBlock(); // Start the block animation
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Animator.SetBool("Block", false); // Stop the block animation when the button is released
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Input.GetKeyUp(KeyCode.F))
|
|
|
|
|
{
|
|
|
|
|
PlayerKick(); // Call the kick function when the F button is pressed
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-07 14:43:43 +01:00
|
|
|
if (Input.GetKeyUp(KeyCode.E))
|
|
|
|
|
{
|
|
|
|
|
PlayerInteract();
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-03 14:35:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LateUpdate()
|
|
|
|
|
{
|
|
|
|
|
CameraRotation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AssignAnimationIDs()
|
|
|
|
|
{
|
|
|
|
|
_animIDSpeed = Animator.StringToHash("Speed");
|
|
|
|
|
_animIDGrounded = Animator.StringToHash("Grounded");
|
|
|
|
|
_animIDJump = Animator.StringToHash("Jump");
|
|
|
|
|
_animIDFreeFall = Animator.StringToHash("FreeFall");
|
|
|
|
|
_animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GroundedCheck()
|
|
|
|
|
{
|
|
|
|
|
// set sphere position, with offset
|
|
|
|
|
Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset,
|
|
|
|
|
transform.position.z);
|
|
|
|
|
Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,
|
|
|
|
|
QueryTriggerInteraction.Ignore);
|
|
|
|
|
|
|
|
|
|
// update animator if using character
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator.SetBool(_animIDGrounded, Grounded);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CameraRotation()
|
|
|
|
|
{
|
|
|
|
|
// if there is an input and camera position is not fixed
|
|
|
|
|
if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
|
|
|
|
|
{
|
|
|
|
|
//Don't multiply mouse input by Time.deltaTime;
|
|
|
|
|
float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;
|
|
|
|
|
|
|
|
|
|
_cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier;
|
|
|
|
|
_cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clamp our rotations so our values are limited 360 degrees
|
|
|
|
|
_cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
|
|
|
|
|
_cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
|
|
|
|
|
|
|
|
|
|
// Cinemachine will follow this target
|
|
|
|
|
CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride,
|
|
|
|
|
_cinemachineTargetYaw, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-06 14:38:02 +01:00
|
|
|
private void CheckForObjectInformation()
|
|
|
|
|
{
|
|
|
|
|
// Define the sphere cast starting point and direction
|
2025-05-07 14:43:43 +01:00
|
|
|
var origin = transform.position;
|
|
|
|
|
var direction = transform.forward;
|
|
|
|
|
const float sphereRadius = 1f;
|
|
|
|
|
const float maxDistance = 4.0f;
|
|
|
|
|
var hit = new RaycastHit();
|
2025-05-06 14:38:02 +01:00
|
|
|
|
|
|
|
|
// Perform the sphere cast
|
|
|
|
|
if (Physics.SphereCast(origin, sphereRadius, direction, out hit, maxDistance))
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
var objectInfo = hit.collider.GetComponent<ObjectInformation>();
|
|
|
|
|
var npc = hit.collider.GetComponent<NPC>();
|
|
|
|
|
|
|
|
|
|
if (objectInfo != null || npc != null)
|
2025-05-06 14:38:02 +01:00
|
|
|
{
|
|
|
|
|
// If the player is looking at a new object, update the pop-up
|
|
|
|
|
if (_currentTarget != hit.transform)
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
_currentTarget = hit.transform;
|
|
|
|
|
_popUpText.text = objectInfo != null
|
|
|
|
|
? $"{objectInfo.objectName}\n{objectInfo.objectDescription}"
|
|
|
|
|
: "[E] Talk to " + npc.npcName;
|
|
|
|
|
_popUpText.gameObject.SetActive(true);
|
|
|
|
|
|
|
|
|
|
// Update popup position
|
|
|
|
|
Vector3 tempPosition = _currentTarget.position;
|
|
|
|
|
tempPosition.y += 1f;
|
|
|
|
|
tempPosition.z += 0.2f;
|
|
|
|
|
_textTransform.position = tempPosition;
|
|
|
|
|
|
|
|
|
|
// Make the pop-up face the camera
|
|
|
|
|
var mainCamera = _mainCamera;
|
|
|
|
|
if (mainCamera != null)
|
|
|
|
|
{
|
|
|
|
|
// Get the direction from the target to the camera
|
|
|
|
|
Vector3 directionToCamera = mainCamera.transform.position - _currentTarget.position;
|
|
|
|
|
// Create rotation that faces the camera
|
|
|
|
|
Quaternion targetRotation = Quaternion.LookRotation(directionToCamera, Vector3.up);
|
|
|
|
|
// Adjust the rotation to face the camera properly
|
|
|
|
|
targetRotation = Quaternion.Euler(0, targetRotation.eulerAngles.y + 180f, 0);
|
|
|
|
|
_textTransform.rotation = targetRotation;
|
|
|
|
|
}
|
2025-05-06 14:38:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If the sphere cast doesn't hit anything, hide the pop-up
|
|
|
|
|
if (_currentTarget != null)
|
|
|
|
|
{
|
2025-05-07 14:43:43 +01:00
|
|
|
_currentTarget = null;
|
|
|
|
|
_popUpText.gameObject.SetActive(false);
|
2025-05-06 14:38:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-03 14:35:35 +01:00
|
|
|
private void Move()
|
|
|
|
|
{
|
|
|
|
|
// set target speed based on move speed, sprint speed and if sprint is pressed
|
|
|
|
|
float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
|
|
|
|
|
|
|
|
|
|
// a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon
|
|
|
|
|
|
|
|
|
|
// note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
|
|
|
|
|
// if there is no input, set the target speed to 0
|
|
|
|
|
if (_input.move == Vector2.zero) targetSpeed = 0.0f;
|
|
|
|
|
|
|
|
|
|
// a reference to the players current horizontal velocity
|
|
|
|
|
float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
|
|
|
|
|
|
|
|
|
|
float speedOffset = 0.1f;
|
|
|
|
|
float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;
|
|
|
|
|
|
|
|
|
|
// accelerate or decelerate to target speed
|
|
|
|
|
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
|
|
|
|
|
currentHorizontalSpeed > targetSpeed + speedOffset)
|
|
|
|
|
{
|
|
|
|
|
// creates curved result rather than a linear one giving a more organic speed change
|
|
|
|
|
// note T in Lerp is clamped, so we don't need to clamp our speed
|
|
|
|
|
_speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
|
|
|
|
|
Time.deltaTime * SpeedChangeRate);
|
|
|
|
|
|
|
|
|
|
// round speed to 3 decimal places
|
|
|
|
|
_speed = Mathf.Round(_speed * 1000f) / 1000f;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_speed = targetSpeed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
|
|
|
|
|
if (_animationBlend < 0.01f) _animationBlend = 0f;
|
|
|
|
|
|
|
|
|
|
// normalise input direction
|
|
|
|
|
Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;
|
|
|
|
|
|
|
|
|
|
// note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
|
|
|
|
|
// if there is a move input rotate player when the player is moving
|
|
|
|
|
if (_input.move != Vector2.zero)
|
|
|
|
|
{
|
|
|
|
|
_targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
|
|
|
|
|
_mainCamera.transform.eulerAngles.y;
|
|
|
|
|
float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
|
|
|
|
|
RotationSmoothTime);
|
|
|
|
|
|
|
|
|
|
// rotate to face input direction relative to camera position
|
|
|
|
|
transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
|
|
|
|
|
|
|
|
|
|
// move the player
|
|
|
|
|
_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
|
|
|
|
|
new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
|
|
|
|
|
|
|
|
|
|
// update animator if using character
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator.SetFloat(_animIDSpeed, _animationBlend);
|
|
|
|
|
_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void JumpAndGravity()
|
|
|
|
|
{
|
|
|
|
|
if (Grounded)
|
|
|
|
|
{
|
|
|
|
|
// reset the fall timeout timer
|
|
|
|
|
_fallTimeoutDelta = FallTimeout;
|
|
|
|
|
|
|
|
|
|
// update animator if using character
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator.SetBool(_animIDJump, false);
|
|
|
|
|
_animator.SetBool(_animIDFreeFall, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stop our velocity dropping infinitely when grounded
|
|
|
|
|
if (_verticalVelocity < 0.0f)
|
|
|
|
|
{
|
|
|
|
|
_verticalVelocity = -2f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Jump
|
|
|
|
|
if (_input.jump && _jumpTimeoutDelta <= 0.0f)
|
|
|
|
|
{
|
|
|
|
|
// the square root of H * -2 * G = how much velocity needed to reach desired height
|
|
|
|
|
_verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
|
|
|
|
|
|
|
|
|
|
// update animator if using character
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator.SetBool(_animIDJump, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// jump timeout
|
|
|
|
|
if (_jumpTimeoutDelta >= 0.0f)
|
|
|
|
|
{
|
|
|
|
|
_jumpTimeoutDelta -= Time.deltaTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// reset the jump timeout timer
|
|
|
|
|
_jumpTimeoutDelta = JumpTimeout;
|
|
|
|
|
|
|
|
|
|
// fall timeout
|
|
|
|
|
if (_fallTimeoutDelta >= 0.0f)
|
|
|
|
|
{
|
|
|
|
|
_fallTimeoutDelta -= Time.deltaTime;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// update animator if using character
|
|
|
|
|
if (_hasAnimator)
|
|
|
|
|
{
|
|
|
|
|
_animator.SetBool(_animIDFreeFall, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we are not grounded, do not jump
|
|
|
|
|
_input.jump = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
|
|
|
|
|
if (_verticalVelocity < _terminalVelocity)
|
|
|
|
|
{
|
|
|
|
|
_verticalVelocity += Gravity * Time.deltaTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
|
|
|
|
|
{
|
|
|
|
|
if (lfAngle < -360f) lfAngle += 360f;
|
|
|
|
|
if (lfAngle > 360f) lfAngle -= 360f;
|
|
|
|
|
return Mathf.Clamp(lfAngle, lfMin, lfMax);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDrawGizmosSelected()
|
|
|
|
|
{
|
|
|
|
|
Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
|
|
|
|
|
Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);
|
|
|
|
|
|
|
|
|
|
if (Grounded) Gizmos.color = transparentGreen;
|
|
|
|
|
else Gizmos.color = transparentRed;
|
|
|
|
|
|
|
|
|
|
// when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
|
|
|
|
|
Gizmos.DrawSphere(
|
|
|
|
|
new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z),
|
|
|
|
|
GroundedRadius);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnFootstep(AnimationEvent animationEvent)
|
|
|
|
|
{
|
|
|
|
|
if (animationEvent.animatorClipInfo.weight > 0.5f)
|
|
|
|
|
{
|
|
|
|
|
if (FootstepAudioClips.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
var index = Random.Range(0, FootstepAudioClips.Length);
|
|
|
|
|
AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnLand(AnimationEvent animationEvent)
|
|
|
|
|
{
|
|
|
|
|
if (animationEvent.animatorClipInfo.weight > 0.5f)
|
|
|
|
|
{
|
|
|
|
|
AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-06 14:38:02 +01:00
|
|
|
public void PlayerAttack()
|
|
|
|
|
{
|
|
|
|
|
Animator.SetTrigger("Attack");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void PlayerBlock()
|
|
|
|
|
{
|
|
|
|
|
Animator.SetBool("Block", true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void PlayerKick()
|
|
|
|
|
{
|
|
|
|
|
Animator.SetTrigger("Kick");
|
|
|
|
|
}
|
2025-05-07 14:43:43 +01:00
|
|
|
|
|
|
|
|
public void PlayerInteract()
|
|
|
|
|
{
|
|
|
|
|
// Create a RaySphere from the player's position
|
|
|
|
|
Vector3 sphereCenter = transform.position + Vector3.up * 1f; // Slightly above the player
|
|
|
|
|
float sphereRadius = 2f;
|
|
|
|
|
|
|
|
|
|
// Find all NPCs within the sphere
|
|
|
|
|
Collider[] hits = Physics.OverlapSphere(sphereCenter, sphereRadius);
|
|
|
|
|
NPC closestNPC = null;
|
|
|
|
|
float closestDistance = float.MaxValue;
|
|
|
|
|
|
|
|
|
|
foreach (Collider hit in hits)
|
|
|
|
|
{
|
|
|
|
|
NPC npc = hit.GetComponent<NPC>();
|
|
|
|
|
if (npc != null && npc.canTalk && npc.isInteractable)
|
|
|
|
|
{
|
|
|
|
|
float distance = Vector3.Distance(transform.position, npc.transform.position);
|
|
|
|
|
if (distance < closestDistance)
|
|
|
|
|
{
|
|
|
|
|
closestDistance = distance;
|
|
|
|
|
closestNPC = npc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we found an NPC, interact with it
|
|
|
|
|
if (closestNPC != null)
|
|
|
|
|
{
|
2025-06-05 22:59:18 +01:00
|
|
|
//closestNPC.Interact();
|
2025-05-07 14:43:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-05-03 14:35:35 +01:00
|
|
|
}
|
|
|
|
|
}
|