extends CharacterBody3D ## 第三人称控制器(胶囊体占位) ## ## 场景结构: ## Player (CharacterBody3D) ← 只负责移动与碰撞,自身不旋转 ## ├─ Collision (CollisionShape3D, CapsuleShape3D) ## ├─ Body (Node3D) ← 视觉层,朝移动方向转身;换角色模型时挂在它下面 ## │ └─ Mesh (MeshInstance3D) ## └─ CameraRig (Node3D) ← 承担偏航 yaw,独立于本体旋转 ## └─ SpringArm (SpringArm3D) ← 承担俯仰 pitch,自动避障 ## └─ Camera (Camera3D) ## ## 操作:WASD/方向键移动,鼠标转视角,Shift 疾跑,空格跳跃,Esc 释放/锁定鼠标 ## 跳跃输入缓冲帧数(输入事件在渲染帧到达,物理帧读取,避免漏跳) const JUMP_BUFFER_FRAMES: int = 6 @export_group("移动") ## 常规移动速度(米/秒) @export var walk_speed: float = 5.0 ## 疾跑速度(米/秒) @export var sprint_speed: float = 8.5 ## 地面加速度(米/秒²) @export var acceleration: float = 45.0 ## 空中加速度(米/秒²) @export var air_acceleration: float = 12.0 ## 起跳初速度(米/秒) @export var jump_velocity: float = 5.0 ## 视觉层转身平滑速度 @export var turn_speed: float = 10.0 @export_group("相机") ## 鼠标灵敏度(弧度/像素) @export var mouse_sensitivity: float = 0.0025 ## 最低俯角(向下看) @export var pitch_min_deg: float = -80.0 ## 最高仰角(向上看) @export var pitch_max_deg: float = 45.0 @onready var body: Node3D = $Body @onready var camera_rig: Node3D = $CameraRig @onready var spring_arm: SpringArm3D = $CameraRig/SpringArm var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8)) var _yaw: float = 0.0 var _pitch: float = 0.0 var _jump_buffer: int = 0 func _ready() -> void: _yaw = camera_rig.rotation.y _pitch = clampf(spring_arm.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg)) spring_arm.rotation.x = _pitch set_mouse_captured(true) func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: _yaw -= event.relative.x * mouse_sensitivity _pitch = clampf( _pitch - event.relative.y * mouse_sensitivity, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg) ) camera_rig.rotation.y = _yaw spring_arm.rotation.x = _pitch elif event.is_action_pressed("jump"): _jump_buffer = JUMP_BUFFER_FRAMES elif event.is_action_pressed("ui_cancel"): set_mouse_captured(Input.mouse_mode != Input.MOUSE_MODE_CAPTURED) elif event is InputEventMouseButton and event.pressed and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: set_mouse_captured(true) func set_mouse_captured(captured: bool) -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if captured else Input.MOUSE_MODE_VISIBLE func _physics_process(delta: float) -> void: # --- 重力 --- if is_on_floor(): if velocity.y < 0.0: velocity.y = 0.0 else: velocity.y -= _gravity * delta # --- 跳跃(读取输入缓冲)--- if _jump_buffer > 0: _jump_buffer -= 1 if is_on_floor(): velocity.y = jump_velocity _jump_buffer = 0 # --- 相对相机的水平输入 --- var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward") var cam_basis := camera_rig.global_transform.basis var wish := cam_basis.x * input_dir.x + (-cam_basis.z) * input_dir.y wish.y = 0.0 var wants_move := wish.length() > 0.001 if wants_move: wish = wish.normalized() # 只转视觉层,不动本体,也不影响相机(Godot 前进方向为 -Z) var target_yaw := atan2(-wish.x, -wish.z) body.rotation.y = lerp_angle(body.rotation.y, target_yaw, clampf(turn_speed * delta, 0.0, 1.0)) var speed := sprint_speed if Input.is_action_pressed("sprint") else walk_speed if not wants_move: speed = 0.0 var accel := acceleration if is_on_floor() else air_acceleration var horizontal := Vector2(velocity.x, velocity.z) horizontal = horizontal.move_toward(Vector2(wish.x, wish.z) * speed, accel * delta) velocity.x = horizontal.x velocity.z = horizontal.y move_and_slide()