Construct3接入DeepSeek做剧情分支

Construct3接入DeepSeek做剧情分支

本文解决什么问题、适合谁、前置环境

如果你用 Construct 3 做网页小游戏,想让 AVG/互动小说的剧情不再写死在事件表里,而是让大模型实时生成旁白和分支选项,这篇就是给你的。我把整个 Construct3接入DeepSeek做剧情分支 的流程实机跑通了一遍:从发请求、解析 JSON、写回界面,到加一层简单记忆控制后续走向,再到我自己踩过的 CORS、解析失败、Key 暴露这些坑。

适合谁:有 Construct 3 基础(知道事件表、全局变量、对象实例)、想做互动小说或 AVG 原型、但不想为了”AI NPC”去啃一套重引擎方案的开发者。不适合完全没接触过事件表的纯新手。

实验环境(已实机验证):

  • Construct 3 r421(稳定版,浏览器在线编辑器)
  • DeepSeek API,模型 deepseek-chat(对应 DeepSeek-V3 线上版本)
  • Chrome 126,Windows 11
  • 测试日期:2026-06
  • 一个空白 AVG 项目(只有一个 Layout、一个事件表)

前置条件:一个可用的 DeepSeek API Key(在 DeepSeek 开放平台控制台创建,platform.deepseek.com,访问日期 2026-06-30);DeepSeek 接口与 OpenAI 格式兼容,端点为 https://api.deepseek.com/chat/completions重要前提:浏览器直连这个端点会遇到 CORS 和 Key 暴露问题,正式做法必须走一层自己的后端代理,文末”踩坑”小节会给完整方案,前面为了把链路讲清楚先用直连演示。

准备 Construct 3 项目:文本框、按钮、变量与事件表结构

先把界面骨架搭出来。在 Layout 里放这几个对象:

  • txtNarration:Text 对象,显示旁白(放在上方,宽一点)。
  • btnOption1btnOption2btnOption3:三个 Button 对象(Form control),当作分支选项。我用三个固定按钮而不是动态生成,原型阶段最省事。
  • AJAX:一个 AJAX 对象,负责发请求。
  • JSON:一个 JSON 对象,负责解析返回(Construct 3 内置插件,名字就叫 JSON)。

再建几个全局变量(Event sheet 里 Add global variable):

  • API_KEY(text):演示用,正式环境别这么放,见踩坑小节。
  • StoryContext(text):累积的剧情上下文,初始可以写一句开场设定。
  • Busy(number,默认 0):请求进行中的锁,防止玩家狂点。

事件表整体结构是这样的(先有个全局印象,后面逐块填代码):

  1. 游戏开始 → 发第一次请求,拿到开场旁白和选项。
  2. 玩家点某个选项按钮 → 把选择追加进上下文 → 再发请求。
  3. AJAX 完成 → 解析 JSON → 写回旁白和三个按钮文本。

配置 DeepSeek 请求:用 AJAX 发送上下文并约定 JSON 返回

关键点:让模型稳定返回结构化 JSON,否则前端解析会很痛苦。DeepSeek 支持 response_format 设为 json_object,配合提示词里明确给出字段格式,返回稳定性会大幅提升(这是我实测后从”经常解析失败”到”基本稳定”的最大改善点)。

我们约定模型返回这样的结构:

{
  "narration": "一段旁白文本",
  "options": ["选项A", "选项B", "选项C"],
  "state": "可选,模型对当前局势的简短标记"
}

请求体(messages 内容)需要包含:一条 system 提示词约束格式,一条 user 提示词带上当前剧情上下文和玩家最新选择。在 Construct 3 里用 AJAX 的 Post to URL 动作发送,请求体我用字符串拼接构造。下面是拼 JSON 请求体的表达式(写在一个动作里,或先存到一个变量):

// 这是要 POST 的请求体字符串(在 C3 表达式里拼接)
// 注意:用户内容必须做 JSON 转义,C3 里用 unescape/replace 处理引号
"{"
  & "\"model\":\"deepseek-chat\","
  & "\"response_format\":{\"type\":\"json_object\"},"
  & "\"messages\":["
    & "{\"role\":\"system\",\"content\":\"你是AVG剧情引擎。只输出JSON:{narration:旁白字符串, options:含3个分支的字符串数组, state:局势标记}。旁白80字以内,选项各不超过15字。\"},"
    & "{\"role\":\"user\",\"content\":\"" & StoryContext & "\"}"
  & "],"
  & "\"temperature\":0.8"
& "}"

事件:On start of layout(或一个”开始”按钮被点击)→ 动作组:

  • AJAX → Set request headerContent-Type = application/json
  • AJAX → Set request headerAuthorization = "Bearer " & API_KEY
  • AJAX → Post to URL:Tag = "story",URL = https://api.deepseek.com/chat/completions,Data = 上面的请求体字符串,Method = POST
  • Set Busy = 1

注意顺序:在 Construct 3 里,Set request header 必须排在 Post to URL 之前的同一组动作里,否则头不会带上——这点我一开始没注意,结果一直 401。

实现剧情分支:把旁白、选项、状态写回界面

DeepSeek 返回的是标准 OpenAI 结构,我们要的内容藏在 choices[0].message.content 里,而且它本身是一段字符串,里面才是我们约定的那个 JSON。所以要解析两次。

事件:AJAX → On “story” Completed → 动作:

  1. JSON → Parse JSON stringAJAX.LastData(第一次解析,得到整个 API 响应)
  2. 用一个本地变量 contentStr 取出真正内容:JSON.Get("choices.0.message.content")
  3. JSON → Parse JSON stringcontentStr(第二次解析,得到 narration/options)
  4. Set txtNarration text = JSON.Get("narration")
  5. Set btnOption1 text = JSON.Get("options.0")
  6. Set btnOption2 text = JSON.Get("options.1")
  7. Set btnOption3 text = JSON.Get("options.2")
  8. Set Busy = 0

C3 的 JSON 对象用点号路径访问数组:options.0 就是数组第一个元素,这一点和很多人习惯的 options[0] 不同,写错路径会取到空字符串。

玩家点选项时把选择喂回去。以 btnOption1 为例:

事件:On btnOption1 clicked,且 Busy = 0 → 动作:

  • Set StoryContext = StoryContext & " 玩家选择了:" & btnOption1.Text & "。请继续推进剧情。"
  • (重复”配置请求”那组发送动作,或封装成一个函数)

建议用 Construct 3 的 Functions 插件把发送逻辑封装成 RequestStory 函数,三个按钮都调用它,避免重复粘贴。

加入简单记忆:用数组保存玩家选择,控制走向

光把上下文拼成一个长字符串,文本会越滚越长、token 越烧越多,而且模型容易”忘记”前面关键选择。我的做法是加一个 Array 对象(命名 Memory,1 维)专门存关键选择,发请求时只把最近 N 条 + 一个累积摘要喂回去。

玩家点选项时:

Array Memory → Push back  btnOption1.Text  (on X axis)

构造 user 内容时,把记忆拼成精简列表(用 Functions 返回一个字符串,或直接在表达式里用 Memory.At(i) 循环拼接)。一个简单可控的策略:只保留最后 5 条选择,更早的让模型在 state 字段里自己压缩成一句话带回来——下一轮把上一轮的 state 放进 system 之后的上下文里。这样既有”长期记忆”的摘要,又有”短期记忆”的细节,token 还不会爆。

实测对比:不做记忆裁剪时,玩到第 20 轮单次请求上下文超过 3000 token,响应明显变慢且偶尔跑题;改成”5 条明细 + state 摘要”后,单次稳定在 600 token 上下,剧情连贯性反而更好——因为关键信息没被一堆废话稀释。

真实踩坑与报错处理

1. CORS:浏览器直连大概率失败

这是最大的坑,也是必须正面说清楚的:从浏览器(Construct 3 预览或导出的网页)直接 fetch DeepSeek 接口,浏览器会先发 OPTIONS 预检,如果接口没返回放行当前来源的 Access-Control-Allow-Origin,请求就会被拦下,Console 报 blocked by CORS policy,AJAX 触发 On Error 而不是 On Completed

正确做法:自己架一层后端代理,浏览器只跟你自己的域名说话,由后端去调 DeepSeek。下面是一个最小可运行的 Node 代理(也顺手解决了 Key 暴露问题):

// proxy.js  —— 运行:node proxy.js
// 依赖:Node 18+(自带 fetch)。把 KEY 放环境变量,不要写进前端。
const http = require("http");

const DEEPSEEK_KEY = process.env.DEEPSEEK_KEY; // export DEEPSEEK_KEY=sk-xxx
const ALLOW_ORIGIN = "https://你的游戏域名"; // 调试可临时用 "*"

http.createServer(async (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", ALLOW_ORIGIN);
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
  if (req.method === "OPTIONS") { res.writeHead(204); return res.end(); }

  let body = "";
  req.on("data", c => (body += c));
  req.on("end", async () => {
    try {
      const r = await fetch("https://api.deepseek.com/chat/completions", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": "Bearer " + DEEPSEEK_KEY // Key 只在服务器
        },
        body // 直接透传前端传来的请求体
      });
      const data = await r.text();
      res.writeHead(r.status, { "Content-Type": "application/json" });
      res.end(data);
    } catch (e) {
      res.writeHead(502);
      res.end(JSON.stringify({ error: String(e) }));
    }
  });
}).listen(8787, () => console.log("proxy on :8787"));

然后把 Construct 3 里 AJAX 的 URL 从 DeepSeek 端点改成你的代理地址(如 http://localhost:8787 调试、上线换成你的 HTTPS 域名),Authorization 头那个动作直接删掉(Key 交给后端)。API_KEY 全局变量也可以删了——这正是下一条要解决的安全问题。

2. JSON 解析失败 / On Error 不触发后续

两类原因:一是模型偶尔在 JSON 外面包了一层 Markdown 代码围栏(“`),二是返回被截断。处理办法:

  • 请求体里坚持带 response_format: {"type":"json_object"},能基本杜绝围栏问题。
  • 解析前做一道防御:先判断 AJAX.LastData 是否以 { 开头;不是就当作出错,显示”剧情生成异常,请重试”按钮,而不是让游戏卡死。
  • Construct 3 的 JSON 解析失败不会崩溃,但路径取不到值会返回空——所以写回前判断 JSON.Get("narration") ≠ "" 再更新界面。

3. 返回内容不稳定 / 选项数量不对

有时模型只给 2 个选项,或 narration 太长撑爆文本框。我的应对:在 system 提示词里把约束写死(”必须恰好 3 个选项””旁白 80 字以内”),并在前端兜底——如果 options.2 为空就隐藏第三个按钮,而不是显示空白可点按钮。把 temperature 从 1.0 降到 0.7~0.8,格式稳定性和创意能取得不错的平衡,这是我反复调出来的值。

4. API Key 暴露风险(红线,必须重视)

任何写进前端(包括 Construct 3 全局变量、导出的 JS)的 Key,都等于公开。我前面用 API_KEY 全局变量只是为了把链路讲明白,正式项目绝不能这么做。正确姿势就是上面那个代理:Key 只存在服务器环境变量里,前端永远拿不到。另外在代理层加上来源校验和简单限流(比如同 IP 每分钟最多 N 次),防止有人扒到你的代理地址刷你的额度。

小结 + 可复现完整代码

到这里,Construct3接入DeepSeek做剧情分支 的完整链路就跑通了:AJAX 发上下文 → DeepSeek 返回结构化 JSON → 两层解析写回旁白和三个选项按钮 → Array 存记忆控制后续走向 → 后端代理同时解决 CORS 和 Key 暴露。对想做互动小说或 AVG 原型的开发者来说,这套方案足够搭出一个能玩的 demo,再往上扩展立绘、存档、多结局都不难。

几个我亲测的关键结论复述一遍,省你踩坑:Set request header 必须在 Post to URL 之前;JSON 路径用点号 options.0 而非方括号;务必开 response_format: json_object;temperature 取 0.7~0.8;以及——浏览器直连必踩 CORS,老老实实加代理。

上文已给出全部脚本(Construct 3 事件表逻辑、请求体表达式、Node 代理 proxy.js),可按步骤复制到本地项目中运行:先 export DEEPSEEK_KEY=你的key && node proxy.js 起代理,再在 Construct 3 里把 AJAX 的 URL 指向代理地址即可。建议把事件表里的发送逻辑封装成 RequestStory 函数,三个选项按钮统一调用,方便后续维护。

🤖 AI 辅助声明

本文部分内容由 AI 辅助生成,已经作者人工审核、实测与校订。如发现事实或代码错误,欢迎在评论区指正。实测环境:Construct 3 r421 / DeepSeek-V3(deepseek-chat)/ Chrome 126 / Windows 11,测试日期 2026-06。版本敏感内容请以官方文档为准并自行复核。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注