组件模型
This commit is contained in:
+38
-124
@@ -1,45 +1,21 @@
|
||||
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
|
||||
## 玩家协调器:只做三件事 —— 多人权限、依赖注入、按固定顺序编排各组件。
|
||||
##
|
||||
## 具体能力全在子节点组件里:
|
||||
## Look 视角 character/player/player_look.gd
|
||||
## Motor 移动跳跃 character/player/player_motor.gd
|
||||
## Pickup 拾取 character/player/player_pickup.gd
|
||||
## Animator 动画表现 character/player/player_animator.gd(挂在 Body/AnimationPlayer 上)
|
||||
##
|
||||
## 本脚本不实现任何操作,只负责「谁先谁后」和「谁有权跑」。
|
||||
|
||||
@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
|
||||
@onready var animation_player: PlayerAnimator = $AnimationPlayer
|
||||
@onready var look: PlayerLook = $Look
|
||||
@onready var motor: PlayerMotor = $Motor
|
||||
@onready var pickup: PlayerPickup = $Pickup
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -47,109 +23,47 @@ 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)
|
||||
look.set_process_unhandled_input(false)
|
||||
motor.set_process_unhandled_input(false)
|
||||
pickup.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()
|
||||
# 依赖注入:组件不自己去 get_node,由这里接线。
|
||||
# 好处是组件不认识玩家的场景结构,换场景 / 单测时注入别的节点即可。
|
||||
look.setup(camera_rig, camera, body)
|
||||
motor.setup(self, camera_rig)
|
||||
pickup.setup(
|
||||
camera.get_node("InteractionRay") as RayCast3D,
|
||||
camera.get_node("InteractionPos") as Marker3D
|
||||
)
|
||||
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"):
|
||||
# 这里只留「跨组件的全局开关」,具体操作归各组件自己处理。
|
||||
if 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:
|
||||
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
|
||||
|
||||
# 顺序固定:算速度 → 推进 → 播动画 → 搬物体。
|
||||
# 由根节点统一编排,各组件自己不许写 _physics_process ——
|
||||
# 否则顺序取决于节点树顺序,改个节点位置就会出现「先滑再算速度」这类隐形 bug。
|
||||
motor.step(delta)
|
||||
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
|
||||
animation_player.set_motion(velocity, is_on_floor(), motor.sprinting, motor.crouching)
|
||||
pickup.physics_step()
|
||||
|
||||
Reference in New Issue
Block a user