Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using UnityEngine;
- using System.Collections;
- using System.Diagnostics;
- [RequireComponent(typeof(CharacterController))]
- public class FPSController : MonoBehaviour
- {
- [Header("Look Settings")]
- public Transform playerCamera;
- public float mouseSensitivity = 100f;
- public float maxLookAngle = 90f;
- public Transform cameraPivot; // Drag in CameraHolder
- [Header("Movement Settings")]
- public float walkSpeed = 5f;
- public float runSpeed = 8f;
- public float crouchSpeed = 3f;
- public float jumpHeight = 1.5f;
- public float gravity = -9.81f;
- [Header("Keybinds")]
- public KeyCode runKey = KeyCode.LeftShift;
- public KeyCode crouchKey = KeyCode.LeftControl;
- [Header("Kick Settings")]
- public float kickRange = 2f;
- public float kickForce = 500f;
- public float kickCooldown = 1f;
- public LayerMask kickableLayers;
- private bool isKicking = false;
- private float kickTimer = 0f;
- [Header("Slide Settings")]
- public float slideSpeed = 10f;
- public float slideDuration = 1f;
- public float slideCooldown = 1.5f;
- public float slideHeight = 1f;
- [Header("Crouch/Slide Shared Settings")]
- public float crouchHeight = 1.2f;
- public float crouchCameraHeight = 0.8f;
- public float cameraSmoothSpeed = 8f;
- [Header("Model Settings")]
- public Transform playerModel;
- public float modelYWhenStanding = 0f;
- public float modelYWhenCrouching = -0.5f;
- public float modelYWhenSliding = -0.7f;
- [Header("Ground Check Settings")]
- public float groundCheckDistance = 0.4f;
- public float groundCheckRadius = 0.3f;
- public LayerMask groundMask;
- [Header("Upper Body Tracking")]
- public Transform upperBodyBone;
- public float upperBodyPitchLimit = 45f;
- public float upperBodyFollowSpeed = 10f;
- private CharacterController controller;
- private Animator animator;
- private float cameraPitch; // Tracks vertical camera angle
- private float originalHeight;
- private Vector3 originalCenter;
- private float originalCameraY;
- private float targetCameraY;
- private Quaternion upperBodyStartRotation;
- private Vector3 airMoveDirection;
- private Vector3 lastGroundedVelocity;
- private bool isPointing = false;
- private Vector3 velocity;
- private bool isSliding = false;
- private bool isCrouching = false;
- private float slideCooldownTimer = 0f;
- private bool isGrounded = false;
- void Start()
- {
- controller = GetComponent<CharacterController>();
- animator = GetComponentInChildren<Animator>();
- animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
- Cursor.lockState = CursorLockMode.Locked;
- Cursor.visible = false;
- // Reset camera to look forward on game start
- cameraPitch = 0f;
- playerCamera.localRotation = Quaternion.Euler(cameraPitch, 0f, 0f);
- originalHeight = controller.height;
- originalCenter = controller.center;
- originalCameraY = playerCamera.localPosition.y;
- targetCameraY = originalCameraY;
- if (playerCamera == null)
- UnityEngine.Debug.LogError("PlayerCamera not assigned!");
- if (playerModel == null)
- UnityEngine.Debug.LogError("Player model not assigned!");
- if (upperBodyBone != null)
- upperBodyStartRotation = upperBodyBone.localRotation;
- }
- void Update()
- {
- GroundCheck();
- Look();
- Move();
- kickTimer -= Time.deltaTime;
- // Toggle pointing pose
- if (Input.GetKeyDown(KeyCode.Alpha1))
- {
- isPointing = true;
- animator.SetBool("IsPointing", true);
- }
- // Cancel pointing on left-click
- if (isPointing && Input.GetMouseButtonDown(0))
- {
- isPointing = false;
- animator.SetBool("IsPointing", false);
- }
- if (Input.GetKeyDown(KeyCode.F) && kickTimer <= 0f && !isKicking && isGrounded)
- {
- StartCoroutine(PerformKick());
- }
- slideCooldownTimer -= Time.deltaTime;
- Vector3 camPos = playerCamera.localPosition;
- camPos.y = Mathf.Lerp(camPos.y, targetCameraY, Time.deltaTime * cameraSmoothSpeed);
- playerCamera.localPosition = camPos;
- UpdateModelPosition();
- }
- void LateUpdate()
- {
- if (upperBodyBone == null || animator == null) return;
- // Skip rotation override if pointing or other emote is active
- if (animator.GetCurrentAnimatorStateInfo(1).normalizedTime < 1f &&
- animator.GetCurrentAnimatorStateInfo(1).IsTag("UpperBody")) return;
- float pitch = Mathf.Clamp(cameraPitch, -upperBodyPitchLimit, upperBodyPitchLimit);
- Quaternion pitchRotation = Quaternion.AngleAxis(pitch, Vector3.right);
- upperBodyBone.localRotation = Quaternion.Slerp(
- upperBodyBone.localRotation,
- upperBodyStartRotation * pitchRotation,
- Time.deltaTime * upperBodyFollowSpeed
- );
- }
- void Look()
- {
- float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
- float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
- cameraPitch -= mouseY;
- cameraPitch = Mathf.Clamp(cameraPitch, -maxLookAngle, maxLookAngle);
- cameraPivot.localRotation = Quaternion.Euler(cameraPitch, 0f, 0f);
- transform.Rotate(Vector3.up * mouseX);
- }
- void Move()
- {
- if (isGrounded && velocity.y < 0)
- velocity.y = -2f;
- float moveX = Input.GetAxis("Horizontal");
- float moveZ = Input.GetAxis("Vertical");
- Vector3 move = transform.right * moveX + transform.forward * moveZ;
- float speed = walkSpeed;
- bool isRunning = Input.GetKey(runKey) && !isSliding && !isCrouching;
- if (isRunning) speed = runSpeed;
- else if (isCrouching) speed = crouchSpeed;
- if (Input.GetKeyDown(crouchKey) && Input.GetKey(runKey) && isGrounded && slideCooldownTimer <= 0 && !isSliding)
- StartCoroutine(Slide());
- if (!isSliding && Input.GetKey(crouchKey) && !Input.GetKey(runKey))
- {
- StartCrouch();
- }
- else if (!isSliding && !Input.GetKey(crouchKey) && isCrouching && CanStandUp())
- {
- StopCrouch();
- }
- controller.Move(move * speed * Time.deltaTime);
- if (Input.GetButtonDown("Jump") && isGrounded && !isSliding && !isCrouching)
- {
- velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
- animator.SetTrigger("Jump");
- }
- velocity.y += gravity * Time.deltaTime;
- controller.Move(velocity * Time.deltaTime);
- // Animations
- animator.SetFloat("Speed", move.magnitude * speed);
- animator.SetBool("IsSliding", isSliding);
- animator.SetBool("IsCrouch", isCrouching);
- animator.SetBool("IsGrounded", isGrounded);
- animator.SetBool("IsFalling", !isGrounded && velocity.y < 0);
- }
- void GroundCheck()
- {
- Vector3 origin = transform.position + Vector3.up * (controller.radius + 0.1f);
- isGrounded = Physics.SphereCast(origin, groundCheckRadius, Vector3.down, out RaycastHit hit, groundCheckDistance, groundMask);
- }
- bool IsHeadBlocked()
- {
- float radius = controller.radius * 0.95f;
- Vector3 bottom = transform.position + Vector3.up * crouchHeight;
- Vector3 top = transform.position + Vector3.up * (originalHeight - 0.05f);
- return Physics.CheckCapsule(bottom, top, radius, groundMask);
- }
- bool CanStandUp()
- {
- return !IsHeadBlocked();
- }
- IEnumerator Slide()
- {
- isSliding = true;
- slideCooldownTimer = slideCooldown;
- controller.height = slideHeight;
- controller.center = new Vector3(0, originalCenter.y - (originalHeight - slideHeight) / 2f, 0);
- targetCameraY = crouchCameraHeight;
- float elapsed = 0f;
- while (elapsed < slideDuration)
- {
- Vector3 slideDir = transform.forward;
- controller.Move(slideDir * slideSpeed * Time.deltaTime);
- elapsed += Time.deltaTime;
- yield return null;
- }
- controller.height = crouchHeight;
- controller.center = new Vector3(0, originalCenter.y - (originalHeight - crouchHeight) / 2f, 0);
- targetCameraY = crouchCameraHeight;
- isSliding = false;
- isCrouching = true;
- }
- IEnumerator PerformKick()
- {
- isKicking = true;
- kickTimer = kickCooldown;
- // ✅ Play kick animation
- animator.SetTrigger("Kick");
- // Wait for hit to land — adjust to match animation timing
- yield return new WaitForSeconds(0.2f);
- // ✅ Raycast to find hit object
- if (Physics.Raycast(playerCamera.position, playerCamera.forward, out RaycastHit hit, kickRange, kickableLayers))
- {
- Rigidbody rb = hit.collider.attachedRigidbody;
- if (rb != null)
- {
- Vector3 forceDir = hit.point - playerCamera.position;
- forceDir.Normalize();
- rb.AddForce(forceDir * kickForce, ForceMode.Impulse);
- }
- }
- // Optional: wait until animation finishes
- yield return new WaitForSeconds(0.3f);
- isKicking = false;
- }
- void StartCrouch()
- {
- if (isCrouching) return;
- isCrouching = true;
- controller.height = crouchHeight;
- controller.center = new Vector3(0, originalCenter.y - (originalHeight - crouchHeight) / 2f, 0);
- targetCameraY = crouchCameraHeight;
- }
- void StopCrouch()
- {
- isCrouching = false;
- controller.height = originalHeight;
- controller.center = originalCenter;
- targetCameraY = originalCameraY;
- }
- void UpdateModelPosition()
- {
- float modelY = modelYWhenStanding;
- if (isSliding)
- modelY = modelYWhenSliding;
- else if (isCrouching)
- modelY = modelYWhenCrouching;
- // Set directly instead of Lerp when sliding
- float targetY = isSliding ? modelY : Mathf.Lerp(playerModel.localPosition.y, modelY, Time.deltaTime * cameraSmoothSpeed);
- Vector3 modelLocalPos = playerModel.localPosition;
- modelLocalPos.y = targetY;
- playerModel.localPosition = modelLocalPos;
- }
- void RotateUpperBodyToCamera()
- {
- if (upperBodyBone == null || playerCamera == null) return;
- float pitch = playerCamera.localEulerAngles.x;
- if (pitch > 180f) pitch -= 360f;
- pitch = Mathf.Clamp(pitch, -upperBodyPitchLimit, upperBodyPitchLimit);
- Quaternion targetRotation = Quaternion.Euler(pitch, 0f, 0f);
- upperBodyBone.localRotation = Quaternion.Slerp(
- upperBodyBone.localRotation,
- targetRotation,
- Time.deltaTime * upperBodyFollowSpeed
- );
- }
- void OnDrawGizmosSelected()
- {
- if (controller == null) return;
- Gizmos.color = Color.yellow;
- float radius = controller.radius * 0.95f;
- Vector3 bottom = transform.position + Vector3.up * crouchHeight;
- Vector3 top = transform.position + Vector3.up * (originalHeight - 0.05f);
- Gizmos.color = Color.red;
- Gizmos.DrawRay(playerCamera.position, playerCamera.forward * kickRange);
- Gizmos.DrawWireSphere(bottom, radius);
- Gizmos.DrawWireSphere(top, radius);
- Gizmos.DrawLine(bottom + Vector3.forward * radius, top + Vector3.forward * radius);
- Gizmos.DrawLine(bottom - Vector3.forward * radius, top - Vector3.forward * radius);
- Gizmos.DrawLine(bottom + Vector3.right * radius, top + Vector3.right * radius);
- Gizmos.DrawLine(bottom - Vector3.right * radius, top - Vector3.right * radius);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement