【项目博客7】三维辩论舞台开发记录
DebatePlace 三维辩论舞台开发记录从原 Debate 页面到角色化赛场一、开发背景项目原本的Debate页面已经能够承担辩论流程中的核心数据职责阶段切换、争议点展示、对话历史记录、存档和回放等功能都已经形成了比较完整的链路。但在表现形式上原页面更接近一个功能型辩论界面重点是把辩论流程、文本发言和辅助信息展示清楚。随着项目进入更偏游戏化和沉浸式的阶段仅仅依靠 UI 面板已经不太能表现“双方辩手正在同场对抗”的感觉。因此新增并逐步替换为DebatePlace目标是把原有辩论逻辑保留下来同时升级成一个有座位、有角色、有镜头、有字幕、有历史记录和争议点的三维辩论场景。这次开发的核心不是重写辩论系统而是把原来的Debate页面升级成更有现场感的舞台版本。二、原 Debate 页面与 DebatePlace 的主要区别2.1 原 Debate 页面流程和数据优先原本的Debate页面更偏向传统 UI 页面重点是让玩家能看到当前阶段、发言文本、争议点和历史信息。它的优势是结构清晰、信息集中也便于接入后端、存档、复盘和裁判结果。可以把原页面理解为“辩论流程控制台”它更关注辩论本身是否能够按阶段推进数据是否能够被记录赛后是否能够复用。2.2 DebatePlace在原流程上加入三维舞台DebatePlace则在这个基础上加入了三维空间表达。场景中不再只是文本和按钮而是有两队辩手坐在舞台两侧。当前发言者会被高亮底部字幕会以文字游戏式的打字机效果显示发言内容争议点会作为场景元素存在历史记录也保留在 UI 中。DebatePlace并没有抛弃原来的系统而是继续复用DebatePhaseManager管理阶段DebateTurnManager管理谁可以发言DialogManager管理对话历史ControversyPointManager管理争议点DebateRecordManager保留存档和回放能力换句话说原Debate页面像是“后台逻辑和 UI 原型”DebatePlace则是“把这些逻辑搬进三维赛场后的正式表现版本”。三、DebatePlace 的整体结构DebatePlace的主控制脚本是Assets/Scripts/Controllers/TestDebateStageController.cs虽然名字里还保留了Test但它现在承担的是DebatePlace场景的主流程控制职责。它主要负责以下几件事绑定场景中的八个辩手位置根据玩家选择的辩位决定哪个角色使用 Player prefab给正方机器人生成 BotB prefab给反方机器人生成 BotA prefab维护当前发言者高亮控制字幕打字机播放控制玩家输入按钮是否可用切换阶段并在完成后进入Jugement页面加载争议点接入历史记录和回放模式场景中每个辩手仍然以character_0到character_7作为逻辑锚点character_0 正方1辩 character_1 正方2辩 character_2 正方3辩 character_3 正方4辩 character_4 反方1辩 character_5 反方2辩 character_6 反方3辩 character_7 反方4辩真正的三维人物模型并不是替代这些锚点而是生成到锚点下面的VisualModel子物体中。这样做的好处是逻辑位置和视觉模型分离辩论流程仍然找character_x视觉表现则可以替换 prefab。四、BotA、BotB 和 Player Prefab 的接入这次DebatePlace的一个重要变化是不再使用简单胶囊体表示辩手而是接入了三个角色 prefabAssets/Prefabs/Player.prefab Assets/Prefabs/BotA.prefab Assets/Prefabs/BotB.prefab当前规则是玩家选择的辩位使用Player.prefab正方机器人使用BotB.prefab反方机器人使用BotA.prefab生成逻辑集中在SpawnSpeakerVisualPrefabs()中。它会遍历character_0到character_7为每个位置找到对应 prefab然后实例化到该位置的VisualModel节点下。角色生成后的本地参数目前是localPosition (0, -0.83, 0) 正方 BotB localRotation.y 55 反方 BotA localRotation.y -55这里的-0.83是为了让模型落到合适的地面高度。之前测试中角色会浮起或插进地面因此最后通过统一的 local offset 来修正视觉模型和座位锚点之间的差异。正反方的 Y 旋转也经过了多次调试。最开始正方和反方分别使用70和-70后来逐步调整到55和-55让双方角色朝向更自然不会显得过度侧身。五、动画控制谁发言谁站起来说话三个 prefab 都挂载了DebateCharacterAnimationController这个脚本的职责很明确监听DebateTurnManager.OnTurnChanged判断自己对应的characterId是否正在发言。当角色开始发言时触发 StandUp当角色结束发言时触发 SitDown脚本会自动从父级层级中推断characterId。比如模型生成在character_4_反方1辩 └── VisualModel └── BotA那么动画脚本会向上查找并识别出自己属于character_4。这样每个 prefab 不需要手动绑定不同的角色 ID复制和生成时更稳定。动画状态机最终统一为简洁结构sitting --StandUp-- Talking Talking --SitDown-- sitting这样 BotA、BotB 和 Player 的动画逻辑保持一致避免某个角色 prefab 因为状态机结构不同而出现瞬移、卡状态或回到 Entry 的问题。六、发言节奏与打字机字幕DebatePlace中的字幕不是一次性显示而是复用了TypewriterEffect做成类似文字小说游戏的底部字幕效果。这里有一个关键调整不能让回合结束太快。因为角色的坐下动画是由CompleteCurrentTurn()触发的如果字幕刚显示完甚至还没显示完就结束回合角色动画就会被提前打断。现在发言流程被调整为TryBeginSpeak 等待 1 秒 显示字幕并启动打字机 等待打字机播放完成 再等待 1 秒 CompleteCurrentTurn这两个 1 秒分别对应发言前留给角色站起来发言后留给角色播放坐下前的收尾这样视觉表现和逻辑推进就对齐了玩家看到的是角色先进入发言状态再出现文字文字结束后角色还有一点停顿然后才完成这一轮。七、固定镜头与当前发言者高亮一开始尝试过“谁说话镜头就切到谁面前”的方案但在实际场景中容易出现镜头位置不稳定、运行结果不一致、角色朝向和机位难以统一的问题。因此最后改为固定摄像机fixedCameraPosition (0, 3.35, -6.7) fixedCameraLookTarget (0, 1.05, 0.15)镜头固定后观众视角更像坐在辩论赛现场正前方观看两队。当前发言者不再依靠镜头切换来强调而是通过名字和角色标签高亮显示。当前发言者高亮包括发言者名字变色发言者标签加粗正方使用偏蓝色高亮反方使用偏红色高亮发言者锚点会轻微放大这种方式比频繁切镜头更稳定也更适合当前这个 8 人同场的舞台布局。八、阶段流程保留原逻辑但显示更清楚DebatePlace继续使用DebatePhaseManager和DebateTurnManager推进阶段和发言人。当前阶段包括Opening 立论 CrossExam 质询 Rebuttal 驳论 FreeDebate 自由辩论 Closing 总结 Finished 完成每个阶段的发言顺序由DebateTurnManager管理立论正方1辩 - 反方1辩 质询正方2辩 - 反方2辩 驳论正方3辩 - 反方3辩 自由辩论双方全员可发言不能连续由同一人发言 总结反方4辩 - 正方4辩这里特别修正过总结阶段。按照常见流程最后总结应由双方四辩承担并且顺序是反方四辩先总结正方四辩后总结。因此当前 Closing 阶段对应character_7 反方4辩 character_3 正方4辩当所有阶段完成后下一阶段按钮会变成进入评审并跳转到Jugement这让DebatePlace不只是一个展示页而是能接上完整的赛后评审流程。九、争议点和历史记录的保留在替换原Debate页面时一个重要原则是不能丢掉原页面已经做好的数据能力。因此DebatePlace保留了争议点和历史记录。9.1 争议点ControversyPointManager会从当前选择的题目中读取争议点。如果题目里没有争议点则尝试从当前辩论记录里读取如果仍然没有则使用默认争议点。争议点数据来源优先级是GameManager.SelectedTopic.controversyPoints DebateRecordManager.CurrentRecord.controversyPoints 默认测试争议点这样不管是正常比赛、回放还是临时测试都能保证场景里有争议点可展示。9.2 历史记录每次字幕正式开始播放时DebatePlace会调用AddHistoryEntry()写入一条对话记录。记录内容包括speakerIdspeakerNamedialogTextphasesequence这些数据会进入DialogManager并进一步被DebateRecordManager用于存档。也就是说历史记录本身仍然保留在原系统中回放模式也能读取历史记录并恢复最新发言。后续调整中普通比赛界面里的历史记录不再作为一个很小的固定 UI 面板挤在主画面上。因为辩论文本一旦接入 AI 后单轮发言长度明显增加原来的小面板很容易出现阅读困难。现在历史记录更适合放到BattleStatusWorldScreen的详情视图中玩家点击场景中的战况大屏后镜头会切到大屏前历史对话、争议点状态和当前对局概况集中展示。这样处理以后主舞台负责“看比赛”大屏详情负责“查资料”信息层级更清楚。9.3 争议点归属的真实数据边界争议点系统在后端联调后变成了一个需要特别区分“真实数据”和“前端表现”的模块。当前真正来自后端的数据包括争议点编号和标题当前占优方currentLeaderSide正反方相关依据 notes最近更新时间lastUpdatedTurnIndex后端总结summary也就是说争议点“归谁”和“为什么归属”应当以后端返回为准而不是由前端根据当前发言人直接决定。前端只负责把这些状态表现出来例如切换争议点模型颜色、更新 tooltip、在详情页中展示归属和依据。开发过程中曾经尝试在BattleStatusWorldScreen中用雷达图表现争议点局势但后端并没有返回每个争议点的百分比或权重字段。前端如果强行把正方占优映射成8、反方占优映射成2、未决映射成5看起来像有精确数据实际只是本地推算。因此后来删掉了这张图只保留后端真实返回的归属摘要。这个取舍很重要宁可少展示一点也不要展示看起来精确但实际没有数据支撑的图。十、回放与存档能力没有被破坏DebatePlace支持回放模式判断入口来自ReplayContextManager.IsReplayMode在回放模式下玩家输入被禁用下一阶段按钮被禁用不再自动推进实时发言从DialogManager的历史记录中读取最新发言根据历史记录恢复当前阶段、当前发言者和字幕同时ReplayContextManager已经将DebatePlace视为可回放场景。这意味着从历史详情页进入回放时可以直接进入新的三维舞台而不是回到旧的Debate页面。这也是本次替换中最重要的点视觉层升级了但存档、历史详情和回放链路仍然接得上。十一、后端联调从本地演示到真实比赛流程DebatePlace最初可以依靠本地阶段和 sample 发言完成演示但真正接入比赛流程后核心变化是推进回合不再只是前端切阶段而是要通过后端接口驱动。当前比赛推进主要依赖DebateBackendSessionManager ApiClient GameManager.CurrentMatchDetail典型流程是点击下一阶段 / 推进 POST /api/matches/{matchId}/advance 后端生成或推进当前步骤 前端刷新 match detail 前端拉取 turns 前端拉取 controversies 显示新增发言 更新争议点状态这里的关键点是前端不能只看按钮是否点了也不能只看本地DebatePhaseManager是否进入下一阶段。真正可靠的状态应该以后端返回的currentStep、currentPhase、currentSpeakerRole、userActionRequired和canAdvance为准。为了调试这个链路前端增加了比较详细的日志[ApiClient] POST ... [DebateBackendSessionManager] Raw turns JSON ... [DebateBackendSessionManager] Raw controversies JSON ... [ControversyPointManager] Applying backend controversies ...这些日志在联调时非常关键。比如“前端没有显示 AI 回复”可能有三种原因后端没有返回 turn前端拿到了 turn但没有进入 DisplayNewTurnsDisplayNewTurns 显示了文本但没有写入历史记录或没有触发角色动画只有把请求、返回、解析、显示几个环节都打出来才能判断到底是哪一层断了。十二、AI 发言、角色动画和字幕的同步接入后端 AI 后角色动画出现过一个典型问题本地测试发言时角色会站起来但后端返回发言时角色没有动作。问题本质是“本地按钮流程”和“后端返回流程”不是同一个入口。最终的处理原则是不管发言来自玩家、本地测试还是后端 AI只要开始把某个角色的文本显示到字幕框就应该同时通知对应角色进入发言动画。动画触发时只做一件事收到发言并开始显示文字 - 对应角色 StandUp trigger 当前对话点击结束 / 播放完毕 - 对应角色 SitDown trigger这里特别避免了同时乱动多个 trigger。之前如果误触发SitDown角色就会一直坐着看起来像 AI 发言没有动作。后续调试时明确要求收到后端 JSON 后在显示对应角色发言时只把对应角色的StandUptrigger 设为 true对话结束后再让该角色坐下。字幕系统也做了几项适配长文本会分页显示避免溢出底部对话框字体和对话框尺寸做了调整让单页能容纳更多内容增加“快进”只加速打字机速度不跳过剧情增加“跳过当前页”可以立刻显示当前页全部文字增加“隐藏/显示对话框”方便观察舞台这些按钮的定位更接近 galgame 的阅读体验而不是普通表单按钮。它们解决的是 AI 文本变长之后的可读性问题。十三、战况大屏把信息面板从 UI 搬进场景DebatePlace中新增了一个重要场景物体BattleStatusWorldScreen它不是普通 HUD而是场景里的世界空间大屏。这样做的原因是原本把历史对话、争议点、当前阶段、双方状态全部塞进主 UI会让画面过于拥挤而辩论舞台本身又需要保留角色、桌子、争议点模型和字幕空间。因此大屏承担了“对局资料中心”的职责默认状态显示当前回合、阶段、争议点归属摘要鼠标悬停时有高亮提示点击后镜头平滑拉近到大屏详情视图展示历史对话、战况概览和争议点状态右键或 Esc 返回舞台视角这个设计让玩家可以在两个视角之间切换舞台视角看角色发言和争议点变化 大屏视角查看完整历史和结构化信息开发中还修正过两个维护性问题。第一大屏布局不能只在运行时生成否则编辑器里看不到实际结构后续很难维护。因此关键布局尽量落在 prefab 或编辑器可见结构中。第二镜头拉近时不能瞬间跳转也不能出现抖动最终通过固定目标点和过渡移动让点击大屏更像进入一个检查视角而不是突然换场景。战况大屏里曾经放过争议点雷达图但因为它不是后端真实权重数据最终被删除改为文字摘要。这也让大屏的信息可信度更高。十四、裁判页和复盘页的加载体验完成比赛后流程会进入Jugement和Review。这两个页面和普通静态 UI 不同它们需要等待后端生成评分或复盘数据。如果进入场景后先显示一大片空白再过一会儿突然填充内容观感会比较差也容易让玩家误以为页面坏了。因此后续补了加载状态进入页面 显示加载提示 请求 score / reviews / controversies / turns 后端返回后填充 UI 隐藏加载提示裁判页重点展示逻辑分攻防分表达分总分胜负方裁判评语关键回合复盘页重点展示关键回合争议点归属个人建议优点和缺点全场总结这里也做过一个重要判断评分和复盘应当尽量来自 AI 根据整场对话生成而不是前端固定文案。为了方便测试流程可以临时让辩论阶段返回固定发言但评分和复盘仍然保留 AI 生成这样既能降低测试耗时又不会让赛后结果失去意义。十五、开发过程中解决的几个关键问题15.1 运行时生成和手动复制之间的冲突早期为了快速搭建 UI 和滚动区域曾经使用运行时生成对象再由编辑器复制到场景中。这样做适合快速试错但如果不清理运行时生成逻辑就会导致每次运行结构不一致。后续处理方式是玩家已经复制进场景的内容保留控制脚本只保留真正需要运行时变化的部分比如角色 prefab 的实例化。15.2 Mesh 缺失导致场景变粉或模型异常复制基础几何体时有些对象的 MeshFilter 会丢失sharedMesh。DebatePlace中加入了FixCopiedPrimitiveMeshes()在运行时检查缺失 mesh 的对象并根据名称补回 cube、capsule 或 sphere。这样可以避免舞台地板、桌子、角色占位体因为 mesh 丢失而显示异常。15.3 BotA 动画瞬移BotA 曾经出现动画瞬移问题主要原因包括状态机结构不统一、过渡状态接到 Exit、以及动画片段中带有 Root Motion 位移。最终通过统一 Animator 结构、去掉多余 trigger、锁定 Root Motion 位移来解决。现在 BotA、BotB 和 Player 使用同样的发言动画控制方式稳定性更高。15.4 镜头每次运行结果不一致逐个角色绑定镜头点位虽然灵活但调试成本较高而且容易受到运行时结构变化影响。最后改为固定镜头加发言者高亮让舞台表现更可控。十六、当前效果总结完成后的DebatePlace已经从一个测试场景变成了可接入主流程的三维辩论页面两边各四名辩手坐在场景中玩家位置使用 Player prefab正方机器人使用 BotB prefab反方机器人使用 BotA prefab当前发言者会高亮发言时角色触发站起/说话/坐下动画字幕使用打字机效果阶段流程清晰显示玩家只在轮到自己时可以输入发言争议点继续沿用原系统历史记录继续沿用原系统战况大屏集中展示历史对话和争议点状态争议点归属以后端返回为准回放和存档能力保留裁判页和复盘页支持等待后端返回时的加载状态完成后能够进入Jugement裁判页面这次开发的本质是把原本偏 UI 的Debate页面升级为一个更具游戏表现力的DebatePlace场景。它没有推翻原来的辩论流程而是把原来的数据链路、阶段管理、历史记录和争议点系统包进了一个更直观的三维舞台里。后续如果继续扩展可以重点考虑三件事为不同阶段增加更细的角色动作例如质询时身体前倾、总结时更正式的站姿。让争议点和发言内容产生更明确的关联例如点击某条历史记录时高亮相关争议点。让后端返回更细的争议点权重或证据结构前端再决定是否恢复更可信的数据可视化。继续优化 AI 返回速度减少测试评审、复盘和完整比赛流程时的等待时间。