fix(movement): camera auto-rotates to face WASD input direction
40250 parity: CPythonPlayer::NEW_MoveToDirection's m_isCmrRot branch continuously rolls the camera toward the held movement direction every frame while a direction key is down, independent of whether a new translation target was just set. Add _camera_auto_rotate() to replicate this (forward/back = no rotation, strafe = max rate, diagonal = half rate), called unconditionally after the frozen/locked/shop/emotion gate in _process(). Godot's yaw-increase handedness is opposite the reference's D3D Roll(), so the sign is flipped as a platform adaptation; verified by convergence invariants in keyboard_motion_timeline_test.gd rather than bit-for-bit angle matching. Closes movement.keyboard.motion/camera-auto-follow-rotation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c93894313a
commit
7df97e01f9
@@ -3,6 +3,7 @@
|
||||
extends SceneTree
|
||||
|
||||
const PlayerController = preload("res://player_controller.gd")
|
||||
const GameCamera = preload("res://game_camera.gd")
|
||||
|
||||
var _fail := 0
|
||||
|
||||
@@ -129,9 +130,75 @@ func _init() -> void:
|
||||
"held direction resumes after emotion ends")
|
||||
pc._input(_key(KEY_W, false))
|
||||
|
||||
pc._input(_key(KEY_W, false))
|
||||
|
||||
# 40250 CPythonPlayer::NEW_MoveToDirection m_isCmrRot: holding a pure
|
||||
# forward/backward direction must not roll the camera (fRotRat == 0 at the
|
||||
# axis), while pure left/right rolls it fastest, and diagonals roll at
|
||||
# half rate. See player_controller.gd::_camera_auto_rotate.
|
||||
pc.stop()
|
||||
var cam := GameCamera.new()
|
||||
pc.camera = cam
|
||||
cam.yaw = 0.0
|
||||
pc._unhandled_input(_key(KEY_W, true))
|
||||
pc._process(0.1)
|
||||
_ck(is_zero_approx(cam.yaw), "holding pure forward does not roll the camera")
|
||||
pc._input(_key(KEY_W, false))
|
||||
|
||||
cam.yaw = 0.0
|
||||
pc._unhandled_input(_key(KEY_S, true))
|
||||
pc._process(0.1)
|
||||
_ck(is_zero_approx(cam.yaw), "holding pure backward does not roll the camera")
|
||||
pc._input(_key(KEY_S, false))
|
||||
|
||||
cam.yaw = 0.0
|
||||
pc._unhandled_input(_key(KEY_A, true))
|
||||
var yaw_after_one := 0.0
|
||||
for _i in range(5):
|
||||
pc._process(0.1)
|
||||
if _i == 0:
|
||||
yaw_after_one = cam.yaw
|
||||
_ck(yaw_after_one > 0.001, "holding left starts rolling the camera immediately")
|
||||
_ck(is_equal_approx(yaw_after_one * 5.0, cam.yaw), "camera roll rate is constant while held")
|
||||
var left_yaw := cam.yaw
|
||||
pc._input(_key(KEY_A, false))
|
||||
|
||||
cam.yaw = 0.0
|
||||
pc._unhandled_input(_key(KEY_D, true))
|
||||
pc._process(0.1)
|
||||
pc._process(0.1)
|
||||
pc._process(0.1)
|
||||
pc._process(0.1)
|
||||
pc._process(0.1)
|
||||
_ck(is_equal_approx(cam.yaw, -left_yaw),
|
||||
"holding right rolls the camera the same rate opposite left")
|
||||
pc._input(_key(KEY_D, false))
|
||||
|
||||
cam.yaw = 0.0
|
||||
pc._unhandled_input(_key(KEY_W, true))
|
||||
pc._unhandled_input(_key(KEY_A, true))
|
||||
for _i in range(5):
|
||||
pc._process(0.1)
|
||||
_ck(is_equal_approx(cam.yaw, left_yaw * 0.5),
|
||||
"a forward-left diagonal rolls the camera at half the pure-left rate")
|
||||
pc._input(_key(KEY_A, false))
|
||||
pc._input(_key(KEY_W, false))
|
||||
|
||||
# The camera-roll side effect is gated exactly like translation: no roll
|
||||
# while the actor cannot move.
|
||||
cam.yaw = 0.0
|
||||
pc.stop()
|
||||
pc.locked = true
|
||||
pc._unhandled_input(_key(KEY_A, true))
|
||||
pc._process(0.2)
|
||||
_ck(is_zero_approx(cam.yaw), "locked movement gate also suppresses the camera roll")
|
||||
pc.locked = false
|
||||
pc._input(_key(KEY_A, false))
|
||||
pc.stop()
|
||||
|
||||
player.free()
|
||||
pc.free()
|
||||
cam.free()
|
||||
if _fail == 0:
|
||||
print("PASS: keyboard_motion_timeline_test")
|
||||
quit(0)
|
||||
|
||||
@@ -38,6 +38,7 @@ const SPEED_RUN := 4.8
|
||||
const KEYBOARD_MOVE_TARGET_M := 3.0 # 40250 NEW_MoveToDirection: Dst = Cur + 300 px
|
||||
const RUN_HOLD_KEY := KEY_SHIFT
|
||||
const ARRIVE_EPS := 0.05
|
||||
const CAMERA_AUTO_ROTATE_SPEED_DEG := 20.0 # 40250 CPythonPlayer::m_fCmrRotSpd 默认值
|
||||
const PICK_RADIUS := 1.4 # 点选命中半径(米)
|
||||
# __IsMovableGroundDistance:点地目标离脚下太近就不动(避免原地抖)。参考默认由
|
||||
# player.SetMovableGroundDistance 从 game.py 设置;此处用参考缺省值(~1 m)。§3.1
|
||||
@@ -503,6 +504,29 @@ func _wasd() -> Vector2:
|
||||
d.x = 1.0
|
||||
return d.normalized()
|
||||
|
||||
# 对齐 40250 CPythonPlayer::NEW_MoveToDirection 的 m_isCmrRot 分支:dir 是 _wasd() 的相机相对
|
||||
# 输入向量(0,-1=正前)。折算成角色相对相机的方位角 fDirRot(0=前 90=左 180=后 270=右,
|
||||
# 与 GetDegreeFromPosition 的取值一致),再按参考实现同样的公式折叠到 [-90,90] 的
|
||||
# fRotRat:正前/正后为 0(不转),正左/右为 ±90(转速最快),对角线为 ±45(减半)。
|
||||
# 参考实现用 CCamera::Roll(fRotDeg)(D3D 增量式 roll,fRotDeg=-speed*dt*fRotRat/90);
|
||||
# Godot 的 yaw 增大对应视线转向左,和 D3D 的 Roll 正方向手性相反,因此这里的增量对
|
||||
# fRotRat 取正号而不是参考的负号 —— 这是platform_adaptation,用“相机持续转向新朝向、
|
||||
# 从不越过目标”这一收敛不变式核验(keyboard_motion_timeline_test.gd),而不是逐位对照
|
||||
# D3D 数值。
|
||||
func _camera_auto_rotate(dir: Vector2, dt: float) -> void:
|
||||
if camera == null or not camera.has_method("heading") or dir == Vector2.ZERO:
|
||||
return
|
||||
var dir_rot_deg := fposmod(rad_to_deg(atan2(-dir.x, -dir.y)), 360.0)
|
||||
var sig_dir_rot := dir_rot_deg
|
||||
if sig_dir_rot > 180.0:
|
||||
sig_dir_rot -= 360.0
|
||||
var rot_rat := sig_dir_rot
|
||||
if rot_rat > 90.0:
|
||||
rot_rat = 180.0 - rot_rat
|
||||
elif rot_rat < -90.0:
|
||||
rot_rat = -180.0 - rot_rat
|
||||
camera.yaw += deg_to_rad(CAMERA_AUTO_ROTATE_SPEED_DEG * dt * rot_rat / 90.0)
|
||||
|
||||
func _blocked(x: float, z: float) -> bool:
|
||||
return world != null and world.has_method("is_blocked") and bool(world.call("is_blocked", x, z))
|
||||
|
||||
@@ -595,6 +619,11 @@ func _process(dt: float) -> void:
|
||||
if world and world.has_method("sample_height") and is_instance_valid(player):
|
||||
player.position.y = float(world.call("sample_height", player.position.x, player.position.z))
|
||||
return
|
||||
# 40250 CPythonPlayer::Update 每帧对仍按住的方向键重试 NEW_SetMultiDirKeyState ->
|
||||
# NEW_MoveToDirection,其 m_isCmrRot 分支与是否已建立平移目标无关,门检查(上面
|
||||
# 的 frozen/locked/shop/emotion)通过后即执行,因此放在移动逻辑之前、无条件按
|
||||
# 当前方向键调用。
|
||||
_camera_auto_rotate(_wasd(), dt)
|
||||
if force_walk:
|
||||
_run = false
|
||||
elif force_run:
|
||||
|
||||
Reference in New Issue
Block a user