【Unity小帮手】Unity+DeepSeek实现AI对话(拖入即用 具备上下文能力)
目录
不需要看懂代码如何编写,按照下述步骤复制粘贴即用,简单易用!
1 获取密钥
首先需要申请DeepSeek的API密钥:
访问DeepSeek平台(https://platform.deepseek.com/api_keys)申请API Key
成功后会显示Key,请立即妥善保存,因为关闭页面后将无法再次查看完整Key

2 Unity设置
在Unity中创建基本的UI界面
2.1 创建一个空物体并挂载脚本
DeepSeekLink代码:
public class DeepSeekLink : MonoBehaviour
{
// API配置区域 - 在Inspector面板中设置这些参数
[Header("API Settings")]
[SerializeField] private string apiKey = "输入自己创建的密钥"; // DeepSeek API密钥 - 从DeepSeek平台获取
[SerializeField] private string modelName = "deepseek-chat"; // 使用的模型名称 - deepseek-chat是对话专用模型
[SerializeField] private string apiUrl = "https://api.deepseek.com/v1/chat/completions"; // API请求地址 - DeepSeek官方API端点
// 对话参数设置 - 控制AI回复的行为特性
[Header("Talk Settings")]
[Range(0, 2)] public float temperature = 0.7f; // 控制生成文本的随机性:0=最确定,1=平衡,2=最随机
[Range(1, 1000)] public int maxTokens = 150; // 单次回复的最大令牌数:控制回复长度,1个token≈0.75个英文单词
// 角色设定类 - 定义AI助手的角色和个性
[System.Serializable]
public class NPCCharacter
{
public string name; // 角色名称 - 在对话中标识AI身份
[TextArea(3, 10)]
public string personalityPrompt = "你名字是:小历,是一个熟读中国历史,知识渊博的智能体,个性严谨稳重。"; // 角色个性提示词 - 决定AI的行为方式和知识范围
}
[SerializeField] public NPCCharacter npcCharacter; // 在Inspector中配置的角色实例
// 回调委托定义 - 用于异步处理API响应结果
public delegate void DialogueCallback(string response, bool isSuccess);
/// <summary>
/// 发送对话请求的主方法 - 外部调用的入口点
/// 使用方法:在其他脚本中调用此方法并传入用户消息和回调函数
/// 示例:deepSeekManager.SendDialogueRequest("你好", OnAIResponse);
/// </summary>
/// <param name="userMessage">玩家输入的内容</param>
/// <param name="callback">处理响应的回调函数,接收回复内容和成功状态</param>
public void SendDialogueRequest(string userMessage, DialogueCallback callback)
{
// 启动协程处理异步API请求
StartCoroutine(ProcessDialogueRequest(userMessage, callback));
}
/// <summary>
/// 处理对话请求的协程 - 核心业务逻辑
/// </summary>
/// <param name="userInput">用户输入的文本</param>
/// <param name="callback">回调函数用于返回处理结果</param>
private IEnumerator ProcessDialogueRequest(string userInput, DialogueCallback callback)
{
// 构建消息列表:包含系统角色设定和用户当前输入
List<Message> messages = new List<Message> {
new Message { role = "system", content = npcCharacter.personalityPrompt }, // 系统消息:定义AI角色和行为准则
new Message { role = "user", content = userInput } // 用户消息:当前对话内容
};
// 构建API请求体 - 符合DeepSeek API格式要求
ChatRequest requestBody = new ChatRequest
{
model = modelName, // 指定使用的AI模型
messages = messages, // 传入对话消息列表
temperature = temperature, // 设置创造性参数
max_tokens = maxTokens // 设置回复长度限制
};
// 将请求体序列化为JSON字符串
string jsonBody = JsonUtility.ToJson(requestBody);
// 调试输出:在Console中查看发送的JSON数据(发布时建议注释掉)
Debug.Log("Sending JSON: " + jsonBody);
// 创建并配置网络请求
UnityWebRequest request = CreateWebRequest(jsonBody);
// 发送请求并等待响应 - yield return使协程在此暂停直到请求完成
yield return request.SendWebRequest();
// 检查请求是否出错
if (IsRequestError(request))
{
// 输出错误信息到控制台
Debug.LogError($"API Error: {request.responseCode}\n{request.downloadHandler.text}");
// 调用回调函数,传递失败状态
callback?.Invoke(null, false);
// 提前退出协程
yield break;
}
// 解析API返回的JSON响应
DeepSeekResponse response = ParseResponse(request.downloadHandler.text);
// 检查响应是否有效且包含选择结果
if (response != null && response.choices.Length > 0)
{
// 提取AI回复内容
string npcReply = response.choices[0].message.content;
// 调用成功回调,传递回复内容
callback?.Invoke(npcReply, true);
}
else
{
// 响应解析失败或为空时的处理
callback?.Invoke("Unity-YuZhang(陷入沉默)", false);
}
}
/// <summary>
/// 创建并配置UnityWebRequest对象
/// </summary>
/// <param name="jsonBody">要发送的JSON数据</param>
/// <returns>配置好的UnityWebRequest对象</returns>
private UnityWebRequest CreateWebRequest(string jsonBody)
{
// 将JSON字符串转换为字节数组
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
// 创建POST请求对象
var request = new UnityWebRequest(apiUrl, "POST");
// 设置上传处理器:负责发送数据
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
// 设置下载处理器:负责接收响应数据
request.downloadHandler = new DownloadHandlerBuffer();
// 设置请求头:声明内容类型为JSON
request.SetRequestHeader("Content-Type", "application/json");
// 设置认证头:Bearer Token认证方式
request.SetRequestHeader("Authorization", $"Bearer {apiKey}");
// 设置接受头:期望接收JSON格式响应
request.SetRequestHeader("Accept", "application/json");
return request;
}
/// <summary>
/// 检查网络请求是否出错
/// </summary>
/// <param name="request">要检查的UnityWebRequest对象</param>
/// <returns>true表示请求出错,false表示成功</returns>
private bool IsRequestError(UnityWebRequest request)
{
// 检查所有可能的错误类型
return request.result == UnityWebRequest.Result.ConnectionError // 连接错误:网络问题
|| request.result == UnityWebRequest.Result.ProtocolError // 协议错误:HTTP错误码
|| request.result == UnityWebRequest.Result.DataProcessingError; // 数据处理错误:本地处理失败
}
/// <summary>
/// 解析API返回的JSON响应
/// </summary>
/// <param name="jsonResponse">API返回的JSON字符串</param>
/// <returns>解析后的DeepSeekResponse对象,解析失败返回null</returns>
private DeepSeekResponse ParseResponse(string jsonResponse)
{
try
{
// 使用Unity的JsonUtility反序列化JSON字符串
return JsonUtility.FromJson<DeepSeekResponse>(jsonResponse);
}
catch (System.Exception e)
{
// 捕获并记录JSON解析异常
Debug.LogError($"JSON解析失败: {e.Message}\n响应内容:{jsonResponse}");
return null;
}
}
// ========== 可序列化数据结构定义 ==========
/// <summary>
/// API请求数据结构 - 与DeepSeek API要求的格式匹配
/// </summary>
[System.Serializable]
private class ChatRequest
{
public string model; // 模型名称
public List<Message> messages; // 消息列表
public float temperature; // 温度参数
public int max_tokens; // 最大令牌数
}
/// <summary>
/// 消息数据结构 - 表示对话中的一条消息
/// </summary>
[System.Serializable]
public class Message
{
public string role; // 消息角色:system/user/assistant
public string content; // 消息内容
}
/// <summary>
/// API响应数据结构 - 解析DeepSeek返回的JSON
/// </summary>
[System.Serializable]
private class DeepSeekResponse
{
public Choice[] choices; // 选择数组,包含AI的回复选项
}
/// <summary>
/// 选择数据结构 - 包含AI生成的一条回复
/// </summary>
[System.Serializable]
private class Choice
{
public Message message; // 消息对象,包含AI的回复内容
}
}
- temperature:建议0.7-1.0之间,创造性对话用高点,技术问答用低点
- maxTokens:根据需求调整,短回复50-100,长回复200-500
- personalityPrompt:详细描述角色特性可获得更符合预期的回复
创建空物体命名:DSLinkObj,添加写好的DeepSeekLink。

2.2 配置对话交互功能
(1)创建脚本NPCInteraction,用于处理交互功能。
代码如下:
public class NPCInteraction : MonoBehaviour
{
[Header("组件引用")]
[SerializeField] private DeepSeekLink deepSeekLink; // 对话管理器 - 引用DeepSeek对话管理脚本
[SerializeField] private InputField inputField; // 玩家输入框 - 用于玩家输入对话内容
[SerializeField] private Text dialogueText; // 对话显示文本 - 显示NPC的回复内容
[SerializeField] private Button sendButton; // 发送按钮 - 点击后发送对话请求
[Header("参数设置")]
[SerializeField] private float typingSpeed = 0.05f; // 打字效果速度 - 每个字符显示的时间间隔(秒)
[SerializeField] private bool clearInputAfterSend = true; // 发送后是否清空输入框
private string characterName; // 存储NPC角色名称
private bool isWaitingResponse = false; // 标记是否正在等待API响应
/// <summary>
/// 初始化方法 - 在游戏开始时执行一次
/// </summary>
void Start()
{
// 从对话管理器中获取NPC角色名称
characterName = deepSeekLink.npcCharacter.name;
// 注册按钮点击事件 - 当按钮被点击时调用SendPlayerMessage方法
sendButton.onClick.AddListener(SendPlayerMessage);
// 注册输入框提交事件 - 当在输入框中按回车时也发送消息
inputField.onSubmit.AddListener((text) => SendPlayerMessage());
// 初始状态设置:禁用发送按钮,等待玩家输入
UpdateUIState();
}
/// <summary>
/// 每帧更新调用 - 用于实时更新UI状态
/// </summary>
void Update()
{
// 动态更新发送按钮状态:有输入内容且不等待响应时才可点击
sendButton.interactable = !isWaitingResponse && !string.IsNullOrEmpty(inputField.text.Trim());
}
/// <summary>
/// 发送玩家消息 - 按钮点击或回车时调用
/// </summary>
public void SendPlayerMessage()
{
// 获取输入框内容并去除首尾空格
string playerMessage = inputField.text.Trim();
// 检查消息是否为空
if (string.IsNullOrEmpty(playerMessage))
{
Debug.LogWarning("输入内容为空,无法发送");
return;
}
// 检查是否正在等待上一个响应
if (isWaitingResponse)
{
Debug.LogWarning("正在等待AI响应,请稍后再试");
return;
}
// 在对话文本中显示玩家消息
dialogueText.text = $"玩家: {playerMessage}\n\n{characterName}: 思考中...";
// 设置等待响应状态
isWaitingResponse = true;
// 更新UI状态
UpdateUIState();
// 发送对话请求到DeepSeek API
deepSeekLink.SendDialogueRequest(playerMessage, HandleAIResponse);
// 发送后清空输入框(如果设置了该选项)
if (clearInputAfterSend)
{
inputField.text = "";
}
}
/// <summary>
/// 处理AI回复的回调函数 - 在收到DeepSeek API响应后调用
/// </summary>
/// <param name="response">AI回复的文本内容</param>
/// <param name="success">请求是否成功</param>
private void HandleAIResponse(string response, bool success)
{
// 重置等待状态
isWaitingResponse = false;
// 更新UI状态
UpdateUIState();
// 根据成功状态显示不同的内容
string displayText = success ?
$"{characterName}: {response}" : // 成功:显示NPC回复
$"{characterName}: (通讯中断,请稍后重试)"; // 失败:显示错误信息
// 启动打字机效果协程,逐字显示文本
StartCoroutine(TypewriterEffect(displayText));
}
/// <summary>
/// 打字机效果协程 - 逐字显示文本,创造打字动画效果
/// </summary>
/// <param name="text">要显示的完整文本</param>
private IEnumerator TypewriterEffect(string text)
{
// 保存当前对话文本,避免在打字过程中被覆盖
string currentDialogue = dialogueText.text.Split('\n')[0] + "\n\n";
// 初始化当前显示的文本
string currentText = "";
// 遍历文本中的每个字符
foreach (char c in text)
{
// 将当前字符添加到显示文本中
currentText += c;
// 更新对话文本显示(保留玩家消息 + 新的NPC回复)
dialogueText.text = currentDialogue + currentText;
// 等待指定时间,控制打字速度
yield return new WaitForSeconds(typingSpeed);
}
}
/// <summary>
/// 更新UI状态 - 根据当前状态启用或禁用UI元素
/// </summary>
private void UpdateUIState()
{
// 等待响应时禁用输入框,响应完成后启用
inputField.interactable = !isWaitingResponse;
// 按钮状态在Update中动态更新,这里只处理极端情况
if (isWaitingResponse)
{
sendButton.interactable = false;
}
}
/// <summary>
/// 清空对话记录 - 可用于开始新的对话
/// </summary>
public void ClearDialogue()
{
dialogueText.text = "";
inputField.text = "";
isWaitingResponse = false;
UpdateUIState();
}
/// <summary>
/// 设置打字速度 - 可在运行时调整
/// </summary>
/// <param name="speed">新的打字速度(秒/字符)</param>
public void SetTypingSpeed(float speed)
{
typingSpeed = Mathf.Clamp(speed, 0.01f, 0.2f); // 限制速度在合理范围内
}
/// <summary>
/// 当脚本被禁用或对象被销毁时调用 - 清理事件监听
/// </summary>
void OnDestroy()
{
// 移除按钮点击事件监听,防止内存泄漏
if (sendButton != null)
sendButton.onClick.RemoveListener(SendPlayerMessage);
}
}
(2)添加UI控件
- 创建一个InputField控件用于接收用户输入
- 创建一个Scroll View控件,为其子物体Content添加Text组件来展示AI回复内容
- 创建一个Button控件用于触发消息发送功能

将脚本NPCInteraction脚本放置在Canvas下,如下配置脚本变量。

3 测试
运行,在InputField输入框中,输入你好,你是谁?随后回车或者点击发送,都可以将消息发送出去,并且输入框内清空内容,Button处于非激活状态。


———————————————————————————————————————————
4 补档(新增AI具备上下文能力)
4.1 核心修改思路
- 存储历史:在DeepSeekLink脚本中,增加一个List<Message>来记录整个对话过程。
- 发送历史:在每次构建API请求时,将存储的历史消息列表与当前用户的新消息合并,一起发送。
- 追加新内容:收到AI回复后,将本轮的用户消息和AI回复都追加到历史列表中。
4.2 具体代码修改
4.2.1 添加历史记录变量
在DeepSeekLink类的顶部,npcCharacter 变量下面,添加一个用于存储对话历史的列表。
public class DeepSeekLink : MonoBehaviour
{
// ... 原有的 [Header] 和变量声明 ...
[SerializeField] public NPCCharacter npcCharacter;
// --- 新增:对话历史记录,用于存储整个对话过程的列表 ---
//
private List<Message> conversationHistory = new List<Message>();
// --- 新增结束 ---
// ......
}
4.2.2 修改核心请求逻辑
找到 ProcessDialogueRequest 协程,修改其内部构建 messages 列表的方式。不再每次都只发系统提示和当前消息,而是基于历史记录来构建。
(可以直接用下面的代码直接替换ProcessDialogueRequest 中的内容)
private IEnumerator ProcessDialogueRequest(string userInput, DialogueCallback callback)
{
// --- 修改从这里开始 ---
// 1. 构建本次要发送的消息列表
List<Message> messagesToSend = new List<Message>();
// 2. 如果历史记录为空,或者是新对话的开始,需要先加入系统设定
// 注意:系统消息通常只需要在第一次对话时发送,但为了确保AI始终记得角色,每次都发送也可以。
// 这里采用每次都发送系统消息的策略,让AI时刻牢记角色。
messagesToSend.Add(new Message { role = "system", content = npcCharacter.personalityPrompt });
// 3. 将之前所有的对话历史(不包含系统消息,因为上面已经单独加了)添加到要发送的列表中
// 注意:conversationHistory 存储的是用户和助手交替的完整对话,我们把它原样加进去
messagesToSend.AddRange(conversationHistory);
// 4. 最后,将用户当前的新消息加入列表
messagesToSend.Add(new Message { role = "user", content = userInput });
// 5. 构建API请求体,使用组装好的 messagesToSend
ChatRequest requestBody = new ChatRequest
{
model = modelName,
messages = messagesToSend, // 使用包含上下文的列表
temperature = temperature,
max_tokens = maxTokens
};
// --- 修改结束 ---
string jsonBody = JsonUtility.ToJson(requestBody);
Debug.Log("Sending JSON: " + jsonBody);
// ... 发送网络请求的代码保持不变 ...
UnityWebRequest request = CreateWebRequest(jsonBody);
yield return request.SendWebRequest();
// ... 错误检查代码保持不变 ...
if (IsRequestError(request))
{
Debug.LogError($"API Error: {request.responseCode}\n{request.downloadHandler.text}");
callback?.Invoke(null, false);
yield break;
}
// 解析响应
DeepSeekResponse response = ParseResponse(request.downloadHandler.text);
if (response != null && response.choices.Length > 0)
{
string npcReply = response.choices[0].message.content;
// --- 新增:将本轮对话存入历史记录 ---
// 先存用户消息
conversationHistory.Add(new Message { role = "user", content = userInput });
// 再存AI回复
conversationHistory.Add(new Message { role = "assistant", content = npcReply });
// --- 新增结束 ---
callback?.Invoke(npcReply, true);
}
else
{
callback?.Invoke("Unity-YuZhang(陷入沉默)", false);
// 注意:如果请求失败,不应该将本轮对话存入历史
}
}
4.2.3 添加清空历史的方法
为了方便开始新对话,可以在 DeepSeekLink 类中添加一个公共方法来清空历史记录。
/// <summary>
/// 清空当前对话历史,开始全新的对话
/// </summary>
public void ClearConversationHistory()
{
conversationHistory.Clear();
Debug.Log("对话历史已清空");
}
在 NPCInteraction 脚本中,新增clearButton,需要开始新对话时调用DeepSeekLink的ClearConversationHistory方法。
仅需新增即可,其余代码不用动。
public class NPCInteraction : MonoBehaviour
{
// ... 原有的 [Header] 和变量声明 ...
// --- 新增:清除历史记录按钮 ---
[SerializeField] private Button clearButton; // 清除历史记录按钮 - 点击后发送请求
// --- 新增结束 ---
void Start()
{
// ... 原有的代码 ...
// --- 新增:调用清除历史功能 ---
// 清除历史记录按钮点击事件 - 当按钮被点击时调用deepSeekLink.ClearConversationHistory方法用于清除原来的历史记录
clearButton.onClick.AddListener(deepSeekLink.ClearConversationHistory);
// --- 新增结束 ---
// …………
}
在UI界面,添加一个新的Button,并拖至clearButton的引用即可。
现在运行,连续提问,就会发现AI能够记住之前聊过的内容了。
重要提示:Token消耗,随着对话进行,发送的历史消息会越来越长,消耗的Token数量也会线性增加。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)