Unity游戏AI开发实战:基于LLM的智能NPC对话与行为驱动指南

📅 2026/7/8 17:42:16 👁️ 阅读次数 📝 编程学习
Unity游戏AI开发实战:基于LLM的智能NPC对话与行为驱动指南

1. 项目概述:当游戏角色学会“思考”

最近和几个做独立游戏的朋友聊天,大家不约而同地提到了一个痛点:想让游戏里的NPC(非玩家角色)更“聪明”。传统的脚本对话树和状态机,玩家玩几次就摸透了,角色显得呆板、重复。我们想要的,是那种能根据玩家行为、对话上下文,给出独一无二、充满惊喜反应的“活”角色。这听起来像天方夜谭?放在几年前确实是,但今天,借助大语言模型,我们完全可以在自己的Unity项目里,低成本地实现这个梦想。

这个“终极指南”要做的,就是帮你把LLM(大语言模型)这个“大脑”塞进你的游戏角色里。整个过程,我把它提炼成了三步:接入、对话、驱动。听起来简单,但每一步都有不少细节和坑。我会基于我最近在一个叙事解谜项目中的实际集成经验,把从零到一的完整路径、工具选型背后的考量、以及那些官方文档不会告诉你的“血泪教训”都摊开来讲。无论你是想做一个能和玩家吟诗作对的江湖侠客,还是一个会根据玩家选择改变态度的酒吧老板,这套方法都能给你一个扎实的起点。

2. 核心思路与架构设计:为什么是“三步走”?

在动手写代码之前,我们必须想清楚整个系统的架构。直接把LLM的API往Update循环里一塞是行不通的,那会带来性能灾难和不可预测的行为。我的设计核心是“异步、事件驱动、分层解耦”

2.1 架构总览:从玩家输入到角色行为

整个流程可以看作一个数据处理管道:

  1. 输入层:捕获玩家的文本输入(UI输入框)或语音转文本后的结果,也可能包括当前游戏状态(如角色位置、任务进度等上下文信息)。
  2. 处理层(LLM集成层):这是核心。将输入信息精心“包装”成一个提示词,发送给LLM服务,并异步等待其返回的文本响应。
  3. 输出层:解析LLM返回的文本,将其转化为游戏内的具体行为。这可能是直接显示为对话气泡,也可能是触发某个动画、改变角色的状态机参数,甚至是执行一段游戏逻辑。

“三步走”正是对应了这个管道的三个关键构建环节:

  • 第一步:接入-> 搭建“处理层”的基础设施,解决如何与LLM服务通信的问题。
  • 第二步:对话-> 完善“输入层”和“输出层”的对话表现部分,实现基本的问答循环。
  • 第三步:驱动-> 深化“输出层”,将LLM的文本输出解析并映射到游戏角色的复杂行为上,实现真正的智能驱动。

2.2 技术选型考量:云端API vs. 本地模型

这是第一个重大决策点,直接决定成本、性能和体验。

方案A:使用云端API(如OpenAI GPT, Anthropic Claude, 国内大模型API)

  • 优点:开箱即用,效果强大且稳定,无需担心硬件。像GPT-4这样的模型在对话逻辑、上下文理解上表现惊人。
  • 缺点:持续产生费用,有网络延迟,且响应速度受API服务器状态影响。对于需要高频交互的游戏,成本可能失控。
  • 适用场景:原型验证、单机叙事游戏(对话频率较低)、或作为后备方案。

方案B:在本地运行轻量级LLM(如Llama.cpp, Ollama, 或使用Unity Barracuda)

  • 优点:零延迟,数据完全本地,无后续费用,适合需要实时响应的场景。
  • 缺点:对玩家硬件有要求,模型能力相对较弱,需要处理模型加载和推理性能优化。
  • 适用场景:对实时性要求高的游戏(如需要LLM控制战斗策略)、注重数据隐私、或希望发行包内集成的项目。

我的实操心得:对于大多数中小团队和独立开发者,我强烈建议从云端API开始。理由很简单:成本可控(原型阶段花费极低),效果最好,能让你快速验证玩法和体验。等核心玩法被验证后,再考虑为了发行版优化(如集成小模型)也不迟。本指南将主要基于OpenAI的Chat Completion API进行讲解,因为它的文档最全、生态最成熟,原理相通,你可以轻松替换为其他API。

3. 第一步:接入——在Unity中搭建LLM通信桥梁

这一步的目标是创建一个可靠、可配置的模块,负责所有与LLM API的交互。

3.1 环境准备与基础设置

首先,你需要在Unity中创建一个新的脚本,比如命名为LLM_Client。这个脚本将使用Unity的UnityWebRequest进行网络通信。

  1. 获取API密钥:前往OpenAI平台注册并获取你的API Key。切记,这个Key如同密码,绝不能硬编码在客户端代码或上传到公开仓库。
  2. 创建配置资产:为了安全灵活地管理配置,我推荐创建一个ScriptableObject。命名为LLMConfig,包含以下字段:
    [CreateAssetMenu(fileName = "LLMConfig", menuName = "AI/LLM Config")] public class LLMConfig : ScriptableObject { public string apiKey; // 在Editor中填写,运行时从安全位置加载 public string apiUrl = "https://api.openai.com/v1/chat/completions"; public string modelName = "gpt-3.5-turbo"; // 或 "gpt-4" public float temperature = 0.7f; // 创造性,0-2之间 public int maxTokens = 150; // 单次回复最大长度 }
    这样,你可以在Editor中创建不同的配置资产,用于开发、测试和生产环境,并且方便地切换模型。

3.2 构建核心请求模块

LLM_Client的核心是一个发送POST请求的方法。你需要构造符合OpenAI API要求的JSON数据。

using UnityEngine; using UnityEngine.Networking; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; public class LLM_Client : MonoBehaviour { public LLMConfig config; // 定义消息结构 [System.Serializable] public class Message { public string role; // "system", "user", "assistant" public string content; } [System.Serializable] public class ChatRequest { public string model; public List<Message> messages; public float temperature; public int max_tokens; } [System.Serializable] public class ChatResponse { public Choice[] choices; [System.Serializable] public class Choice { public Message message; } } public async Task<string> SendChatRequestAsync(List<Message> messageHistory) { var requestBody = new ChatRequest { model = config.modelName, messages = messageHistory, temperature = config.temperature, max_tokens = config.maxTokens }; string jsonBody = JsonUtility.ToJson(requestBody); byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody); using (UnityWebRequest request = new UnityWebRequest(config.apiUrl, "POST")) { request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); // 安全地设置Authorization头 request.SetRequestHeader("Authorization", "Bearer " + LoadApiKeySecurely()); var operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); // 异步等待,不阻塞主线程 } if (request.result == UnityWebRequest.Result.Success) { var response = JsonUtility.FromJson<ChatResponse>(request.downloadHandler.text); if (response.choices != null && response.choices.Length > 0) { return response.choices[0].message.content; } else { Debug.LogError("LLM响应格式异常: " + request.downloadHandler.text); return "(思考中...)"; } } else { Debug.LogError($"LLM请求失败: {request.error} - {request.downloadHandler.text}"); // 根据错误类型返回降级处理,例如网络错误返回缓存对话 return HandleError(request.responseCode); } } } private string LoadApiKeySecurely() { // 方法1(开发期):从ScriptableObject读取(仅限Editor,发布时不安全) // return config.apiKey; // 方法2(推荐):从持久化数据或环境变量读取,或在构建时由CI/CD流程注入 // 例如:return System.Environment.GetEnvironmentVariable("OPENAI_API_KEY"); // 对于独立游戏,可考虑在游戏首次启动时让玩家自行输入(适用于单机)。 // 此处为示例,返回配置值,但你必须为你的发行版本设计安全方案。 return config.apiKey; } private string HandleError(long responseCode) { // 简单的错误处理 if (responseCode == 401) return "[错误:认证失败,请检查API Key]"; if (responseCode == 429) return "[错误:请求过于频繁,请稍后再试]"; if (responseCode >= 500) return "[错误:AI服务暂时不可用]"; return "[网络连接出现问题]"; } }

关键注意事项

  1. 异步与协程:这里使用了async/awaitTask.Yield()来避免阻塞主线程。你也可以用UnityWebRequest.SendWebRequest()配合协程yield return request.SendWebRequest(),但async/await的代码逻辑更清晰。
  2. API密钥安全LoadApiKeySecurely方法是关键。在Editor开发时,用ScriptableObject很方便。但发布游戏时,绝对不能将密钥硬编码在游戏包里。对于单机游戏,一个折中方案是首次运行时让玩家输入自己的API Key(并向玩家说明计费情况)。对于网络游戏,最佳实践是搭建一个简单的后端中转服务器,游戏客户端请求你的服务器,你的服务器再带着密钥去请求OpenAI。这样密钥完全保存在你的服务器上。
  3. 错误处理:网络请求充满不确定性。必须对超时、鉴权失败、额度不足、服务器错误等情况进行降级处理,给玩家友好的提示,避免游戏崩溃或卡死。

3.3 构建对话历史管理器

LLM的强大在于上下文理解。我们需要一个模块来维护和管理角色与玩家之间的对话历史。

public class DialogueHistoryManager { private List<LLM_Client.Message> _messageHistory = new List<LLM_Client.Message>(); private int _maxHistoryLength = 20; // 控制上下文长度,避免token超限和成本激增 // 添加系统指令,设定角色身份和背景 public void InitializeSystemPrompt(string systemPrompt) { _messageHistory.Clear(); _messageHistory.Add(new LLM_Client.Message { role = "system", content = systemPrompt }); } // 添加用户(玩家)消息 public void AddUserMessage(string content) { _messageHistory.Add(new LLM_Client.Message { role = "user", content = content }); TrimHistory(); } // 添加助手(NPC)消息 public void AddAssistantMessage(string content) { _messageHistory.Add(new LLM_Client.Message { role = "assistant", content = content }); TrimHistory(); } // 获取当前完整的历史记录,用于发送请求 public List<LLM_Client.Message> GetCurrentHistory() { return new List<LLM_Client.Message>(_messageHistory); } // 清理最早的非系统消息,保持历史窗口 private void TrimHistory() { // 保留第一条系统消息 while (_messageHistory.Count > _maxHistoryLength && _messageHistory.Count > 1) { // 找到第一条非系统消息并移除 int indexToRemove = _messageHistory.FindIndex(1, m => m.role != "system"); // 从索引1开始找 if (indexToRemove != -1) { _messageHistory.RemoveAt(indexToRemove); } else { break; } } } // 可选的:清空历史(例如开始新对话时) public void ClearHistory(bool keepSystemPrompt = true) { if (keepSystemPrompt && _messageHistory.Count > 0 && _messageHistory[0].role == "system") { var systemMsg = _messageHistory[0]; _messageHistory.Clear(); _messageHistory.Add(systemMsg); } else { _messageHistory.Clear(); } } }

这个管理器是对话连贯性的灵魂。通过system角色消息,你可以为NPC注入灵魂:“你是一个中世纪的铁匠,性格暴躁但手艺精湛,讨厌别人讨价还价。” 后续所有的对话都会在这个人格背景下展开。

4. 第二步:对话——实现基础的问答循环与UI呈现

有了通信能力和历史管理,现在我们需要搭建一个能让玩家与NPC交互的前端界面和逻辑。

4.1 设计简单的对话UI

在Unity Canvas中创建一个简单的对话面板,通常包含:

  • 一个TextTextMeshPro组件用于显示NPC的对话内容。
  • 一个InputField让玩家输入文本。
  • 一个发送按钮。
  • 可能还需要一个滚动视图来显示完整的对话历史记录。

创建一个DialogueUI脚本来管理这些UI元素。

4.2 串联所有模块:创建对话控制器

这是驱动整个对话流程的“导演”脚本。

public class DialogueController : MonoBehaviour { public LLM_Client llmClient; public DialogueUI dialogueUI; public string npcSystemPrompt; // 在Inspector中配置NPC身份 private DialogueHistoryManager _historyManager; private bool _isWaitingForResponse = false; void Start() { _historyManager = new DialogueHistoryManager(); _historyManager.InitializeSystemPrompt(npcSystemPrompt); // 初始可以有一句开场白 // SendMessageToLLM("[开场]"); } // 由UI的发送按钮调用 public void OnPlayerInputSubmitted(string playerText) { if (string.IsNullOrEmpty(playerText) || _isWaitingForResponse) return; // 1. 在UI上显示玩家的话 dialogueUI.DisplayPlayerMessage(playerText); // 2. 添加到历史 _historyManager.AddUserMessage(playerText); // 3. 发送给LLM并等待回复 _isWaitingForResponse = true; dialogueUI.SetInputActive(false); // 禁用输入,等待响应 GetLLMResponseAsync(); } private async void GetLLMResponseAsync() { try { var history = _historyManager.GetCurrentHistory(); string npcResponse = await llmClient.SendChatRequestAsync(history); // 处理响应 if (!string.IsNullOrEmpty(npcResponse)) { // 4. 添加到历史 _historyManager.AddAssistantMessage(npcResponse); // 5. 在UI上显示NPC的话 dialogueUI.DisplayNPCMessage(npcResponse); // 6. 可以在这里触发语音合成(TTS) // TriggerTTS(npcResponse); } } catch (Exception e) { Debug.LogError($"获取LLM响应时出错: {e.Message}"); dialogueUI.DisplayNPCMessage("[抱歉,我好像有点卡壳了。]"); } finally { _isWaitingForResponse = false; dialogueUI.SetInputActive(true); // 重新激活输入 dialogueUI.ClearInputField(); // 清空输入框 } } }

至此,一个最基础的、基于文本的智能对话系统就完成了。玩家输入,NPC能根据设定的人格和历史上下文进行回复。

4.3 优化提示词工程

对话质量90%取决于提示词。除了基本的系统指令,你还可以在每次请求时动态注入游戏上下文。

public string BuildDynamicPrompt(string playerAction, GameState gameState) { StringBuilder context = new StringBuilder(); context.AppendLine($"当前时间:{gameState.currentTime}(游戏内)"); context.AppendLine($"玩家声望:{gameState.playerReputation}"); context.AppendLine($"玩家与我的关系:{gameState.relationshipWithNPC}"); context.AppendLine($"最近发生的事件:{gameState.recentEvent}"); context.AppendLine($"玩家刚刚做了:{playerAction}"); return context.ToString(); } // 然后在发送请求前,将这个动态上下文作为一条新的“user”消息插入历史,或者拼接到最新的用户消息中。

5. 第三步:驱动——从文本到游戏内行为

这是将LLM从“聊天机器人”升级为“游戏角色大脑”的关键一步。我们需要解析LLM返回的文本,并驱动游戏内的具体行为。

5.1 定义行为指令与解析

我们约定一种简单的“指令协议”,让LLM在回复文本中嵌入可解析的命令。例如,用特殊标记包裹指令:

(玩家:你觉得这把剑怎么样?) (NPC:这把剑的锻造工艺很一般,刃口有细微的卷刃。(动作:不屑地摇头,表情:轻蔑)如果你需要修理,我可以帮忙,但得加钱。(指令:offer_service repair_sword))

我们需要一个BehaviorParser来解析这些响应。

public class ParsedResponse { public string dialogueText; // 纯对话文本 public string animationTrigger; // 动画触发器名 public string emotionState; // 情绪状态 public List<string> gameInstructions; // 游戏指令列表 } public class BehaviorParser { // 简单正则匹配指令,例如匹配 (指令:xxx) private Regex _instructionRegex = new Regex(@"\(指令\:([^\)]+)\)"); private Regex _actionRegex = new Regex(@"\(动作\:([^\)]+)\)"); private Regex _emotionRegex = new Regex(@"\(表情\:([^\)]+)\)"); public ParsedResponse Parse(string rawLLMResponse) { ParsedResponse result = new ParsedResponse(); result.gameInstructions = new List<string>(); // 提取并移除指令部分,得到纯净对话文本 string cleanText = rawLLMResponse; var instructionMatch = _instructionRegex.Match(cleanText); while (instructionMatch.Success) { result.gameInstructions.Add(instructionMatch.Groups[1].Value.Trim()); cleanText = cleanText.Replace(instructionMatch.Value, ""); instructionMatch = instructionMatch.NextMatch(); } // 类似地提取动作和表情 var actionMatch = _actionRegex.Match(cleanText); if (actionMatch.Success) { result.animationTrigger = actionMatch.Groups[1].Value.Trim(); cleanText = cleanText.Replace(actionMatch.Value, ""); } var emotionMatch = _emotionRegex.Match(cleanText); if (emotionMatch.Success) { result.emotionState = emotionMatch.Groups[1].Value.Trim(); cleanText = cleanText.Replace(emotionMatch.Value, ""); } // 清理多余空格和标点 result.dialogueText = cleanText.Trim().TrimEnd(',', '。', ' '); return result; } }

5.2 构建行为执行器

解析出指令后,需要一个执行器来将这些指令转化为具体的游戏逻辑。

public class BehaviorExecutor : MonoBehaviour { public Animator npcAnimator; public GameEventSystem gameEventSystem; // 假设你有一个游戏事件系统 public NPCEmotionController emotionController; // 控制表情变化的组件 public void ExecuteParsedResponse(ParsedResponse response) { // 1. 播放动画 if (!string.IsNullOrEmpty(response.animationTrigger)) { npcAnimator.SetTrigger(response.animationTrigger); } // 2. 更新情绪状态 if (!string.IsNullOrEmpty(response.emotionState)) { emotionController.SetEmotion(response.emotionState); } // 3. 执行游戏指令 foreach (var instruction in response.gameInstructions) { ProcessGameInstruction(instruction); } } private void ProcessGameInstruction(string instruction) { // 这里将字符串指令映射到具体的游戏功能 // 例如,可以使用 switch 语句或字典查找 switch (instruction.ToLower()) { case "offer_service repair_sword": // 触发“修理服务”对话选项或任务 gameEventSystem.TriggerEvent("NPC_OFFER_REPAIR"); break; case "start_quest_goblin_hunt": // 开启“哥布林讨伐”任务 QuestManager.Instance.StartQuest("GoblinHunt"); break; case "increase_relationship 5": // 增加5点好感度 RelationshipManager.ModifyRelationship("Blacksmith", 5); break; case "give_item health_potion": // 给予玩家一个治疗药水 InventoryManager.PlayerInstance.AddItem("HealthPotion", 1); break; default: Debug.LogWarning($"未知的游戏指令: {instruction}"); break; } } }

5.3 训练LLM输出结构化指令

要让LLM乖乖地按你的格式输出指令,需要在系统提示词中进行严格规定和少量示例训练。

强化版系统提示词示例:

你是一个游戏中的铁匠NPC,名叫“老锤”。你的性格暴躁但心地善良,痴迷于锻造技术。 请严格按照以下格式进行回复: 1. 首先是你想说的对话内容。 2. 如果需要伴随动作或表情,请在括号内注明,格式为:(动作:动作描述)或(表情:表情描述)。例如:(动作:用力敲打铁砧) 3. 如果你想触发游戏内的特定功能(如给予任务、打开商店、改变关系),请在对话末尾的独立括号内注明,格式为:(指令:指令代码)。例如:(指令:open_shop_weapon) 可用的指令代码包括: - open_shop_[类型]:打开商店(类型:weapon, armor, material) - start_quest_[任务名]:开启任务 - give_item_[物品ID]:给予物品 - change_mood_[情绪]:改变你的情绪(情绪:angry, happy, neutral) 请确保指令代码准确无误。 当前游戏上下文:[此处注入动态游戏状态] 现在开始与玩家对话。 玩家说:“你好,老锤。”

通过这样详细的规则和示例,LLM输出结构化指令的准确率会大大提高。你甚至可以在游戏后台运行一个“指令验证”步骤,如果LLM输出了无法识别的指令,可以要求它重试或降级处理。

6. 高级技巧与深度优化

基础三步走通后,我们可以追求更极致的体验和性能。

6.1 性能优化与成本控制

  • 缓存与记忆:为每个NPC维护一个持久的“长期记忆”向量数据库(可以使用简单的JSON存储关键事实摘要)。每次对话时,将长期记忆中相关的片段作为上下文注入,而不是无限制地增长对话历史。这能显著减少Token消耗。
  • 请求节流与队列:不要玩家每按一次键就发送请求。可以设置一个输入延迟(如停止输入0.5秒后再发送),并将请求放入队列顺序处理,防止瞬时高并发。
  • 模型阶梯使用:对于简单的确认、问候语,可以尝试用规则或更小、更便宜的模型(如gpt-3.5-turbo)来响应。只有复杂的、需要创造性的对话才调用gpt-4。这需要设计一个意图识别层。
  • 本地后备方案:当网络超时或API不可用时,立即切换到一个本地的、基于决策树的简单对话系统,保证游戏流程不中断。

6.2 提升沉浸感:与游戏系统深度集成

  • 环境感知:将NPC周围的游戏世界状态作为上下文。例如,“(你看到玩家手里拿着一把罕见的龙鳞剑)”。
  • 目标与动机:为每个NPC定义简单的目标(如“今天卖出5把剑”)和情绪状态机。LLM的回复会受到当前目标和情绪的影响。
  • 语音合成:将LLM生成的文本通过TTS服务(如ElevenLabs、Azure TTS)或本地TTS插件转换为语音,并配上口型动画。
  • 面部表情与肢体动画:将解析出的emotionStateanimationTrigger映射到角色的面部混合形状和动画状态机,让角色的表演更生动。

6.3 测试与调试策略

  • 录制与回放:记录下每次对话的输入、输出和游戏状态,便于复现和调试奇怪的回复。
  • 提示词沙盒:在游戏内建立一个隐藏的调试控制台,允许你实时修改系统提示词并立即看到效果。
  • 隔离测试:为每个NPC的对话系统创建独立的测试场景,方便快速迭代人格设定。

7. 常见问题与避坑指南

在实际集成中,我遇到了不少坑,这里列几个最有代表性的:

问题一:LLM回复慢,游戏卡顿。

  • 现象:点击发送后,游戏帧率下降,UI无响应。
  • 排查:检查是否在主线程中同步调用了网络请求。UnityWebRequestSendWebRequest后如果使用while (!request.isDone) {}这样的循环等待,就会阻塞主线程。
  • 解决必须使用异步编程。如前文所示,用async/await配合Task.Yield(),或者用协程yield return request.SendWebRequest(),确保等待期间主线程可以继续运行。

问题二:NPC“胡言乱语”或脱离角色。

  • 现象:NPC突然开始以开发者的口吻说话,或者讨论与游戏世界无关的内容。
  • 排查:首先检查系统提示词是否足够强硬和具体。其次,检查对话历史是否包含了导致角色“漂移”的上下文。有时玩家故意“调戏”AI会导致这种情况。
  • 解决:1. 强化系统提示词,开头使用“你必须始终扮演…”、“绝对不能…”等强约束语句。2. 在历史管理器中,更激进地修剪旧对话,或者定期在历史中重新插入强化后的系统提示。3. 在后端对LLM的输出做一个简单的“角色一致性”过滤,如果检测到严重偏离,可以丢弃该回复并让LLM重试。

问题三:Token消耗巨大,成本飙升。

  • 现象:API账单增长过快。
  • 排查:对话历史是否无限增长?每次请求是否携带了过多不必要的游戏状态信息?
  • 解决:1. 严格限制对话历史条数(如最多10轮对话)。2. 对游戏状态进行摘要,而不是罗列所有数据。例如,将“玩家拥有剑、盾、药水…”概括为“玩家装备精良”。3. 考虑使用GPT-3.5-Turbo等更便宜的模型进行大部分对话,仅关键剧情点使用GPT-4。

问题四:指令解析失败。

  • 现象:LLM没有按约定格式输出指令,或者指令代码拼写错误。
  • 排查:提示词中的指令描述是否清晰?是否提供了足够多的正确示例?
  • 解决:1. 采用更鲁棒的解析方式,比如尝试匹配关键词而非严格格式。例如,只要回复中出现“open shop”,就尝试触发开店逻辑。2. 实现一个“指令纠正”环节:如果解析失败,可以将错误信息和期望格式再次发送给LLM,要求它修正回复。但这会增加一次API调用。

问题五:内容安全与不可控输出。

  • 现象:玩家输入恶意或诱导性问题,NPC可能产生不符合游戏评级或世界观的回复。
  • 解决绝对不能完全信任LLM的输出。1. 在客户端或服务器端设置一个“内容过滤器”,对LLM返回的文本进行关键词过滤。2. 使用OpenAI等API提供的 moderation 端点,在发送用户输入和接收AI回复时都进行审核。3. 设计游戏对话系统时,为敏感话题准备一套“安全回应”的备用脚本,一旦触发关键词,则覆盖LLM的回复。

将LLM集成到Unity中打造智能角色,是一个从“对话”到“驱动”的渐进过程。起步时,不妨先聚焦于让一个NPC能进行有趣、不重复的对话,这已经能带来巨大的体验提升。随着你对提示词工程、状态管理和行为解析的深入理解,再逐步赋予NPC更强大的感知和行动能力。这个过程最迷人的地方在于,你是在和一种“涌现”的智能合作,每一次调试和优化,都像是在打磨一个真正有灵魂的角色。