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(); _anim = GetComponentInChildren(); 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.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(); 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; } }