ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity3d控制鼠标光标坐标位置案例:用SetCursorPos与user32.dll把光标改到TaoToken窗口

Unity3d控制鼠标光标坐标位置案例:用SetCursorPos与user32.dll把光标改到TaoToken窗口 1. Unity3d 控制鼠标光标坐标位置到底能做什么Unity3d 控制鼠标光标坐标位置说白了就是在桌面端运行时绕过 Input 系统直接调用 Windows 的user32.dll用SetCursorPos把光标强行挪到屏幕上的某个像素点。它不依赖鼠标硬件事件也不走 Unity 的 Input 管线属于操作系统级别的光标定位。适合谁用做桌面小工具、自动化演示、教学录屏、整蛊小游戏、UI 自动化测试的开发者都能用得上。我试过在编辑器里跑通后再打包成 exe效果一致但有几个坑必须提前说清楚。核心检索词先摆出来Unity3d 鼠标光标坐标位置控制靠的是System.Runtime.InteropServices命名空间下的DllImport特性把user32.dll里的SetCursorPos(int x, int y)映射进 C#。坐标单位是屏幕像素原点在屏幕左上角x 向右递增y 向下递增。这和 Unity 的屏幕坐标系一致但和游戏世界坐标完全是两码事。如果你想把游戏里某个物体的世界坐标变成光标位置必须先Camera.main.WorldToScreenPoint转成屏幕像素再喂给SetCursorPos。为什么这个案例值得单独写一篇因为很多人第一次写[DllImport(user32.dll)]时要么忘了using System.Runtime.InteropServices;要么把参数写成 float要么在打包后遇到权限或 DPI 缩放问题。更关键的是桌面端自动化往往需要配合一个稳定的模型调用通道来做智能决策比如让模型判断“光标该移到哪个按钮上”这时候统一 Key/API 通道就派上用场了。下面我会把可复制的 DllImport 声明、坐标换算脚本、运行验证步骤全部交付并顺带演示如何把光标定位到 TaoToken 窗口区域。先明确适用边界仅 Windows 桌面端有效Mac/Linux 不认user32.dllWebGL 和移动端直接编译报错。编辑器里能跑打包后也能跑但打包时要确保 Player Settings 里没有剥离相关 API。另外SetCursorPos是同步调用返回值 int 表示成功与否但实际开发中很少检查返回值因为失败通常意味着权限或坐标越界。我踩过的坑一开始把SetCursorPos的参数写成float编译能过但运行时光标乱跳因为 P/Invoke 默认按 int 封送float 会被截断成奇怪的值。后来改成 int 就正常了。还有一个坑是 DPI 缩放在 125% 缩放的显示器上Screen.width返回的是逻辑像素而SetCursorPos要的是物理像素两者不一致会导致光标偏移。解决办法是用Screen.currentResolution.width或者调用SetProcessDpiAwareness。这些细节后面章节会展开。这一章先建立认知SetCursorPos是入口坐标换算是桥梁窗口定位是目标。你不需要理解 Windows 消息循环只要会声明、会调用、会换算就行。下一章讲怎么把 TaoToken 的 Key 和 API 通道准备好让光标定位这件事和模型决策串起来。2. TaoToken 统一 Key/API 通道前置准备在写光标控制脚本之前先把模型调用通道准备好。为什么因为纯SetCursorPos只能做固定坐标移动真正实用的场景是“让模型根据屏幕内容决定光标去哪”。比如你截一张图发给模型模型返回“点击坐标 (820, 460)”你再调SetCursorPos(820, 460)。这条链路需要一个稳定的 API 入口TaoToken 就是干这个的。TaoToken 官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数直接写就行。你需要先注册账号然后在控制台创建 API Key。控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建 Key 的页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后你有三种使用方式。第一种是直接在代码里用 HTTP 请求调模型对话接口适合轻量集成。第二种是用 Coding Plan 做长期编码和 Agent 任务适合需要多轮决策的场景。第三种是接入 Claude Code 这类工具通过 Anthropic 兼容接口调用。模型对话入口https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。Coding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用 Claude Code需要配置 Base URL、API Key 和 Model ID 三件套。Base URL 填https://taotoken.net/apiAPI Key 填你创建的 KeyModel ID 按文档里支持的模型名填。Claude Code 的 Anthropic 接入入口https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。配置方式是在环境变量或配置文件里写{ anthropic_base_url: https://taotoken.net/api, anthropic_api_key: sk-你的Key, anthropic_model: claude-3-5-sonnet }如果你用 Cline 或 MCP 类工具同样需要 Base URL、Key、Model ID 三件套。Cline 的 MCP 配置里把 provider 设为 openai-compatiblebase URL 填https://taotoken.net/apikey 填你的 Keymodel 填对应模型 ID。Codex 的auth.json也是类似结构{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: gpt-4o }为什么要强调这三件套因为很多接入失败都是因为只填了 Key 没填 Base URL或者 Model ID 写错。TaoToken 的 API 是 OpenAI 兼容格式所以任何支持自定义 Base URL 的客户端都能接。你可以在模型对话页面先测试一下 Key 是否有效发一条“你好”能收到回复就说明通道通了。这一章的目标不是教你注册而是让你手里有一个可用的 API 通道。后面光标控制脚本里我会留一个函数AskModelForCursorPosition你可以把截图 base64 编码后发给模型让模型返回坐标 JSON。这样整条链路就完整了截图 → 模型决策 → SetCursorPos 执行。如果你暂时不想接模型也可以先用固定坐标跑通光标移动再逐步加决策逻辑。注意API Key 不要硬编码在提交到 Git 的脚本里用环境变量或本地配置文件。TaoToken 的 Key 管理页面可以随时吊销和重建建议每个项目单独建一个 Key方便追踪用量。3. 可复制的 DllImport 声明与坐标换算脚本这一章直接上代码。先给完整的MoveTest.cs包含 DllImport 声明、坐标换算、窗口定位到 TaoToken 区域、以及按键控制开关。你可以直接复制到 Unity 项目的 Assets 目录下绑定到场景里任意 GameObject 上。using System; using System.Runtime.InteropServices; using UnityEngine; public class MoveTest : MonoBehaviour { // 固定写法声明 user32.dll 中的 SetCursorPos [DllImport(user32.dll)] public static extern int SetCursorPos(int x, int y); // 获取当前光标位置用于验证 [DllImport(user32.dll)] public static extern bool GetCursorPos(out POINT lpPoint); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } // 查找窗口句柄用于定位 TaoToken 窗口 [DllImport(user32.dll, SetLastError true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); // 获取窗口矩形 [DllImport(user32.dll)] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private bool isWorking true; void Start() { isWorking true; Debug.Log(光标控制已启动按 Esc 停止按 P 继续); } void Update() { if (Input.GetKeyDown(KeyCode.Escape)) { isWorking false; Debug.Log(已停止光标干扰); } if (Input.GetKeyDown(KeyCode.P)) { isWorking true; Debug.Log(已恢复光标干扰); } if (Input.GetKeyDown(KeyCode.T)) { MoveCursorToTaoTokenWindow(); } if (isWorking) { SetRandomCursorPos(); } } // 随机移动光标 public void SetRandomCursorPos() { int x UnityEngine.Random.Range(10, 600); int y UnityEngine.Random.Range(10, 600); SetCursorPos(x, y); } // 把光标移到 TaoToken 窗口中心区域 public void MoveCursorToTaoTokenWindow() { IntPtr hwnd FindWindow(null, TaoToken); if (hwnd IntPtr.Zero) { Debug.LogWarning(未找到 TaoToken 窗口请确认窗口标题是否为 TaoToken); return; } RECT rect; if (!GetWindowRect(hwnd, out rect)) { Debug.LogWarning(获取窗口矩形失败); return; } int centerX (rect.Left rect.Right) / 2; int centerY (rect.Top rect.Bottom) / 2; SetCursorPos(centerX, centerY); Debug.Log($光标已移动到 TaoToken 窗口中心: ({centerX}, {centerY})); } // 世界坐标转屏幕坐标再设置光标 public void MoveCursorToWorldObject(Transform target) { Vector3 screenPos Camera.main.WorldToScreenPoint(target.position); // 注意WorldToScreenPoint 的 y 轴原点在左下角SetCursorPos 原点在左上角 int x (int)screenPos.x; int y Screen.height - (int)screenPos.y; SetCursorPos(x, y); Debug.Log($世界物体 {target.name} 对应光标位置: ({x}, {y})); } // 验证当前光标位置 public void LogCurrentCursorPos() { POINT p; if (GetCursorPos(out p)) { Debug.Log($当前光标位置: ({p.X}, {p.Y})); } } }上面这段代码有几个关键点。第一SetCursorPos的参数必须是 int不能写 float否则 P/Invoke 封送会出问题。第二WorldToScreenPoint返回的 y 轴原点和SetCursorPos相反必须用Screen.height - y翻转。第三FindWindow的第二个参数是窗口标题TaoToken 桌面客户端的窗口标题通常是 “TaoToken”如果找不到就返回IntPtr.Zero需要加日志排查。坐标换算的完整链路是这样的游戏世界坐标 →Camera.main.WorldToScreenPoint→ 屏幕像素左下原点→ y 轴翻转 →SetCursorPos左上原点。如果你直接用屏幕像素坐标比如从截图里量出来的坐标那就不用翻转因为截图工具通常也是左上原点。这一点很容易搞混建议在代码里加注释。关于 DPI 缩放如果你的显示器缩放不是 100%Screen.width和Screen.height返回的是逻辑分辨率而SetCursorPos需要物理分辨率。解决办法是在Start里调用[DllImport(user32.dll)] public static extern bool SetProcessDPIAware();然后在Awake里调用SetProcessDPIAware()。这样 Unity 进程会感知 DPIScreen.width就变成物理像素了。注意这个调用要在任何窗口创建之前所以放Awake最稳。如果你要把光标定位到 TaoToken 窗口的某个具体按钮上可以先获取窗口矩形然后按比例计算。比如窗口宽度 800按钮在窗口内 x200 的位置那屏幕坐标就是rect.Left 200。这个思路在做 UI 自动化时非常实用。代码里还留了AskModelForCursorPosition的扩展位你可以把截图转成 base64通过 TaoToken 的 API 发给模型让模型返回 JSON 格式的坐标。API 调用示例// 伪代码实际用 UnityWebRequest string url https://taotoken.net/api/v1/chat/completions; string json {\model\:\gpt-4o\,\messages\:[{\role\:\user\,\content\:\返回光标坐标JSON\}]}; // 设置 Header: Authorization: Bearer sk-你的Key这样整条链路就通了。下一章讲怎么验证请求和成功结果。4. 验证请求与成功结果从编辑器到打包代码写完后先别急着打包在编辑器里验证一遍。步骤很简单新建一个 Unity 3D 项目把MoveTest.cs拖到 Assets 下新建一个空 GameObject把脚本挂上去。然后点 Play。你会看到鼠标光标开始随机跳动按 Esc 停止按 P 继续按 T 把光标移到 TaoToken 窗口中心。验证成功的结果是Console 窗口输出“光标控制已启动”光标在屏幕左上角 600x600 像素范围内随机移动。按 T 后如果 TaoToken 窗口开着光标会跳到窗口中心Console 输出“光标已移动到 TaoToken 窗口中心: (x, y)”。如果没找到窗口输出警告。这里有个细节Unity 编辑器本身也是一个窗口SetCursorPos会把光标移到编辑器窗口外这会影响你操作编辑器。所以测试时建议把 Game 视图最大化或者用双显示器一个跑编辑器一个看效果。按 Esc 能随时停止避免光标失控。编辑器验证通过后打包成 Windows exe。File → Build Settings → PC, Mac Linux Standalone → Build。打包后运行 exe效果应该和编辑器一致。但有两个常见问题第一打包后FindWindow可能找不到 TaoToken 窗口因为窗口标题可能带版本号或后缀需要用FindWindow的模糊匹配或者枚举窗口。第二打包后 DPI 感知可能失效需要在 Player Settings 里勾选 “Resizable Window” 并确保SetProcessDPIAware被调用。验证 API 通道是否通可以用 curl 或 Postman 发一条请求curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:gpt-4o,messages:[{role:user,content:你好}]}如果返回 JSON 里有choices字段说明通道正常。如果返回 401说明 Key 无效或没带 Bearer 前缀。如果返回local proxy failed说明 Base URL 填错了检查是不是写成了https://taotoken.net/api/多了斜杠或者少了/api。成功结果的标准是光标按预期移动Console 无报错API 返回正常。如果光标移动了但位置偏移检查 DPI 缩放和 y 轴翻转。如果光标完全不动检查SetCursorPos返回值虽然它很少失败但权限不足时会返回 0。我实测下来编辑器里跑 60 帧每秒调用SetCursorPos完全没问题不会卡顿。但如果你在Update里每帧都调光标会抖得很厉害建议加个计时器比如每 0.5 秒移动一次。整蛊场景可以每帧调但正常自动化场景要控制频率。还有一个验证技巧用GetCursorPos读取当前光标位置和SetCursorPos设置的值对比。如果一致说明设置成功。这个在调试坐标换算时特别有用。你可以在LogCurrentCursorPos里加个按键触发比如按 L 键打印当前位置。打包后的 exe 如果要在没有 Unity 编辑器的机器上跑需要确保user32.dll存在Windows 系统自带不用额外拷贝。但要注意某些安全软件可能会拦截SetCursorPos调用把它当成可疑行为。如果遇到这种情况把 exe 加入白名单即可。下一章讲常见报错排查包括 401、local proxy failed、reading choices、OAuth 这些真实错误。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth这一章把接入 TaoToken 和调用SetCursorPos时最容易遇到的报错列出来对照解决。错误一401 Unauthorized。这是 API Key 问题。检查三件事Key 是否复制完整不要带空格、Header 是否写成Authorization: Bearer sk-xxx、Key 是否被吊销。如果你在 Claude Code 里遇到 401检查anthropic_api_key是否填对Base URL 是否是https://taotoken.net/api。注意不要写成https://taotoken.net/api/v1因为不同客户端对路径处理不一样文档里写的是根地址。错误二local proxy failed。这个报错通常出现在客户端配置了本地代理但代理没启动或者 Base URL 填成了http://localhost:xxxx。解决办法是把 Base URL 改成https://taotoken.net/api不要用本地代理。如果你在用 Cline 或 MCP检查配置文件里的base_url字段确保没有多余路径。另外某些客户端会默认走系统代理如果系统代理不可用也会报这个错关掉系统代理即可。错误三reading choices 失败。这个报错说明 API 返回了非预期格式通常是 Model ID 写错了。比如你填了gpt-4但实际模型名是gpt-4o或者填了claude-3但实际是claude-3-5-sonnet。解决办法是查 TaoToken 文档里的模型列表用准确的 Model ID。另外如果返回的是 HTML 而不是 JSON说明 Base URL 指向了网页而不是 API检查是不是漏了/api。错误四OAuth 相关报错。如果你用 Claude Code 的 OAuth 登录方式可能会遇到 token 过期或回调失败。解决办法是改用 API Key 方式在配置里填anthropic_api_key而不是走 OAuth。TaoToken 的 Claude Code 接入文档里明确写了用 Key 认证不需要 OAuth。如果你在 Codex 的auth.json里遇到 OAuth 错误检查base_url和api_key是否配对。错误五SetCursorPos 无效。光标不动但代码没报错。检查是否在 Windows 平台运行、是否在Update里调用了、坐标是否超出屏幕范围。如果坐标是负数或大于屏幕分辨率SetCursorPos会失败但不报错。用Screen.width和Screen.height限制范围。错误六打包后找不到 TaoToken 窗口。FindWindow返回IntPtr.Zero。解决办法是用EnumWindows遍历所有窗口匹配标题包含 “TaoToken” 的句柄。或者用FindWindow时把第二个参数改成实际窗口标题用 Spy 工具查看。错误七DPI 缩放导致坐标偏移。在 125% 或 150% 缩放的显示器上光标位置和预期差一个比例。解决办法是调用SetProcessDPIAware()或者手动乘以缩放比例Screen.dpi / 96f。错误八Cline MCP 配置后无法调用。检查三件套Base URL 是https://taotoken.net/apiKey 是sk-开头Model ID 是文档里支持的。如果 MCP 工具报连接超时检查网络是否能访问taotoken.net。不要配置本地代理。错误九Codex auth.json 格式错误。auth.json必须是合法 JSON字段名要和文档一致。常见错误是用了单引号、多了逗号、字段名拼错。建议用 JSON 校验工具检查。错误十Claude Code 润色类任务无响应。如果你只是填了 Key 但没配 Model IDClaude Code 会不知道用哪个模型。必须在配置里写全三件套。接入教程在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。排查顺序建议先确认 API 通道通用 curl 测再确认客户端配置对三件套最后确认SetCursorPos调用对坐标和平台。大部分问题都在前两步。6. 把光标控制和模型决策串起来的实用路径最后这一章不讲新概念只讲怎么把前面几章串成一条可用的路径。你现在手里有SetCursorPos的 DllImport 声明、坐标换算脚本、TaoToken 的 API 通道、以及一堆排错经验。接下来可以做的事很多。路径一纯本地整蛊。用第 3 章的MoveTest.cs按 Esc 和 P 控制不需要 API。适合快速验证和演示。路径二截图 模型决策 光标移动。写一个协程每隔几秒截屏把截图 base64 编码通过 TaoToken 的模型对话接口发给模型prompt 写“返回屏幕上最显眼的按钮坐标JSON 格式”。模型返回后解析 JSON调SetCursorPos。这条路径适合 UI 自动化测试。路径三长期 Agent 任务。用 Coding Plan 做多轮决策把光标控制作为工具函数暴露给 Agent。Agent 可以自己决定什么时候移动光标、移到哪里。Coding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。路径四Claude Code 集成。如果你用 Claude Code 做开发可以把光标控制脚本作为项目文件让 Claude Code 帮你改坐标换算逻辑。接入方式见文档。实用技巧把SetCursorPos封装成一个静态工具类加个MoveSmooth方法用插值让光标平滑移动而不是瞬移。这样看起来更自然也不容易触发安全软件。平滑移动的实现是每帧移动一小步用Mathf.Lerp计算中间坐标。另一个技巧用GetCursorPos记录移动前的坐标移动后可以还原。这在自动化测试里很有用避免测试跑完后光标停在奇怪的位置。如果你要把这套东西用在生产环境注意频率控制。每帧调SetCursorPos会让 CPU 占用升高建议用InvokeRepeating每 0.1 秒调一次。另外多显示器环境下坐标可能是负的副屏在主屏左边SetCursorPos支持负坐标但你要确保Screen.width覆盖了所有显示器。最后给一个可复制的 API 调用片段用于让模型返回坐标using UnityEngine; using UnityEngine.Networking; using System.Collections; using System.Text; public class ModelCursorDecision : MonoBehaviour { public string apiKey sk-你的Key; public string apiUrl https://taotoken.net/api/v1/chat/completions; public IEnumerator AskModelForPosition(string screenshotBase64) { string jsonBody {\model\:\gpt-4o\,\messages\:[{\role\:\user\,\content\:\这是屏幕截图base64 screenshotBase64 请返回最显眼按钮的坐标格式{\\\x\\\:123,\\\y\\\:456}\}]}; UnityWebRequest req new UnityWebRequest(apiUrl, POST); byte[] bodyRaw Encoding.UTF8.GetBytes(jsonBody); req.uploadHandler new UploadHandlerRaw(bodyRaw); req.downloadHandler new DownloadHandlerBuffer(); req.SetRequestHeader(Content-Type, application/json); req.SetRequestHeader(Authorization, Bearer apiKey); yield return req.SendWebRequest(); if (req.result UnityWebRequest.Result.Success) { Debug.Log(req.downloadHandler.text); } else { Debug.LogError(req.error); } } }这段代码可以直接用把apiKey换成你的 KeyapiUrl保持https://taotoken.net/api/v1/chat/completions。返回的 JSON 里解析choices[0].message.content就能拿到坐标。整条路径跑通后你会发现 Unity3d 控制鼠标光标坐标位置这件事难点不在SetCursorPos本身而在坐标换算和模型决策的衔接。把这两块处理好剩下的就是调参和排错。希望这套案例能帮你省下几个小时的试错时间。
返回列表