手柄互动功能添加

This commit is contained in:
Rowland 2025-09-29 10:52:39 +08:00
parent e5d2e485b9
commit a16c4bba13
6 changed files with 1708 additions and 28 deletions

View File

@ -60,6 +60,12 @@ class VRController(DirectObject):
self.touchpad_pos = Vec3(0, 0, 0) self.touchpad_pos = Vec3(0, 0, 0)
self.touchpad_touched = False self.touchpad_touched = False
# 摇杆状态 - 用于传送和转向交互
self.joystick_pos = Vec3(0, 0, 0) # 摇杆位置 (x, y, 0)
self.joystick_touched = False # 摇杆是否被触摸
self.joystick_pressed = False # 摇杆是否被按下
self.previous_joystick_pos = Vec3(0, 0, 0) # 上一帧摇杆位置
# 3D节点和可视化 # 3D节点和可视化
self.anchor_node = None self.anchor_node = None
self.visualizer = None self.visualizer = None
@ -146,8 +152,9 @@ class VRController(DirectObject):
if not self.is_connected or not OPENVR_AVAILABLE or not vr_system: if not self.is_connected or not OPENVR_AVAILABLE or not vr_system:
return return
# 保存上一帧的按钮状态 # 保存上一帧的按钮状态和摇杆位置
self.previous_button_states = self.button_states.copy() self.previous_button_states = self.button_states.copy()
self.previous_joystick_pos = Vec3(self.joystick_pos)
# 获取控制器状态 # 获取控制器状态
try: try:
@ -159,20 +166,71 @@ class VRController(DirectObject):
# OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed # OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed
self.button_states[i] = (state.ulButtonPressed & button_mask) != 0 self.button_states[i] = (state.ulButtonPressed & button_mask) != 0
# 更新轴状态(扳机、握把、触摸板) # 更新轴状态(扳机、握把、触摸板、摇杆)
# OpenVR Python绑定中使用vAxis而不是rAxis # 兼容不同版本的OpenVR Python绑定
if hasattr(state, 'vAxis') and len(state.vAxis) > 0: axis_data = None
if hasattr(state, 'rAxis'):
axis_data = state.rAxis # 旧版本使用rAxis
elif hasattr(state, 'vAxis'):
axis_data = state.vAxis # 新版本使用vAxis
if axis_data is not None and len(axis_data) > 0:
# 调试输出 - 显示所有轴数据(仅当有变化时)
self._debug_axis_data(axis_data)
# 扳机轴通常在axis[1].x # 扳机轴通常在axis[1].x
if len(state.vAxis) > 1: if len(axis_data) > 1:
self.trigger_value = state.vAxis[1].x self.trigger_value = axis_data[1].x
# 触摸板轴通常在axis[0] # 触摸板/摇杆轴通常在axis[0]
if len(state.vAxis) > 0: if len(axis_data) > 0:
self.touchpad_pos = Vec3(state.vAxis[0].x, state.vAxis[0].y, 0) self.touchpad_pos = Vec3(axis_data[0].x, axis_data[0].y, 0)
# 摇杆和触摸板通常使用同一个轴,但可以区分设备类型
self.joystick_pos = Vec3(axis_data[0].x, axis_data[0].y, 0)
# 触摸板触摸状态 - 使用正确的OpenVR属性名 # 额外检查其他轴(某些控制器可能将摇杆分配到不同轴)
if len(axis_data) > 2:
# 有些控制器可能在axis[2]有摇杆数据
axis2_magnitude = abs(axis_data[2].x) + abs(axis_data[2].y)
if axis2_magnitude > 0.1: # 如果有显著输入,使用这个轴作为摇杆
self.joystick_pos = Vec3(axis_data[2].x, axis_data[2].y, 0)
# 检查axis[3]和axis[4]Quest控制器可能使用这些轴
for axis_idx in range(3, min(len(axis_data), 5)):
axis_magnitude = abs(axis_data[axis_idx].x) + abs(axis_data[axis_idx].y)
if axis_magnitude > 0.1: # 如果有显著输入
# 覆盖之前的摇杆数据,使用最有活动的轴
self.joystick_pos = Vec3(axis_data[axis_idx].x, axis_data[axis_idx].y, 0)
# 调试输出
if not hasattr(self, '_last_axis_notify'):
self._last_axis_notify = {}
if self._last_axis_notify.get(axis_idx, 0) == 0:
print(f"🎮 {self.name}手检测到axis[{axis_idx}]活动: ({axis_data[axis_idx].x:.3f}, {axis_data[axis_idx].y:.3f})")
self._last_axis_notify[axis_idx] = 60 # 60帧后再次提醒
else:
self._last_axis_notify[axis_idx] -= 1
# 触摸板和摇杆触摸状态
self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
# 摇杆触摸状态(检查多个可能的按钮)
joystick_touch_mask = 0
if hasattr(openvr, 'k_EButton_Joystick'):
joystick_touch_mask |= (1 << openvr.k_EButton_Joystick)
if hasattr(openvr, 'k_EButton_Thumbstick'):
joystick_touch_mask |= (1 << openvr.k_EButton_Thumbstick)
self.joystick_touched = (state.ulButtonTouched & joystick_touch_mask) != 0 or self.touchpad_touched
# 摇杆按下状态
joystick_press_mask = 0
if hasattr(openvr, 'k_EButton_Joystick'):
joystick_press_mask |= (1 << openvr.k_EButton_Joystick)
if hasattr(openvr, 'k_EButton_Thumbstick'):
joystick_press_mask |= (1 << openvr.k_EButton_Thumbstick)
self.joystick_pressed = (state.ulButtonPressed & joystick_press_mask) != 0
except Exception as e: except Exception as e:
# 减少错误输出频率 # 减少错误输出频率
if not hasattr(self, '_last_input_error_frame'): if not hasattr(self, '_last_input_error_frame'):
@ -260,12 +318,92 @@ class VRController(DirectObject):
return Vec3(0, 0, 0) return Vec3(0, 0, 0)
def get_forward_direction(self): def get_forward_direction(self):
"""获取手柄指向的方向向量""" """获取手柄指向的方向向量(包含视角转向)"""
if self.anchor_node: if self.anchor_node:
# Y轴正方向为前方 # 获取相对于世界坐标系的方向包含tracking_space的旋转
return self.anchor_node.getMat().getRow3(1).getXyz().normalized() if hasattr(self.vr_manager, 'world') and self.vr_manager.world:
# 使用世界变换,包含所有父节点的旋转
world_transform = self.anchor_node.getMat(self.vr_manager.world.render)
forward = Vec3(world_transform.getRow3(1)) # Y轴 = 前方
else:
# 备选:使用局部变换
forward = Vec3(self.anchor_node.getMat().getRow3(1))
if forward.length() > 0:
return forward.normalized()
return Vec3(0, 1, 0) return Vec3(0, 1, 0)
def is_joystick_touched(self):
"""检查摇杆是否被触摸"""
return self.joystick_touched
def is_joystick_pressed(self):
"""检查摇杆是否被按下"""
return self.joystick_pressed
def get_joystick_position(self):
"""获取摇杆位置
Returns:
Vec3: 摇杆位置 (x, y, 0)范围 [-1, 1]
"""
return Vec3(self.joystick_pos)
def get_joystick_delta(self):
"""获取摇杆位置变化
Returns:
Vec3: 摇杆位置变化向量
"""
return self.joystick_pos - self.previous_joystick_pos
def is_joystick_moved(self, threshold=0.01):
"""检查摇杆是否移动
Args:
threshold: 移动阈值
Returns:
bool: 是否移动
"""
delta = self.get_joystick_delta()
return delta.length() > threshold
def _debug_axis_data(self, axis_data):
"""调试输出轴数据"""
try:
# 只在有活动时输出调试信息
has_activity = False
active_axes = []
for i, axis in enumerate(axis_data):
magnitude = abs(axis.x) + abs(axis.y)
if magnitude > 0.01: # 检测到活动
has_activity = True
active_axes.append(f"axis[{i}]: ({axis.x:.3f}, {axis.y:.3f})")
if has_activity:
# 初始化调试计数器
if not hasattr(self, '_debug_axis_counter'):
self._debug_axis_counter = 0
self._debug_axis_counter += 1
# 每30帧输出一次详细信息
if self._debug_axis_counter % 30 == 1:
print(f"🔍 {self.name}手轴数据调试:")
print(f" 总轴数: {len(axis_data)}")
print(f" 活跃轴: {', '.join(active_axes)}")
# 显示所有轴的当前值(不管是否活跃)
all_axes = []
for i, axis in enumerate(axis_data):
all_axes.append(f"[{i}]:({axis.x:.3f},{axis.y:.3f})")
print(f" 所有轴: {' '.join(all_axes)}")
except Exception as e:
print(f"⚠️ 轴数据调试失败: {e}")
def cleanup(self): def cleanup(self):
"""清理资源""" """清理资源"""
self.ignoreAll() self.ignoreAll()

701
core/vr_joystick.py Normal file
View File

@ -0,0 +1,701 @@
"""
VR摇杆交互系统模块
提供类似SteamVR的摇杆交互功能
- 摇杆左右转向旋转视角
- 摇杆向前传送预览抛物线轨迹
- 松开摇杆执行传送
- 死区处理和平滑控制
"""
import math
from panda3d.core import Vec2, Vec3, Vec4
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
class VRJoystickManager(DirectObject):
"""VR摇杆管理器 - 处理手柄摇杆的转向和传送功能"""
def __init__(self, vr_manager):
"""初始化VR摇杆管理器
Args:
vr_manager: VR管理器实例
"""
super().__init__()
self.vr_manager = vr_manager
self.teleport_system = None # 传送系统引用,稍后初始化
# 摇杆参数配置
self.deadzone = 0.15 # 摇杆死区 (0-1)
self.turn_threshold = 0.3 # 转向激活阈值
self.teleport_threshold = 0.5 # 传送激活阈值
self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度
self.smooth_turning = True # 是否平滑转向
self.snap_turn_angle = 30.0 # 分段转向角度(度)
# 摇杆状态跟踪
self.left_joystick_state = JoystickState()
self.right_joystick_state = JoystickState()
# 转向状态
self.left_turn_cooldown = 0.0 # 分段转向冷却时间
self.right_turn_cooldown = 0.0
self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒)
# 传送状态
self.active_teleport_controller = None # 正在传送的控制器
self.teleport_preview_active = False # 传送预览是否激活
# 互斥状态管理 - 防止同时触发多种操作
self.interaction_mode = 'none' # 当前交互模式: 'none', 'turning', 'teleporting'
self.left_controller_mode = 'none' # 左手控制器状态
self.right_controller_mode = 'none' # 右手控制器状态
self.mode_lock_timeout = 0.1 # 模式锁定超时时间(秒)
self.left_mode_timer = 0.0 # 左手模式计时器
self.right_mode_timer = 0.0 # 右手模式计时器
print("✓ VR摇杆管理器初始化完成")
def initialize(self, teleport_system):
"""初始化摇杆系统
Args:
teleport_system: VR传送系统实例
"""
self.teleport_system = teleport_system
print("✅ VR摇杆系统初始化成功")
def update(self, dt):
"""更新摇杆系统 - 每帧调用
Args:
dt: 帧间隔时间
"""
if not self.vr_manager.are_controllers_connected():
return
# 调试计数器
if not hasattr(self, '_debug_frame_count'):
self._debug_frame_count = 0
print("🎮 VR摇杆系统开始更新")
self._debug_frame_count += 1
# 更新转向冷却时间
if self.left_turn_cooldown > 0:
self.left_turn_cooldown -= dt
if self.right_turn_cooldown > 0:
self.right_turn_cooldown -= dt
# 更新互斥状态计时器
self._update_interaction_modes(dt)
# 处理左手控制器摇杆
if self.vr_manager.left_controller:
self._update_controller_joystick(
self.vr_manager.left_controller,
self.left_joystick_state,
'left',
dt
)
# 处理右手控制器摇杆
if self.vr_manager.right_controller:
self._update_controller_joystick(
self.vr_manager.right_controller,
self.right_joystick_state,
'right',
dt
)
# 每5秒输出一次状态报告
if self._debug_frame_count % 300 == 1: # 假设60fps
self._print_debug_status()
def _update_controller_joystick(self, controller, joystick_state, hand, dt):
"""更新单个控制器的摇杆状态
Args:
controller: VR控制器实例
joystick_state: 摇杆状态对象
hand: 'left' 'right'
dt: 帧间隔时间
"""
# 获取摇杆输入
joystick_input = self._get_joystick_input(controller, hand)
if joystick_input is None:
return
# 应用死区
filtered_input = self._apply_deadzone(joystick_input)
# 更新摇杆状态
joystick_state.update(filtered_input)
# 处理转向(左右移动)
self._handle_turning(filtered_input, hand, dt)
# 处理传送(向前移动)
self._handle_teleport(controller, filtered_input, joystick_state, hand)
def _update_interaction_modes(self, dt):
"""更新互斥交互模式状态"""
# 更新左手模式计时器
if self.left_mode_timer > 0:
self.left_mode_timer -= dt
if self.left_mode_timer <= 0:
self.left_controller_mode = 'none'
# 更新右手模式计时器
if self.right_mode_timer > 0:
self.right_mode_timer -= dt
if self.right_mode_timer <= 0:
self.right_controller_mode = 'none'
# 更新全局交互模式
if self.left_controller_mode == 'none' and self.right_controller_mode == 'none':
if self.interaction_mode != 'none':
# 所有操作结束,恢复自由模式
self.interaction_mode = 'none'
print("🔓 摇杆交互模式解锁,恢复自由操作")
def _set_controller_mode(self, hand, mode):
"""设置控制器交互模式
Args:
hand: 'left' 'right'
mode: 'turning', 'teleporting', 'none'
"""
if hand == 'left':
if self.left_controller_mode != mode:
old_mode = self.left_controller_mode
self.left_controller_mode = mode
self.left_mode_timer = self.mode_lock_timeout
if mode != 'none':
print(f"🔒 左手控制器: {old_mode}{mode}模式")
else:
print(f"🔓 左手控制器解锁: {old_mode} → 自由")
else:
if self.right_controller_mode != mode:
old_mode = self.right_controller_mode
self.right_controller_mode = mode
self.right_mode_timer = self.mode_lock_timeout
if mode != 'none':
print(f"🔒 右手控制器: {old_mode}{mode}模式")
else:
print(f"🔓 右手控制器解锁: {old_mode} → 自由")
# 更新全局模式
if mode != 'none' and self.interaction_mode != mode:
self.interaction_mode = mode
def _can_use_mode(self, hand, requested_mode):
"""检查是否可以使用指定的交互模式
Args:
hand: 'left' 'right'
requested_mode: 'turning' 'teleporting'
Returns:
bool: 是否可以使用该模式
"""
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
# 如果当前控制器已经是该模式,允许继续
if current_mode == requested_mode:
return True
# 如果当前控制器是空闲的,且全局模式兼容,允许切换
if current_mode == 'none':
if self.interaction_mode == 'none' or self.interaction_mode == requested_mode:
return True
# 其他情况不允许
return False
def _get_joystick_input(self, controller, hand):
"""获取摇杆输入
Args:
controller: VR控制器实例
hand: 'left' 'right'
Returns:
Vec2: 摇杆位置 (x, y) None
"""
# 直接从控制器读取摇杆输入(绕过动作系统)
joystick_input = Vec2(0, 0)
# 优先读取joystick_pos
if hasattr(controller, 'joystick_pos') and controller.joystick_pos:
joystick_input = Vec2(controller.joystick_pos.x, controller.joystick_pos.y)
# 检查是否有有效输入
if joystick_input.length() > 0.01:
# 调试输出 - 仅在有输入时显示
if not hasattr(self, '_last_debug_time'):
self._last_debug_time = 0
self._debug_counter = 0
self._debug_counter += 1
# 每30帧输出一次调试信息
if self._debug_counter % 30 == 1:
print(f"🎮 {hand}手摇杆输入: ({joystick_input.x:.3f}, {joystick_input.y:.3f})")
return joystick_input
# 备选方案读取touchpad_posQuest等设备
if hasattr(controller, 'touchpad_pos') and controller.touchpad_pos:
touchpad_input = Vec2(controller.touchpad_pos.x, controller.touchpad_pos.y)
# 检查是否有有效输入
if touchpad_input.length() > 0.01:
# 调试输出 - 仅在有输入时显示
if not hasattr(self, '_last_debug_time'):
self._last_debug_time = 0
self._debug_counter = 0
self._debug_counter += 1
# 每30帧输出一次调试信息
if self._debug_counter % 30 == 1:
print(f"🎮 {hand}手触摸板输入: ({touchpad_input.x:.3f}, {touchpad_input.y:.3f})")
return touchpad_input
# 可选:尝试从动作系统获取(如果可用)
if (self.vr_manager.action_manager and
hasattr(self.vr_manager.action_manager, 'get_analog_action_value')):
try:
device_path = f'/user/hand/{hand}'
# 尝试摇杆
joystick_value, _ = self.vr_manager.action_manager.get_analog_action_value('joystick', device_path)
if joystick_value is not None:
return Vec2(joystick_value.x, joystick_value.y)
# 尝试触摸板
trackpad_value, _ = self.vr_manager.action_manager.get_analog_action_value('trackpad', device_path)
if trackpad_value is not None:
return Vec2(trackpad_value.x, trackpad_value.y)
except Exception:
# 静默忽略动作系统错误
pass
return Vec2(0, 0)
def _apply_deadzone(self, input_vec):
"""应用摇杆死区
Args:
input_vec: 原始摇杆输入
Returns:
Vec2: 应用死区后的输入
"""
magnitude = input_vec.length()
if magnitude < self.deadzone:
return Vec2(0, 0)
# 重新映射到 [0, 1] 范围
normalized_magnitude = (magnitude - self.deadzone) / (1.0 - self.deadzone)
normalized_magnitude = min(normalized_magnitude, 1.0)
if magnitude > 0:
direction = input_vec / magnitude
return direction * normalized_magnitude
return Vec2(0, 0)
def _handle_turning(self, input_vec, hand, dt):
"""处理摇杆转向
Args:
input_vec: 摇杆输入向量
hand: 'left' 'right'
dt: 帧间隔时间
"""
# 检查是否超过转向阈值
if abs(input_vec.x) < self.turn_threshold:
# 没有转向输入,重置该控制器的转向模式
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode == 'turning':
self._set_controller_mode(hand, 'none')
return
# 检查是否可以使用转向模式
if not self._can_use_mode(hand, 'turning'):
# 当前控制器被传送锁定,忽略转向输入
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode != 'turning':
print(f"{hand}手转向被阻止 - 当前模式: {current_mode}")
return
# 激活转向模式
self._set_controller_mode(hand, 'turning')
# 检查冷却时间(分段转向)
cooldown = self.left_turn_cooldown if hand == 'left' else self.right_turn_cooldown
if self.smooth_turning:
# 平滑转向 - 反转方向:摇杆右移(+x)应该向右转(-angle)
turn_amount = -input_vec.x * self.turn_sensitivity * dt
self._apply_rotation(turn_amount)
# 调试输出转向
if not hasattr(self, '_turn_debug_counter'):
self._turn_debug_counter = 0
self._turn_debug_counter += 1
if self._turn_debug_counter % 60 == 1: # 每秒输出一次
print(f"🔄 {hand}手转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°")
else:
# 分段转向
if cooldown <= 0:
# 反转方向:摇杆右移应该向右转
turn_amount = -self.snap_turn_angle if input_vec.x > 0 else self.snap_turn_angle
self._apply_rotation(turn_amount)
print(f"🔄 {hand}手分段转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°")
# 设置冷却时间
if hand == 'left':
self.left_turn_cooldown = self.snap_turn_cooldown
else:
self.right_turn_cooldown = self.snap_turn_cooldown
def _apply_rotation(self, angle_degrees):
"""应用旋转到VR跟踪空间
Args:
angle_degrees: 旋转角度
"""
if not self.vr_manager.tracking_space:
return
# 绕Z轴旋转垂直轴
current_h = self.vr_manager.tracking_space.getH()
new_h = current_h + angle_degrees
self.vr_manager.tracking_space.setH(new_h)
def _handle_teleport(self, controller, input_vec, joystick_state, hand):
"""处理摇杆传送
Args:
controller: VR控制器实例
input_vec: 摇杆输入向量
joystick_state: 摇杆状态对象
hand: 'left' 'right'
"""
# 检查Y轴向前移动是否超过阈值
forward_input = input_vec.y
if forward_input > self.teleport_threshold:
# 检查是否可以使用传送模式
if not self._can_use_mode(hand, 'teleporting'):
# 当前控制器被转向锁定,忽略传送输入
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode != 'teleporting':
print(f"{hand}手传送被阻止 - 当前模式: {current_mode}")
return
# 激活传送模式
self._set_controller_mode(hand, 'teleporting')
# 开始或更新传送预览
if not joystick_state.teleport_active:
joystick_state.teleport_active = True
self.active_teleport_controller = controller
self.teleport_preview_active = True
# 触发震动反馈
controller.trigger_haptic_feedback(0.002, 0.3)
# 计算传送方向
direction = self._calculate_teleport_direction(controller, input_vec)
# 更新传送预览
if self.teleport_system:
if joystick_state.teleport_just_started:
self.teleport_system.start_teleport_preview(controller, direction)
joystick_state.teleport_just_started = False
else:
self.teleport_system.update_teleport_preview(controller, direction)
else:
# 检查是否需要执行传送
if joystick_state.teleport_active:
# 松开摇杆,执行传送
self._execute_teleport(controller)
joystick_state.teleport_active = False
joystick_state.teleport_just_started = True
# 重置该控制器的传送模式
self._set_controller_mode(hand, 'none')
else:
# 没有传送输入,检查是否需要重置传送模式
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode == 'teleporting':
self._set_controller_mode(hand, 'none')
def _calculate_teleport_direction(self, controller, input_vec):
"""计算传送方向向量 - 基于手柄姿态
Args:
controller: VR控制器实例
input_vec: 摇杆输入向量仅用于激活不影响方向
Returns:
Vec3: 世界坐标中的传送方向
"""
# 方法1直接使用手柄指向推荐
if controller and hasattr(controller, 'get_forward_direction'):
try:
# 获取手柄的前向方向
controller_forward = controller.get_forward_direction()
# 确保方向向量在水平面上
horizontal_direction = Vec3(controller_forward.x, controller_forward.y, 0)
if horizontal_direction.length() > 0:
horizontal_direction.normalize()
# 调试输出每30帧输出一次
if not hasattr(self, '_direction_debug_counter'):
self._direction_debug_counter = 0
self._direction_debug_counter += 1
if self._direction_debug_counter % 30 == 1:
# 获取当前tracking_space的旋转角度用于调试
tracking_rotation = 0
if self.vr_manager.tracking_space:
tracking_rotation = self.vr_manager.tracking_space.getH()
print(f"🎯 综合传送方向: 视角旋转({tracking_rotation:.1f}°) + 手柄姿态({controller_forward.x:.2f},{controller_forward.y:.2f},{controller_forward.z:.2f}) → 最终方向({horizontal_direction.x:.2f},{horizontal_direction.y:.2f})")
return horizontal_direction
except Exception as e:
print(f"⚠️ 获取手柄方向失败: {e}")
# 备选方案:使用玩家朝向(保留原逻辑作为备选)
print("🔄 使用备选传送方向计算...")
# 获取玩家当前朝向的变换矩阵
player_transform = None
# 优先使用头显的世界变换
if self.vr_manager.hmd_anchor and hasattr(self.vr_manager, 'world') and self.vr_manager.world:
player_transform = self.vr_manager.hmd_anchor.getMat(self.vr_manager.world.render)
# 备选使用tracking_space的世界变换
elif self.vr_manager.tracking_space and hasattr(self.vr_manager, 'world') and self.vr_manager.world:
player_transform = self.vr_manager.tracking_space.getMat(self.vr_manager.world.render)
if player_transform:
# 从变换矩阵提取前向向量
forward = Vec3(player_transform.getRow3(1)) # Y轴 = 前向
# 确保向量在水平面上
forward.z = 0
if forward.length() > 0:
forward.normalize()
return forward
# 最终备选方案:默认前向
print("⚠️ 无法获取任何朝向,使用默认方向")
return Vec3(0, 1, 0)
def _execute_teleport(self, controller):
"""执行传送
Args:
controller: VR控制器实例
"""
if self.teleport_system and self.teleport_preview_active:
success = self.teleport_system.execute_teleport()
if success:
# 传送成功,触发震动反馈
controller.trigger_haptic_feedback(0.005, 0.8)
else:
# 传送失败,触发不同的震动反馈
controller.trigger_haptic_feedback(0.001, 0.2)
# 停止传送预览
self.teleport_system.stop_teleport_preview()
self.teleport_preview_active = False
self.active_teleport_controller = None
def set_turning_mode(self, smooth=True):
"""设置转向模式
Args:
smooth: True为平滑转向False为分段转向
"""
self.smooth_turning = smooth
print(f"✓ 转向模式设置为: {'平滑转向' if smooth else '分段转向'}")
def set_turn_sensitivity(self, sensitivity):
"""设置转向灵敏度
Args:
sensitivity: 转向灵敏度/
"""
self.turn_sensitivity = max(10.0, min(180.0, sensitivity))
print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒")
def apply_config(self, config):
"""应用配置
Args:
config: VRJoystickConfig配置实例
"""
try:
self.deadzone = config.deadzone
self.turn_threshold = config.turn_threshold
self.teleport_threshold = config.teleport_threshold
self.turn_sensitivity = config.turn_sensitivity
self.smooth_turning = (config.turn_mode.value == 'smooth')
self.snap_turn_angle = config.snap_turn_angle
self.snap_turn_cooldown = config.snap_turn_cooldown
# 应用传送系统配置
if self.teleport_system and hasattr(config, 'teleport_range'):
self.teleport_system.teleport_range = config.teleport_range
if hasattr(config, 'teleport_arc_resolution'):
self.teleport_system.arc_resolution = config.teleport_arc_resolution
if hasattr(config, 'teleport_initial_velocity'):
self.teleport_system.initial_velocity = config.teleport_initial_velocity
if hasattr(config, 'min_teleport_distance'):
self.teleport_system.min_teleport_distance = config.min_teleport_distance
print("✅ 摇杆配置已成功应用")
except Exception as e:
print(f"⚠️ 应用配置失败: {e}")
def get_current_config(self):
"""获取当前配置
Returns:
dict: 当前配置参数
"""
return {
'deadzone': self.deadzone,
'turn_threshold': self.turn_threshold,
'teleport_threshold': self.teleport_threshold,
'turn_sensitivity': self.turn_sensitivity,
'smooth_turning': self.smooth_turning,
'snap_turn_angle': self.snap_turn_angle,
'snap_turn_cooldown': self.snap_turn_cooldown
}
def _print_debug_status(self):
"""打印调试状态信息"""
try:
print("🔍 ======= VR摇杆调试状态 =======")
# 控制器连接状态
left_connected = self.vr_manager.left_controller is not None
right_connected = self.vr_manager.right_controller is not None
print(f"📱 控制器状态: 左手={left_connected}, 右手={right_connected}")
# 检查控制器属性
if left_connected:
left_ctrl = self.vr_manager.left_controller
has_joystick = hasattr(left_ctrl, 'joystick_pos')
has_touchpad = hasattr(left_ctrl, 'touchpad_pos')
print(f"📊 左手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}")
if has_joystick and left_ctrl.joystick_pos:
pos = left_ctrl.joystick_pos
print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if has_touchpad and left_ctrl.touchpad_pos:
pos = left_ctrl.touchpad_pos
print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if right_connected:
right_ctrl = self.vr_manager.right_controller
has_joystick = hasattr(right_ctrl, 'joystick_pos')
has_touchpad = hasattr(right_ctrl, 'touchpad_pos')
print(f"📊 右手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}")
if has_joystick and right_ctrl.joystick_pos:
pos = right_ctrl.joystick_pos
print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if has_touchpad and right_ctrl.touchpad_pos:
pos = right_ctrl.touchpad_pos
print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
# 摇杆配置
print(f"⚙️ 摇杆配置:")
print(f" 死区: {self.deadzone}")
print(f" 转向阈值: {self.turn_threshold}")
print(f" 传送阈值: {self.teleport_threshold}")
print(f" 转向模式: {'平滑' if self.smooth_turning else '分段'}")
# 动作系统状态
action_mgr_available = (self.vr_manager.action_manager is not None)
print(f"🎯 动作系统: {'可用' if action_mgr_available else '不可用'}")
# 传送系统状态
teleport_available = (self.teleport_system is not None)
print(f"🚀 传送系统: {'可用' if teleport_available else '不可用'}")
print("🔍 ==============================")
except Exception as e:
print(f"⚠️ 调试状态输出失败: {e}")
def cleanup(self):
"""清理摇杆系统资源"""
try:
# 停止任何活跃的传送预览
if self.teleport_preview_active and self.teleport_system:
self.teleport_system.stop_teleport_preview()
self.teleport_preview_active = False
self.active_teleport_controller = None
self.ignoreAll()
print("🧹 VR摇杆系统已清理")
except Exception as e:
print(f"⚠️ 清理摇杆系统失败: {e}")
class JoystickState:
"""摇杆状态跟踪类"""
def __init__(self):
self.current_input = Vec2(0, 0) # 当前摇杆输入
self.previous_input = Vec2(0, 0) # 上一帧摇杆输入
self.teleport_active = False # 传送是否激活
self.teleport_just_started = True # 传送是否刚开始
def update(self, new_input):
"""更新摇杆状态
Args:
new_input: 新的摇杆输入
"""
self.previous_input = Vec2(self.current_input)
self.current_input = Vec2(new_input)
def is_input_changed(self, threshold=0.01):
"""检查输入是否发生变化
Args:
threshold: 变化阈值
Returns:
bool: 是否发生变化
"""
diff = self.current_input - self.previous_input
return diff.length() > threshold

268
core/vr_joystick_config.py Normal file
View File

@ -0,0 +1,268 @@
"""
VR摇杆配置模块
提供摇杆交互的配置选项和预设
- 不同的转向模式和灵敏度设置
- 传送参数调整
- 用户体验优化选项
"""
from enum import Enum
class TurnMode(Enum):
"""转向模式枚举"""
SMOOTH = "smooth" # 平滑转向
SNAP = "snap" # 分段转向
class JoystickProfile(Enum):
"""摇杆配置预设"""
COMFORTABLE = "comfortable" # 舒适模式 - 低灵敏度,平滑转向
STANDARD = "standard" # 标准模式 - 中等灵敏度,分段转向
GAMING = "gaming" # 游戏模式 - 高灵敏度,快速响应
ACCESSIBLE = "accessible" # 无障碍模式 - 更大死区,更高阈值
class VRJoystickConfig:
"""VR摇杆配置类"""
def __init__(self):
"""初始化默认配置"""
# 基础参数
self.deadzone = 0.15 # 摇杆死区 (0-1)
self.turn_threshold = 0.3 # 转向激活阈值
self.teleport_threshold = 0.5 # 传送激活阈值
# 转向设置
self.turn_mode = TurnMode.SNAP # 转向模式
self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度
self.snap_turn_angle = 30.0 # 分段转向角度(度)
self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒)
# 传送设置
self.teleport_range = 20.0 # 最大传送距离
self.teleport_arc_resolution = 50 # 抛物线精度
self.teleport_initial_velocity = 10.0 # 传送初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
# 反馈设置
self.haptic_feedback_enabled = True # 是否启用震动反馈
self.teleport_start_haptic = (0.002, 0.3) # 传送开始震动 (时长, 强度)
self.teleport_success_haptic = (0.005, 0.8) # 传送成功震动
self.teleport_fail_haptic = (0.001, 0.2) # 传送失败震动
# 可视化设置
self.show_teleport_arc = True # 显示传送抛物线
self.show_teleport_target = True # 显示传送目标标记
self.arc_valid_color = (0.2, 0.9, 0.2, 0.8) # 有效抛物线颜色
self.arc_invalid_color = (0.9, 0.2, 0.2, 0.8) # 无效抛物线颜色
def apply_profile(self, profile: JoystickProfile):
"""应用预设配置
Args:
profile: 配置预设
"""
if profile == JoystickProfile.COMFORTABLE:
self._apply_comfortable_profile()
elif profile == JoystickProfile.STANDARD:
self._apply_standard_profile()
elif profile == JoystickProfile.GAMING:
self._apply_gaming_profile()
elif profile == JoystickProfile.ACCESSIBLE:
self._apply_accessible_profile()
def _apply_comfortable_profile(self):
"""舒适模式 - 适合长时间使用"""
self.deadzone = 0.2
self.turn_threshold = 0.4
self.teleport_threshold = 0.6
self.turn_mode = TurnMode.SMOOTH
self.turn_sensitivity = 100.0 # 适中的转向速度
self.snap_turn_cooldown = 0.4
print("✓ 已应用舒适模式配置")
def _apply_standard_profile(self):
"""标准模式 - 平衡的体验"""
self.deadzone = 0.15
self.turn_threshold = 0.3
self.teleport_threshold = 0.5
self.turn_mode = TurnMode.SNAP
self.turn_sensitivity = 150.0 # 更快的转向速度
self.snap_turn_angle = 30.0
self.snap_turn_cooldown = 0.3
print("✓ 已应用标准模式配置")
def _apply_gaming_profile(self):
"""游戏模式 - 快速响应"""
self.deadzone = 0.1
self.turn_threshold = 0.2
self.teleport_threshold = 0.4
self.turn_mode = TurnMode.SMOOTH
self.turn_sensitivity = 200.0 # 高速转向
self.snap_turn_cooldown = 0.2
print("✓ 已应用游戏模式配置")
def _apply_accessible_profile(self):
"""无障碍模式 - 更容易控制"""
self.deadzone = 0.25
self.turn_threshold = 0.5
self.teleport_threshold = 0.7
self.turn_mode = TurnMode.SNAP
self.turn_sensitivity = 80.0 # 较慢但可控的转向
self.snap_turn_angle = 45.0
self.snap_turn_cooldown = 0.5
print("✓ 已应用无障碍模式配置")
def set_turn_mode(self, mode: TurnMode):
"""设置转向模式
Args:
mode: 转向模式
"""
self.turn_mode = mode
print(f"✓ 转向模式设置为: {mode.value}")
def set_turn_sensitivity(self, sensitivity: float):
"""设置转向灵敏度
Args:
sensitivity: 转向灵敏度/
"""
self.turn_sensitivity = max(10.0, min(180.0, sensitivity))
print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒")
def set_deadzone(self, deadzone: float):
"""设置摇杆死区
Args:
deadzone: 死区大小 (0-1)
"""
self.deadzone = max(0.0, min(0.5, deadzone))
print(f"✓ 摇杆死区设置为: {self.deadzone}")
def set_teleport_range(self, range_meters: float):
"""设置传送范围
Args:
range_meters: 传送范围
"""
self.teleport_range = max(5.0, min(50.0, range_meters))
print(f"✓ 传送范围设置为: {self.teleport_range}")
def enable_haptic_feedback(self, enabled: bool):
"""启用或禁用震动反馈
Args:
enabled: 是否启用
"""
self.haptic_feedback_enabled = enabled
print(f"✓ 震动反馈: {'启用' if enabled else '禁用'}")
def get_config_dict(self):
"""获取配置字典
Returns:
dict: 配置参数字典
"""
return {
'deadzone': self.deadzone,
'turn_threshold': self.turn_threshold,
'teleport_threshold': self.teleport_threshold,
'turn_mode': self.turn_mode.value,
'turn_sensitivity': self.turn_sensitivity,
'snap_turn_angle': self.snap_turn_angle,
'snap_turn_cooldown': self.snap_turn_cooldown,
'teleport_range': self.teleport_range,
'haptic_feedback_enabled': self.haptic_feedback_enabled
}
def apply_to_joystick_manager(self, joystick_manager):
"""将配置应用到摇杆管理器
Args:
joystick_manager: VRJoystickManager实例
"""
try:
joystick_manager.deadzone = self.deadzone
joystick_manager.turn_threshold = self.turn_threshold
joystick_manager.teleport_threshold = self.teleport_threshold
joystick_manager.turn_sensitivity = self.turn_sensitivity
joystick_manager.smooth_turning = (self.turn_mode == TurnMode.SMOOTH)
joystick_manager.snap_turn_angle = self.snap_turn_angle
joystick_manager.snap_turn_cooldown = self.snap_turn_cooldown
# 应用传送系统配置
if hasattr(joystick_manager, 'teleport_system') and joystick_manager.teleport_system:
teleport_sys = joystick_manager.teleport_system
teleport_sys.teleport_range = self.teleport_range
teleport_sys.arc_resolution = self.teleport_arc_resolution
teleport_sys.initial_velocity = self.teleport_initial_velocity
teleport_sys.min_teleport_distance = self.min_teleport_distance
print("✅ 配置已成功应用到摇杆管理器")
except Exception as e:
print(f"⚠️ 应用配置失败: {e}")
# 预定义的配置实例
COMFORTABLE_CONFIG = VRJoystickConfig()
COMFORTABLE_CONFIG.apply_profile(JoystickProfile.COMFORTABLE)
STANDARD_CONFIG = VRJoystickConfig()
STANDARD_CONFIG.apply_profile(JoystickProfile.STANDARD)
GAMING_CONFIG = VRJoystickConfig()
GAMING_CONFIG.apply_profile(JoystickProfile.GAMING)
ACCESSIBLE_CONFIG = VRJoystickConfig()
ACCESSIBLE_CONFIG.apply_profile(JoystickProfile.ACCESSIBLE)
def create_custom_config(**kwargs):
"""创建自定义配置
Args:
**kwargs: 配置参数
Returns:
VRJoystickConfig: 自定义配置实例
"""
config = VRJoystickConfig()
for key, value in kwargs.items():
if hasattr(config, key):
setattr(config, key, value)
else:
print(f"⚠️ 未知的配置参数: {key}")
return config
def print_usage_guide():
"""打印使用指南"""
print("🎮 ======= VR摇杆交互使用指南 =======")
print()
print("📋 基本操作:")
print(" • 摇杆左右移动 → 转向(旋转视角)")
print(" • 摇杆向前推 → 显示传送抛物线")
print(" • 松开摇杆 → 执行传送到落点")
print()
print("⚙️ 配置选项:")
print(" • 舒适模式 → 低灵敏度,适合长时间使用")
print(" • 标准模式 → 平衡体验,推荐大多数用户")
print(" • 游戏模式 → 高灵敏度,快速响应")
print(" • 无障碍模式 → 更大死区,容易控制")
print()
print("🎯 使用建议:")
print(" • 首次使用建议从标准模式开始")
print(" • 根据个人喜好调整转向模式(平滑/分段)")
print(" • 可以随时调整死区和灵敏度")
print(" • 传送范围可根据场景大小调整")
print()
if __name__ == "__main__":
print_usage_guide()

View File

@ -28,10 +28,12 @@ except ImportError:
OPENVR_AVAILABLE = False OPENVR_AVAILABLE = False
print("警告: OpenVR未安装VR功能将不可用") print("警告: OpenVR未安装VR功能将不可用")
# 导入手柄控制器、动作系统和交互系统 # 导入手柄控制器、动作系统、交互系统、摇杆系统和传送系统
from .vr_controller import LeftController, RightController from .vr_controller import LeftController, RightController
from .vr_actions import VRActionManager from .vr_actions import VRActionManager
from .vr_interaction import VRInteractionManager from .vr_interaction import VRInteractionManager
from .vr_joystick import VRJoystickManager
from .vr_teleport import VRTeleportSystem
class VRManager(DirectObject): class VRManager(DirectObject):
@ -236,13 +238,19 @@ class VRManager(DirectObject):
self.controllers = {} # 设备索引到控制器的映射 self.controllers = {} # 设备索引到控制器的映射
self.tracked_device_anchors = {} # 跟踪设备锚点 self.tracked_device_anchors = {} # 跟踪设备锚点
# VR动作系统 - 添加异常保护 # VR动作系统 - 可选择禁用用于API兼容性问题
try: self.disable_action_system = True # 禁用动作系统,使用直接控制器输入
self.action_manager = VRActionManager(self)
print("✓ VR动作管理器初始化完成") if not self.disable_action_system:
except Exception as e: try:
print(f"⚠️ VR动作管理器初始化失败: {e}") self.action_manager = VRActionManager(self)
print("✓ VR动作管理器初始化完成")
except Exception as e:
print(f"⚠️ VR动作管理器初始化失败: {e}")
self.action_manager = None
else:
self.action_manager = None self.action_manager = None
print("🚫 VR动作系统已禁用使用直接控制器输入")
# VR交互系统 - 添加异常保护 # VR交互系统 - 添加异常保护
try: try:
@ -252,6 +260,22 @@ class VRManager(DirectObject):
print(f"⚠️ VR交互管理器初始化失败: {e}") print(f"⚠️ VR交互管理器初始化失败: {e}")
self.interaction_manager = None self.interaction_manager = None
# VR传送系统 - 添加异常保护
try:
self.teleport_system = VRTeleportSystem(self)
print("✓ VR传送系统初始化完成")
except Exception as e:
print(f"⚠️ VR传送系统初始化失败: {e}")
self.teleport_system = None
# VR摇杆系统 - 添加异常保护
try:
self.joystick_manager = VRJoystickManager(self)
print("✓ VR摇杆管理器初始化完成")
except Exception as e:
print(f"⚠️ VR摇杆管理器初始化失败: {e}")
self.joystick_manager = None
print("✓ VR管理器初始化完成") print("✓ VR管理器初始化完成")
def _initialize_object_pools(self): def _initialize_object_pools(self):
@ -522,15 +546,18 @@ class VRManager(DirectObject):
# 初始化手柄控制器 # 初始化手柄控制器
self._initialize_controllers() self._initialize_controllers()
# 初始化动作系统 - 检查是否存在 # 初始化动作系统 - 仅在启用时初始化
if self.action_manager: if not self.disable_action_system and self.action_manager:
try: try:
if not self.action_manager.initialize(): if not self.action_manager.initialize():
print("⚠️ VR动作系统初始化失败但VR系统将继续运行") print("⚠️ VR动作系统初始化失败但VR系统将继续运行")
except Exception as e: except Exception as e:
print(f"⚠️ VR动作系统初始化异常: {e}") print(f"⚠️ VR动作系统初始化异常: {e}")
else: else:
print("⚠️ VR动作管理器未创建跳过动作系统初始化") if self.disable_action_system:
print("🚫 VR动作系统已禁用跳过初始化")
else:
print("⚠️ VR动作管理器未创建跳过动作系统初始化")
# 初始化交互系统 - 检查是否存在 # 初始化交互系统 - 检查是否存在
if self.interaction_manager: if self.interaction_manager:
@ -542,6 +569,27 @@ class VRManager(DirectObject):
else: else:
print("⚠️ VR交互管理器未创建跳过交互系统初始化") print("⚠️ VR交互管理器未创建跳过交互系统初始化")
# 初始化VR传送系统
if self.teleport_system:
try:
if not self.teleport_system.initialize():
print("⚠️ VR传送系统初始化失败但VR系统将继续运行")
except Exception as e:
print(f"⚠️ VR传送系统初始化异常: {e}")
else:
print("⚠️ VR传送系统未创建跳过传送系统初始化")
# 初始化VR摇杆系统
if self.joystick_manager:
try:
# 传入传送系统引用给摇杆管理器
self.joystick_manager.initialize(self.teleport_system)
print("✓ VR摇杆系统初始化成功")
except Exception as e:
print(f"⚠️ VR摇杆系统初始化异常: {e}")
else:
print("⚠️ VR摇杆管理器未创建跳过摇杆系统初始化")
# 可选:禁用异步重投影(备选方案) # 可选:禁用异步重投影(备选方案)
if self.disable_async_reprojection: if self.disable_async_reprojection:
self._disable_async_reprojection() self._disable_async_reprojection()
@ -956,11 +1004,29 @@ class VRManager(DirectObject):
print(" 这确保每个 VSync 周期只渲染一次,解决 AlreadySubmitted 错误") print(" 这确保每个 VSync 周期只渲染一次,解决 AlreadySubmitted 错误")
self._new_architecture_logged = True self._new_architecture_logged = True
# 更新VR动作状态 # 更新VR动作状态 - 仅在启用时更新
self.action_manager.update_actions() if not self.disable_action_system and self.action_manager:
self.action_manager.update_actions()
# 更新VR交互系统 # 更新VR交互系统
self.interaction_manager.update() if self.interaction_manager:
self.interaction_manager.update()
# 更新VR摇杆系统
if self.joystick_manager:
# 计算帧间隔时间
import time
if not hasattr(self, '_last_frame_time'):
self._last_frame_time = time.time()
dt = 0.016 # 默认60fps
else:
current_time = time.time()
dt = current_time - self._last_frame_time
self._last_frame_time = current_time
# 限制dt范围避免异常情况
dt = max(0.001, min(0.1, dt))
self.joystick_manager.update(dt)
# 更新系统性能指标(减少频率) # 更新系统性能指标(减少频率)
if self.frame_count % 30 == 1: # 每30帧更新一次减少开销 if self.frame_count % 30 == 1: # 每30帧更新一次减少开销
@ -1527,6 +1593,32 @@ class VRManager(DirectObject):
"""禁用VR模式""" """禁用VR模式"""
self.vr_enabled = False self.vr_enabled = False
# 清理手柄可视化(但保留控制器对象以便重新启用)
if hasattr(self, 'left_controller') and self.left_controller:
if hasattr(self.left_controller, 'visualizer') and self.left_controller.visualizer:
try:
self.left_controller.visualizer.cleanup()
self.left_controller.visualizer = None
print("✓ 左手控制器可视化已清理")
except Exception as e:
print(f"⚠️ 清理左手控制器可视化失败: {e}")
if hasattr(self, 'right_controller') and self.right_controller:
if hasattr(self.right_controller, 'visualizer') and self.right_controller.visualizer:
try:
self.right_controller.visualizer.cleanup()
self.right_controller.visualizer = None
print("✓ 右手控制器可视化已清理")
except Exception as e:
print(f"⚠️ 清理右手控制器可视化失败: {e}")
# 隐藏手柄锚点节点
if hasattr(self, 'left_controller') and self.left_controller and self.left_controller.anchor_node:
self.left_controller.anchor_node.hide()
if hasattr(self, 'right_controller') and self.right_controller and self.right_controller.anchor_node:
self.right_controller.anchor_node.hide()
# 恢复主相机 # 恢复主相机
self._enable_main_cam() self._enable_main_cam()
@ -1536,7 +1628,7 @@ class VRManager(DirectObject):
self.world.qtWidget.synchronizer.setInterval(int(1000/60)) self.world.qtWidget.synchronizer.setInterval(int(1000/60))
print("✓ Qt Timer恢复为60Hz") print("✓ Qt Timer恢复为60Hz")
print("✅ VR模式已禁用") print("✅ VR模式已禁用,手柄模型已隐藏")
def cleanup(self): def cleanup(self):
"""清理VR资源""" """清理VR资源"""
@ -1598,6 +1690,61 @@ class VRManager(DirectObject):
self.vr_right_camera.removeNode() self.vr_right_camera.removeNode()
self.vr_right_camera = None self.vr_right_camera = None
# 清理控制器
if hasattr(self, 'left_controller') and self.left_controller:
try:
self.left_controller.cleanup()
self.left_controller = None
print("✓ 左手控制器已清理")
except Exception as e:
print(f"⚠️ 清理左手控制器失败: {e}")
if hasattr(self, 'right_controller') and self.right_controller:
try:
self.right_controller.cleanup()
self.right_controller = None
print("✓ 右手控制器已清理")
except Exception as e:
print(f"⚠️ 清理右手控制器失败: {e}")
# 清理控制器字典
if hasattr(self, 'controllers'):
self.controllers.clear()
print("✓ 控制器字典已清理")
# 清理VR子系统
if hasattr(self, 'joystick_manager') and self.joystick_manager:
try:
self.joystick_manager.cleanup()
self.joystick_manager = None
print("✓ VR摇杆系统已清理")
except Exception as e:
print(f"⚠️ 清理VR摇杆系统失败: {e}")
if hasattr(self, 'teleport_system') and self.teleport_system:
try:
self.teleport_system.cleanup()
self.teleport_system = None
print("✓ VR传送系统已清理")
except Exception as e:
print(f"⚠️ 清理VR传送系统失败: {e}")
if hasattr(self, 'interaction_manager') and self.interaction_manager:
try:
self.interaction_manager.cleanup()
self.interaction_manager = None
print("✓ VR交互系统已清理")
except Exception as e:
print(f"⚠️ 清理VR交互系统失败: {e}")
if hasattr(self, 'action_manager') and self.action_manager:
try:
self.action_manager.cleanup()
self.action_manager = None
print("✓ VR动作系统已清理")
except Exception as e:
print(f"⚠️ 清理VR动作系统失败: {e}")
# 关闭OpenVR # 关闭OpenVR
if self.vr_system and OPENVR_AVAILABLE: if self.vr_system and OPENVR_AVAILABLE:
try: try:

411
core/vr_teleport.py Normal file
View File

@ -0,0 +1,411 @@
"""
VR传送系统模块
提供VR传送功能
- 抛物线轨迹计算和可视化
- 传送点有效性检测
- 传送执行
- 可视化反馈抛物线落点标记
"""
import math
from panda3d.core import (
Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser,
CollisionNode, CollisionHandlerQueue, BitMask32, NodePath,
CollisionSphere, CollisionPlane, LineSegs, GeomNode, Material,
RenderState, TransparencyAttrib, ColorAttrib
)
from direct.showbase.DirectObject import DirectObject
class VRTeleportSystem(DirectObject):
"""VR传送系统 - 处理传送功能和可视化"""
def __init__(self, vr_manager):
"""初始化VR传送系统
Args:
vr_manager: VR管理器实例
"""
super().__init__()
self.vr_manager = vr_manager
self.world = vr_manager.world if hasattr(vr_manager, 'world') else None
# 传送参数
self.teleport_range = 20.0 # 最大传送距离
self.arc_resolution = 50 # 抛物线精度
self.gravity = -9.8 # 重力系数
self.initial_velocity = 10.0 # 初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
# 可视化元素
self.teleport_arc_node = None # 抛物线节点
self.teleport_target_node = None # 落点标记节点
self.teleport_invalid_node = None # 无效位置标记
# 传送状态
self.is_teleport_active = False # 是否正在预览传送
self.teleport_target_pos = None # 传送目标位置
self.teleport_valid = False # 传送位置是否有效
self.active_controller = None # 正在使用传送的控制器
# 碰撞检测
self.teleport_collision_traverser = CollisionTraverser()
self.teleport_collision_queue = CollisionHandlerQueue()
# 可视化颜色
self.valid_arc_color = Vec4(0.2, 0.9, 0.2, 0.8) # 绿色抛物线
self.invalid_arc_color = Vec4(0.9, 0.2, 0.2, 0.8) # 红色抛物线
self.target_color = Vec4(0.2, 0.7, 1.0, 0.9) # 蓝色落点
print("✓ VR传送系统初始化完成")
def initialize(self):
"""初始化传送系统"""
try:
print("🔧 正在初始化VR传送系统...")
# 创建可视化元素
self._create_teleport_visuals()
# 设置地面检测
self._setup_ground_detection()
print("✅ VR传送系统初始化成功")
return True
except Exception as e:
print(f"❌ VR传送系统初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_teleport_visuals(self):
"""创建传送可视化元素"""
if not self.world or not hasattr(self.world, 'render'):
print("⚠️ 无法创建传送可视化 - 缺少世界渲染节点")
return
# 创建抛物线节点
self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc")
self.teleport_arc_node.hide() # 初始隐藏
# 创建落点标记
self._create_target_marker()
# 创建无效位置标记
self._create_invalid_marker()
print("✓ 传送可视化元素已创建")
def _create_target_marker(self):
"""创建传送目标标记"""
try:
# 创建一个圆形平台作为落点标记
from panda3d.core import CardMaker
cm = CardMaker("teleport_target")
cm.setFrame(-1, 1, -1, 1) # 2x2的平面
self.teleport_target_node = self.world.render.attachNewNode(cm.generate())
self.teleport_target_node.setScale(1.0) # 2米直径的圆形
self.teleport_target_node.setP(-90) # 平放在地面
self.teleport_target_node.setColor(self.target_color)
self.teleport_target_node.setTransparency(TransparencyAttrib.MAlpha)
self.teleport_target_node.hide()
# 添加PBR材质以兼容RenderPipeline
material = Material()
material.setBaseColor(self.target_color)
material.setRoughness(0.8)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
self.teleport_target_node.setMaterial(material, 1)
except Exception as e:
print(f"⚠️ 创建传送目标标记失败: {e}")
def _create_invalid_marker(self):
"""创建无效位置标记"""
try:
from panda3d.core import CardMaker
cm = CardMaker("teleport_invalid")
cm.setFrame(-0.8, 0.8, -0.8, 0.8) # 稍小的红色叉号
self.teleport_invalid_node = self.world.render.attachNewNode(cm.generate())
self.teleport_invalid_node.setScale(1.0)
self.teleport_invalid_node.setP(-90)
self.teleport_invalid_node.setColor(self.invalid_arc_color)
self.teleport_invalid_node.setTransparency(TransparencyAttrib.MAlpha)
self.teleport_invalid_node.hide()
# 添加PBR材质
material = Material()
material.setBaseColor(self.invalid_arc_color)
material.setRoughness(0.8)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
self.teleport_invalid_node.setMaterial(material, 1)
except Exception as e:
print(f"⚠️ 创建无效位置标记失败: {e}")
def _setup_ground_detection(self):
"""设置地面检测"""
try:
# 假设地面在Z=0平面使用正确的CollisionPlane API
from panda3d.core import Plane
ground_plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))
ground_node = CollisionNode("ground_detection")
ground_node.addSolid(ground_plane)
ground_node.setIntoCollideMask(BitMask32.bit(1)) # 地面碰撞掩码
# 附加到渲染节点
if self.world and hasattr(self.world, 'render'):
ground_node_path = self.world.render.attachNewNode(ground_node)
print("✓ 地面检测设置成功")
except Exception as e:
print(f"⚠️ 设置地面检测失败: {e}")
# 继续运行,不让这个错误阻止传送系统
def start_teleport_preview(self, controller, direction):
"""开始传送预览
Args:
controller: VR控制器实例
direction: 传送方向向量摇杆输入转换的世界方向
"""
if not controller or not controller.anchor_node:
return False
self.is_teleport_active = True
self.active_controller = controller
# 计算抛物线轨迹
start_pos = controller.get_world_position()
self._calculate_teleport_trajectory(start_pos, direction)
# 显示可视化
self._show_teleport_visuals()
return True
def _calculate_teleport_trajectory(self, start_pos, direction):
"""计算传送抛物线轨迹
Args:
start_pos: 起始位置
direction: 方向向量
"""
try:
# 确保方向向量已标准化
direction = direction.normalized()
# 计算抛物线点
trajectory_points = []
hit_ground = False
self.teleport_valid = False
# 初始速度向量
velocity = direction * self.initial_velocity
velocity.z += 2.0 # 给一些向上的初始速度
dt = 0.05 # 时间步长
current_pos = Vec3(start_pos)
current_velocity = Vec3(velocity)
for i in range(self.arc_resolution):
trajectory_points.append(Point3(current_pos))
# 更新位置和速度
current_pos += current_velocity * dt
current_velocity.z += self.gravity * dt # 应用重力
# 检查是否碰撞地面
if current_pos.z <= 0.1: # 假设地面在z=0
# 精确计算落地点
ground_pos = self._calculate_ground_intersection(
trajectory_points[-2] if len(trajectory_points) > 1 else start_pos,
current_pos
)
trajectory_points.append(ground_pos)
# 检查传送有效性
distance = (ground_pos - start_pos).length()
if (distance >= self.min_teleport_distance and
distance <= self.teleport_range):
self.teleport_target_pos = ground_pos
self.teleport_valid = True
else:
self.teleport_target_pos = ground_pos
self.teleport_valid = False
hit_ground = True
break
# 超出范围
if (current_pos - start_pos).length() > self.teleport_range:
break
# 如果没有碰撞地面,传送无效
if not hit_ground:
if trajectory_points:
self.teleport_target_pos = trajectory_points[-1]
self.teleport_valid = False
# 创建抛物线几何体
self._create_arc_geometry(trajectory_points)
except Exception as e:
print(f"⚠️ 计算传送轨迹失败: {e}")
self.teleport_valid = False
def _calculate_ground_intersection(self, p1, p2):
"""计算与地面的精确交点"""
if p1.z == p2.z:
return Point3(p2)
# 线性插值找到z=0的点
t = -p1.z / (p2.z - p1.z)
t = max(0, min(1, t)) # 限制在0-1范围内
intersection = p1 + (p2 - p1) * t
intersection.z = 0.1 # 稍微高于地面
return Point3(intersection)
def _create_arc_geometry(self, points):
"""创建抛物线几何体"""
if not points or len(points) < 2:
return
try:
# 创建线段
line_segs = LineSegs()
line_segs.setThickness(3)
# 根据有效性设置颜色
color = self.valid_arc_color if self.teleport_valid else self.invalid_arc_color
line_segs.setColor(color)
# 添加线段点
line_segs.moveTo(points[0])
for point in points[1:]:
line_segs.drawTo(point)
# 清除旧的几何体
self.teleport_arc_node.removeNode()
self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc")
# 创建新的几何体
geom_node = line_segs.create()
arc_node_path = self.teleport_arc_node.attachNewNode(geom_node)
# 设置材质
material = Material()
material.setBaseColor(color)
material.setRoughness(0.1)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
arc_node_path.setMaterial(material, 1)
# 设置透明度
arc_node_path.setTransparency(TransparencyAttrib.MAlpha)
except Exception as e:
print(f"⚠️ 创建抛物线几何体失败: {e}")
def _show_teleport_visuals(self):
"""显示传送可视化"""
if self.teleport_arc_node:
self.teleport_arc_node.show()
if self.teleport_target_pos:
if self.teleport_valid and self.teleport_target_node:
# 显示有效的传送目标
self.teleport_target_node.setPos(self.teleport_target_pos)
self.teleport_target_node.show()
self.teleport_invalid_node.hide()
elif self.teleport_invalid_node:
# 显示无效的传送位置
self.teleport_invalid_node.setPos(self.teleport_target_pos)
self.teleport_invalid_node.show()
self.teleport_target_node.hide()
def stop_teleport_preview(self):
"""停止传送预览"""
self.is_teleport_active = False
self.active_controller = None
# 隐藏可视化
if self.teleport_arc_node:
self.teleport_arc_node.hide()
if self.teleport_target_node:
self.teleport_target_node.hide()
if self.teleport_invalid_node:
self.teleport_invalid_node.hide()
def execute_teleport(self):
"""执行传送"""
if not self.teleport_valid or not self.teleport_target_pos:
print("⚠️ 传送位置无效,无法执行传送")
return False
try:
# 计算传送偏移
if self.vr_manager.tracking_space:
current_pos = self.vr_manager.tracking_space.getPos()
target_offset = self.teleport_target_pos - self.active_controller.get_world_position()
new_pos = current_pos + target_offset
# 执行传送
self.vr_manager.tracking_space.setPos(new_pos)
print(f"✅ 传送成功: {current_pos}{new_pos}")
# 停止预览
self.stop_teleport_preview()
return True
else:
print("⚠️ 无法获取VR跟踪空间传送失败")
return False
except Exception as e:
print(f"❌ 执行传送失败: {e}")
return False
def update_teleport_preview(self, controller, direction):
"""更新传送预览(摇杆移动时调用)"""
if self.is_teleport_active and controller == self.active_controller:
start_pos = controller.get_world_position()
self._calculate_teleport_trajectory(start_pos, direction)
self._show_teleport_visuals()
def cleanup(self):
"""清理传送系统资源"""
try:
self.stop_teleport_preview()
if self.teleport_arc_node:
self.teleport_arc_node.removeNode()
self.teleport_arc_node = None
if self.teleport_target_node:
self.teleport_target_node.removeNode()
self.teleport_target_node = None
if self.teleport_invalid_node:
self.teleport_invalid_node.removeNode()
self.teleport_invalid_node = None
self.ignoreAll()
print("🧹 VR传送系统已清理")
except Exception as e:
print(f"⚠️ 清理传送系统失败: {e}")

View File

@ -708,7 +708,22 @@ class VRControllerVisualizer:
def cleanup(self): def cleanup(self):
"""清理资源""" """清理资源"""
# 清理射线节点
if hasattr(self, 'ray_node') and self.ray_node:
self.ray_node.removeNode()
self.ray_node = None
print(f"🧹 {self.controller.name}手柄射线已清理")
# 清理模型节点
if hasattr(self, 'model_node') and self.model_node:
self.model_node.removeNode()
self.model_node = None
print(f"🧹 {self.controller.name}手柄模型已清理")
# 清理主可视化节点
if self.visual_node: if self.visual_node:
self.visual_node.removeNode() self.visual_node.removeNode()
self.visual_node = None
print(f"🧹 {self.controller.name}手柄主节点已清理")
print(f"🧹 {self.controller.name}手柄可视化已清理") print(f" {self.controller.name}手柄可视化已彻底清理")