ai实现基础通话功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
class_name Body
|
||||
#class_name Body
|
||||
extends Node3D
|
||||
|
||||
# 线性插值速度
|
||||
const LERP_VELOCITY: float = 0.15
|
||||
|
||||
@export_category("Objects")
|
||||
|
||||
+32
-32
@@ -3,17 +3,17 @@ extends CharacterBody3D
|
||||
|
||||
enum SkinColor { BLUE, YELLOW, GREEN, RED }
|
||||
|
||||
const NORMAL_SPEED = 6.0
|
||||
const SPRINT_SPEED = 10.0
|
||||
const JUMP_VELOCITY = 7.5
|
||||
const FALL_GRAVITY_MULTIPLIER = 1.6
|
||||
const BASE_NICKNAME_HEIGHT := 2.0
|
||||
const SERVER_ANIMATION_REQUESTS_PER_SECOND := 10.0
|
||||
const SERVER_ANIMATION_REQUEST_BURST := 6.0
|
||||
const PICKUP_ANIMATION_DELAY_MSEC := 1000
|
||||
const PICKUP_ANIMATION_WINDOW_MSEC := 2500
|
||||
const PICKUP_REQUEST_COOLDOWN_MSEC := 1000
|
||||
const ALLOWED_ANIMATION_STATES := {
|
||||
@export var 正常速度 = 6.0
|
||||
@export var 冲刺速度 = 10.0
|
||||
@export var 跳跃初速度 = 7.5
|
||||
@export var 下落重力倍率 = 1.6
|
||||
@export var 昵称基准高度 := 2.0
|
||||
@export var 服务器每秒动画请求数 := 10.0
|
||||
@export var 服务器动画请求突发上限 := 6.0
|
||||
@export var 拾取动画延迟毫秒 := 1000
|
||||
@export var 拾取动画时间窗口毫秒 := 2500
|
||||
@export var 拾取请求冷却毫秒 := 1000
|
||||
const 允许动画状态 := {
|
||||
&"Idle": true,
|
||||
&"Run": true,
|
||||
&"Sprint": true,
|
||||
@@ -66,10 +66,10 @@ var _animation_sequence := 0
|
||||
var _last_applied_animation_sequence := 0
|
||||
var _last_requested_animation: StringName = &""
|
||||
var _appearance_sync_requesters: Dictionary = {}
|
||||
var _server_animation_request_tokens := SERVER_ANIMATION_REQUEST_BURST
|
||||
var _server_animation_request_tokens := 服务器动画请求突发上限
|
||||
var _last_server_animation_token_update_msec := 0
|
||||
var _server_pickup_animation_started_msec := -1
|
||||
var _last_server_pickup_request_msec := -PICKUP_REQUEST_COOLDOWN_MSEC
|
||||
var _last_server_pickup_request_msec := -拾取请求冷却毫秒
|
||||
var _pickup_area_camera_yaw_offset := 0.0
|
||||
var _equipped_hat_visual_id := ""
|
||||
|
||||
@@ -103,7 +103,7 @@ func _ready():
|
||||
animation_player.animation_finished.connect(_on_animation_finished)
|
||||
_body.play_animation_state(&"Idle")
|
||||
nickname.visible = true
|
||||
_set_nickname_height(BASE_NICKNAME_HEIGHT)
|
||||
_set_nickname_height(昵称基准高度)
|
||||
call_deferred("_update_nickname_height")
|
||||
if not multiplayer.is_server():
|
||||
call_deferred("_request_equipment_appearance")
|
||||
@@ -194,14 +194,14 @@ func _physics_process(delta):
|
||||
has_double_jumped = false
|
||||
|
||||
if Input.is_action_just_pressed("jump"):
|
||||
velocity.y = JUMP_VELOCITY
|
||||
velocity.y = 跳跃初速度
|
||||
can_double_jump = true
|
||||
_request_animation(&"Jump", true)
|
||||
else:
|
||||
_apply_gravity(delta)
|
||||
|
||||
if can_double_jump and not has_double_jumped and Input.is_action_just_pressed("jump"):
|
||||
velocity.y = JUMP_VELOCITY
|
||||
velocity.y = 跳跃初速度
|
||||
has_double_jumped = true
|
||||
can_double_jump = false
|
||||
_request_animation(&"Jump2", true)
|
||||
@@ -217,7 +217,7 @@ func _physics_process(delta):
|
||||
func _apply_gravity(delta: float) -> void:
|
||||
if is_on_floor():
|
||||
return
|
||||
var gravity_multiplier = FALL_GRAVITY_MULTIPLIER if velocity.y < 0 else 1.0
|
||||
var gravity_multiplier = 下落重力倍率 if velocity.y < 0 else 1.0
|
||||
velocity.y -= gravity * gravity_multiplier * delta
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ func _on_animation_finished(animation_name: StringName) -> void:
|
||||
|
||||
|
||||
func _request_animation(state: StringName, restart: bool = false) -> void:
|
||||
if not ALLOWED_ANIMATION_STATES.has(state):
|
||||
if not 允许动画状态.has(state):
|
||||
return
|
||||
if not restart and _last_requested_animation == state:
|
||||
return
|
||||
@@ -255,7 +255,7 @@ func _request_animation(state: StringName, restart: bool = false) -> void:
|
||||
func request_animation_state(state: StringName) -> void:
|
||||
if not multiplayer.is_server() or not _is_owner_request():
|
||||
return
|
||||
if not ALLOWED_ANIMATION_STATES.has(state):
|
||||
if not 允许动画状态.has(state):
|
||||
return
|
||||
if not _server_consume_animation_request_token():
|
||||
return
|
||||
@@ -275,8 +275,8 @@ func _server_consume_animation_request_token() -> bool:
|
||||
else:
|
||||
var elapsed_seconds := (now - _last_server_animation_token_update_msec) / 1000.0
|
||||
_server_animation_request_tokens = minf(
|
||||
SERVER_ANIMATION_REQUEST_BURST,
|
||||
_server_animation_request_tokens + elapsed_seconds * SERVER_ANIMATION_REQUESTS_PER_SECOND
|
||||
服务器动画请求突发上限,
|
||||
_server_animation_request_tokens + elapsed_seconds * 服务器每秒动画请求数
|
||||
)
|
||||
_last_server_animation_token_update_msec = now
|
||||
|
||||
@@ -303,7 +303,7 @@ func sync_animation_state(state: StringName, sequence: int) -> void:
|
||||
return
|
||||
_last_applied_animation_sequence = sequence
|
||||
|
||||
if not ALLOWED_ANIMATION_STATES.has(state):
|
||||
if not 允许动画状态.has(state):
|
||||
return
|
||||
|
||||
# The owner already played the input-driven state immediately.
|
||||
@@ -366,9 +366,9 @@ func _move() -> void:
|
||||
|
||||
func _is_running() -> bool:
|
||||
if Input.is_action_pressed("shift"):
|
||||
_current_speed = SPRINT_SPEED
|
||||
_current_speed = 冲刺速度
|
||||
return true
|
||||
_current_speed = NORMAL_SPEED
|
||||
_current_speed = 正常速度
|
||||
return false
|
||||
|
||||
|
||||
@@ -704,12 +704,12 @@ func _update_nickname_height(hat_id: String = "") -> void:
|
||||
|
||||
|
||||
func _calculate_nickname_height(hat_id: String) -> float:
|
||||
var target_height := BASE_NICKNAME_HEIGHT
|
||||
var target_height := 拾取动画延迟毫秒
|
||||
var equipped_hat := _get_hat_node(hat_id)
|
||||
if equipped_hat:
|
||||
var hat_top := _get_visual_top(equipped_hat)
|
||||
if hat_top > -INF and hat_top < INF:
|
||||
target_height = max(BASE_NICKNAME_HEIGHT, hat_top + nickname_clearance)
|
||||
target_height = max(拾取动画延迟毫秒, hat_top + nickname_clearance)
|
||||
return target_height
|
||||
|
||||
|
||||
@@ -724,9 +724,9 @@ func apply_synced_nickname_height(height: float) -> void:
|
||||
return
|
||||
nickname.visible = not _is_local_first_person()
|
||||
if height > -INF and height < INF:
|
||||
_set_nickname_height(maxf(BASE_NICKNAME_HEIGHT, height))
|
||||
_set_nickname_height(maxf(昵称基准高度, height))
|
||||
else:
|
||||
_set_nickname_height(BASE_NICKNAME_HEIGHT)
|
||||
_set_nickname_height(昵称基准高度)
|
||||
|
||||
|
||||
func _is_local_first_person() -> bool:
|
||||
@@ -734,7 +734,7 @@ func _is_local_first_person() -> bool:
|
||||
|
||||
|
||||
func get_current_nickname_height() -> float:
|
||||
return nickname.position.y if nickname else BASE_NICKNAME_HEIGHT
|
||||
return nickname.position.y if nickname else 昵称基准高度
|
||||
|
||||
|
||||
func _get_hat_node(hat_id: String) -> Node3D:
|
||||
@@ -808,15 +808,15 @@ func request_pickup() -> void:
|
||||
if not multiplayer.is_server() or not _is_owner_request():
|
||||
return
|
||||
var now := Time.get_ticks_msec()
|
||||
if now - _last_server_pickup_request_msec < PICKUP_REQUEST_COOLDOWN_MSEC:
|
||||
if now - _last_server_pickup_request_msec < 拾取请求冷却毫秒:
|
||||
return
|
||||
_last_server_pickup_request_msec = now
|
||||
|
||||
var animation_elapsed := now - _server_pickup_animation_started_msec
|
||||
if (
|
||||
_server_pickup_animation_started_msec < 0
|
||||
or animation_elapsed < PICKUP_ANIMATION_DELAY_MSEC
|
||||
or animation_elapsed > PICKUP_ANIMATION_WINDOW_MSEC
|
||||
or animation_elapsed < 拾取动画时间窗口毫秒
|
||||
or animation_elapsed > 拾取动画时间窗口毫秒
|
||||
):
|
||||
return
|
||||
if not _is_grounded_on_server():
|
||||
|
||||
@@ -28,6 +28,12 @@ func _ready() -> void:
|
||||
_pitch = _spring_arm.rotation.x if _spring_arm else 0.0
|
||||
_apply_axis_lock()
|
||||
_apply_perspective()
|
||||
|
||||
# 进入直接切换第一人称
|
||||
is_first_person = not is_first_person
|
||||
_apply_perspective()
|
||||
perspective_changed.emit(is_first_person)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
@@ -45,12 +51,12 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
if current_scene.is_camera_input_blocked():
|
||||
return
|
||||
|
||||
if event.is_action_pressed("toggle_camera"):
|
||||
is_first_person = not is_first_person
|
||||
_apply_perspective()
|
||||
perspective_changed.emit(is_first_person)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
#if event.is_action_pressed("toggle_camera"):
|
||||
#is_first_person = not is_first_person
|
||||
#_apply_perspective()
|
||||
#perspective_changed.emit(is_first_person)
|
||||
#get_viewport().set_input_as_handled()
|
||||
#return
|
||||
|
||||
if not (event is InputEventMouseMotion):
|
||||
return
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
extends Node
|
||||
## 最简近距离语音:按住 C(talk)采集麦克风 → 广播给所有对端 →
|
||||
## 在说话者位置的 RaytracedAudioPlayer3D 回放。
|
||||
## 距离衰减/声像由 3D 音频完成,墙体闷音、环境混响由 raytraced_audio 插件完成。
|
||||
## 依赖:default_bus_layout.tres 的 mic 总线(静音 + AudioEffectCapture)、
|
||||
## RaytracedReverb/RaytracedAmbient 总线(插件用)、player.tscn 根节点下的 Voice 节点。
|
||||
|
||||
@export var max_distance := 25.0
|
||||
|
||||
var _capture: AudioEffectCapture
|
||||
var _voice_3d: RaytracedAudioPlayer3D
|
||||
var _playback: AudioStreamGeneratorPlayback
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_capture = AudioServer.get_bus_effect(AudioServer.get_bus_index("mic"), 0)
|
||||
_voice_3d = RaytracedAudioPlayer3D.new()
|
||||
var gen := AudioStreamGenerator.new()
|
||||
gen.mix_rate = 48000.0 # 与 audio/driver/mix_rate 一致,否则变速变调
|
||||
_voice_3d.stream = gen
|
||||
_voice_3d.position = Vector3(0, 1.5, 0) # 嘴部高度
|
||||
_voice_3d.max_distance = max_distance
|
||||
add_child(_voice_3d)
|
||||
if is_multiplayer_authority() and DisplayServer.get_name() != "headless":
|
||||
var mic := AudioStreamPlayer.new()
|
||||
mic.stream = AudioStreamMicrophone.new()
|
||||
mic.bus = "mic"
|
||||
add_child(mic)
|
||||
mic.play()
|
||||
# 监听器只建在本地玩家身上(多个监听器会互相抢 current)
|
||||
var listener := RaytracedAudioListener.new()
|
||||
listener.position = Vector3(0, 1.6, 0) # 头部高度
|
||||
add_child(listener)
|
||||
listener.owner = get_parent() # 插件的 ambient 计算会读 owner,代码创建的节点必须手动指定
|
||||
else:
|
||||
_voice_3d.play()
|
||||
_playback = _voice_3d.get_stream_playback()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not is_multiplayer_authority() or _capture == null:
|
||||
return
|
||||
if not Input.is_action_pressed("talk"):
|
||||
_capture.clear_buffer() # 松开时丢弃积压,避免下次开口带旧音频
|
||||
return
|
||||
var frames := _capture.get_buffer(_capture.get_frames_available())
|
||||
if not frames.is_empty():
|
||||
receive_voice.rpc(frames)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "unreliable_ordered", 1)
|
||||
func receive_voice(frames: PackedVector2Array) -> void:
|
||||
if not _playback:
|
||||
_playback = _voice_3d.get_stream_playback()
|
||||
_playback.push_buffer(frames)
|
||||
@@ -0,0 +1 @@
|
||||
uid://3i8mgirdlpli
|
||||
Reference in New Issue
Block a user