200 lines
7.5 KiB
C#
200 lines
7.5 KiB
C#
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(CharacterController))]
|
|
public class charecter : MonoBehaviour
|
|
{
|
|
[Header("Movement")]
|
|
public float walkSpeed = 5f;
|
|
public float runSpeed = 9f;
|
|
public float crouchSpeed = 2.5f;
|
|
public float jumpHeight = 1.5f;
|
|
public float gravity = -9.81f;
|
|
|
|
[Header("References")]
|
|
[Tooltip("Head BONE of the character.")]
|
|
public Transform head;
|
|
|
|
[Header("Look")]
|
|
public float mouseSensitivity = 2f;
|
|
public float maxPitch = 80f;
|
|
|
|
[Header("Body Turn")]
|
|
public float maxHeadYaw = 60f;
|
|
public float bodyAlignSpeed = 300f;
|
|
|
|
// ── private ──────────────────────────────────────────────────────────────
|
|
CharacterController _cc;
|
|
Animator _anim;
|
|
|
|
float _verticalVelocity;
|
|
float _bodyYaw;
|
|
float _headYaw;
|
|
float _pitch;
|
|
Quaternion _headBindPose;
|
|
bool _jumpRequested = false;
|
|
|
|
// Animator parameter hashes — no hardcoded strings at runtime
|
|
static readonly int HashSpeed = Animator.StringToHash("Speed");
|
|
static readonly int HashIsMoving = Animator.StringToHash("IsMoving");
|
|
static readonly int HashIsCrouching = Animator.StringToHash("IsCrouching");
|
|
static readonly int HashIsGrounded = Animator.StringToHash("IsGrounded");
|
|
static readonly int HashJump = Animator.StringToHash("Jump");
|
|
|
|
// ── Unity lifecycle ───────────────────────────────────────────────────────
|
|
void Start()
|
|
{
|
|
_cc = GetComponent<CharacterController>();
|
|
_anim = GetComponentInChildren<Animator>();
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
Cursor.visible = false;
|
|
|
|
_bodyYaw = transform.eulerAngles.y;
|
|
_headYaw = _bodyYaw;
|
|
_headBindPose = head != null ? head.localRotation : Quaternion.identity;
|
|
|
|
// Убедиться что PlayerInput существует
|
|
if (PlayerInput.Instance == null)
|
|
{
|
|
var go = new GameObject("PlayerInput");
|
|
go.AddComponent<PlayerInput>();
|
|
}
|
|
|
|
PlayerInput.Instance.OnPickupPressed += TryPickupNearbyItem;
|
|
PlayerInput.Instance.OnJumpPressed += OnJumpPressed;
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
if (PlayerInput.Instance != null)
|
|
{
|
|
PlayerInput.Instance.OnPickupPressed -= TryPickupNearbyItem;
|
|
PlayerInput.Instance.OnJumpPressed -= OnJumpPressed;
|
|
}
|
|
}
|
|
|
|
void OnJumpPressed() => _jumpRequested = true;
|
|
|
|
void Update()
|
|
{
|
|
HandleLook();
|
|
HandleMovement();
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
ApplyRotations();
|
|
}
|
|
|
|
// ── Item Pickup ───────────────────────────────────────────────────────────
|
|
void TryPickupNearbyItem()
|
|
{
|
|
float pickupRadius = 2.5f;
|
|
Collider[] colliders = Physics.OverlapSphere(transform.position, pickupRadius);
|
|
|
|
WorldItem closestItem = null;
|
|
float closestDist = float.MaxValue;
|
|
|
|
foreach (var col in colliders)
|
|
{
|
|
// Ищем WorldItem на коллайдере ИЛИ на любом родителе —
|
|
// чтобы дочерние меши (Visual, default и т.д.) тоже находили своего владельца
|
|
var worldItem = col.GetComponentInParent<WorldItem>();
|
|
if (worldItem == null) continue;
|
|
|
|
// Дедупликация: пропускаем если этот WorldItem уже учтён через другой коллайдер
|
|
if (worldItem == closestItem) continue;
|
|
|
|
float dist = Vector3.Distance(transform.position, worldItem.transform.position);
|
|
if (dist < closestDist && dist <= worldItem.interactRange)
|
|
{
|
|
closestDist = dist;
|
|
closestItem = worldItem;
|
|
}
|
|
}
|
|
|
|
if (closestItem != null)
|
|
closestItem.TryPickupByButton();
|
|
}
|
|
|
|
// ── Mouse look ────────────────────────────────────────────────────────────
|
|
void HandleLook()
|
|
{
|
|
var input = PlayerInput.Instance;
|
|
if (input != null)
|
|
{
|
|
_headYaw += input.LookInput.x * mouseSensitivity;
|
|
_pitch -= input.LookInput.y * mouseSensitivity;
|
|
}
|
|
_pitch = Mathf.Clamp(_pitch, -maxPitch, maxPitch);
|
|
}
|
|
|
|
// ── Movement ──────────────────────────────────────────────────────────────
|
|
void HandleMovement()
|
|
{
|
|
var input = PlayerInput.Instance;
|
|
float h = input != null ? input.MoveInput.x : 0f;
|
|
float v = input != null ? input.MoveInput.y : 0f;
|
|
|
|
bool isCrouching = input != null && input.Crouch;
|
|
bool isRunning = input != null && input.Run && !isCrouching;
|
|
bool isMoving = (h * h + v * v) > 0.01f;
|
|
|
|
float speed = isCrouching ? crouchSpeed : isRunning ? runSpeed : walkSpeed;
|
|
|
|
// Body aligns toward camera when moving
|
|
if (isMoving)
|
|
_bodyYaw = Mathf.MoveTowardsAngle(_bodyYaw, _headYaw, bodyAlignSpeed * Time.deltaTime);
|
|
|
|
// Head-drag clamp
|
|
float offset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
|
|
float clampedOffset = Mathf.Clamp(offset, -maxHeadYaw, maxHeadYaw);
|
|
_bodyYaw += offset - clampedOffset;
|
|
_headYaw = _bodyYaw + clampedOffset;
|
|
|
|
// Jump
|
|
if (_cc.isGrounded)
|
|
{
|
|
if (_verticalVelocity < 0f) _verticalVelocity = -2f;
|
|
|
|
if (_jumpRequested && !isCrouching)
|
|
{
|
|
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
|
if (_anim != null) _anim.SetTrigger(HashJump);
|
|
}
|
|
_jumpRequested = false;
|
|
}
|
|
|
|
_verticalVelocity += gravity * Time.deltaTime;
|
|
|
|
// Move
|
|
Quaternion headRot = Quaternion.Euler(0f, _headYaw, 0f);
|
|
Vector3 moveDir = (headRot * Vector3.forward * v +
|
|
headRot * Vector3.right * h).normalized;
|
|
moveDir.y = _verticalVelocity;
|
|
_cc.Move(moveDir * speed * Time.deltaTime);
|
|
|
|
// Animator
|
|
if (_anim != null)
|
|
{
|
|
float animSpeed = !isMoving ? 0f : isRunning ? 1f : 0.5f;
|
|
_anim.SetFloat(HashSpeed, animSpeed, 0.1f, Time.deltaTime);
|
|
_anim.SetBool (HashIsMoving, isMoving);
|
|
_anim.SetBool (HashIsCrouching, isCrouching);
|
|
_anim.SetBool (HashIsGrounded, _cc.isGrounded);
|
|
}
|
|
}
|
|
|
|
// ── Apply bone rotations ──────────────────────────────────────────────────
|
|
void ApplyRotations()
|
|
{
|
|
transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f);
|
|
|
|
if (head == null) return;
|
|
|
|
float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
|
|
Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f);
|
|
Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f);
|
|
head.localRotation = _headBindPose * yawRot * pitchRot;
|
|
}
|
|
}
|