游戏技能范围边界计算优化及相机角度适配问询
问题描述
我正在开发一款游戏的技能范围计算脚本,当前脚本用椭圆标识技能范围(如Image 1所示),但实际需要实现的是Image 2那种非圆非椭圆的技能范围边界。我找不到当前脚本的问题所在,而且游戏支持相机角度调整,推测需要引入三维(z轴)相关信息,但不知道具体怎么实现。希望优化脚本以实现正确的范围计算,还要适配不同相机角度(如Image 3-5所示)。现有脚本如下:
import cv2 import numpy as np import pyautogui import win32gui window_name = "Game Window" hwnd = win32gui.FindWindow(None, window_name) window_rect = win32gui.GetWindowRect(hwnd) screen_width = window_rect[2] screen_height = window_rect[3] # Range in pixels. SKILL_RANGE_LIMIT = 380 # Defines the center (the caster is in the center of the screen). center_x = screen_width / 2 center_y = screen_height / 2 # Defines the color of the circle (in BGR format). circle_color = (0, 255, 0) # Green while True: # Gets the current mouse position. mouse_x, mouse_y = pyautogui.position() # Gets the caster position (it's actually the center of the screen). caster_x, caster_y = screen_width // 2, screen_height // 2 # Calculates the distance between the caster and the target. distance = np.sqrt((mouse_x - caster_x) ** 2 + (mouse_y - caster_y) ** 2) # Checks if the mouse is within the skill range limit. if distance <= SKILL_RANGE_LIMIT: print("Inside") else: print("Outside") # Calculates the normalized direction vector from the caster to the max range. dx = mouse_x - caster_x dy = mouse_y - caster_y direction = np.array([dx, dy]) / distance # Calculates the new mouse position at the edge of the limit. new_x = int(caster_x + direction[0] * SKILL_RANGE_LIMIT) new_y = int(caster_y + direction[1] * SKILL_RANGE_LIMIT) # Moves the mouse to the new position. pyautogui.moveTo(new_x, new_y, _pause=False) screenshot = np.array(pyautogui.screenshot(region=window_rect)) # Draws the skill range limit ellipse. img = cv2.ellipse(screenshot, (caster_x, caster_y), (SKILL_RANGE_LIMIT, SKILL_RANGE_LIMIT), 0, 0, 360, (0, 255, 0), 2) cv2.imshow(window_name, img) # Waits for a key press to exit the script. if cv2.waitKey(1) & 0xFF == ord("q"): break # Cleans up the window. cv2.destroyAllWindows()
问题分析与优化方案
核心问题
当前脚本直接用2D屏幕距离和椭圆绘制,完全忽略了3D世界到2D屏幕的透视投影逻辑。游戏中的技能范围是3D空间内的区域,相机角度变化时,3D范围会被投影成不同的2D形状,绝非简单椭圆能匹配。
优化步骤
1. 引入3D到2D的投影转换
技能范围是3D世界中的区域(比如圆形、扇形、自定义多边形),需要先定义3D范围,再通过相机参数投影到屏幕:
- 需获取游戏相机参数:包括相机位置、朝向、FOV(视野角)、近远裁剪面;若无法直接获取,可通过游戏内已知3D点(如角色位置、地面标记)反向计算投影矩阵。
- 用投影矩阵将3D范围的顶点转换为屏幕坐标,再连接这些坐标绘制边界。
2. 替换距离判断逻辑
当前的2D屏幕距离判断不符合3D世界实际范围,需改为:
- 将鼠标屏幕坐标转换为3D射线(通过相机反投影)。
- 判断射线与技能3D范围的交点,或计算角色到射线与地面交点的3D距离,再和技能范围阈值比较。
3. 自定义范围的绘制实现
如果技能是自定义形状(如扇形、矩形):
- 在3D世界中定义该形状的顶点。
- 将每个顶点投影为屏幕坐标。
- 使用
cv2.polylines()连接坐标点,绘制准确边界。
4. 适配相机角度变化
每次相机角度改变时,重新计算投影矩阵,再重新投影3D顶点到屏幕,更新绘制的边界。
示例修改代码(核心逻辑框架)
import cv2 import numpy as np import pyautogui import win32gui window_name = "Game Window" hwnd = win32gui.FindWindow(None, window_name) window_rect = win32gui.GetWindowRect(hwnd) screen_width = window_rect[2] - window_rect[0] screen_height = window_rect[3] - window_rect[1] # 角色在3D世界中的位置(需从游戏获取或校准) caster_world_pos = np.array([0.0, 0.0, 0.0]) # 技能在3D世界中的实际范围(比如10米) skill_range_3d = 10.0 # 相机参数(需根据游戏实际情况调整或动态获取) camera_params = { "pos": np.array([5.0, 5.0, 3.0]), "target": np.array([0.0, 0.0, 0.0]), "fov": 60.0, "aspect_ratio": screen_width / screen_height, "near": 0.1, "far": 100.0 } def get_projection_matrix(cam_params): # 计算透视投影矩阵 fov_rad = np.radians(cam_params["fov"]) tan_half_fov = np.tan(fov_rad / 2) proj_mat = np.zeros((4,4)) proj_mat[0,0] = 1 / (cam_params["aspect_ratio"] * tan_half_fov) proj_mat[1,1] = 1 / tan_half_fov proj_mat[2,2] = -(cam_params["far"] + cam_params["near"]) / (cam_params["far"] - cam_params["near"]) proj_mat[2,3] = -2 * cam_params["far"] * cam_params["near"] / (cam_params["far"] - cam_params["near"]) proj_mat[3,2] = -1 return proj_mat def get_view_matrix(cam_pos, target_pos): # 计算视图矩阵(相机朝向转换) forward = target_pos - cam_pos forward = forward / np.linalg.norm(forward) right = np.cross(np.array([0.0, 1.0, 0.0]), forward) right = right / np.linalg.norm(right) up = np.cross(forward, right) view_mat = np.eye(4) view_mat[0:3, 0] = right view_mat[0:3, 1] = up view_mat[0:3, 2] = -forward view_mat[0:3, 3] = -np.dot(right, cam_pos), -np.dot(up, cam_pos), np.dot(forward, cam_pos) return view_mat def world_to_screen(world_pos, view_mat, proj_mat, screen_size): # 将3D世界坐标转换为屏幕坐标 homogenous_pos = np.array([world_pos[0], world_pos[1], world_pos[2], 1.0]) view_pos = np.dot(view_mat, homogenous_pos) proj_pos = np.dot(proj_mat, view_pos) if proj_pos[3] == 0: return None ndc_pos = proj_pos / proj_pos[3] screen_x = (ndc_pos[0] + 1) * screen_size[0] / 2 screen_y = (1 - ndc_pos[1]) * screen_size[1] / 2 return (int(screen_x), int(screen_y)) def generate_skill_range_3d_points(caster_pos, range_radius, num_points=32): # 生成3D技能范围顶点(可替换为自定义形状的顶点生成逻辑) points = [] for i in range(num_points): angle = 2 * np.pi * i / num_points x = caster_pos[0] + range_radius * np.cos(angle) z = caster_pos[2] + range_radius * np.sin(angle) y = caster_pos[1] points.append(np.array([x, y, z])) return points while True: mouse_x, mouse_y = pyautogui.position() win_mouse_x = mouse_x - window_rect[0] win_mouse_y = mouse_y - window_rect[1] # 计算当前相机的视图和投影矩阵 view_mat = get_view_matrix(camera_params["pos"], camera_params["target"]) proj_mat = get_projection_matrix(camera_params) # 转换3D技能范围顶点到屏幕坐标 skill_world_points = generate_skill_range_3d_points(caster_world_pos, skill_range_3d) skill_screen_points = [] for wp in skill_world_points: sp = world_to_screen(wp, view_mat, proj_mat, (screen_width, screen_height)) if sp is not None: skill_screen_points.append(sp) skill_screen_points = np.array(skill_screen_points, np.int32) # 鼠标范围判断逻辑(需替换为真实的3D射线检测) in_range = False if in_range: print("Inside") else: print("Outside") # 鼠标边界移动逻辑(需基于3D范围投影实现) # 绘制技能范围 screenshot = np.array(pyautogui.screenshot(region=window_rect)) if len(skill_screen_points) > 2: cv2.polylines(screenshot, [skill_screen_points], isClosed=True, color=(0,255,0), thickness=2) cv2.imshow(window_name, screenshot) if cv2.waitKey(1) & 0xFF == ord("q"): break cv2.destroyAllWindows()
关键注意事项
- 相机参数获取:若无法直接从游戏API获取,可通过校准实现——记录游戏内两个已知3D坐标点的屏幕位置,反向求解投影和视图矩阵。
- 自定义范围适配:如果技能是扇形、矩形等非圆形,直接修改
generate_skill_range_3d_points函数,生成对应形状的3D顶点即可。 - 性能优化:投影计算和顶点转换可缓存,仅在相机参数或技能范围变化时重新计算,避免每帧重复运算。
内容的提问来源于stack exchange,提问作者KenOaza
相关产品推荐
相关产品推荐

