1. 鼠标指针隐藏到底在解决什么问题
做第一人称视角或者拖拽交互时,鼠标指针乱跑是最影响沉浸感的一件事。玩家转动视角,指针却飘到屏幕边缘点到了别的窗口;拖拽物体时指针突然消失,松手后不知道光标在哪。这些问题的根源,是很多人只写了Cursor.visible = false,却没有处理Cursor.lockState,两者配合不当就会出现「指针看不见但还在动」或者「锁定了却没法解锁」的尴尬。
Cursor.visible控制的是鼠标指针画不画出来,它是一个布尔值,设为 false 指针就不可见,但指针的坐标依然在屏幕空间里移动,点击事件照样会触发。Cursor.lockState控制的是指针能不能动、动到哪里,它接收CursorLockMode枚举,有三个值:None表示不锁定,指针自由移动;Locked表示锁定在屏幕中心,指针坐标不再变化,但依然可以读取鼠标的位移量;Confined表示锁定在 Game 窗口范围内,指针可以在窗口内移动但不会跑出去。
这两个属性是正交的,可以自由组合。第一人称视角通常用visible = false加lockState = Locked,指针既看不见也不会乱跑,鼠标位移用来转视角。拖拽操作通常用visible = true加lockState = None,指针可见且自由移动。菜单界面则用visible = true加lockState = None,让玩家正常点击按钮。
Cursor.SetCursor是另一个维度的东西,它负责换指针的外观。参数一是指针图片的 Texture2D,参数二是热点偏移(相对于图片左上角),参数三是平台支持的光标模式,一般用CursorMode.Auto。这个 API 和前面两个属性不冲突,你可以在指针可见的时候换成自定义图标,也可以在指针隐藏的时候提前设置好,等指针显示出来就是新图标。
理解这三者的分工,是写出稳定指针控制逻辑的前提。很多人踩坑就是因为把「隐藏」和「锁定」当成一回事,结果在需要解锁的时候只改了visible,指针虽然显示出来了,但lockState还是Locked,指针依然卡在屏幕中心动不了。
2. TaoToken 统一 Key 与 API 通道的前置准备
在写指针控制代码之前,先说一下为什么这篇要提 TaoToken。Unity 项目里如果接了 AI 相关的调用,比如用大模型生成对话、做智能 NPC,或者用 coding agent 辅助写脚本,通常会散落好几个 Key 和 Base URL。TaoToken 的作用是把这些调用统一到一个 Key、一个 API 通道上,省得在每个脚本里硬编码不同的地址。
你需要先拿到一个可用的 Key。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台里创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,进去之后找到 API Keys 页面,点新建,复制那串以sk-开头的字符串。这个 Key 就是后面所有调用的凭证。
API 的基础地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为 Base URL 使用。如果你用的是 OpenAI 兼容的 SDK,把base_url设成这个值,api_key设成刚才复制的 Key,就能直接调通。模型 ID 根据你实际用的模型填,比如gpt-4o、claude-3-5-sonnet这类,具体以控制台里列出的为准。
这里要强调一点,TaoToken 是正规的 API 聚合通道,不是那种来路不明的中转。它的作用是帮你把多个模型的调用收敛到一个入口,方便管理和计费。你在 Unity 里写UnityWebRequest或者用HttpClient发请求时,把 URL 拼成https://taotoken.net/api/v1/chat/completions,Header 里带上Authorization: Bearer sk-你的Key,body 按 OpenAI 格式写,就能拿到返回。
如果你只是做指针控制,其实用不到 AI 调用。但很多项目会把指针控制和 AI 对话绑在一起,比如按 Esc 解锁指针后弹出对话框,对话框内容由大模型生成。这种情况下,统一用 TaoToken 的 Key 和通道,比每个功能单独配一套要省心得多。后面第三节的配置片段里,我会把指针控制的代码和 API 调用的配置分开写,你可以按需取用。
3. 可复制的 Cursor 配置代码与运行时切换
先给一个完整的CursorController脚本,挂在场景里的任意 GameObject 上即可。这个脚本处理第一人称视角的指针隐藏与锁定,按 Esc 解锁,再点回 Game 窗口重新锁定。
using UnityEngine; public class CursorController : MonoBehaviour { [Header("指针设置")] public bool hideOnStart = true; public CursorLockMode startLockMode = CursorLockMode.Locked; [Header("自定义指针")] public Texture2D cursorTexture; public Vector2 hotspot = Vector2.zero; public CursorMode cursorMode = CursorMode.Auto; private bool isLocked = false; void Start() { if (cursorTexture != null) { Cursor.SetCursor(cursorTexture, hotspot, cursorMode); } if (hideOnStart) { LockCursor(); } } void Update() { if (Input.GetKeyDown(KeyCode.Escape)) { UnlockCursor(); } if (isLocked == false && Input.GetMouseButtonDown(0)) { LockCursor(); } } public void LockCursor() { Cursor.visible = false; Cursor.lockState = startLockMode; isLocked = true; } public void UnlockCursor() { Cursor.visible = true; Cursor.lockState = CursorLockMode.None; isLocked = false; } }这段代码的核心逻辑是:LockCursor同时设置visible = false和lockState = Locked,两个属性一起改,避免出现指针看不见但还能点的情况。UnlockCursor则把两个都恢复,指针可见且自由移动。Update里监听 Esc 键解锁,监听鼠标左键重新锁定,这是第一人称游戏最常见的交互模式。
如果你需要Confined模式,比如拖拽窗口内的物体但不想指针跑出 Game 窗口,把startLockMode改成CursorLockMode.Confined即可。注意Confined模式下visible通常设为 true,因为你需要看到指针才能拖拽。
接下来是 API 调用的配置片段。如果你在项目里用 TaoToken 做 AI 调用,可以建一个TaoTokenConfig.json放在StreamingAssets目录下,内容如下:
{ "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key", "modelId": "gpt-4o", "timeout": 30 }然后在 C# 里读取这个配置:
using System.IO; using UnityEngine; [System.Serializable] public class TaoTokenConfig { public string baseUrl; public string apiKey; public string modelId; public int timeout; } public class ConfigLoader : MonoBehaviour { public TaoTokenConfig LoadConfig() { string path = Path.Combine(Application.streamingAssetsPath, "TaoTokenConfig.json"); if (File.Exists(path)) { string json = File.ReadAllText(path); return JsonUtility.FromJson<TaoTokenConfig>(json); } Debug.LogError("配置文件不存在: " + path); return null; } }这样指针控制和 API 调用就解耦了,指针脚本只管交互,配置脚本只管读 Key 和地址。你换模型或者换 Key 的时候,只改 JSON 文件,不用动代码。
如果你用的是 Claude Code 或者类似的 coding agent 来辅助开发,可以在项目根目录建一个.taotoken配置文件,把 Base URL 和 Key 写进去,agent 会自动读取。具体格式参考接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有详细的字段说明。
4. 验证请求与成功结果
代码写完之后,怎么确认指针控制真的生效了?最直接的方法是在LockCursor和UnlockCursor里加日志,运行后看 Console 输出。
public void LockCursor() { Cursor.visible = false; Cursor.lockState = startLockMode; isLocked = true; Debug.Log($"锁定指针: visible={Cursor.visible}, lockState={Cursor.lockState}"); } public void UnlockCursor() { Cursor.visible = true; Cursor.lockState = CursorLockMode.None; isLocked = false; Debug.Log($"解锁指针: visible={Cursor.visible}, lockState={Cursor.lockState}"); }运行场景后,你应该看到 Console 里输出锁定指针: visible=False, lockState=Locked,此时鼠标指针消失,移动鼠标视角转动。按 Esc 后输出解锁指针: visible=True, lockState=None,指针出现且可以自由移动。再点一下 Game 窗口,指针再次消失并锁定。
如果指针控制没问题,接下来验证 API 调用。写一个简单的测试脚本,用UnityWebRequest发一个请求到 TaoToken:
using UnityEngine; using UnityEngine.Networking; using System.Collections; using System.Text; public class ApiTest : MonoBehaviour { private TaoTokenConfig config; void Start() { config = GetComponent<ConfigLoader>().LoadConfig(); StartCoroutine(SendTestRequest()); } IEnumerator SendTestRequest() { string url = config.baseUrl + "/v1/chat/completions"; string jsonBody = "{\"model\":\"" + config.modelId + "\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}]}"; UnityWebRequest request = new UnityWebRequest(url, "POST"); byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Authorization", "Bearer " + config.apiKey); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { Debug.Log("API 返回: " + request.downloadHandler.text); } else { Debug.LogError("API 错误: " + request.error + " | " + request.downloadHandler.text); } } }把这个脚本挂到场景里,运行后看 Console。成功的话会输出一段 JSON,里面包含模型返回的内容。如果失败,错误信息会告诉你具体原因,常见的是 401 未授权或者 404 地址不对。
验证模型是否可用,也可以直接用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 发一条消息,确认 Key 和通道是通的。这样在 Unity 里调试的时候,至少知道问题不在 Key 上。
5. 本篇常见错误排查
第一个高频错误是Cursor.visible = false写了但指针还能点。这是因为lockState还是None,指针虽然看不见,但坐标还在屏幕空间里移动,点击事件照样触发。解决办法是同时设置lockState = CursorLockMode.Locked,两个属性一起改。
第二个错误是解锁后指针卡在屏幕中心动不了。这通常是因为只改了visible = true,忘了把lockState改回None。Locked状态下指针坐标是固定的,你就算把指针画出来,它也只会停在中心。正确的解锁写法是visible = true加lockState = CursorLockMode.None,两个都要改。
第三个错误是Cursor.SetCursor设置了自定义指针但没生效。检查三点:Texture2D 的 Texture Type 是不是Cursor,热点偏移是不是在图片范围内,CursorMode是不是Auto。如果图片类型不对,Unity 会忽略这个设置。另外SetCursor要在指针可见的时候设置才看得到效果,如果visible = false,设置了也看不见。
第四个错误是 API 调用返回 401。报错信息通常是{"error":{"message":"Invalid API key","type":"invalid_request_error"}}。这说明 Key 不对或者没带上。检查AuthorizationHeader 是不是Bearer sk-xxx格式,中间有没有多余空格。如果 Key 是从控制台复制的,确认没有复制到换行符。
第五个错误是local proxy failed或者连接超时。这通常是网络环境问题,不是代码问题。检查 Base URL 是不是https://taotoken.net/api,有没有多写或者少写路径。如果用的是UnityWebRequest,确认timeout设置得够长,默认是 10 秒,网络慢的时候容易超时。
第六个错误是reading choices相关报错。这通常出现在解析返回 JSON 的时候,说明返回结构和你预期的字段对不上。先打印完整的request.downloadHandler.text,看看实际返回是什么。如果是错误信息,按错误信息排查;如果是正常返回,检查你的解析类字段名和 JSON 里的 key 是否一致。
第七个错误是 OAuth 相关报错。如果你用的是 Claude Code 或者类似的工具,报错里出现 OAuth 字样,说明认证方式不对。TaoToken 用的是 API Key 认证,不是 OAuth。检查配置文件里是不是误填了 OAuth 相关的字段,改成apiKey即可。
6. 指针控制与 API 通道的配合使用
指针控制和 API 调用在项目里通常是两条线,但有些场景需要它们配合。比如按 Esc 解锁指针后弹出 AI 对话框,对话框内容由大模型生成。这时候指针要先解锁,让玩家能点击输入框,然后发请求到 TaoToken 拿回复,回复显示完再让玩家点关闭按钮重新锁定指针。
这种流程的关键是状态管理。用一个枚举记录当前指针状态,比如Free、Locked、Dialog,每个状态对应不同的visible和lockState组合。切换状态的时候统一走一个方法,避免在多个地方零散地改属性。
public enum CursorState { Free, Locked, Dialog } public void SetCursorState(CursorState state) { switch (state) { case CursorState.Free: Cursor.visible = true; Cursor.lockState = CursorLockMode.None; break; case CursorState.Locked: Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; break; case CursorState.Dialog: Cursor.visible = true; Cursor.lockState = CursorLockMode.None; break; } }这样不管有多少个界面需要切换指针,都调这一个方法,逻辑清晰也不容易出错。
API 通道这边,如果你项目里调用比较频繁,建议把 Key 和 Base URL 放在一个单例里,全局共用。不要在每个脚本里重复读配置文件,那样既浪费性能也容易不一致。TaoToken 的 Key 可以在控制台里管理,如果发现 Key 泄露或者额度异常,直接去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 删掉重建就行。
如果你做的是长期编码项目,需要频繁调用模型辅助开发,可以看看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它针对持续编码场景做了额度优化,比按次调用划算。指针控制这种交互逻辑,配合 AI 辅助写代码,效率会高很多。
最后说一个实际踩过的坑:在 Editor 里测试的时候,指针锁定后按 Esc 解锁,再点回 Game 窗口,有时候指针不会自动重新锁定。这是因为 Editor 的 Game 窗口焦点切换和运行时不一样。解决办法是在OnApplicationFocus回调里处理锁定逻辑,当窗口重新获得焦点时自动锁定指针。这样在 Editor 和打包后都能正常工作。