完善客户端功能并加入40250一致性审计

This commit is contained in:
shen
2026-09-21 16:38:58 -07:00
parent 3eb001a1fd
commit 1522a8a1a1
22 changed files with 876 additions and 105 deletions
+64 -22
View File
@@ -65,6 +65,13 @@ var rotation_speed_deg := ROT_SPEED_DEFAULT_DEG # 上/下马时由 net_play 切
# CActorInstance::CanSkipCollision():网络状态包驱动的移动整段跳过 Actor 碰撞
# (§3.2 __EnableSkipCollision,观战 / 服务器纠正时置真)。
var skip_actor_collision := false
# 40250 CPythonPlayer::NEW_SetSingleDirKeyState:方向由进入游戏后的按下/松开事件
# 维护,不能每帧查询系统全局键盘状态。否则加载场景时仍按着的键会被继承,表现为
# 刚进入游戏角色就自动移动。
var _key_up := false
var _key_down := false
var _key_left := false
var _key_right := false
var _last_wasd := Vector2.ZERO
var _mobile_axis := Vector2.ZERO
var _mobile_active := false
@@ -79,13 +86,21 @@ var _dst_rot := 0.0
var _reserved_ground: Variant = null # __ReserveClickGround 的待处理点
var _reserved_delay_time := 0.0 # NEW_IsEmptyReservedDelayTime 递减到 0 才生效
var active := true # 进游戏装配/加载中置假,防止过早响应点地
var active := true: # 进游戏装配/加载中置假,防止过早响应点地
set(value):
if active == value:
return
active = value
# 切换输入阶段相当于 40250 game.Close/Open 重建按键状态。只有在游戏窗
# 激活后新收到的 KeyDown 才能驱动角色。
_clear_direction_keys()
# 40250 CInstanceBase::NEW_Stop() / CPythonPlayer::NEW_Stop():停止点地移动与方向输入,重置预约
func stop() -> void:
_is_going = false
_reserved_ground = null
_reserved_delay_time = 0.0
_last_wasd = Vector2.ZERO
_clear_direction_keys()
_mobile_active = false
_mobile_axis = Vector2.ZERO
_mobile_ui_touches.clear()
@@ -97,6 +112,39 @@ func stop() -> void:
_dst_pos = player.position
anim_state.emit("wait")
func _clear_direction_keys() -> void:
_key_up = false
_key_down = false
_key_left = false
_key_right = false
func _notification(what: int) -> void:
if what == NOTIFICATION_APPLICATION_FOCUS_OUT:
# KeyUp 可能在窗口失焦期间丢失;参考客户端失去游戏窗口输入后停止移动。
_clear_direction_keys()
_last_wasd = Vector2.ZERO
func _input(e: InputEvent) -> void:
if not (e is InputEventKey) or e.echo:
return
var key := e as InputEventKey
# 松开事件必须始终接收,即使此时 UI 正在拦截游戏输入,否则会留下粘键。
# KeyDown 则在 _unhandled_input 中接收,让聊天输入框有机会先消费 WASD。
if not key.pressed:
_set_direction_key_state(key, false)
func _set_direction_key_state(e: InputEventKey, pressed: bool) -> void:
var logical := e.keycode
var physical := e.physical_keycode
if logical in [KEY_W, KEY_UP] or physical in [KEY_W, KEY_UP]:
_key_up = pressed
elif logical in [KEY_S, KEY_DOWN] or physical in [KEY_S, KEY_DOWN]:
_key_down = pressed
elif logical in [KEY_A, KEY_LEFT] or physical in [KEY_A, KEY_LEFT]:
_key_left = pressed
elif logical in [KEY_D, KEY_RIGHT] or physical in [KEY_D, KEY_RIGHT]:
_key_right = pressed
# CInstanceBase::__IsSyncing()Dead / Stun / Pushing 中不接受移动输入(net_play 置 frozen)。
func is_going() -> bool:
return _is_going
@@ -184,7 +232,11 @@ func _goto(dst_flat: Vector3) -> bool:
func _unhandled_input(e: InputEvent) -> void:
if not active:
return
if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and e.pressed:
if e is InputEventKey:
var key := e as InputEventKey
if key.pressed and not key.echo:
_set_direction_key_state(key, true)
elif e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and e.pressed:
_on_click(e.position)
elif e is InputEventScreenTouch:
_on_touch(e)
@@ -326,26 +378,17 @@ func _ray_ground(from: Vector3, dir: Vector3) -> Variant:
return null
func _wasd() -> Vector2:
if ui_manager and ui_manager.has_method("blocks_game_input"):
var dummy := InputEventKey.new()
dummy.keycode = KEY_W
if ui_manager.blocks_game_input(dummy):
return Vector2.ZERO
var is_up := Input.is_key_pressed(KEY_W) or Input.is_physical_key_pressed(KEY_W) or Input.is_key_pressed(KEY_UP)
var is_down := Input.is_key_pressed(KEY_S) or Input.is_physical_key_pressed(KEY_S) or Input.is_key_pressed(KEY_DOWN)
var is_left := Input.is_key_pressed(KEY_A) or Input.is_physical_key_pressed(KEY_A) or Input.is_key_pressed(KEY_LEFT)
var is_right := Input.is_key_pressed(KEY_D) or Input.is_physical_key_pressed(KEY_D) or Input.is_key_pressed(KEY_RIGHT)
if not (is_up or is_down or is_left or is_right):
if not (_key_up or _key_down or _key_left or _key_right):
return Vector2.ZERO
var d := Vector2.ZERO
# 对齐 40250 CPythonPlayer::NEW_GetMultiKeyDirRotation: Up优先于DownLeft优先于Right
if is_up:
if _key_up:
d.y = -1.0
elif is_down:
elif _key_down:
d.y = 1.0
if is_left:
if _key_left:
d.x = -1.0
elif is_right:
elif _key_right:
d.x = 1.0
return d.normalized()
@@ -396,18 +439,17 @@ func _actor_blocked(next_pos: Vector3) -> bool:
func _turn_toward(target_yaw: float, dt: float) -> void:
if player == null:
return
player.rotation.x = 0.0
player.rotation.z = 0.0
var cur_yaw: float = wrapf(player.rotation.y, -PI, PI)
var diff: float = wrapf(target_yaw - cur_yaw, -PI, PI)
var spd := rotation_speed_deg
if absf(diff) <= deg_to_rad(45.0):
spd *= 5.0 / 12.0
var max_step := deg_to_rad(spd) * dt
if max_step <= 0.0 or absf(diff) <= max_step:
player.rotation.y = target_yaw
else:
player.rotation.y = wrapf(cur_yaw + signf(diff) * max_step, -PI, PI)
var next_yaw := target_yaw if max_step <= 0.0 or absf(diff) <= max_step \
else wrapf(cur_yaw + signf(diff) * max_step, -PI, PI)
# 只允许直立偏航。逐项修改 Euler 分量在 W/S 180° 快切和 ±PI 边界附近可能
# 被引擎重新分解为 X/Z = PI,看起来就是角色翻跟头。
player.rotation = Vector3(0.0, wrapf(next_yaw, -PI, PI), 0.0)
func _process(dt: float) -> void:
if player == null or not active: