完善客户端功能并同步差距文档
This commit is contained in:
+192
-27
@@ -15,6 +15,11 @@ signal moved(pos: Vector3)
|
||||
var player: Node3D
|
||||
var camera: Camera3D
|
||||
var world: Node
|
||||
var cursor_manager: Node
|
||||
var ui_manager: Node
|
||||
var ground_items: Node
|
||||
var cancel_fishing_input := Callable() # NEW_SetSingleDIKKeyState 等价入口
|
||||
var cancel_fishing_ground := Callable() # __OnPressGround 专用入口
|
||||
var pickables: Array[Node3D] = [] # 可点选实体(NPC/怪等)
|
||||
|
||||
const SPEED_WALK := 8.0
|
||||
@@ -22,16 +27,53 @@ const SPEED_RUN := 18.0
|
||||
const RUN_HOLD_KEY := KEY_SHIFT
|
||||
const ARRIVE_EPS := 0.4
|
||||
const PICK_RADIUS := 1.4 # 点选命中半径(米)
|
||||
# __IsMovableGroundDistance:点地目标离脚下太近就不动(避免原地抖)。参考默认由
|
||||
# player.SetMovableGroundDistance 从 game.py 设置;此处用参考缺省值(~1 m)。§3.1
|
||||
const MOVABLE_GROUND_DISTANCE_M := 1.0
|
||||
# __ReserveClickGround 设的 m_fReservedDelayTime(PythonPlayerInput.cpp:834)。
|
||||
const RESERVED_GROUND_DELAY := 0.1
|
||||
# Actor-Actor 碰撞(对齐 CInstanceBase::CheckAdvancing,InstanceBaseBattle.cpp:469 +
|
||||
# CActorInstance::TestActorCollision,ActorInstanceCollisionDetection.cpp)。参考用 race
|
||||
# 碰撞球,POC 无骨骼碰撞数据 → 固定 body 半径近似;自身 + 对方两球相加为判定阈值。§3.1
|
||||
const ACTOR_BODY_RADIUS_M := 0.55
|
||||
# TestActorCollision 的距离门:LengthSq(victim - self) > 800 cm² 直接不检。
|
||||
const ACTOR_COLLIDE_MAX_DIST_M := 8.0
|
||||
# c_fDefaultRotationSpeed 1200 / c_fDefaultHorseRotationSpeed 300(InstanceBase.cpp:17-18):
|
||||
# MountHorse() 把转向速度降到 300,下马恢复 1200。
|
||||
const ROT_SPEED_DEFAULT_DEG := 1200.0
|
||||
const ROT_SPEED_HORSE_DEG := 300.0
|
||||
|
||||
var force_run := false # 脚本化 / 自动跑
|
||||
var frozen := false # 受击硬直等:本帧不响应移动输入(net_play 设)
|
||||
var locked := false # CInstanceBase::isLock():完全锁死移动(过场 / 特定 affect)
|
||||
var moving_skill := false # IsUsingMovingSkill():移动技能中,只允许转向不平移
|
||||
var server_speed_scale := 1.0 # GC_CHANGE_SPEED moving_speed / 100
|
||||
var rotation_speed_deg := ROT_SPEED_DEFAULT_DEG # 上/下马时由 net_play 切换(§3.1 / §6.4)
|
||||
# CActorInstance::CanSkipCollision():网络状态包驱动的移动整段跳过 Actor 碰撞
|
||||
# (§3.2 __EnableSkipCollision,观战 / 服务器纠正时置真)。
|
||||
var skip_actor_collision := false
|
||||
var _last_wasd := Vector2.ZERO
|
||||
|
||||
# --- §3.1 Src/Dst 移动模型(对齐 InstanceBaseMovement.cpp NEW_Goto / NEW_MoveToDirection)---
|
||||
# _is_going = m_isGoing(点地走向 Dst);方向键移动 _is_going 保持 false。
|
||||
var _src_pos := Vector3.ZERO
|
||||
var _dst_pos := Vector3.ZERO
|
||||
var _is_going := false
|
||||
var _dst_rot := 0.0
|
||||
var _reserved_ground: Variant = null # __ReserveClickGround 的待处理点
|
||||
var _reserved_delay_time := 0.0 # NEW_IsEmptyReservedDelayTime 递减到 0 才生效
|
||||
|
||||
# CInstanceBase::__IsSyncing():Dead / Stun / Pushing 中不接受移动输入(net_play 置 frozen)。
|
||||
func is_going() -> bool:
|
||||
return _is_going
|
||||
|
||||
# NEW_Goto / NEW_MoveToDirection 的三道前置门:syncing / moving-skill(只转向) / lock。
|
||||
func _can_translate() -> bool:
|
||||
return not frozen and not locked and not moving_skill
|
||||
|
||||
const _TAP_TRAVEL_MAX := 12.0 # 触点位移超过这么多像素 -> 视作拖拽(归相机),不是轻点
|
||||
const _TAP_TIME_MAX := 0.35 # 按下到抬起超过这么久 -> 不是轻点
|
||||
|
||||
var _move_target := Vector3.ZERO
|
||||
var _has_target := false
|
||||
var _run := false
|
||||
var _touch_count := 0
|
||||
var _multi_gesture := false # 曾有 ≥2 指同时按下 -> 本轮不产生点地
|
||||
@@ -46,10 +88,36 @@ func set_server_speed(moving_speed: int) -> void:
|
||||
return
|
||||
server_speed_scale = clampf(float(moving_speed) / 100.0, 0.25, 3.0)
|
||||
|
||||
# 程序化下发一个点地目标(脚本化截图 / AI)
|
||||
func set_input_surfaces(cursor: Node, ui: Node = null, ground: Node = null,
|
||||
cancel_fishing_cb := Callable(), ground_cancel_fishing_cb := Callable()) -> void:
|
||||
cursor_manager = cursor
|
||||
ui_manager = ui
|
||||
ground_items = ground
|
||||
cancel_fishing_input = cancel_fishing_cb
|
||||
cancel_fishing_ground = ground_cancel_fishing_cb
|
||||
|
||||
# 程序化下发一个点地目标(脚本化截图 / AI)——等价 NEW_MoveToDestPixelPositionDirection。
|
||||
func walk_to(world_pos: Vector3) -> void:
|
||||
_move_target = Vector3(world_pos.x, 0.0, world_pos.z)
|
||||
_has_target = true
|
||||
_goto(Vector3(world_pos.x, 0.0, world_pos.z))
|
||||
|
||||
# CInstanceBase::NEW_Goto(InstanceBaseMovement.cpp:251):三门通过后设 Src/Dst + m_isGoing。
|
||||
# 被门挡下(syncing / lock)时按 __ReserveClickGround 预约,delay 后重试。
|
||||
func _goto(dst_flat: Vector3) -> bool:
|
||||
if player == null:
|
||||
return false
|
||||
var flat := Vector3(dst_flat.x - player.position.x, 0.0, dst_flat.z - player.position.z)
|
||||
# __IsMovableGroundDistance:离脚下太近不动
|
||||
if flat.length() < MOVABLE_GROUND_DISTANCE_M:
|
||||
return true
|
||||
if not _can_translate():
|
||||
_reserved_ground = dst_flat
|
||||
_reserved_delay_time = RESERVED_GROUND_DELAY
|
||||
return false
|
||||
_src_pos = player.position
|
||||
_dst_pos = Vector3(dst_flat.x, 0.0, dst_flat.z)
|
||||
_dst_rot = atan2(flat.x, flat.z)
|
||||
_is_going = true
|
||||
return true
|
||||
|
||||
func _unhandled_input(e: InputEvent) -> void:
|
||||
if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and e.pressed:
|
||||
@@ -103,11 +171,14 @@ func _on_click(screen_pos: Vector2) -> void:
|
||||
target_selected.emit(best)
|
||||
return
|
||||
|
||||
# 2) 点地:射线与地表求交(沿射线二分找 y == 地表高度)
|
||||
# 2) 点地:射线与地表求交(沿射线二分找 y == 地表高度)→ NEW_Goto
|
||||
var hit: Variant = _ray_ground(from, dir)
|
||||
if hit != null:
|
||||
_move_target = hit
|
||||
_has_target = true
|
||||
if cancel_fishing_ground.is_valid() and bool(cancel_fishing_ground.call()):
|
||||
return
|
||||
if cancel_fishing_input.is_valid() and bool(cancel_fishing_input.call()):
|
||||
return
|
||||
_goto(hit)
|
||||
|
||||
# 射线 vs 实体(节点下所有 MeshInstance3D 的世界 AABB 合并)的进入距离 t;不命中返回 -1。
|
||||
func _ray_pick_t(from: Vector3, dir: Vector3, node: Node3D) -> float:
|
||||
@@ -189,11 +260,56 @@ func _wasd() -> Vector2:
|
||||
func _blocked(x: float, z: float) -> bool:
|
||||
return world != null and world.has_method("is_blocked") and bool(world.call("is_blocked", x, z))
|
||||
|
||||
# CActorInstance::TestActorCollision(ActorInstanceCollisionDetection.cpp)近似:距离门
|
||||
# (800 cm)→ body 球重叠 → 「趋近」判定(新位比旧位更近才算撞,远离时放行 → 能从
|
||||
# 重叠里走出来)。每个 pickable(已生成的远端 NPC / 怪 / 他人节点)当作一个 Actor 碰撞体;
|
||||
# `skip_actor_collision`(CanSkipCollision)为真整段跳过(≈ CheckAdvancing 开头)。
|
||||
func _actor_blocked(next_pos: Vector3) -> bool:
|
||||
if skip_actor_collision or player == null:
|
||||
return false
|
||||
var cur := player.position
|
||||
var combined := ACTOR_BODY_RADIUS_M * 2.0
|
||||
for n in pickables:
|
||||
if not is_instance_valid(n):
|
||||
continue
|
||||
if bool(n.get_meta("dead", false)): # rVictim.IsDead()
|
||||
continue
|
||||
var op: Vector3 = n.global_position
|
||||
var d_now := Vector2(op.x - cur.x, op.z - cur.z).length()
|
||||
if d_now > ACTOR_COLLIDE_MAX_DIST_M: # LengthSq > 800² → 不检
|
||||
continue
|
||||
var d_next := Vector2(op.x - next_pos.x, op.z - next_pos.z).length()
|
||||
if d_next < combined and d_next <= d_now + 1e-4:
|
||||
return true
|
||||
return false
|
||||
|
||||
# SetAdvancingRotation(InstanceBaseMovement.cpp):按转向速度逐帧收敛朝向;移动方向本身
|
||||
# 不受此限制(骑乘时 rotation_speed_deg = 300 → 转向明显变慢)。
|
||||
func _turn_toward(target_yaw: float, dt: float) -> void:
|
||||
if player == null:
|
||||
return
|
||||
var max_step := deg_to_rad(rotation_speed_deg) * dt
|
||||
var diff: float = wrapf(target_yaw - player.rotation.y, -PI, PI)
|
||||
if max_step <= 0.0 or absf(diff) <= max_step:
|
||||
player.rotation.y = target_yaw
|
||||
else:
|
||||
player.rotation.y += signf(diff) * max_step
|
||||
|
||||
func _process(dt: float) -> void:
|
||||
if player == null:
|
||||
return
|
||||
if frozen:
|
||||
_has_target = false
|
||||
_update_hover_cursor()
|
||||
|
||||
# __ReserveClickGround 的 delay 递减(NEW_IsEmptyReservedDelayTime);到点且门已开则重试。
|
||||
if _reserved_delay_time > 0.0:
|
||||
_reserved_delay_time -= dt
|
||||
if _reserved_delay_time <= 0.0 and _reserved_ground != null and _can_translate():
|
||||
var rg: Vector3 = _reserved_ground
|
||||
_reserved_ground = null
|
||||
_goto(rg)
|
||||
|
||||
if frozen or locked:
|
||||
# 停止平移,但保留 m_isGoing(解锁后继续走向 Dst,等价预约不丢)。
|
||||
anim_state.emit("wait")
|
||||
return
|
||||
_run = force_run or Input.is_key_pressed(RUN_HOLD_KEY)
|
||||
@@ -201,34 +317,54 @@ func _process(dt: float) -> void:
|
||||
var wasd := _wasd()
|
||||
|
||||
if wasd != Vector2.ZERO:
|
||||
_has_target = false # 键盘覆盖点地
|
||||
# PythonPlayerInputKeyboard 只在方向键按下时尝试取消钓鱼;记录方向
|
||||
# 边沿,避免按住键时每帧重复发送 CG_FISHING(0)。
|
||||
if wasd != _last_wasd and cancel_fishing_input.is_valid() \
|
||||
and bool(cancel_fishing_input.call()):
|
||||
_last_wasd = wasd
|
||||
anim_state.emit("wait")
|
||||
return
|
||||
_last_wasd = wasd
|
||||
_is_going = false # 键盘覆盖点地(NEW_MoveToDirection:m_isGoing = FALSE)
|
||||
var yaw: float = (camera.heading() if camera and camera.has_method("heading") else 0.0)
|
||||
var fwd := Vector3(sin(yaw), 0, cos(yaw))
|
||||
var right := Vector3(fwd.z, 0, -fwd.x)
|
||||
wish = (fwd * -wasd.y + right * wasd.x).normalized()
|
||||
elif _has_target:
|
||||
var flat := Vector3(_move_target.x - player.position.x, 0, _move_target.z - player.position.z)
|
||||
elif _is_going:
|
||||
var flat := Vector3(_dst_pos.x - player.position.x, 0, _dst_pos.z - player.position.z)
|
||||
if flat.length() <= ARRIVE_EPS:
|
||||
_has_target = false
|
||||
_is_going = false
|
||||
else:
|
||||
wish = flat.normalized()
|
||||
else:
|
||||
_last_wasd = Vector2.ZERO
|
||||
|
||||
var speed := 0.0
|
||||
if wish != Vector3.ZERO:
|
||||
var want_speed := (SPEED_RUN if _run else SPEED_WALK) * server_speed_scale
|
||||
var before := player.position
|
||||
var np := player.position + wish * want_speed * dt
|
||||
if not _blocked(np.x, player.position.z):
|
||||
player.position.x = np.x
|
||||
if not _blocked(player.position.x, np.z):
|
||||
player.position.z = np.z
|
||||
# 动画看「实际位移」而非意图:贴墙磨蹭时该切回 wait
|
||||
var disp := Vector2(player.position.x - before.x, player.position.z - before.z).length()
|
||||
if disp > want_speed * dt * 0.25:
|
||||
speed = want_speed
|
||||
player.rotation.y = atan2(wish.x, wish.z)
|
||||
# IsUsingMovingSkill():移动技能中只转向,不平移。
|
||||
if moving_skill:
|
||||
_turn_toward(atan2(wish.x, wish.z), dt)
|
||||
else:
|
||||
_has_target = false
|
||||
var want_speed := (SPEED_RUN if _run else SPEED_WALK) * server_speed_scale
|
||||
var before := player.position
|
||||
var np := player.position + wish * want_speed * dt
|
||||
# 逐轴推进:地形 ATTRIBUTE_BLOCK + Actor 碰撞任一挡下该轴 → 不动那一轴
|
||||
# (单个 Actor 时等价 AdjustDynamicCollisionMovement 的滑移;两轴都挡 = BlockMovement)。
|
||||
var block_x := _actor_blocked(Vector3(np.x, before.y, player.position.z)) \
|
||||
or _blocked(np.x, player.position.z)
|
||||
var block_z := _actor_blocked(Vector3(player.position.x, before.y, np.z)) \
|
||||
or _blocked(player.position.x, np.z)
|
||||
if not block_x:
|
||||
player.position.x = np.x
|
||||
if not block_z:
|
||||
player.position.z = np.z
|
||||
# 动画看「实际位移」而非意图:贴墙 / 顶人磨蹭时该切回 wait
|
||||
var disp := Vector2(player.position.x - before.x, player.position.z - before.z).length()
|
||||
if disp > want_speed * dt * 0.25:
|
||||
speed = want_speed
|
||||
_turn_toward(atan2(wish.x, wish.z), dt)
|
||||
else:
|
||||
_is_going = false
|
||||
|
||||
if world and world.has_method("sample_height"):
|
||||
player.position.y = float(world.call("sample_height", player.position.x, player.position.z))
|
||||
@@ -240,3 +376,32 @@ func _process(dt: float) -> void:
|
||||
anim_state.emit("walk")
|
||||
else:
|
||||
anim_state.emit("run")
|
||||
|
||||
func _update_hover_cursor() -> void:
|
||||
if cursor_manager == null or not cursor_manager.has_method("set_cursor") or camera == null:
|
||||
return
|
||||
var mouse_pos := get_viewport().get_mouse_position()
|
||||
if ui_manager and ui_manager.has_method("blocks_game_input"):
|
||||
var motion := InputEventMouseMotion.new()
|
||||
motion.position = mouse_pos
|
||||
if ui_manager.blocks_game_input(motion):
|
||||
cursor_manager.set_cursor("NORMAL")
|
||||
return
|
||||
if ground_items and ground_items.has_method("hover_at") and ground_items.hover_at(camera, mouse_pos):
|
||||
cursor_manager.set_cursor("PICK")
|
||||
return
|
||||
var from := camera.project_ray_origin(mouse_pos)
|
||||
var dir := camera.project_ray_normal(mouse_pos)
|
||||
var best: Node3D = null
|
||||
var best_t := 1e20
|
||||
for n in pickables:
|
||||
if not is_instance_valid(n):
|
||||
continue
|
||||
var t := _ray_pick_t(from, dir, n)
|
||||
if t >= 0.0 and t < best_t:
|
||||
best_t = t
|
||||
best = n
|
||||
if best:
|
||||
cursor_manager.set_cursor(String(best.get_meta("cursor_shape", "ATTACK")))
|
||||
else:
|
||||
cursor_manager.set_cursor("NORMAL")
|
||||
|
||||
Reference in New Issue
Block a user