156 lines
5.4 KiB
GDScript
156 lines
5.4 KiB
GDScript
extends CharacterBody3D
|
|
|
|
## 跳跃输入缓冲(秒):按下跳跃后这段时间内若落地,自动补上一次起跳
|
|
const JUMP_BUFFER_TIME: float = 0.1
|
|
|
|
@export_group("视角")
|
|
## 鼠标灵敏度(弧度/像素)
|
|
@export var mouse_sensitivity: float = 0.0025
|
|
## 最低俯角(向下看)
|
|
@export var pitch_min_deg: float = -89.0
|
|
## 最高仰角(向上看)
|
|
@export var pitch_max_deg: float = 89.0
|
|
|
|
@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
|
|
|
|
@onready var body: Node3D = $Body
|
|
## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
|
|
## 分开是为了让 pitch 不掺进移动方向 —— 移动只用 rig 的 basis,低头看地也不会走歪。
|
|
@onready var camera_rig: Node3D = $CameraRig
|
|
@onready var camera: Camera3D = $CameraRig/Camera
|
|
## 动画状态机。控制器只把「运动结果」交给它,它也只管自己播动画。
|
|
@onready var animator: PlayerAnimator = $Body/AnimationPlayer
|
|
@onready var interaction_pos: Marker3D = $CameraRig/Camera/InteractionPos
|
|
@onready var interaction_ray: RayCast3D = $CameraRig/Camera/InteractionRay
|
|
|
|
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: float = 0.0
|
|
# 拾取
|
|
var _pickObj = null
|
|
var _pickPower = 10
|
|
|
|
|
|
func _ready() -> void:
|
|
# 远端玩家只是一具「回放身体」:
|
|
# 拆掉相机 —— 否则每多一个玩家就多一台 Camera3D 抢渲染;
|
|
# 关掉输入与物理 —— 否则与权威端双重模拟,表现为抖动、漂移、穿地。
|
|
# 它的 position 与 Body.rotation 由 Sync 子节点同步;血条由 health 同步。
|
|
if not is_multiplayer_authority():
|
|
camera_rig.queue_free()
|
|
set_physics_process(false)
|
|
set_process_unhandled_input(false)
|
|
return
|
|
|
|
_yaw = camera_rig.rotation.y
|
|
_pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
|
|
_apply_look()
|
|
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)
|
|
)
|
|
_apply_look()
|
|
elif event.is_action_pressed("jump"):
|
|
_jump_buffer = JUMP_BUFFER_TIME
|
|
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)
|
|
|
|
if event is InputEventMouseButton and event.is_action_pressed("carry"):
|
|
if _pickObj == null:
|
|
_pickObject()
|
|
else:
|
|
_removePickObject()
|
|
|
|
func set_mouse_captured(captured: bool) -> void:
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if captured else Input.MOUSE_MODE_VISIBLE
|
|
|
|
|
|
## 把 _yaw / _pitch 落到节点上。鼠标一动就调一次(渲染帧),所以视角跟手、不等物理帧。
|
|
## 身体跟着一起转 —— 第一人称里「你看哪,身体就朝哪」,别人看你也是这个朝向。
|
|
func _apply_look() -> void:
|
|
camera_rig.rotation.y = _yaw
|
|
camera.rotation.x = _pitch
|
|
body.rotation.y = _yaw
|
|
|
|
|
|
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.0:
|
|
_jump_buffer -= delta
|
|
if is_on_floor():
|
|
velocity.y = jump_velocity
|
|
_jump_buffer = 0.0
|
|
|
|
# --- 相对视线方向的水平输入 ---
|
|
# 只用 rig 的 basis(只有 yaw),低头看地时前进方向不会跟着往下扎。
|
|
var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
|
|
var look_basis := camera_rig.global_transform.basis
|
|
var wish := look_basis.x * input_dir.x - look_basis.z * input_dir.y
|
|
wish.y = 0.0
|
|
if wish.length_squared() > 0.000001:
|
|
wish = wish.normalized()
|
|
|
|
# 只有真正按了方向键才允许疾跑(避免原地"疾跑"触发跑动动画)
|
|
var sprinting := Input.is_action_pressed("sprint") and input_dir.length_squared() > 0.000001
|
|
var target_speed := sprint_speed if sprinting else walk_speed
|
|
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) * target_speed, accel * delta)
|
|
velocity.x = horizontal.x
|
|
velocity.z = horizontal.y
|
|
|
|
move_and_slide()
|
|
|
|
# 动画只读运动结果。控制器不认识任何片段名,动画器也不认识输入。
|
|
animator.set_motion(
|
|
velocity,
|
|
is_on_floor(),
|
|
sprinting,
|
|
Input.is_action_pressed("crouch"),
|
|
)
|
|
|
|
# 物体拖动
|
|
if _pickObj != null:
|
|
var pickObjPos = _pickObj.global_position
|
|
var pickPlacePos = interaction_pos.global_position
|
|
_pickObj.set_linear_velocity((pickPlacePos - pickObjPos) * _pickPower)
|
|
|
|
|
|
func _pickObject() -> void:
|
|
var pickCollider = interaction_ray.get_collider()
|
|
print(pickCollider)
|
|
if pickCollider != null and pickCollider is RigidBody3D:
|
|
print(pickCollider)
|
|
_pickObj = pickCollider
|
|
|
|
func _removePickObject() -> void:
|
|
_pickObj = null
|