<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>游戏开发 on 热尘的博客</title><link>https://rechenz.github.io/tags/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/</link><description>Recent content in 游戏开发 on 热尘的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Mon, 31 Aug 2026 13:39:38 +0800</lastBuildDate><atom:link href="https://rechenz.github.io/tags/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/index.xml" rel="self" type="application/rss+xml"/><item><title>抽卡系统拆解</title><link>https://rechenz.github.io/post/%E6%8A%BD%E5%8D%A1%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3/</link><pubDate>Mon, 31 Aug 2026 13:39:38 +0800</pubDate><guid>https://rechenz.github.io/post/%E6%8A%BD%E5%8D%A1%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; 以原神作为参考&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="系统设计目的"&gt;系统设计目的
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;抽卡系统旨在提供玩家良好的抽卡体验，无论是交互体验还是实际数据体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;抽卡系统在大多数情况下与充值等服务强结合，所以需要严格保证安全性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="系统架构设计"&gt;系统架构设计
&lt;/h2&gt;&lt;p&gt;为了保证安全性，抽卡系统的所有逻辑均应该放在服务端，只给客户端暴露最基本的调用接口。&lt;/p&gt;
&lt;h3 id="服务端设计"&gt;服务端设计
&lt;/h3&gt;&lt;p&gt;首先，原神作为在线的游戏，我们需要在服务端对玩家数据进行存储，那么为了与抽卡系统进行搭配，我们需要在每个用户的数据内存下可能会有用处的数据，比如限定池的抽数、常驻池的抽数、保底情况等。&lt;/p&gt;
&lt;p&gt;那么我们应该给客户端暴露什么接口呢，不考虑其他乱七八糟的功能的话，我认为主要的有且只有一个，就是进行一次单抽的接口，然后返回抽卡结果或者是资源不够的回退。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;PS：&lt;/strong&gt; 那么这里考虑到可能的实现，我们知道CE（cheat Engine）可以去通过注入实现数值更改，那么这里我们就可以做一个检测，如果客户端的资源审批通过了但是服务端没有，那么我们便可以初步检测作弊。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;然后是服务端逻辑架构，我们希望这是一个独立的系统，并且要与别的系统进行解耦，那么首先我们按照用户ID进行基础数据的fetch，然后进行更改回调，这个主要使用了用户数据存储系统暴露的接口。&lt;/p&gt;
&lt;p&gt;然后考虑卡池更换，在每一次热更新或者重启的时候从json里读取各种up或者卡池内容，这些不会涉及到用户差异，所以不用过多考虑。&lt;/p&gt;
&lt;h3 id="客户端设计"&gt;客户端设计
&lt;/h3&gt;&lt;p&gt;我们在客户端主要应该考虑的是时序、演出、流畅的玩家体验等。&lt;/p&gt;
&lt;p&gt;考虑用户点击抽卡按钮后的流程。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端进行资源检测，资源不够直接回调。&lt;/li&gt;
&lt;li&gt;如果检测成功，那么发送请求给服务端。&lt;/li&gt;
&lt;li&gt;服务端返回抽卡结果并且触发客户端数据更新（这样我们可以把结果与资源数据全部放在服务端更改，客户端只有一次原子操作，防作弊）&lt;/li&gt;
&lt;li&gt;然后客户端按照服务端返回的结果加载所需的动画和素材资源（这里不需要异步操作，因为本来就是在等待进程）。&lt;/li&gt;
&lt;li&gt;完成后播放抽卡动画，触发物品入库事件或动画。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="算法逻辑设计"&gt;算法逻辑设计
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; 这里只考虑服务端流程&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;首先，我们知道每一个卡池在启动的时候已经实现了池子更新，那么我们只考虑随机算法实现即可。&lt;/p&gt;
&lt;p&gt;为了保证稳定随机，我们采用分层随机的方式。&lt;/p&gt;
&lt;p&gt;比如，数值策划给我们的数据是10%的四星概率，那么我们首先rand这一发是否是四星，然后我们再去rand实际的抽卡结果，这个分层随机保证了数值稳定。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;向着星辰与深渊&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>Unity转UE笔记 长久更新</title><link>https://rechenz.github.io/post/unity%E8%BD%ACue%E7%AC%94%E8%AE%B0-%E9%95%BF%E4%B9%85%E6%9B%B4%E6%96%B0/</link><pubDate>Sat, 08 Aug 2026 14:40:15 +0800</pubDate><guid>https://rechenz.github.io/post/unity%E8%BD%ACue%E7%AC%94%E8%AE%B0-%E9%95%BF%E4%B9%85%E6%9B%B4%E6%96%B0/</guid><description>&lt;h2 id="环境配置"&gt;环境配置
&lt;/h2&gt;&lt;p&gt;显然，作为vscode重度用户以及半个完美主义者，我完全忍受不了VS的低扩展性以及丑的要死的界面还有它的重量，但是vscode的C++扩展对于UE的支持就是一坨，C++扩展的intelligense会把整个UE库全部塞进去，先不说内存占用，就是内联建议都没我的copilot快，这纯扯淡，所以我们需要去自己寻找更优秀的方案。&lt;/p&gt;
&lt;p&gt;建议参考我的上一篇文章 &lt;a class="link" href="https://rechenz.github.io/post/%E4%BD%BF%E7%94%A8vscode%E8%BF%9B%E8%A1%8Cue%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/" target="_blank" rel="noopener"
 &gt;vscode进行UE开发配置指南&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="代码踩坑长期更新"&gt;代码踩坑(长期更新)
&lt;/h2&gt;&lt;p&gt;你或许可以在我的&lt;a class="link" href="https://github.com/rechenz/IWannaBecomeUEMaster" target="_blank" rel="noopener"
 &gt;UE学习库&lt;/a&gt;里找到一些代码示例。&lt;/p&gt;
&lt;h3 id="头文件顺序"&gt;头文件顺序
&lt;/h3&gt;&lt;p&gt;UE库在底层使用了超级多诡异的宏定义，并且在编译的时候要一层一层去展开，那么这就导致了C++没有办法去自己找到一个合理的展开顺序，所以我们需要自己对头文件进行排序，当然其实也没有那么麻烦，我们只需要保证“generate.h”头文件要在最下面就好了。&lt;/p&gt;
&lt;p&gt;当然我们要注意一点，如果你使用了vscode的自动格式化，那么一定要去改一下规则，因为有些情况格式化会把你的头文件提前，这个上面的配置教程里有写。&lt;/p&gt;
&lt;h3 id="类引用"&gt;类引用
&lt;/h3&gt;&lt;p&gt;在Unity的C#环境中，C#集成了一个非常优秀的定义池，不仅保证了索引速度，还有实时热更新。&lt;/p&gt;
&lt;p&gt;但是UE的C++环境中完全没有，我们甚至不能直接去在一个C++类的头文件里包含另一个C++类的头文件，这和头文件顺序是同一个错误类型，这会导致我们的宏定义展开错乱。&lt;/p&gt;
&lt;p&gt;所以我们的正确实现变为了先在头文件里进行要引用的类声明，然后再在.cpp文件里引入完成的头文件。&lt;/p&gt;
&lt;p&gt;下面是代码示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#pragma once
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;GameFramework/Actor.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;Actor2.generated.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AActor1&lt;/span&gt;;&lt;span style="color:#75715e"&gt;//提前定义需要使用的类
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;UCLASS()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IWANNABECOMEUEMASTER_API&lt;/span&gt; AActor2 : &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; AActor
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	GENERATED_BODY()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	AActor2();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	UPROPERTY(EditAnywhere, Category &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Event Binding&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	AActor1&lt;span style="color:#f92672"&gt;*&lt;/span&gt; BoundActor;&lt;span style="color:#75715e"&gt;//定义类的实例
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;level1/Actor2.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;level1/Actor1.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;//在使用的时候引入完整的头文件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AActor2&lt;span style="color:#f92672"&gt;::&lt;/span&gt;AActor2()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	PrimaryActorTick.bCanEverTick &lt;span style="color:#f92672"&gt;=&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AActor2&lt;span style="color:#f92672"&gt;::&lt;/span&gt;BeginPlay()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Super&lt;span style="color:#f92672"&gt;::&lt;/span&gt;BeginPlay();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	UE_LOG(LogTemp, Warning, TEXT(&lt;span style="color:#e6db74"&gt;&amp;#34;Actor2 BeginPlay() called!&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>使用vscode进行UE开发指南</title><link>https://rechenz.github.io/post/%E4%BD%BF%E7%94%A8vscode%E8%BF%9B%E8%A1%8Cue%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</link><pubDate>Sat, 08 Aug 2026 12:30:00 +0800</pubDate><guid>https://rechenz.github.io/post/%E4%BD%BF%E7%94%A8vscode%E8%BF%9B%E8%A1%8Cue%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</guid><description>&lt;blockquote&gt;
 &lt;p&gt;适用：UE 5.x 项目 + VS Code 开发，用 clangd 做代码索引/补全/报错。
本文是完整配置教学 + 实战踩坑记录，按顺序做就能跑通。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="为什么要用-vs-code--clangd"&gt;为什么要用 VS Code + clangd
&lt;/h2&gt;&lt;p&gt;UE 官方标配是 Visual Studio，但用 VS Code 开发完全可行，而且更轻。核心组件是 &lt;strong&gt;clangd&lt;/strong&gt; —— 一个基于 LLVM 的 C++ 语言服务器，负责补全、跳转、报错、重构提示。&lt;/p&gt;
&lt;p&gt;clangd 有个铁律：&lt;strong&gt;不自己猜编译参数，全靠 &lt;code&gt;compile_commands.json&lt;/code&gt; 喂&lt;/strong&gt;。UE 的 UnrealBuildTool 可以一键生成这份文件，于是整条链路就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;UE 的 UnrealBuildTool
 │ -mode=GenerateClangDatabase
 ▼
compile_commands.json ← 每个 .cpp 的完整编译命令（include 路径、宏定义、标准库）
 │
 ▼
clangd 读取 → 索引 → 补全 / 跳转 / 报错 / inlay hints
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以配置的关键就两件事：&lt;strong&gt;让 compile_commands.json 正确生成&lt;/strong&gt;、&lt;strong&gt;让 clangd 正确读到它&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="环境要求"&gt;环境要求
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;UE 5.x（本文基于 5.8）&lt;/li&gt;
&lt;li&gt;VS Code&lt;/li&gt;
&lt;li&gt;VS Code 扩展：&lt;code&gt;clangd&lt;/code&gt;（Microsoft 官方）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="第一步安装扩展"&gt;第一步：安装扩展
&lt;/h2&gt;&lt;p&gt;安装 clangd 扩展后，把 C/C++ 扩展的 IntelliSense 关掉，两个会打架（重复报错、吃性能）：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.vscode/settings.json&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;C_Cpp.intelliSenseEngine&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;disabled&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="第二步配置-tasksjson重点"&gt;第二步：配置 tasks.json（重点！）
&lt;/h2&gt;&lt;p&gt;任务用来跑 UBT。&lt;strong&gt;这里有一个大坑&lt;/strong&gt;（见踩坑 1），先直接抄正确配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0.0&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;tasks&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;label&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Build Editor (Development)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;process&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;cmd.exe&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/c&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;E:/Epicgames/games/UE_5.8/Engine/Build/BatchFiles/Build.bat&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;YourProjectEditor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Win64&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Development&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-Project=${workspaceFolder}/YourProject.uproject&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-WaitMutex&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;group&amp;#34;&lt;/span&gt;: { &lt;span style="color:#f92672"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;build&amp;#34;&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;#34;isDefault&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;problemMatcher&amp;#34;&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;$msCompile&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;label&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Generate Clang Database&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;process&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;E:/Epicgames/games/UE_5.8/Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.exe&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-mode=GenerateClangDatabase&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-project=${workspaceFolder}/YourProject.uproject&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;YourProjectEditor&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Win64&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Development&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-OutputDir=${workspaceFolder}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;problemMatcher&amp;#34;&lt;/span&gt;: []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只需改三处：引擎路径、项目名（&lt;code&gt;YourProject&lt;/code&gt;）、&lt;code&gt;.uproject&lt;/code&gt; 路径。&lt;code&gt;${workspaceFolder}&lt;/code&gt; 会自动展开，不用动。&lt;/p&gt;
&lt;h2 id="第三步生成编译数据库"&gt;第三步：生成编译数据库
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Ctrl+Shift+P&lt;/code&gt; → &lt;code&gt;Tasks: Run Task&lt;/code&gt; → &lt;strong&gt;Generate Clang Database&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;跑完项目根目录出现 &lt;code&gt;compile_commands.json&lt;/code&gt; 就成功。首次大概 1 分钟（要跑 UHT）。&lt;/p&gt;
&lt;h2 id="第四步配置-settingsjson"&gt;第四步：配置 settings.json
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;C_Cpp.intelliSenseEngine&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;disabled&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;editor.inlayHints.enabled&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;clangd.arguments&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--compile-commands-dir=${workspaceFolder}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--background-index&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-j=6&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--header-insertion=never&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--compile-commands-dir&lt;/code&gt;：指向 compile_commands.json 所在目录（项目根），这是 clangd 找到编译数据库的钥匙&lt;/li&gt;
&lt;li&gt;&lt;code&gt;editor.inlayHints.enabled&lt;/code&gt;：开启 &lt;strong&gt;inlay hints&lt;/strong&gt;（内联提示）——函数调用边上显示参数名、&lt;code&gt;auto&lt;/code&gt; 显示推断类型的灰字。clangd 默认会发，但 VS Code 默认不显示，必须手动开&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;改完配置后：&lt;code&gt;Ctrl+Shift+P&lt;/code&gt; → &lt;code&gt;clangd: Restart language server&lt;/code&gt; 生效。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;⚠️ 注意：&lt;strong&gt;不要&lt;/strong&gt;往 &lt;code&gt;clangd.arguments&lt;/code&gt; 里加 &lt;code&gt;--inlay-hints&lt;/code&gt; 之类的参数——clangd 22 不认这个命令行参数，会让 clangd 直接启动失败。inlay hints 的开关就是 &lt;code&gt;editor.inlayHints.enabled&lt;/code&gt; 这一个设置，clangd 会通过 LSP 自动发送。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="日常使用规则就一条"&gt;日常使用规则（就一条）
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;你做了什么&lt;/th&gt;
					&lt;th&gt;要不要刷&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;改 .cpp / .h 内容&lt;/td&gt;
					&lt;td&gt;❌ clangd 自动重新解析&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;新增 .cpp / .h 文件&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;✅ 跑一次 Generate Clang Database&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;改 Build.cs / 模块依赖&lt;/td&gt;
					&lt;td&gt;✅ 跑一次&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;UE 编辑器里的 &lt;code&gt;Tools → Generate Visual Studio project files&lt;/code&gt; &lt;strong&gt;不用点&lt;/strong&gt;——那是给 Visual Studio 生成 .sln 的，跟 clangd 无关。放着别删，以后万一要用 VS 调试还有用。&lt;/p&gt;
&lt;h2 id="踩坑记录全是血泪"&gt;踩坑记录（全是血泪）
&lt;/h2&gt;&lt;h3 id="坑-1任务报-cmd-c--is-not-recognized"&gt;坑 1：任务报 &lt;code&gt;'cmd /c ...' is not recognized&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;amp; &amp;#39;cmd /c E:/.../UnrealBuildTool.exe&amp;#39; is not recognized as a name of a cmdlet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; tasks.json 写成 &lt;code&gt;type: &amp;quot;shell&amp;quot;&lt;/code&gt; + &lt;code&gt;command: &amp;quot;cmd /c xxx&amp;quot;&lt;/code&gt;。VS Code 的默认 shell 是 PowerShell，执行 shell 任务时会生成 &lt;code&gt;&amp;amp; 'cmd /c ...' 参数&lt;/code&gt;，PowerShell 把整串当&lt;strong&gt;命令名&lt;/strong&gt;，自然找不到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt; 任务类型改 &lt;code&gt;&amp;quot;type&amp;quot;: &amp;quot;process&amp;quot;&lt;/code&gt;，绕开 shell 解析，直接 CreateProcess 调 exe。Build.bat 是批处理不能直接被 CreateProcess 调，所以用 &lt;code&gt;cmd.exe&lt;/code&gt; + &lt;code&gt;/c&lt;/code&gt; 显式包一层。&lt;/p&gt;
&lt;h3 id="坑-2clangd-报-xxxh-file-not-found但文件明明存在"&gt;坑 2：clangd 报 &lt;code&gt;'xxx.h' file not found&lt;/code&gt;，但文件明明存在
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt; clangd 报找不到自己的头文件，后面跟一串&amp;quot;连锁反应&amp;quot;报错（未声明标识符之类）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先别信任何人的&amp;quot;根因分析&amp;quot;，自己实测三步：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;① 看 compile_commands.json 里这个文件的记录，确认 rsp 文件存在：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test-Path &lt;span style="color:#e6db74"&gt;&amp;#34;项目/Intermediate/Build/Win64/x64/UnrealEditor/Development/模块名/Actor1.cpp.obj.rsp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;② 用 clang-cl 按 rsp 实测语法检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 关键：工作目录必须切到 compile_commands.json 里的 directory（Engine/Source）！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# rsp 里全是相对路径，目录不对引擎头文件全找不到，会误导排查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd E:/Epicgames/games/UE_5.&lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;/Engine/Source
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clang-cl -fsyntax-only &lt;span style="color:#e6db74"&gt;&amp;#34;@...Actor1.cpp.obj.rsp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;③ 直接调 clangd 复现（最接近 VS Code 里的真相）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clangd --compile-commands-dir=F:/project/YourProject --check=F:/project/YourProject/Source/.../Actor1.cpp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 看最后一行：All checks completed, N errors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;真实案例：&lt;/strong&gt; 曾有人（AI）分析说&amp;quot;clangd 解析不了嵌套 rsp 所以丢了 include 路径&amp;quot;，建议往 &lt;code&gt;.clangd&lt;/code&gt; 里硬塞 &lt;code&gt;-I&lt;/code&gt;。实测 clangd 22 &lt;strong&gt;完美展开嵌套 rsp&lt;/strong&gt;，Public 路径全在，头文件根本没丢。真正报错是另外两个，见坑 3、坑 4。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;教训：rsp 嵌套、路径解析这种问题，别猜，跑一遍就知道。往 &lt;code&gt;.clangd&lt;/code&gt; 加 &lt;code&gt;-I&lt;/code&gt; 是给健康代码贴创可贴。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="坑-3member-access-into-incomplete-type-class-ftimermanager"&gt;坑 3：&lt;code&gt;member access into incomplete type 'class FTimerManager'&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt; &lt;code&gt;GetWorldTimerManager().SetTimer(...)&lt;/code&gt; 报 FTimerManager 不完整类型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; 头文件（Actor.h）对 FTimerManager 只有前向声明，调用它的方法需要完整定义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt; cpp 里加：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;TimerManager.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是 UE 官方标准用法，所有用 SetTimer 的 Actor 都该带。&lt;/p&gt;
&lt;h3 id="坑-4声明了函数却没实现--链接期-lnk2019"&gt;坑 4：声明了函数却没实现 → 链接期 LNK2019
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt; clangd 不报（语法没错），但编译链接时炸。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; 头文件里 &lt;code&gt;UFUNCTION() void TriggerEvent();&lt;/code&gt; 声明了，UHT 生成的 &lt;code&gt;.gen.cpp&lt;/code&gt; 会引用它，cpp 里却没有定义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt; 补实现，别留空壳：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AActor1&lt;span style="color:#f92672"&gt;::&lt;/span&gt;TriggerEvent()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UE_LOG(LogTemp, Warning, TEXT(&lt;span style="color:#e6db74"&gt;&amp;#34;Actor1 TriggerEvent() called!&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OnSomethingHappened.Broadcast();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="坑-5clangd-启动失败-unknown-command-line-argument"&gt;坑 5：clangd 启动失败 &lt;code&gt;Unknown command line argument&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;clangd.exe: Unknown command line argument &amp;#39;&amp;#34;--inlay-hints=parameters+types&amp;#34;&amp;#39;
Server crashed 5 times... The server will not be restarted
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; &lt;code&gt;clangd.arguments&lt;/code&gt; 里加了 clangd 22 不认的参数（比如 &lt;code&gt;--inlay-hints=...&lt;/code&gt;），clangd 直接拒绝启动。更阴险的是：&lt;strong&gt;VS Code 里用户级设置和工作区设置的 &lt;code&gt;clangd.arguments&lt;/code&gt; 数组是覆盖关系，不是合并&lt;/strong&gt;——用户设置里残留一个残缺的数组，会把工作区里 &lt;code&gt;--compile-commands-dir&lt;/code&gt; 等正确参数全部顶掉，导致 clangd 找不到编译数据库、引擎头文件全炸（&lt;code&gt;Stats/Stats.h&lt;/code&gt; file not found、&lt;code&gt;UE_BUILD_DEVELOPMENT&lt;/code&gt; 未定义等连锁报错）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查用户级设置（&lt;code&gt;Ctrl+Shift+P&lt;/code&gt; → Preferences: Open User Settings (JSON)），删掉残留的 &lt;code&gt;clangd.arguments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;inlay hints 只需要 &lt;code&gt;editor.inlayHints.enabled: &amp;quot;on&amp;quot;&lt;/code&gt;，clangd 22 不支持 &lt;code&gt;--inlay-hints&lt;/code&gt; 命令行参数，别加&lt;/li&gt;
&lt;li&gt;重载窗口后 &lt;code&gt;clangd: Restart language server&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;诊断技巧：&lt;/strong&gt; 当出现一坨引擎头文件连锁报错（file not found + 宏未定义）时，先怀疑 clangd 拿到的参数对不对——查看 clangd 输出面板的启动日志，里面会打印完整参数列表。&lt;/p&gt;
&lt;h3 id="坑-6format-on-save-把-include-顺序排乱ue-宏系统连锁爆炸"&gt;坑 6：format on save 把 include 顺序排乱，UE 宏系统连锁爆炸
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt; 代码本来好好的，某次保存后 clangd 突然报一坨引擎头文件错误：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;unknown type name &amp;#39;FID_Engine_Source_Runtime_Engine_Classes_GameFramework_Actor_h_11_PROLOG&amp;#39;
UCLASS() 展开失败 → GENERATED_BODY() 失败 → 引擎 Actor.h 解析失败 → 连锁爆炸
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; VS Code 开启了 &lt;code&gt;editor.formatOnSave&lt;/code&gt;，而 clangd 的格式化器默认 &lt;code&gt;SortIncludes&lt;/code&gt; 会把 include &lt;strong&gt;按字母序重排&lt;/strong&gt;！UE 的 &lt;code&gt;.generated.h&lt;/code&gt; 以 A 开头，被直接拎到 include 列表最前面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UE 铁律：&lt;code&gt;.generated.h&lt;/code&gt; 必须放在 include 列表的最后&lt;/strong&gt;。它会 &lt;code&gt;#define CURRENT_FILE_ID&lt;/code&gt; 指向当前文件，让本文件的 &lt;code&gt;UCLASS()&lt;/code&gt; / &lt;code&gt;GENERATED_BODY()&lt;/code&gt; 展开成正确的宏。顺序一乱：先定义了 Actor1 的 ID，然后 include Actor.h 时引擎的 generated.h 把 &lt;code&gt;CURRENT_FILE_ID&lt;/code&gt; 覆盖成引擎 Actor 的 → &lt;code&gt;UCLASS()&lt;/code&gt; 拼出 &lt;code&gt;FID_Engine_..._Actor_h_11_PROLOG&lt;/code&gt;（不存在的宏）→ unknown type name → 连锁炸穿。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 include 顺序改回：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;CoreMinimal.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;GameFramework/Actor.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;Actor1.generated.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt; &lt;/span&gt;&lt;span style="color:#75715e"&gt;// 永远在最后！
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;
&lt;p&gt;重新跑一次 Generate Clang Database（UHT 会基于当前文件重新生成 &lt;code&gt;.generated.h&lt;/code&gt;，因为格式化也把行号弄错位了，宏名对不上）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;根治：配置 &lt;code&gt;.clang-format&lt;/code&gt;，禁止重排 include&lt;/strong&gt;。推荐放用户级（所有项目生效），&lt;code&gt;C:\Users\&amp;lt;你&amp;gt;\.clang-format&lt;/code&gt;：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;BasedOnStyle&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LLVM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Language&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Cpp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;IndentWidth&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;UseTab&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;TabWidth&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;BreakBeforeBraces&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Allman&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;ColumnLimit&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Standard&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;c++20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;SortIncludes&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Never &lt;/span&gt; &lt;span style="color:#75715e"&gt;# 保命配置，禁止重排 include&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;查找优先级：项目根 &lt;code&gt;.clang-format&lt;/code&gt; &amp;gt; 用户级 &lt;code&gt;~/.clang-format&lt;/code&gt;。某个项目想单独配就放项目根。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;教训：&lt;/strong&gt; UE 项目必须配 &lt;code&gt;.clang-format&lt;/code&gt; 且 &lt;code&gt;SortIncludes: Never&lt;/code&gt;，否则 format on save 会静默破坏 UE 宏系统——报错全在引擎头文件里，根本想不到是自己的 include 顺序被格式化器动了。&lt;/p&gt;
&lt;h3 id="坑-7配了-clang-format-还是被重排凶手是-cc-扩展的-vcformat"&gt;坑 7：配了 &lt;code&gt;.clang-format&lt;/code&gt; 还是被重排？凶手是 C/C++ 扩展的 vcFormat
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象：&lt;/strong&gt; 明明在用户级配了 &lt;code&gt;SortIncludes: Never&lt;/code&gt;，保存文件后 include 还是被打乱（&lt;code&gt;generated.h&lt;/code&gt; 又跑最前面），而且后面几个 include 被 clangd 标灰（unused include 提示，因为符号已被 generated.h 间接引入）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; VS Code 里可能有&lt;strong&gt;两个格式化引擎&lt;/strong&gt;同时在候选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clangd 自带的 formatter（读 &lt;code&gt;.clang-format&lt;/code&gt;，尊重 &lt;code&gt;SortIncludes: Never&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C/C++ 扩展的 &lt;code&gt;vcFormat&lt;/code&gt;&lt;/strong&gt;（不读 &lt;code&gt;.clang-format&lt;/code&gt;，有自己的 include 排序逻辑）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用户设置里 &lt;code&gt;&amp;quot;C_Cpp.formatting&amp;quot;: &amp;quot;vcFormat&amp;quot;&lt;/code&gt; 就是雷——当 clangd 没在跑（崩溃、没重载）或格式化 fallback 时，保存会走 vcFormat，它无视你的 &lt;code&gt;.clang-format&lt;/code&gt; 直接按自己的规则重排 include。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法：&lt;/strong&gt; 用户设置里把 C/C++ 扩展的格式化引擎切换成 clangFormat：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;C_Cpp.formatting&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;clangFormat&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;clangFormat&lt;/code&gt; 模式走 clang-format 逻辑，&lt;strong&gt;会读 &lt;code&gt;.clang-format&lt;/code&gt; 文件&lt;/strong&gt;（包括用户级的），&lt;code&gt;SortIncludes: Never&lt;/code&gt; 才会真正生效。改完重载窗口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;排查技巧：&lt;/strong&gt; 被重排后看代码风格就知道是谁干的——vcFormat 会把大括号改成同行（Allman 变 Attach），clangd 按你的 &lt;code&gt;.clang-format&lt;/code&gt; 来。另外如果 clangd 崩过 5 次（&lt;code&gt;The server will not be restarted&lt;/code&gt;），格式化会静默 fallback 到 cpptools，这是最常见的触发条件。&lt;/p&gt;
&lt;h2 id="验证流程速查"&gt;验证流程速查
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. 生成/刷新编译数据库（新增文件后必跑）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# VS Code: Tasks: Run Task → Generate Clang Database&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. 让 clangd 全量检查单文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clangd --compile-commands-dir=F:/project/YourProject --check=F:/project/YourProject/Source/.../你的文件.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看到 &lt;code&gt;All checks completed, 0 errors&lt;/code&gt; 就是全绿。&lt;/p&gt;
&lt;h2 id="附录环境备忘实测值"&gt;附录：环境备忘（实测值）
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;项&lt;/th&gt;
					&lt;th&gt;值&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;UE 引擎&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;E:/Epicgames/games/UE_5.8&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;clangd&lt;/td&gt;
					&lt;td&gt;22.1.3（VS 18 自带 LLVM，比 UE 首选 20.1.8 新，UBT 有警告但能用）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;compile_commands 的 directory&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;E:/Epicgames/games/UE_5.8/Engine/Source&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;手动验证必切目录&lt;/td&gt;
					&lt;td&gt;就是上面那个，别在项目目录跑&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;最后提示：千万不要在UE里Refresh Vscode Project&lt;/em&gt;&lt;/p&gt;</description></item><item><title>基于DAG的任务管理系统</title><link>https://rechenz.github.io/post/%E5%9F%BA%E4%BA%8Edag%E7%9A%84%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/</link><pubDate>Fri, 26 Jun 2026 21:46:41 +0800</pubDate><guid>https://rechenz.github.io/post/%E5%9F%BA%E4%BA%8Edag%E7%9A%84%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/</guid><description>&lt;p&gt;本文将对一个基于 $\text{DAG}$ 的任务管理系统实现详细逻辑和代码拆解。&lt;/p&gt;
&lt;p&gt;（基于 Unity 实现）&lt;/p&gt;
&lt;h2 id="理论基底讲解"&gt;理论基底讲解
&lt;/h2&gt;&lt;p&gt;我们知道 $\text{DAG}$ 是有向无环图，而在 $\text{DAG}$ 上可以进行拓扑排序，我们发现拓扑排序的本质就是按照节点依赖关系进行排序，所以我们便可以利用这个进行管理任何依存关系。&lt;/p&gt;
&lt;p&gt;是的，任意依存关系，所以这个实现的任务管理系统过于底层，我们完全可以在不进行高级封装的情况下将其使用在任何有依存关系的地方。&lt;/p&gt;
&lt;p&gt;另外，这个任务管理系统因为过于底层，所以几乎没有复杂度浪费，在不进行系统层级上的优化的情况下性能已经非常高，当然更多的还可能取决于具体实现与内存管理，但是这不会有理论复杂度的区别。&lt;/p&gt;
&lt;h2 id="程序结构分析"&gt;程序结构分析
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;classDiagram
 class TaskManager {
 +Instance TaskManager
 -tasks Dictionary~string, TaskNode~
 -activeTasks HashSet~TaskNode~
 -isLoadingTasks bool
 -_initGraphCoroutine Coroutine
 +isGraphInitialized bool
 +mainPlayer GameObject
 +AddTask(taskId, taskNode)
 +GetTask(taskId) TaskNode
 +RegisterActive(node)
 +UnregisterActive(node)
 +SaveAllTaskNodes()
 +LoadAllTaskNodes()
 +SaveTaskNode(ID)
 +LoadTaskNode(ID)
 +ReloadAndRestartTasks()
 +ClearAllTaskNodesForLoad()
 +RebuildGraphFromSave()
 +OnGoalReached(goal)
 +FinishAllActiveTasks()
 -InitTaskGraph() IEnumerator
 -StartAllReadyTasks()
 -DelayedStartReadyTasks() IEnumerator
 +Awake()
 }

 class TaskNode {
 +taskId string
 +taskName string
 +Inn int
 +Out int
 +isTaskFinished bool
 +nextNodes List~TaskNode~
 +nextNodesIds List~string~
 +taskGoals List~TaskGoal~
 +StartTask()
 +CancelTask()
 +RefreshStatus()
 +ResetForLoad()
 }

 class TaskGoal {
 +IsDone bool
 +targetScript
 }

 class GameFlowManager {
 +PlayingData PlayingData
 }

 class PlayingData {
 +TaskNodesDic Dictionary~string, (int, bool)~
 }

 TaskManager --&gt; TaskNode : 管理
 TaskManager --&gt; GameFlowManager : 读写存档
 TaskNode --&gt; TaskGoal : 包含
 GameFlowManager --&gt; PlayingData : 包含&lt;/pre&gt;&lt;h3 id="taskmanager-脚本"&gt;TaskManager 脚本
&lt;/h3&gt;&lt;p&gt;按照 Unity 风格，我们习惯用一个脚本实现为单例，负责注册服务和事件。&lt;/p&gt;
&lt;p&gt;也就是我们的 TaskManager 脚本。&lt;/p&gt;
&lt;p&gt;我们使用这个脚本进行任务节点初始化，节点出入度的统一管理还有一些调试功能的实现。&lt;/p&gt;
&lt;p&gt;这个脚本相当于整个系统的管理器与统一调度器。&lt;/p&gt;
&lt;p&gt;此脚本也实现了存档功能，这重点是与存档系统合理对接然后重新初始化，不在此赘述。&lt;/p&gt;
&lt;p&gt;以下是任务初始化流程图：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant Unity as Unity Engine
 participant TM as TaskManager
 participant Tasks as tasks Dictionary
 participant Archive as GameFlowManager.PlayingData

 Note over Unity: 场景加载
 Unity-&gt;&gt;TM: Awake()
 TM-&gt;&gt;TM: 单例检查 + DontDestroyOnLoad
 TM-&gt;&gt;TM: StartCoroutine(InitTaskGraph())
 
 TM-&gt;&gt;TM: WaitForEndOfFrame x2
 Note over TM: 确保所有 TaskNode 已注册
 
 TM-&gt;&gt;Tasks: 遍历 tasks 字典
 
 loop 每个任务
 TM-&gt;&gt;Tasks: SaveTaskNode(key) ← 初始存档
 TM-&gt;&gt;TM: 构建 nextNodes 连接
 TM-&gt;&gt;Tasks: targetTask.Inn++
 TM-&gt;&gt;Tasks: taskNode.Out++
 end
 
 TM-&gt;&gt;Archive: LoadAllTaskNodes()
 Note over TM,Archive: 恢复存档中的 Inn / isFinished
 
 TM-&gt;&gt;TM: isGraphInitialized = true
 
 TM-&gt;&gt;TM: StartAllReadyTasks()
 Note over TM: 启动 入度≤0 且 未完成 的任务&lt;/pre&gt;&lt;p&gt;（注：这里的 GameFlowManager 是游戏流程管理器，这里主要负责游戏数据的对接）&lt;/p&gt;
&lt;h3 id="tasknode-脚本"&gt;TaskNode 脚本
&lt;/h3&gt;&lt;p&gt;这个脚本实现了 TaskNode 类，作为 $\text{DAG}$ 的节点实例化.&lt;/p&gt;
&lt;p&gt;结构图：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;classDiagram
 class TaskNode {
 +taskName string
 +taskId string
 +nextNodesIds List~string~
 +taskEffects List~TaskEffect~
 +taskEndEffects List~TaskEffect~
 +taskGoals List~TaskGoal~
 +nextNodes List~TaskNode~
 +Out int
 -In int
 +isTaskFinished bool
 -_taskCts CancellationTokenSource
 -isTaskRunning bool
 -isCompleting bool
 +Inn int (property)
 +Awake()
 +OnDestroy()
 +StartTask()
 +CancelTask()
 +ResetForLoad()
 +RefreshStatus()
 -OnTaskSuccess()
 -StartTaskAsync() UniTaskVoid
 +OnDrawGizmos()
 }

 class TaskEffect {
 +ApplyEffect()
 +RevertEffect()
 }

 class TaskGoal {
 +IsDone bool
 }

 class TaskManager {
 +AddTask()
 +SaveTaskNode()
 +RegisterActive()
 +UnregisterActive()
 +IsGraphInitialized bool
 }

 TaskNode --&gt; TaskEffect : 包含
 TaskNode --&gt; TaskGoal : 包含
 TaskNode --&gt; TaskManager : 注册/通知&lt;/pre&gt;&lt;p&gt;我们在这里实现对于任务节点的信息存储（如：出度，入度，任务效果，任务目标等），也实现了任务节点的启动逻辑，结束逻辑。&lt;/p&gt;
&lt;p&gt;为了便于解耦，我们的任务流程使用 Unitask 实现，Unitask 是一个更优秀的异步库，可以便携地对异步函数进行取消，异常处理等操作。&lt;/p&gt;
&lt;p&gt;以下是 TaskNode 对象的生命周期流程图：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; 未注册: 对象实例化
 未注册 --&gt; 已注册: Awake()&lt;br/&gt;TaskManager.AddTask()
 已注册 --&gt; 等待中: InitTaskGraph&lt;br/&gt;入度 &gt; 0
 
 等待中 --&gt; 运行中: Inn--&lt;br/&gt;Inn ≤ 0 &amp;&amp; 未完成
 已注册 --&gt; 运行中: StartAllReadyTasks()&lt;br/&gt;Inn ≤ 0 &amp;&amp; 未完成
 
 运行中 --&gt; 已完成: RefreshStatus()&lt;br/&gt;全部 Goal.IsDone == true
 运行中 --&gt; 已取消: CancelTask()&lt;br/&gt;(读档/场景切换)
 
 已完成 --&gt; [*]: OnTaskSuccess()&lt;br/&gt;保存存档 + 通知后继
 
 已取消 --&gt; 已注册: ResetForLoad()&lt;br/&gt;(读档调用)
 
 state 运行中 {
 [*] --&gt; 应用开始效果: ApplyEffect()
 应用开始效果 --&gt; 轮询检查: RefreshStatus()
 轮询检查 --&gt; 已完成: 全部完成
 轮询检查 --&gt; 继续等待: 未完成
 继续等待 --&gt; 轮询检查: UniTask.Delay(250ms)
 }&lt;/pre&gt;&lt;p&gt;以下是 TaskNode 的运行逻辑流程图：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant Caller as 外部/Inn Setter
 participant TN as TaskNode
 participant TM as TaskManager
 participant Effects as TaskEffects
 participant Goals as TaskGoals

 Caller-&gt;&gt;TN: StartTask()
 TN-&gt;&gt;TN: CancelTask() (清理旧任务)
 TN-&gt;&gt;TN: 创建新 CancellationTokenSource
 TN-&gt;&gt;TN: isTaskRunning = true
 TN-&gt;&gt;TM: RegisterActive(this)

 Note over TN: StartTaskAsync() 开始

 TN-&gt;&gt;Effects: ApplyEffect() (开始效果)

 TN-&gt;&gt;TN: RefreshStatus() (初始检查)
 
 loop 每 250ms
 TN-&gt;&gt;Goals: 遍历检查 IsDone
 alt 全部完成
 TN-&gt;&gt;TN: OnTaskSuccess()
 else 未完成
 TN-&gt;&gt;TN: UniTask.Delay(250ms)
 end
 end

 Note over TN: OnTaskSuccess() 触发

 TN-&gt;&gt;TM: SaveTaskNode(taskId)
 TN-&gt;&gt;Effects: RevertEffect() (还原开始效果)
 TN-&gt;&gt;Effects: ApplyEffect() (应用结束效果)
 
 loop 每个后继节点
 TN-&gt;&gt;TN.nextNodes: node.Inn-- (通知后继)
 end
 
 TN-&gt;&gt;TM: UnregisterActive(this)
 TN-&gt;&gt;TN: CancelTask() (停止异步)
 TN-&gt;&gt;TN: isTaskRunning = false&lt;/pre&gt;&lt;h3 id="taskeffecttaskgoaltaskbasic-脚本"&gt;TaskEffect,TaskGoal,TaskBasic 脚本
&lt;/h3&gt;&lt;p&gt;TaskEffect 和 TaskGoal 脚本都是作为纯粹的数据类存在，为了可以在 Inspector 中序列化方便配置而存在。&lt;/p&gt;
&lt;p&gt;需要注意的是，这两个脚本都强烈跟游戏的数据管理系统结合，所以并无很大的参考价值。&lt;/p&gt;
&lt;p&gt;而 TaskBasic 则是为了自定义 TaskGoal 和 TaskEffect 而存在的基类，TaskBasic 中定义了自定义脚本的基本实现规则和属性，确保自定义脚本实现方便并且使用安全。&lt;/p&gt;
&lt;h2 id="附录"&gt;附录
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://rechenz.github.io/code/TaskBasic.cs" &gt;TaskManager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://rechenz.github.io/code/TaskNode.cs" &gt;TaskNode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://rechenz.github.io/code/TaskEffect.cs" &gt;TaskEffect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://rechenz.github.io/code/TaskGoal.cs" &gt;TaskGoal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://rechenz.github.io/code/TaskBasic.cs" &gt;TaskBasic&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>