using Unity.Netcode; using UnityEngine; namespace AfterHours.Networking { /// /// Spielerbewegung mit Client-Prediction: der Owner bewegt sich sofort, /// NetworkTransform repliziert. Bewegung ist der EINZIGE Bereich, in dem /// wir dem Client vertrauen – siehe CLAUDE.md §3. /// [RequireComponent(typeof(CharacterController))] public sealed class PlayerAvatar : NetworkBehaviour { [SerializeField] private float _walkSpeed = 3.4f; [SerializeField] private float _mouseSensitivity = 2f; [SerializeField] private Transform _cameraRoot; [SerializeField] private float _gravity = -12f; private CharacterController _controller; private float _pitch; private float _verticalVelocity; private void Awake() => _controller = GetComponent(); public override void OnNetworkSpawn() { if (!IsOwner) { if (_cameraRoot != null && _cameraRoot.GetComponentInChildren() is { } cam) cam.gameObject.SetActive(false); enabled = false; return; } Cursor.lockState = CursorLockMode.Locked; } private void Update() { if (!IsOwner) return; Look(); Move(); } private void Look() { float mouseX = Input.GetAxis("Mouse X") * _mouseSensitivity; float mouseY = Input.GetAxis("Mouse Y") * _mouseSensitivity; transform.Rotate(Vector3.up, mouseX); _pitch = Mathf.Clamp(_pitch - mouseY, -85f, 85f); if (_cameraRoot != null) _cameraRoot.localRotation = Quaternion.Euler(_pitch, 0f, 0f); } private void Move() { Vector3 input = new(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")); Vector3 direction = transform.TransformDirection(Vector3.ClampMagnitude(input, 1f)); _verticalVelocity = _controller.isGrounded ? -1f : _verticalVelocity + _gravity * Time.deltaTime; Vector3 velocity = direction * _walkSpeed + Vector3.up * _verticalVelocity; _controller.Move(velocity * Time.deltaTime); } } }