【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录&解决方案

【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录&解决方案

背景前摇:(省流可不看)

Unity是大学学的,AI是研究生学的,DeepSeek是第一份实习偷师的,三合一的梦是最近开始做的,BUG是今天遇到并且解决的。

关于Unity和大模型结合的教程网上并不多,正好符合Unity+DeepSeek的我目前只看到这一篇《【Unity+AI01】在Unity中调用DeepSeek大模型!实现AI对话功能!》:https://blog.csdn.net/leoysq/article/details/139547284
传送门

阅读全文后,发现这篇文章工程量不大,涉及的知识点(UGUI,C#编程,DeepSeek的API接入)都是我目前比较熟悉的,这个项目可以跟着做,于是就愉快地开始了尝试。

在复现工程的时候我遇到了一些奇奇怪怪的问题,查了CSDN和百度以后发现相关的解释帖子还很少,决定自己开个帖子记录下来。


BUG集锦:

Unity版本:Unity6

1.Unity报错Error: HTTP/1.1 401 Unauthorized

解决方法:看看API有没有写对。

UnityEngine.Debug:LogError (object)

DeepSeekChat/d__8:MoveNext () (at Assets/Scripts/DeepSeekChat.cs:93)

UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr),

这种联网报错我处理经验不多,看红感叹号出来人都麻了,GPT查了一下,这个报错表示未授权

上网搜了一圈,没有我这种情境下报错的解决方案,我先在原作者的评论区下方留言等待好心人答复,也厚着脸皮通过DeepSeek官网的聊天渠道反馈了BUG。

很快我就收到了邮件和聊天界面双重回复------未授权一般是API没写对

我觉得DeepSeek的这一点做得特别好,聊天界面和邮件都可以看到过往记录并且回复,这样沟通就很方便自由。

我检查了一下我的代码,确实,API抄错了 ,写成隔壁智谱清言的了。

同时我也获得了教程原作者的友善回复:

2.base_url连接报错Error: HTTP/1.1 404 Not Found

解决方法:根据需要,换形如https://api.deepseek.com/chat/completions 这样的完整接口地址

UnityEngine.Debug:LogError (object)

DeepSeekChat/d__8:MoveNext () (at Assets/Scripts/DeepSeekChat.cs:94)

UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

当时在跟着写代码的时候,我就发现一件很奇怪的事情------原教程作者写的url很特别。

我之前用Python+DeepSeek的时候,用的是官网的base_url,一切都很顺利。

https://platform.deepseek.com/api-docs/zh-cn/
DeepSeek官网传送门

但是我发现,如果Unity里面也用这个链接,Unity会报错。

但如果用教程作者老师的这个我没见过的"野生"链接,就没事。

这我就不明白了,因为我之前用python做过DeepSeek开发,一直照着官方文档这么用都没问题。

于是我又厚着脸皮请教了DeepSeek的老师们。

很快我得到了详细的答案:

我理解的一句话总结是:使用Openai python sdk最省事,base_url能应付所有问题。

但如果是其他平台,就得根据功能需要换具体的链接了。

比如我现在用Unity做的功能可以归类为【对话补全】,那么就要需要这个页面。

https://platform.deepseek.com/api-docs/zh-cn/api/create-chat-completion/
传送门

复制下图红框所示的链接,也就是原教程的"野生"链接:

这样就可以在Unity里顺利调用DeepSeek的API啦!

3.TextMeshPro不显示AI的回复

解决方案:看看UGUI的排列、颜色

按代码逻辑,成功发送消息以后,AI应该回复,并且在背景板上显示出来。

但现在什么也没有,我首先排除了最不应该的UI层级错误(背景白色image把text盖住了),不是这个原因。

然后也Debug.Log了各个核心环节,发现AI其实回复了,但我的TextMeshPro没有正常显示内容。

所以大概BUG是从下面这一步开始的。

我按GPT给的办法加了个存在与否的判断,但我感觉不该是这种错误,因为确实把按钮拖进了脚本对应位置,要是没找到对象的话早ERROR了。

我觉得还是UI本身的问题,想着会不会还是我层级搞错了,于是就尝试把image隐藏看看是不是字在背后,结果------

破案了,原来是白色字体和背景融为一体了......

啥好玩意默认字体是白色啊,我以为默认黑色字来着,真是给我整的哭笑不得。

于是我给image换了个底色,问题解决了。

以及,因为要显示聊天记录,这个TextMeshPro不会在发信息后清空原来的内容,要美观的话记得把默认的New Text删除了。

4.安装Unity的可集成HTTP请求库(原作者的NuGet For Unity插件)

解决方法:从GitHub下载NuGet For Unity插件:https://github.com/GlitchEnzo/NuGetForUnity

Unity的HTTP请求库有UnityWebRequest,或第三方库如LitJson、Newtonsoft.Json等,可以将这三者类比为Python的Request、BeautifulSoup、Scrapy库 ,它们可以实现(指定数据格式)+(指定目标网页)的情况下爬取网站内容的任务

-UnityWebRequest:Unity内置的HTTP客户端库,不需要额外安装,集成在Unity引擎中。其他的第三方库需要自己安装,比如原作者使用的NuGet For Unity。

第一步的下载链接:https://github.com/GlitchEnzo/NuGetForUnity
传送门

这个下载下来是个Unitypackage,直接导入Unity就行。

第4步的详细操作如下:

其他可能的优化方案

已实现:

1.点击Send按钮以后自动清空InputField的内容。

2.对话界面也加上用户的输入。

未实现:

1.显示历史记录的TextMeshPro添加滚轮滑动条。

说来惭愧,学了这么久Unity,做个滑动查看的文本框还一直没有清晰的解决方案。

知道要加Scrollbar组件,并且把TextMeshPro这个要滑动查看的内容放在Content下面,但是经常不显示滑动效果。这历史遗留问题都很久了,我都还不是很确定该怎么做。

UGUI这个操作我会:

今天在GPT的帮助下加了段代码,算是勉强实现了能滑动的功能,不过不是很美观,只能说将就。


而且如果输入的文本太长了,还是会出现滑动条显示不全的问题,并且Unity会报错:Assertion failed on expression: '!(o->TestHideFlag(Object::kDontSaveInEditor) && (options & kAllowDontSaveObjectsToBePersistent) == 0)'

UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

这个问题我目前还不知道怎么解决。

2.加入中文字库允许中文交互。

中文字体这个网上有很多解决方案,我之前查了一下觉得很麻烦,暂时没精力(懒得)搞。

一句话我的理解就是要自己导入个字体,而且要尽可能包含多的中文字 ,不然遇到一个生字就会显示为下图这种方框。

第二份实习做过一些编辑工作,使用了两款商用字体:中文------阿里普惠体,英文------Monsterrate(拼写不确定) ,有精力的码友可以试试这两款字体。


添加注释&功能后的完整代码:

工程我发到了CSDN个人资源,有需要的朋友可以自行下载。

https://download.csdn.net/download/bailichen800/89822761
传送门

csharp 复制代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System.Text;
using TMPro;
using UnityEngine.UI;

public class DeepSeekChat : MonoBehaviour
{
    //参考链接:https://blog.csdn.net/leoysq/article/details/139547284

    //DeepSeek API配置
    private string apikey = "你的API"; //注意不要粘贴成别家api了
    private string apiURL = "https://api.deepseek.com/chat/completions";
    // private string apiURL = "https://api.deepseek.com";

    //Unity UI元素
    public TMP_InputField userInputField;
    public TextMeshProUGUI chatOutputText;
    public Button sendButton;
    public ScrollRect scrollRect; // 引用 ScrollRect 组件

    //存储对话历史
    private List<Dictionary<string, string>> messages = new List<Dictionary<string, string>>();

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        // 配置 ScrollRect
        scrollRect.content = chatOutputText.rectTransform; // 将 TextMeshProUGUI 的 RectTransform 设置为 Content
        scrollRect.vertical = true; // 启用垂直滚动
        if (sendButton != null)
        {
            sendButton.onClick.AddListener(OnSendButtonClicked);
            Debug.Log("Click Send Button!");
            
        }
        //初始化系统消息
        messages.Add(new Dictionary<string, string> { { "role", "system" }, { "content", "You are a helpful assistant." } });
    }


    public void OnSendButtonClicked()
    {
        //用户没说话,return
        string userMessage = userInputField.text;
        if (string.IsNullOrEmpty(userMessage))
        {
            Debug.Log("Empty Input!");
            return;
        }
        //显示响应
        if (chatOutputText != null)
        {
            chatOutputText.text += "\nUser: " + userMessage;//将AI的回复追加到聊天输出文本框中,以显示给用户。
        }
        else
        {
            Debug.LogError("TextMeshPro component not assigned.");
        }
        // 清空输入框
        userInputField.text = "";

        //添加用户消息到对话历史
        messages.Add(new Dictionary<string, string> { { "role", "user" }, { "content", userMessage } });

        //调用DeepSeek API
        StartCoroutine(CallDeepSeekAPI());
    }

    private IEnumerator CallDeepSeekAPI()
    {
        Debug.Log("CallDeepSeekAPI!");
        //创建请求数据
        var requestData = new  //定义一个匿名对象requestData
        {
            model = "deepseek-chat", //表示使用的模型,这里是"deepseek-chat"
            messages = messages, //一个变量,应该是一个包含消息的数组,用于与聊天模型交互
            stream = false //一个布尔值,表示是否以流的形式接收响应,这里设置为false
        };

        string jsonData = JsonConvert.SerializeObject(requestData); //将requestData对象序列化为JSON格式的字符串jsonData。

        //创建UnityWebRequest 向指定的API URL发送一个POST请求,请求体为JSON格式的数据,并设置必要的请求头
        UnityWebRequest request = new UnityWebRequest(apiURL, "POST"); //创建一个网络请求对象request,指定请求的URL和请求方法(POST)
        byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData); //将序列化后的JSON数据转换为字节数组bodyRaw
        request.uploadHandler = new UploadHandlerRaw(bodyRaw); //创建一个UploadHandlerRaw对象,并将其赋值给request的uploadHandler属性,这样就将JSON数据作为请求体发送
        request.downloadHandler = new DownloadHandlerBuffer(); //创建一个DownloadHandlerBuffer对象作为request的downloadHandler,用于接收服务器响应的数据
        request.SetRequestHeader("Content-Type", "application/json");  //通过SetRequestHeader方法设置请求的Content-Type为application/json,表明发送的是JSON数据
        request.SetRequestHeader("Authorization", "Bearer " + apikey); //同时设置Authorization请求头,包含一个API密钥apiKey,用于身份验证。

        //发送请求  发送一个网络请求到服务器,获取AI模型的回复,并将该回复显示在聊天界面中,同时更新对话历史。如果请求失败,则输出错误信息。
        yield return request.SendWebRequest(); //使用yield return来异步发送网络请求,而不会阻塞主线程

        if (request.result == UnityWebRequest.Result.Success) //检查网络请求是否成功
        {
            Debug.Log("Success!");
            //解析响应
            var response = JsonConvert.DeserializeObject<DeepSeekResponse>(request.downloadHandler.text); //将下载的响应文本(JSON格式)反序列化为一个DeepSeekResponse类型的对象
            string botMessage = response.choices[0].message.content; //从反序列化后的响应对象中提取AI回复的消息内容。

            Debug.Log("botMessage = " + botMessage);
            //显示响应
            if (chatOutputText != null)
            {
                chatOutputText.text += "\nAI: " + botMessage;//将AI的回复追加到聊天输出文本框中,以显示给用户。
            }
            else
            {
                Debug.LogError("TextMeshPro component not assigned.");
            }
            //添加AI消息到对话历史
            messages.Add(new Dictionary<string, string> { { "role", "assistant" }, { "content", botMessage } }); // 将AI的回复添加到消息列表中
        }
        else
        {
            Debug.LogError("Error: " + request.error);
        }
    }

    // 使用这些类,Unity将能够将JSON响应反序列化为一个DeepSeekResponse对象,你可以通过DeepSeekResponse对象访问choices数组,然后访问第一个Choice对象的message属性,最后得到content字符串,即AI的回复。
    [System.Serializable] //标记为[System.Serializable]的公共类,表示它可以被Unity的序列化系统序列化和反序列化。
    public class DeepSeekResponse
    {
        public Choice[] choices; //包含一个名为choices的数组,该数组应该包含多个Choice对象
    }

    [System.Serializable]
    public class Choice
    {
        public Message message; //包含一个名为message的属性,该属性是Message类型
    }

    [System.Serializable]
    public class Message
    {
        public string content; //包含一个名为content的字符串属性,该属性用于存储消息的实际内容
    }
}
相关推荐
码银6 分钟前
冲破AI 浪潮冲击下的 迷茫与焦虑
人工智能
敲上瘾10 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
何大春10 分钟前
【弱监督语义分割】Self-supervised Image-specific Prototype Exploration for WSSS 论文阅读
论文阅读·人工智能·python·深度学习·论文笔记·原型模式
uncle_ll18 分钟前
PyTorch图像预处理:计算均值和方差以实现标准化
图像处理·人工智能·pytorch·均值算法·标准化
宋1381027972018 分钟前
Manus Xsens Metagloves虚拟现实手套
人工智能·机器人·vr·动作捕捉
SEVEN-YEARS22 分钟前
深入理解TensorFlow中的形状处理函数
人工智能·python·tensorflow
世优科技虚拟人25 分钟前
AI、VR与空间计算:教育和文旅领域的数字转型力量
人工智能·vr·空间计算
EterNity_TiMe_27 分钟前
【论文复现】(CLIP)文本也能和图像配对
python·学习·算法·性能优化·数据分析·clip
sanguine__30 分钟前
java学习-集合
学习
lxlyhwl30 分钟前
【STK学习】part2-星座-目标可见性与覆盖性分析
学习