<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>BearChild · Blog</title>
  
  
  <link href="https://blog.bearchild.top/atom.xml" rel="self"/>
  
  <link href="https://blog.bearchild.top/"/>
  <updated>2025-06-25T16:04:00.515Z</updated>
  <id>https://blog.bearchild.top/</id>
  
  <author>
    <name>BearChild</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>[UE]GameplayRequestSystem框架</title>
    <link href="https://blog.bearchild.top/2025/06/25/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]GameplayRequestSystem%E6%A1%86%E6%9E%B6/"/>
    <id>https://blog.bearchild.top/2025/06/25/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]GameplayRequestSystem%E6%A1%86%E6%9E%B6/</id>
    <published>2025-06-24T16:00:00.000Z</published>
    <updated>2025-06-25T16:04:00.515Z</updated>
    
    <content type="html"><![CDATA[<h1>GameplayRequestSystem框架</h1><p>在联网游戏中经常需要有多个玩家之间请求交互的操作，提供一套 <code>GameplayRequestSystem</code> 用于快速自定义出一类新的 <code>Request</code>，自定义可发起请求交互对象、操作回调；</p><h2 id="基本框架">基本框架</h2><p>从一次 <code>Request</code> 基本的流程出发：</p><p>对于某一类 <code>Request</code>，首先 A 需要筛选出关心的 B （其它玩家） ，判断这些玩家是否可以发起该类请求，如果不可以的话，还可能需要知道无法发起请求的原因；</p><p>然后由 A 发起请求，并等待一系列请求交互操作：</p><blockquote><ol><li><code>SendRequest</code> ：由 A 向 B 发起某个请求，等待请求回复；</li><li><code>AcceptRequest</code>：B / Manager 接受某个请求；</li><li><code>RejectRequest</code>：B / Manager 拒绝某个请求；</li><li><code>CancelRequest</code>：A / B / Manager 取消某个请求；</li></ol></blockquote><p>每个操作需要通知给请求交互的双方 A、B；</p><p>对于一个新的 <code>Request</code>，两个玩家的 <code>Client</code> 需要同步到该 <code>Request</code> 信息，获取请求当前状态与一些同步属性等；</p><ol><li><p>由 <code>Player</code> 的 <code>RequestComponent</code> 执行对应 <code>Request</code> 交互、持有 <code>RequestFilter</code>、同步 <code>Request</code> ；</p></li><li><p>由 <code>Player</code> 的 <code>RequestFilter</code> 进行 可以发起请求的目标 的注册与更新等；</p></li><li><p>由<code>RequestMananger</code> 持有实际的 <code>Request</code>，管理其生命周期；</p></li></ol><p>对于 <code>Request</code> 的数据与操作，需要一些自定义参数，通过 <a href="https://www.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DCommonParams%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/">CommonParams</a> 实现；</p><pre class="mermaid">classDiagramdirection TDUGameplayRequestUtils ..> UGameplayRequestManagerUGameplayRequestUtils ..> UGameplayRequestComponent    class UGameplayRequestManager {        CreateRequest()        CancelRequest()        ResponseRequest()        ActiveRequests : TMap~uint64|UGameplayRequestBase*~    }        class UGameplayRequestComponent {        SendRequest()        CancelRequest()        AcceptRequest()        RejectRequest()        GetFilter()        Requests : TArray~UGameplayRequestBase*~        Filters : FGameplaySubSystemCollection~UGameplayRequestFilterBase~    }        UGameplayRequestManager --> UGameplayRequestBase    UGameplayRequestComponent --> UGameplayRequestBase    class UGameplayRequestBase {        GetType()        GetFilterType()        State : EGameplayRequestState        Result : EGameplayRequestResult    }        UGameplayRequestComponent --> UGameplayRequestFilterBase    UGameplayRequestFilterBase --> FGameplayRequestFilterTargetData    class UGameplayRequestFilterBase {    GetType()        IsTargetValid()        RegisterPossibleTarget()        UnregisterPossibleTarget()        FilterTargets()        FilterTargetDatas : TArray~FGameplayRequestFilterTargetData~    }        class FGameplayRequestFilterTargetData {        PlayerUID : uint64        bValid : bool        Params : FCommonVariantParams    }</pre><h2 id="Request-Manager">Request Manager</h2><p>作为全局的 <code>Manager</code> 管理所有 <code>Request</code> 的生命周期，持有 <code>Request</code> 实例；</p><p>在这里控制 <code>Request</code> 增加/移除，将其同步 增加/移除 到 <code>Sender</code> 与 <code>Target</code> 的 <code>RequestComponent</code> 上，通过 <code>RequestComponent</code> 同步 <code>Request</code> 给对应的 <code>Client</code> ；</p><p>根据 <code>UGameplayRequestBase</code> 注册所有的 <code>RequestCDO</code> 用于生成 <code>Request</code> 实例；</p><p>特别的，为了 <code>Request</code> 状态能正确同步与通知，当其 <code>Finish</code> 时，并不会直接将其 <code>Remove</code>，而是 <code>KeepAlive</code>，到 <code>KeepAliveDuration</code> 时间结束才将其实例移除；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestManager.h</span></span><br><span class="line"><span class="built_in">UCLASS</span>(meta = (BlueprintSpawnableComponent))</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestManager</span> :</span> <span class="keyword">public</span> UGameStateComponentBase</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="built_in">UGameplayRequestManager</span>();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInitSelf</span><span class="params">(AGameStateBase* Owner)</span> <span class="keyword">override</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">uint64 <span class="title">CreateRequest</span><span class="params">(uint32 Type, uint64 SenderUID, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">CancelRequest</span><span class="params">(uint64 RequestID, uint64 CancelUID = <span class="number">0</span>, FCommonVariantParams Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">ResponseRequest</span><span class="params">(uint64 RequestID, <span class="keyword">bool</span> bAccept, uint64 ResponseUID = <span class="number">0</span>, FCommonVariantParams Params = &#123;&#125;)</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">UGameplayRequestBase* <span class="title">GetRequest</span><span class="params">(uint64 RequestID)</span> <span class="keyword">const</span></span>;</span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; <span class="built_in">GetAllRequests</span>() <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">InitRequests</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">UninitRequests</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">AddRequest</span><span class="params">(UGameplayRequestBase* Request)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">RemoveRequest</span><span class="params">(uint64 RequestID)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">ClearRequests</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    TMap &lt;uint32, UGameplayRequestBase*&gt; RequestCDOs;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    TMap&lt;uint64, UGameplayRequestBase*&gt; ActiveRequests;</span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    TMap&lt;uint64, FTimerHandle&gt; RequestKeepAliveTimers;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">ExecuteFunctionForRequestPlayers</span><span class="params">(UGameplayRequestBase* Request, TFunction&lt;<span class="keyword">void</span>(UGameplayRequestComponent*)&gt; Callback)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestManager.cpp</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line">UGameplayRequestManager::<span class="built_in">UGameplayRequestManager</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">SetIsReplicatedByDefault</span>(<span class="literal">true</span>);</span><br><span class="line">    PrimaryComponentTick.bCanEverTick = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::OnInitSelf</span><span class="params">(AGameStateBase* Owner)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Super::<span class="built_in">OnInitSelf</span>(Owner);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">InitRequests</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::OnUninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Super::<span class="built_in">OnUninit</span>();</span><br><span class="line"></span><br><span class="line">    <span class="built_in">UninitRequests</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line"></span><br><span class="line"><span class="function">uint64 <span class="title">UGameplayRequestManager::CreateRequest</span><span class="params">(uint32 Type, uint64 SenderUID, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> RequestCDO = RequestCDOs.<span class="built_in">FindRef</span>(Type);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(RequestCDO))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (SenderUID == TargetUID)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    UGameplayRequestBase* NewRequest = NewObject&lt;UGameplayRequestBase&gt;(<span class="keyword">this</span>, RequestCDO-&gt;<span class="built_in">GetClass</span>());</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(NewRequest)) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    uint64 RequestID = NewRequest-&gt;<span class="built_in">Init</span>(SenderUID, TargetUID, Params);</span><br><span class="line">    <span class="keyword">if</span> (RequestID &lt;= <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">AddRequest</span>( NewRequest );</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// RegisterKeepAliveTimer</span></span><br><span class="line">    <span class="keyword">float</span> KeepAliveDuration = FMath::<span class="built_in">Max</span>( <span class="number">10.0f</span>, NewRequest-&gt;<span class="built_in">GetKeepAliveTime</span>());</span><br><span class="line">    </span><br><span class="line">    FTimerHandle&amp; TimerHandle = RequestKeepAliveTimers.<span class="built_in">FindOrAdd</span>(RequestID);</span><br><span class="line">    UWorldTimerManager::<span class="built_in">SetTimer</span>(<span class="built_in">GetWorld</span>(), TimerHandle,</span><br><span class="line">    [WeakSelfPtr = TWeakObjectPtr&lt;UGameplayRequestManager&gt;(<span class="keyword">this</span>), RequestID = RequestID]()</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!WeakSelfPtr.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">        WeakSelfPtr-&gt;<span class="built_in">RemoveRequest</span>( RequestID );</span><br><span class="line">    &#125;, KeepAliveDuration, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    NewRequest-&gt;<span class="built_in">NotifyCreated</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> RequestID;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestManager::CancelRequest</span><span class="params">(uint64 RequestID, uint64 CancelUID, FCommonVariantParams Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> Request = <span class="built_in">GetRequest</span>(RequestID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 由 Manager / Sender / Target 发起</span></span><br><span class="line">    <span class="keyword">if</span> ( !TSet&lt;uint64&gt;&#123; <span class="number">0LL</span>, Request-&gt;<span class="built_in">GetSenderUID</span>(), Request-&gt;<span class="built_in">GetTargetUID</span>() &#125;.<span class="built_in">Contains</span>( CancelUID ))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Request-&gt;<span class="built_in">IsFinished</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    Params.<span class="built_in">SetValue</span>( <span class="string">&quot;CancelUID&quot;</span>, CancelUID );</span><br><span class="line">    Request-&gt;<span class="built_in">Cancel</span>( Params );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestManager::ResponseRequest</span><span class="params">(uint64 RequestID, <span class="keyword">bool</span> bAccept, uint64 ResponseUID, FCommonVariantParams Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> Request = <span class="built_in">GetRequest</span>(RequestID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 由 Manager / TargetUID 发起</span></span><br><span class="line">    <span class="keyword">if</span> ( !TSet&lt;uint64&gt;&#123; <span class="number">0LL</span>, Request-&gt;<span class="built_in">GetTargetUID</span>() &#125;.<span class="built_in">Contains</span>( ResponseUID ))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Request-&gt;<span class="built_in">IsFinished</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    Params.<span class="built_in">SetValue</span>( <span class="string">&quot;ResponseUID&quot;</span>, ResponseUID );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (bAccept)</span><br><span class="line">    &#123;</span><br><span class="line">        Request-&gt;<span class="built_in">Accept</span>( Params );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        Request-&gt;<span class="built_in">Reject</span>( Params );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestBase* <span class="title">UGameplayRequestManager::GetRequest</span><span class="params">(uint64 RequestID)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ActiveRequests.<span class="built_in">FindRef</span>( RequestID );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplayRequestBase*&gt; <span class="title">UGameplayRequestManager::GetAllRequests</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; OutArray;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; [RequestID, Request] : ActiveRequests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;;</span><br><span class="line">        OutArray.<span class="built_in">Add</span>( Request );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OutArray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::InitRequests</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    TArray&lt;UClass*&gt; RequestClasses;</span><br><span class="line">    <span class="built_in">GetDerivedClasses</span>(UGameplayRequestBase::<span class="built_in">StaticClass</span>(), RequestClasses, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Class : RequestClasses)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Class)) <span class="keyword">continue</span>;</span><br><span class="line">    <span class="keyword">if</span> (Class-&gt;<span class="built_in">HasAnyClassFlags</span>(CLASS_Abstract)) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">auto</span> CDO = Class-&gt;GetDefaultObject&lt;UGameplayRequestBase&gt;();</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(CDO)) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        RequestCDOs.<span class="built_in">Add</span>( CDO-&gt;<span class="built_in">GetType</span>(), CDO );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::UninitRequests</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">ClearRequests</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::AddRequest</span><span class="params">(UGameplayRequestBase* Request)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span>;</span><br><span class="line">    ActiveRequests.<span class="built_in">Add</span>( Request-&gt;<span class="built_in">GetRequestID</span>(), Request );</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">ExecuteFunctionForRequestPlayers</span>(Request, [Request](UGameplayRequestComponent* Component)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">IsValid</span>(Component))</span><br><span class="line">        &#123;</span><br><span class="line">            Component-&gt;<span class="built_in">AddRequest</span>( Request );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::RemoveRequest</span><span class="params">(uint64 RequestID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> Request = <span class="built_in">GetRequest</span>(RequestID);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">IsValid</span>(Request))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">ExecuteFunctionForRequestPlayers</span>(Request, [RequestID](UGameplayRequestComponent* Component)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">IsValid</span>(Component))</span><br><span class="line">            &#123;</span><br><span class="line">                Component-&gt;<span class="built_in">RemoveRequest</span>( RequestID );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        Request-&gt;<span class="built_in">Uninit</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">auto</span>* TimerHandle = RequestKeepAliveTimers.<span class="built_in">Find</span>(RequestID))</span><br><span class="line">    &#123;</span><br><span class="line">        UWorldTimerManager::<span class="built_in">ClearTimer</span>(<span class="built_in">GetWorld</span>(), *TimerHandle);</span><br><span class="line">        RequestKeepAliveTimers.<span class="built_in">Remove</span>(RequestID);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ActiveRequests.<span class="built_in">Remove</span>(RequestID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::ClearRequests</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    TArray &lt;uint64&gt; RequestIDs;</span><br><span class="line">    ActiveRequests.<span class="built_in">GetKeys</span>(RequestIDs);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> RequestID : RequestIDs)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">RemoveRequest</span>( RequestID );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestManager::ExecuteFunctionForRequestPlayers</span><span class="params">(UGameplayRequestBase* Request, TFunction&lt;<span class="keyword">void</span>(UGameplayRequestComponent*)&gt; Callback)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">auto</span> Sender = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), Request-&gt;<span class="built_in">GetSenderUID</span>()); <span class="built_in">IsValid</span>(Sender))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">Callback</span>(Sender);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">auto</span> Target = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), Request-&gt;<span class="built_in">GetTargetUID</span>()); <span class="built_in">IsValid</span>(Target))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">Callback</span>(Target);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="RequestComponent">RequestComponent</h2><p>作为 <code>Player</code> 组件进行 <code>Request</code> 操作（请求、同意、拒绝、取消）；</p><p>将与 <code>Player</code> 相关的 <code>Request</code> 同步（当 <code>Player</code> 为 <code>Sender</code> / <code>Target</code> 时认为其相关）；</p><p>通过 <a href="https://www.bearchild.top/2025/03/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DGameplaySubSystem%E5%AE%9E%E7%8E%B0/">GameplaySubSystem</a> 持有每一种 <code>RequestType</code> 对应的 <code>RequestFilter</code> ，进行 <code>FilterTargetData</code> （可能发起请求的人的相关信息）的更新与同步；</p><p>后续每次对 <code>Request</code> 进行 <code>Verify</code> 时，需要通过 <code>Player</code> 上对应 <code>RequestFilter</code> 进行校验是否 <code>IsTargetValid</code>；</p><p>同时客户端可以根据 <code>RequestFilter</code> 上的 <code>FilterTargetDatas</code> 信息，提前进行部分操作（比如 <code>Client</code> 知道该类请求无可以请求对象时、自己 / 对方处于不可请求状态时，进行提示）</p><p>这里 <code>ReplicateRequest</code> 时，在 <code>Client</code> 创建对应 <code>UObject</code>，需要通过 <code>ReplicateSubobjects_OwnerOnly</code> 来进行同步：</p><p>当 <code>Request</code> 作为 <code>Subobject</code> 同步下去时，会在 <code>Client</code> 找到其对应的 <code>Outer</code>（也就是 <code>UGameplayRequestManager</code>，所以 <code>UGameplayRequestManager</code> 需要开启同步）创建在其 <code>ActorChannel</code> 的 <code>CreateSubObjects</code> 中；</p><p>在 <code>RequestComponent</code> 中，由一个同步字段 <code>Requests</code> 持有对应指针；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UPlayerStateComponentBase::ReplicateSubobjects</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> WroteSomething = Super::<span class="built_in">ReplicateSubobjects</span>(Channel, Bunch, RepFlags);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (APlayerStateBase* PS = <span class="built_in">GetPlayerState</span>(); <span class="built_in">IsValid</span>(PS) &amp;&amp; <span class="built_in">IsValid</span>(Channel) &amp;&amp; <span class="built_in">IsValid</span>(Channel-&gt;Connection))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Channel-&gt;Connection-&gt;PlayerController == PS-&gt;<span class="built_in">GetPlayerController</span>())</span><br><span class="line">&#123;</span><br><span class="line">WroteSomething |= <span class="built_in">ReplicateSubobjects_OwnerOnly</span>(Channel, Bunch, RepFlags);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下为具体实现：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestComponent.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(meta=(BlueprintSpawnableComponent))</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestComponent</span> :</span> <span class="keyword">public</span> UPlayerStateComponentBase, <span class="keyword">public</span> IGameplaySubSystemCollectionOwnerInterface</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UGameplayRequestComponent</span>();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInitSelf</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">SendRequest</span><span class="params">(uint32 Type, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">CancelRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">AcceptRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">RejectRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region C2S</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_SendRequest</span><span class="params">(uint32 Type, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_AcceptRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_RejectRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_CancelRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion C2S</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UFUNCTION</span>()</span><br><span class="line">    <span class="function">UGameplayRequestBase* <span class="title">GetRequest</span><span class="params">(uint64 RequestID)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>()</span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; <span class="built_in">GetRequestsByType</span>(uint32 Type);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">AddRequest</span><span class="params">(UGameplayRequestBase* Request)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">RemoveRequest</span><span class="params">(uint64 RequestID)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">ClearRequests</span><span class="params">()</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="built_in">UFUNCTION</span>()</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">OnRep_Requests</span><span class="params">()</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(ReplicatedUsing=OnRep_Requests)</span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; Requests;</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Filter</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> TArray&lt;UClass*&gt; <span class="title">GetFilterClasses</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> &#123;&#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UFUNCTION</span>()</span><br><span class="line">    <span class="function">UGameplayRequestFilterBase* <span class="title">GetFilter</span><span class="params">(UClass* InClass)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">ReplicateSubobjects_OwnerOnly</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">DECLARE_GAMEPLAYSUBSYSTEM_ACCESSORS_BY_NAME</span>(Filter, UGameplayRequestFilterBase, Filters);</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> TArray&lt;FGameplaySubSystemCollectionBase*&gt; <span class="title">GetSubSystemCollectionPtrs</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> &#123; &amp;Filters &#125;; &#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    FGameplaySubSystemCollection &lt;UGameplayRequestFilterBase&gt; Filters;</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Filter</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Debug</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_DebugSendRequest</span><span class="params">(uint32 Type, uint64 SenderUID, uint64 TargetUID)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_DebugAcceptRequests</span><span class="params">(uint64 PlayerUID)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_DebugRejectRequests</span><span class="params">(uint64 PlayerUID)</span></span>;</span><br><span class="line">    <span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">C2S_DebugCancelRequests</span><span class="params">(uint64 PlayerUID)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Debug</span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestComponent.cpp</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line">UGameplayRequestComponent::<span class="built_in">UGameplayRequestComponent</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">SetIsReplicatedByDefault</span>(<span class="literal">true</span>);</span><br><span class="line">    PrimaryComponentTick.bCanEverTick = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Super::<span class="built_in">GetLifetimeReplicatedProps</span>(OutLifetimeProps);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line">    <span class="comment">// --- OwnerOnly</span></span><br><span class="line"></span><br><span class="line">    FDoRepLifetimeParams OwnerOnlyRepParams;</span><br><span class="line">    OwnerOnlyRepParams.bIsPushBased = <span class="literal">true</span>;</span><br><span class="line">    OwnerOnlyRepParams.Condition = COND_OwnerOnly;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestComponent, Requests, OwnerOnlyRepParams);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::OnInitSelf</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Super::<span class="built_in">OnInitSelf</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>))</span><br><span class="line">    &#123;</span><br><span class="line">        Filters.<span class="built_in">Init</span>(<span class="keyword">this</span>, <span class="built_in">GetFilterClasses</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::OnUninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Super::<span class="built_in">OnUninit</span>();</span><br><span class="line">    </span><br><span class="line">    Filters.<span class="built_in">Uninit</span>();</span><br><span class="line">    <span class="built_in">ClearRequests</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestComponent::SendRequest</span><span class="params">(uint32 Type, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> Manager = UGameplayRequestUtils::<span class="built_in">GetManager</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Manager)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">bool</span> bSucceed = Manager-&gt;<span class="built_in">CreateRequest</span>(Type, <span class="built_in">GetUID</span>(), TargetUID, Params);</span><br><span class="line">    <span class="keyword">return</span> bSucceed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestComponent::AcceptRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> Manager = UGameplayRequestUtils::<span class="built_in">GetManager</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Manager)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">bool</span> bSucceed = Manager-&gt;<span class="built_in">ResponseRequest</span>(RequestID, <span class="literal">true</span>, <span class="built_in">GetUID</span>(), Params);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> bSucceed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestComponent::RejectRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> Manager = UGameplayRequestUtils::<span class="built_in">GetManager</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Manager)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">bool</span> bSucceed = Manager-&gt;<span class="built_in">ResponseRequest</span>(RequestID, <span class="literal">false</span>, <span class="built_in">GetUID</span>(), Params);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> bSucceed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestComponent::CancelRequest</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> Manager = UGameplayRequestUtils::<span class="built_in">GetManager</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Manager)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">bool</span> bSucceed = Manager-&gt;<span class="built_in">CancelRequest</span>(RequestID, <span class="built_in">GetUID</span>(), Params);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> bSucceed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region C2S</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_SendRequest_Implementation</span><span class="params">(uint32 Type, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">SendRequest</span>(Type, TargetUID, Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_AcceptRequest_Implementation</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">AcceptRequest</span>(RequestID, Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_RejectRequest_Implementation</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">RejectRequest</span>(RequestID, Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_CancelRequest_Implementation</span><span class="params">(uint64 RequestID, <span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">CancelRequest</span>(RequestID, Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion C2S</span></span><br><span class="line">    </span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestBase* <span class="title">UGameplayRequestComponent::GetRequest</span><span class="params">(uint64 RequestID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : Requests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (Request-&gt;<span class="built_in">GetRequestID</span>() == RequestID)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> Request;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplayRequestBase*&gt; <span class="title">UGameplayRequestComponent::GetRequestsByType</span><span class="params">(uint32 Type)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; OutArray;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : Requests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (Request-&gt;<span class="built_in">GetType</span>() == Type)</span><br><span class="line">        &#123;</span><br><span class="line">            OutArray.<span class="built_in">Add</span>( Request );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OutArray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::AddRequest</span><span class="params">(UGameplayRequestBase* Request)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">GetRequests_Mutable</span>().<span class="built_in">AddUnique</span>( Request );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::RemoveRequest</span><span class="params">(uint64 RequestID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> Request = <span class="built_in">GetRequest</span>( RequestID );</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">GetRequests_Mutable</span>().<span class="built_in">Remove</span>( Request );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::ClearRequests</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">GetRequests_Mutable</span>().<span class="built_in">Empty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::OnRep_Requests</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : Requests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        Request-&gt;<span class="built_in">NotifyCreated</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Filter</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestComponent::ReplicateSubobjects_OwnerOnly</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> WroteSomething = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    WroteSomething |= Super::<span class="built_in">ReplicateSubobjects_OwnerOnly</span>(Channel, Bunch, RepFlags);</span><br><span class="line">    WroteSomething |= Filters.<span class="built_in">ReplicateSubSystems</span>( Channel, Bunch, RepFlags );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">IsValid</span>(Channel) &amp;&amp; RepFlags-&gt;bNetInitial == <span class="literal">false</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span> Request : Requests)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">            WroteSomething |= Channel-&gt;<span class="built_in">ReplicateSubobject</span>(Request, *Bunch, *RepFlags);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestFilterBase* <span class="title">UGameplayRequestComponent::GetFilter</span><span class="params">(UClass* InClass)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Filter : <span class="built_in">GetFilters</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">IsValid</span>(Filter) &amp;&amp; Filter-&gt;<span class="built_in">GetClass</span>() == InClass)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> Filter;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Filter</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Debug</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_DebugSendRequest_Implementation</span><span class="params">(uint32 Type, uint64 SenderUID, uint64 TargetUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> UE_BUILD_SHIPPING</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">    SenderUID = SenderUID &gt; <span class="number">0</span> ? SenderUID : <span class="built_in">GetUID</span>();</span><br><span class="line">    TargetUID = TargetUID &gt; <span class="number">0</span> ? TargetUID : <span class="built_in">GetUID</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> Component = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), SenderUID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    Component-&gt;<span class="built_in">SendRequest</span>( Type, TargetUID );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_DebugAcceptRequests_Implementation</span><span class="params">(uint64 PlayerUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> UE_BUILD_SHIPPING</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    PlayerUID = PlayerUID &gt; <span class="number">0</span> ? PlayerUID : <span class="built_in">GetUID</span>();</span><br><span class="line">    <span class="keyword">auto</span> Component = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), PlayerUID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; PlayerRequests = Component-&gt;<span class="built_in">GetRequests</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : PlayerRequests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        Component-&gt;<span class="built_in">AcceptRequest</span>(Request-&gt;<span class="built_in">GetRequestID</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_DebugRejectRequests_Implementation</span><span class="params">(uint64 PlayerUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> UE_BUILD_SHIPPING</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    </span><br><span class="line">    PlayerUID = PlayerUID &gt; <span class="number">0</span> ? PlayerUID : <span class="built_in">GetUID</span>();</span><br><span class="line">    <span class="keyword">auto</span> Component = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), PlayerUID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; PlayerRequests = Component-&gt;<span class="built_in">GetRequests</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : PlayerRequests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        Component-&gt;<span class="built_in">RejectRequest</span>(Request-&gt;<span class="built_in">GetRequestID</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestComponent::C2S_DebugCancelRequests_Implementation</span><span class="params">(uint64 PlayerUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> UE_BUILD_SHIPPING</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">    PlayerUID = PlayerUID &gt; <span class="number">0</span> ? PlayerUID : <span class="built_in">GetUID</span>();</span><br><span class="line">    <span class="keyword">auto</span> Component = UGameplayRequestUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), PlayerUID);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    TArray &lt;UGameplayRequestBase*&gt; PlayerRequests = Component-&gt;<span class="built_in">GetRequests</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Request : PlayerRequests)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line">        Component-&gt;<span class="built_in">CancelRequest</span>(Request-&gt;<span class="built_in">GetRequestID</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Debug</span></span><br></pre></td></tr></table></figure><h2 id="RequestFilter">RequestFilter</h2><p>对于每一类 <code>Request</code>，每个玩家身上会有一个对应的 <code>RequestFilter</code>，用来更新可能可以发起请求的目标；</p><p>通过 <code>Register / Unregister</code> <code>PossibleTarget</code>，然后更新对应的 <code>FilterTargetData</code> 并同步给 <code>Client</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FilterTargetData </span></span><br><span class="line"></span><br><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FGameplayRequestFilterTargetData</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="built_in">FGameplayRequestFilterTargetData</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="built_in">FGameplayRequestFilterTargetData</span>(uint64 PlayerUID) : <span class="built_in">PlayerUID</span>(PlayerUID) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>==(<span class="keyword">const</span> FGameplayRequestFilterTargetData&amp; Other) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (PlayerUID != Other.PlayerUID) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span> !=(<span class="keyword">const</span> FGameplayRequestFilterTargetData&amp; Other) <span class="keyword">const</span> &#123; <span class="keyword">return</span> !(*<span class="keyword">this</span> == Other); &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line">uint64 PlayerUID;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line"><span class="keyword">bool</span> bValid = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line">FCommonVariantParams Params = &#123;&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestFilterBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(Abstract)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestFilterBase</span> :</span> <span class="keyword">public</span> UGameplaySubSystemBase</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Config</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> uint32 <span class="title">GetType</span><span class="params">()</span> <span class="keyword">const</span> <span class="title">PURE_VIRTUAL</span><span class="params">(UGameplayRequestFilterBase::GetType, <span class="keyword">return</span> <span class="number">0</span>;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Config</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">TWeakObjectPtr &lt;<span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestComponent</span>&gt;</span> Owner;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Replicate</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt; &amp; OutLifetimeProps)</span> <span class="keyword">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsSupportedForNetworking</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Replicate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region FilterTarget</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsTargetValid</span><span class="params">(uint64 TargetUID)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function">FGameplayRequestFilterTargetData <span class="title">GetFilterTargetData</span><span class="params">(uint64 PlayerUID)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FilterTargets</span><span class="params">()</span></span>; <span class="comment">// 主动调用</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ClearPossibleTargets</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">RegisterPossibleTarget</span><span class="params">(uint64 TargetUID)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UnregisterPossibleTarget</span><span class="params">(uint64 TargetUID)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">CheckSelfConditionValid</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125; </span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">CheckTargetConditionValid</span><span class="params">(uint64 TargetUID)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OnRep_FilterTargetDatas</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated, ReplicatedUsing = OnRep_FilterTargetDatas)</span><br><span class="line">TArray &lt;FGameplayRequestFilterTargetData&gt; FilterTargetDatas;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion FilterTarget</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function">uint64 <span class="title">GetUID</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">APlayerStateBase* <span class="title">GetPlayerState</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">GetTimeNow</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestFilterBase.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::OnInit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">OnInit</span>();</span><br><span class="line">Owner = Cast&lt;UGameplayRequestComponent&gt;(<span class="built_in">GetOuter</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::OnUninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">OnUninit</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">UGameplayRequestFilterBase::ToString</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;[%llu|%s]&quot;</span>), <span class="built_in">GetUID</span>(), *<span class="built_in">GetName</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Replicate</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">GetLifetimeReplicatedProps</span>(OutLifetimeProps);</span><br><span class="line"><span class="comment">// --------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment">// --- OwnerOnly</span></span><br><span class="line"></span><br><span class="line">FDoRepLifetimeParams OwnerOnlyRepParams;</span><br><span class="line">OwnerOnlyRepParams.bIsPushBased = <span class="literal">true</span>;</span><br><span class="line">OwnerOnlyRepParams.Condition = COND_OwnerOnly;</span><br><span class="line"></span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestFilterBase, FilterTargetDatas, OwnerOnlyRepParams);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Replicate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region FilterTarget</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestFilterBase::IsTargetValid</span><span class="params">(uint64 TargetUID)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span>&amp; Data = <span class="built_in">GetFilterTargetData</span>(TargetUID);</span><br><span class="line"><span class="keyword">return</span> Data.bValid;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FGameplayRequestFilterTargetData <span class="title">UGameplayRequestFilterBase::GetFilterTargetData</span><span class="params">(uint64 PlayerUID)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Data : FilterTargetDatas)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Data.PlayerUID == PlayerUID)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> Data;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::ClearPossibleTargets</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">GetFilterTargetDatas_Mutable</span>().<span class="built_in">Empty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::RegisterPossibleTarget</span><span class="params">(uint64 TargetUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">if</span> (FilterTargetDatas.<span class="built_in">Contains</span>( TargetUID )) <span class="keyword">return</span>;</span><br><span class="line">FilterTargetDatas.<span class="built_in">Add</span>( &#123; TargetUID &#125; );</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::UnregisterPossibleTarget</span><span class="params">(uint64 TargetUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">if</span> (!FilterTargetDatas.<span class="built_in">Contains</span>( TargetUID )) <span class="keyword">return</span>;</span><br><span class="line">FilterTargetDatas.<span class="built_in">Remove</span>( TargetUID );</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::FilterTargets</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> bSelfConditionValid = <span class="built_in">CheckSelfConditionValid</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span>&amp; Data : FilterTargetDatas)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">bool</span> bTargetConditionValid = <span class="built_in">CheckTargetConditionValid</span>(Data.PlayerUID);</span><br><span class="line">Data.bValid = (bSelfConditionValid &amp;&amp; bTargetConditionValid);</span><br><span class="line"><span class="comment">// TODO : More Params</span></span><br><span class="line">Data.Params.<span class="built_in">Clear</span>();</span><br><span class="line">Data.Params.<span class="built_in">SetValue</span>( <span class="string">&quot;bSelfConditionValid&quot;</span>, bSelfConditionValid );</span><br><span class="line">Data.Params.<span class="built_in">SetValue</span>( <span class="string">&quot;bTargetConditionValid&quot;</span>, bTargetConditionValid );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">MARK_PROPERTY_DIRTY_FROM_NAME</span>(UGameplayRequestFilterBase, FilterTargetDatas, <span class="keyword">this</span>);</span><br><span class="line"><span class="built_in">OnRep_FilterTargetDatas</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestFilterBase::OnRep_FilterTargetDatas</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion FilterTarget</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="function">uint64 <span class="title">UGameplayRequestFilterBase::GetUID</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Owner.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">return</span> Owner-&gt;<span class="built_in">GetUID</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">APlayerStateBase* <span class="title">UGameplayRequestFilterBase::GetPlayerState</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Owner.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> Owner-&gt;GetPlayerState&lt;APlayerStateBase&gt;();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">UGameplayRequestFilterBase::GetTimeNow</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UFunctionLibrary::<span class="built_in">GetGameServerTimeWithSecond</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Request">Request</h2><p>一个 <code>Request</code> 由 <code>RequestManager</code> 持有实际的 <code>Request</code>；</p><p>通过 <code>Sender</code> 、<code>Target</code> 的 <code>RequestComponent</code> 将其同步到对应 <code>Client</code>；</p><p>对于 <code>Request</code>，定义基本属性、状态以及提供生命周期相关回调；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(Abstract)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestBase</span> :</span> <span class="keyword">public</span> UObject</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Config</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> uint32 <span class="title">GetType</span><span class="params">()</span> <span class="keyword">const</span> <span class="title">PURE_VIRTUAL</span><span class="params">(UGameplayRequestBase::GetType, <span class="keyword">return</span> <span class="number">0</span>;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> UClass* <span class="title">GetFilterType</span><span class="params">()</span> <span class="keyword">const</span> <span class="title">PURE_VIRTUAL</span><span class="params">(UGameplayRequestBase::GetFilterType, <span class="keyword">return</span> UGameplayRequestFilterBase::StaticClass();)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Config</span></span><br><span class="line"></span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    <span class="function">uint64 <span class="title">Init</span><span class="params">(uint64 SenderUID, uint64 TargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; InParams)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Uninit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">inline</span> <span class="keyword">static</span> uint64 REQUEST_ID_COUNTER = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Replicate</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsSupportedForNetworking</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Replicate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Validate</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">ValidateRequest</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Validata</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Timeout</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">RegisterTimeoutTimer</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">FTimerHandle TimeoutTimer;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Timeout</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">friend</span> <span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestManager</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Accept</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Reject</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Cancel</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Timeout</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Interrupt</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnAccepted</span><span class="params">()</span> </span>&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRejected</span><span class="params">()</span> </span>&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnCanceled</span><span class="params">()</span> </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Create</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">NotifyCreated</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">bool</span> bHasNotifyCreated = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Create</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Complete</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">NotifyCompleted</span><span class="params">(EGameplayRequestResult InResult)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnCompleted</span><span class="params">(EGameplayRequestResult InResult)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OnRep_Result</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated, ReplicatedUsing=OnRep_Result)</span><br><span class="line">EGameplayRequestResult Result = EGameplayRequestResult::None;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Complete</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region State</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UpdateState</span><span class="params">(EGameplayRequestState InState)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnStateChanged</span><span class="params">(EGameplayRequestState)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRep_State</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated, ReplicatedUsing=OnRep_State)</span><br><span class="line">EGameplayRequestState State = EGameplayRequestState::Invalid;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion State</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region KeepAlive</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">GetKeepAliveTime</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> KeepAliveTime; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="comment">// 最大保留时间 (完成后也可能保留)</span></span><br><span class="line"><span class="keyword">float</span> KeepAliveTime = <span class="number">60.0f</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion KeepAlive</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Params</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">CombineParams</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated)</span><br><span class="line">FCommonVariantParams RequestParams;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Params</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Data</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OnRep_RequestID</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated)</span><br><span class="line">    uint64 SenderUID = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated)</span><br><span class="line">    uint64 TargetUID = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated)</span><br><span class="line">    <span class="keyword">float</span> CreateTime = <span class="number">0.0f</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated)</span><br><span class="line">    <span class="keyword">float</span> TimeoutDuration = <span class="number">30.0f</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>(Replicated, ReplicatedUsing=OnRep_RequestID)</span><br><span class="line">uint64 RequestID = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Data</span></span><br><span class="line"></span><br><span class="line">    </span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsFinished</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">GetTimeNow</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">GetRunningTime</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line">    </span><br><span class="line">&#125;; </span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestBase.cpp</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">UGameplayRequestBase::ToString</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;&#123;[ID=%llu][Type=%d][UID=%llu-&gt;%llu][Params=%s][%s][Time=%.f+%.f(s)][%.f(s), %.f(s)]&#125;&quot;</span>),</span><br><span class="line">RequestID,</span><br><span class="line"><span class="built_in">GetType</span>(), SenderUID, TargetUID, *RequestParams.<span class="built_in">ToString</span>(),</span><br><span class="line">*UStringUtils::<span class="built_in">ToString</span>(State),</span><br><span class="line">CreateTime, <span class="built_in">GetRunningTime</span>(),</span><br><span class="line">TimeoutDuration, KeepAliveTime</span><br><span class="line">);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">uint64 <span class="title">UGameplayRequestBase::Init</span><span class="params">(uint64 InSenderUID, uint64 InTargetUID, <span class="keyword">const</span> FCommonVariantParams&amp; InParams)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetRequestID</span>( ++REQUEST_ID_COUNTER );</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetSenderUID</span>( InSenderUID );</span><br><span class="line"><span class="built_in">SetTargetUID</span>( InTargetUID );</span><br><span class="line"><span class="built_in">SetRequestParams</span>( InParams );</span><br><span class="line"><span class="built_in">SetState</span>( EGameplayRequestState::Waiting );</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetCreateTime</span>( <span class="built_in">GetTimeNow</span>() );</span><br><span class="line"><span class="built_in">SetTimeoutDuration</span>( <span class="number">30.0f</span> );</span><br><span class="line"></span><br><span class="line">KeepAliveTime = FMath::<span class="built_in">Max</span>(KeepAliveTime, TimeoutDuration);</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> bValid = <span class="built_in">ValidateRequest</span>();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (bValid == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">RegisterTimeoutTimer</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> RequestID;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Uninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">UWorldTimerManager::<span class="built_in">ClearTimer</span>(<span class="built_in">GetWorld</span>(), TimeoutTimer);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Replicate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">GetLifetimeReplicatedProps</span>(OutLifetimeProps);</span><br><span class="line"></span><br><span class="line">FDoRepLifetimeParams SharedParams;</span><br><span class="line">SharedParams.bIsPushBased = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, RequestID, SharedParams);</span><br><span class="line"></span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, SenderUID, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, TargetUID, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, RequestParams, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, CreateTime, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, TimeoutDuration, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, RequestID, SharedParams);</span><br><span class="line"></span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, State, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UGameplayRequestBase, Result, SharedParams);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Replicate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Validate</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestBase::ValidateRequest</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">GetType</span>() &lt;= <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果没有需要的 Filter 则默认为 true</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> Filter = UGameplayRequestUtils::<span class="built_in">GetPlayerFilterByUID</span>(<span class="built_in">GetWorld</span>(), SenderUID, <span class="built_in">GetFilterType</span>()); <span class="built_in">IsValid</span>(Filter))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!Filter-&gt;<span class="built_in">IsTargetValid</span>(TargetUID))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="comment">// TODO : 重复 Request</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Validate</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Timeout</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::RegisterTimeoutTimer</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (TimeoutDuration &lt;= <span class="number">0.0f</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">UWorldTimerManager::<span class="built_in">SetTimer</span>(<span class="built_in">GetWorld</span>(), TimeoutTimer,</span><br><span class="line">[WeakSelfPtr = TWeakObjectPtr&lt;UGameplayRequestBase&gt;(<span class="keyword">this</span>)]()</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!WeakSelfPtr.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">WeakSelfPtr-&gt;<span class="built_in">Timeout</span>();</span><br><span class="line">&#125;, TimeoutDuration, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Timeout</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Interact</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Accept</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsFinished</span>()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">CombineParams</span>(Params);</span><br><span class="line"><span class="built_in">UpdateState</span>(EGameplayRequestState::Accepted);</span><br><span class="line"><span class="built_in">OnAccepted</span>();</span><br><span class="line"><span class="built_in">NotifyCompleted</span>(EGameplayRequestResult::Success);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Reject</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsFinished</span>()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">CombineParams</span>(Params);</span><br><span class="line"><span class="built_in">UpdateState</span>(EGameplayRequestState::Rejected);</span><br><span class="line"><span class="built_in">OnRejected</span>();</span><br><span class="line"><span class="built_in">NotifyCompleted</span>(EGameplayRequestResult::Failed);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Cancel</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsFinished</span>()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">CombineParams</span>(Params);</span><br><span class="line"><span class="built_in">UpdateState</span>(EGameplayRequestState::Canceled);</span><br><span class="line"><span class="built_in">OnCanceled</span>();</span><br><span class="line"><span class="built_in">NotifyCompleted</span>(EGameplayRequestResult::Canceled);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Timeout</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsFinished</span>()) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">Cancel</span>( &#123; <span class="string">&quot;CancelReason&quot;</span>, EGameplayRequestCancelReason::Timeout &#125; );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::Interrupt</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsFinished</span>()) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">Cancel</span>( &#123;<span class="string">&quot;CancelReason&quot;</span>, EGameplayRequestCancelReason::Interrupt&#125; );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Interact</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Create</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::NotifyCreated</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (bHasNotifyCreated) <span class="keyword">return</span>;</span><br><span class="line">bHasNotifyCreated = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Create</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Complete</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::NotifyCompleted</span><span class="params">(EGameplayRequestResult InResult)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetResult</span>(InResult);</span><br><span class="line"><span class="built_in">OnCompleted</span>(InResult);</span><br><span class="line"><span class="built_in">OnRep_Result</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::OnRep_Result</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Complete</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region State</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::UpdateState</span><span class="params">(EGameplayRequestState InState)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (InState == State) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetState</span>(InState);</span><br><span class="line"><span class="built_in">OnStateChanged</span>(InState);</span><br><span class="line"><span class="built_in">OnRep_State</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::OnRep_State</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion State</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Params</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::CombineParams</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Params.<span class="built_in">IsEmpty</span>()) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">SetRequestParams</span>( RequestParams + Params );</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Params</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Data</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplayRequestBase::OnRep_RequestID</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Data</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Helper</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplayRequestBase::IsFinished</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> State != EGameplayRequestState::Waiting &amp;&amp; State != EGameplayRequestState::Invalid;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">UGameplayRequestBase::GetTimeNow</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UFunctionLibrary::<span class="built_in">GetGameServerTimeWithSecond</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">UGameplayRequestBase::GetRunningTime</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">float</span> TimeNow = <span class="built_in">GetTimeNow</span>();</span><br><span class="line"><span class="keyword">return</span> TimeNow - CreateTime;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Helper</span></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="GameplayRequestUtils">GameplayRequestUtils</h2><p>通过该 <code>Utils</code> 提供一些方法给外部使用；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplayRequestUtils.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestUtils</span> :</span> <span class="keyword">public</span> UBlueprintFunctionLibrary</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestManager* <span class="title">GetManager</span><span class="params">(UWorld* InWorld)</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestComponent* <span class="title">GetComponent</span><span class="params">(APlayerStateBase* PS)</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestComponent* <span class="title">GetComponentByUID</span><span class="params">(UWorld* InWorld, uint64 UID)</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Filter</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestFilterBase* <span class="title">GetPlayerFilter</span><span class="params">(APlayerStateBase* PS, UClass* FilterType)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestFilterBase* <span class="title">GetPlayerFilterByUID</span><span class="params">(UWorld* InWorld, uint64 UID, UClass* FilterType)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Filter</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestBase* <span class="title">GetRequest</span><span class="params">(UWorld* InWorld, uint64 RequestID)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> UGameplayRequestBase* <span class="title">GetPlayerRequest</span><span class="params">(APlayerStateBase* PS, uint64 RequestID)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> TArray&lt;UGameplayRequestBase*&gt; <span class="title">GetPlayerRequests</span><span class="params">(APlayerStateBase* PS)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">static</span> TArray&lt;UGameplayRequestBase*&gt; <span class="title">GetPlayerRequestsByType</span><span class="params">(APlayerStateBase* PS, uint32 Type)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplayRequestUtils.cpp</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestManager* <span class="title">UGameplayRequestUtils::GetManager</span><span class="params">(UWorld* InWorld)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (InWorld == <span class="literal">nullptr</span>) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> UFunctionLibrary::GetGameStateComponentFromWorld&lt;UGameplayRequestManager&gt;(InWorld);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestComponent* <span class="title">UGameplayRequestUtils::GetComponent</span><span class="params">(APlayerStateBase* PS)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(PS)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> UFunctionLibrary::GetPlayerStateComponent&lt;UGameplayRequestComponent&gt;(PS);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestComponent* <span class="title">UGameplayRequestUtils::GetComponentByUID</span><span class="params">(UWorld* InWorld, uint64 UID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InWorld)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">auto</span> PS = UFunctionLibrary::<span class="built_in">GetPlayerStateByUID</span>(InWorld, UID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(PS)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">GetComponent</span>(PS);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Filter</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestFilterBase* <span class="title">UGameplayRequestUtils::GetPlayerFilter</span><span class="params">(APlayerStateBase* PS, UClass* FilterType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Component = <span class="built_in">GetComponent</span>(PS);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> Component-&gt;<span class="built_in">GetFilter</span>(FilterType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestFilterBase* <span class="title">UGameplayRequestUtils::GetPlayerFilterByUID</span><span class="params">(UWorld* InWorld, uint64 UID, UClass* FilterType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Component = <span class="built_in">GetComponentByUID</span>(InWorld, UID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> Component-&gt;<span class="built_in">GetFilter</span>(FilterType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Filter</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Request</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestBase* <span class="title">UGameplayRequestUtils::GetRequest</span><span class="params">(UWorld* InWorld, uint64 RequestID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InWorld)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsStandaloneOrDS</span>(InWorld))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Manager = <span class="built_in">GetManager</span>(InWorld);</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(Manager))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> Manager-&gt;<span class="built_in">GetRequest</span>( RequestID );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">IsClient</span>(InWorld))</span><br><span class="line">&#123;</span><br><span class="line">APlayerStateBase* HostPS = UFunctionLibrary::<span class="built_in">GetPlayerStateByUID</span>(InWorld, UFunctionLibrary::<span class="built_in">GetMainPlayerUID</span>(InWorld));</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(HostPS))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">GetPlayerRequest</span>( HostPS, RequestID );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplayRequestBase* <span class="title">UGameplayRequestUtils::GetPlayerRequest</span><span class="params">(APlayerStateBase* PS, uint64 RequestID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Component = <span class="built_in">GetComponent</span>(PS);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> Component-&gt;<span class="built_in">GetRequest</span>( RequestID );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplayRequestBase*&gt; <span class="title">UGameplayRequestUtils::GetPlayerRequests</span><span class="params">(APlayerStateBase* PS)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Component = <span class="built_in">GetComponent</span>(PS);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Component)) <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line"><span class="keyword">return</span> Component-&gt;<span class="built_in">GetRequests</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplayRequestBase*&gt; <span class="title">UGameplayRequestUtils::GetPlayerRequestsByType</span><span class="params">(APlayerStateBase* PS, uint32 Type)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Requests = <span class="built_in">GetPlayerRequests</span>(PS);</span><br><span class="line">TArray &lt;UGameplayRequestBase*&gt; OutRequests;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> Request : Requests)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Request)) <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (Request-&gt;<span class="built_in">GetType</span>() == Type)</span><br><span class="line">&#123;</span><br><span class="line">OutRequests.<span class="built_in">Add</span>( Request );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> OutRequests;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Request</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Sample">Sample</h2><p>创建一个请求时，只需要创建该类型对应的 <code>RequestFilter</code>、<code>Request</code> 即可；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Request_Test.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequest_Test</span> :</span> <span class="keyword">public</span> UGameplayRequestBase</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Config</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> uint32 <span class="title">GetType</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="number">1001</span>; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> UClass* <span class="title">GetFilterType</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> UGameplayRequestFilter_Test::<span class="built_in">StaticClass</span>(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Config</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnAccepted</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRejected</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnCanceled</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RequestFilter_Test.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplayRequestFilter_Test</span> :</span> <span class="keyword">public</span> UGameplayRequestFilterBase</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Config</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> uint32 <span class="title">GetType</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="number">1001</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Config</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsTargetValid</span><span class="params">(uint64 TargetUID)</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;GameplayRequestSystem框架&lt;/h1&gt;
&lt;p&gt;在联网游戏中经常需要有多个玩家之间请求交互的操作，提供一套 &lt;code&gt;GameplayRequestSystem&lt;/code&gt; 用于快速自定义出一类新的 &lt;code&gt;Request&lt;/code&gt;，自定义可发</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]GameplaySubSystem实现</title>
    <link href="https://blog.bearchild.top/2025/03/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]GameplaySubSystem%E5%AE%9E%E7%8E%B0/"/>
    <id>https://blog.bearchild.top/2025/03/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]GameplaySubSystem%E5%AE%9E%E7%8E%B0/</id>
    <published>2025-03-14T16:00:00.000Z</published>
    <updated>2025-06-25T15:11:12.524Z</updated>
    
    <content type="html"><![CDATA[<h1>GameplaySubSystem实现</h1><p>维护 <code>SubSystem</code> 的解决方案；</p><p>在维护各个业务时，经常需要将 <code>Manager</code>，下面再拆出若干个子系统，需要一种快捷的方法快速扩展出一套 <code>SubSystem</code> 系统；</p><ol><li>基本结构：由 <code>Manager</code> 持有 <code>SubSystemCollection</code> ，<code>Collection</code> 中持有若干个继承于 <code>SubSystemBase</code> 的自定义 <code>SubSystem</code>；</li><li>同步方案：当 <code>Manager</code>  <code>IsReplicated</code> 且 <code>Collection</code> 中的 <code>SubSystem</code> 需要同步数据时，提供 <code>SubSystem</code> 的同步策略，保证双端统一；</li></ol><h2 id="基本结构">基本结构</h2><pre class="mermaid">classDiagramdirection LRIGameplaySubSystemCollectionOwnerInterface<|--Managerclass IGameplaySubSystemCollectionOwnerInterface {GetSubSystemCollectionPtrs()GetSubSystemCollectionPtr(UClass* InClass)}Manager..*FGameplaySubSystemCollectionclass Manager {SubSystemCollections : FGameplaySubSystemCollection~UGameplaySubSystem~}class FGameplaySubSystemCollectionBase {        Outer : TWeakObjectPtr~UObject~        SubSystemMap : TMap~UClass*,TStrongObjectPtr[UGameplaySubSystemBase]~        BaseType : UClass*                Init()        SetOuter()        Uninit()        Tick()                        AddSubSystem()        RemoveSubSystem()        GetSubSystem()        ReplicateSubSystems()    }    FGameplaySubSystemCollectionBase<|--FGameplaySubSystemCollection    FGameplaySubSystemCollectionBase..>UGameplaySubSystemBaseclass FGameplaySubSystemCollection {FGameplaySubSystemCollection()}       IInterface_ActorSubobject<|--UGameplaySubSystemBase    class IInterface_ActorSubobject {    + OnCreatedFromReplication()    + OnDestroyedFromReplication()    }        class UGameplaySubSystemBase {    LastTickTime : float        + Init()    + Uninit()        # OnInit()    # OnUnInit()        + Tick()    # OnTick(float DeltaTime)    # GetTickInternal()    # GetTimeNow()        + IsSupportedForNetworking()    + OnCreatedFromReplication()    + OnDestroyedFromReplication()    }        UGameplaySubSystemBase<|--UGameplaySubSystem    class UGameplaySubSystem {    # OnInit()    # OnUnInit()    # OnTick(float DeltaTime)        # GetTickInternal()    }</pre><h3 id="Manager">Manager</h3><p>由 <code>Manager</code> 持有对应类型的 <code>SubSystemCollection</code>，主动调用 <code>Init</code>、<code>Uninit</code>、<code>Tick</code> 。</p><p>同时，为了后续的同步， <code>Manager</code> 需要重载 <code>IGameplaySubSystemCollectionOwnerInterface</code> 的 <code>GetSubSystemCollectionPtrs</code>，用于找到对应的 <code>Collection</code>（如果不需要同步，也可以不实现）；</p><p>一个 <code>Manager</code> 可能同时持有多个 <code>SubSystemCollection</code>，所以这里需要提供 <code>GetCollections</code>；</p><p>提供一个宏，方便地进行注册：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DECLARE_GAMEPLAYSUBSYSTEM_ACCESSORS(BaseClass, SubSystemCollections) \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;class T = BaseClass&gt;</span> \</span></span><br><span class="line"><span class="meta">typename TEnableIf<span class="meta-string">&lt;TIsDerivedFrom&lt;T, BaseClass&gt;</span>::Value, T*&gt;::Type \</span></span><br><span class="line"><span class="meta">GetSubSystem() \</span></span><br><span class="line"><span class="meta">&#123; \</span></span><br><span class="line"><span class="meta">return SubSystemCollections.GetSubSystem<span class="meta-string">&lt;T&gt;</span>(); \</span></span><br><span class="line"><span class="meta">&#125; \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;class T = BaseClass&gt;</span> \</span></span><br><span class="line"><span class="meta">typename TEnableIf<span class="meta-string">&lt;TIsDerivedFrom&lt;T, BaseClass&gt;</span>::Value, TArray<span class="meta-string">&lt;T*&gt;</span>&gt;::Type \</span></span><br><span class="line"><span class="meta">GetSubSystems() \</span></span><br><span class="line"><span class="meta">&#123; \</span></span><br><span class="line"><span class="meta">return SubSystemCollections.GetSubSystems(); \</span></span><br><span class="line"><span class="meta">&#125;</span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IGameplaySubSystemCollectionOwnerInterface.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UINTERFACE</span>(BlueprintType, Blueprintable)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplaySubSystemCollectionOwnerInterface</span> :</span> <span class="keyword">public</span> UInterface</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">IGameplaySubSystemCollectionOwnerInterface</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> TArray&lt;FGameplaySubSystemCollectionBase*&gt; <span class="title">GetSubSystemCollectionPtrs</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">FGameplaySubSystemCollectionBase* <span class="title">GetSubSystemCollectionPtr</span><span class="params">(UClass* InClass)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IGameplaySubSystemCollectionOwnerInterface.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function">FGameplaySubSystemCollectionBase* <span class="title">IGameplaySubSystemCollectionOwnerInterface::GetSubSystemCollectionPtr</span><span class="params">(UClass* InClass)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> CollectionPtrs = <span class="built_in">GetSubSystemCollectionPtrs</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> CollectionPtr : CollectionPtrs)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (CollectionPtr != <span class="literal">nullptr</span> &amp;&amp; InClass-&gt;<span class="built_in">IsChildOf</span>(CollectionPtr-&gt;<span class="built_in">GetBaseType</span>()))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> CollectionPtr;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Manager.h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">DECLARE_GAMEPLAYSUBSYSTEM_ACCESSORS</span>(USubSystemBase_A, SubSystemCollection_A);</span><br><span class="line"><span class="built_in">DECLARE_GAMEPLAYSUBSYSTEM_ACCESSORS</span>(USubSystemBase_B, SubSystemCollection_B);</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> TArray&lt;FGameplaySubSystemCollectionBase*&gt; <span class="title">GetSubSystemCollectionPtrs</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> &#123; &amp;SubSystemCollection_A, &amp;SubSystemCollection_B &#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">FGameplaySubSystemCollection &lt;USubSystemBase_A&gt; SubSystemCollection_A;</span><br><span class="line">FGameplaySubSystemCollection &lt;USubSystemBase_B&gt; SubSystemCollection_B;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Manager.cpp</span></span><br><span class="line"><span class="comment">// 在合适的时机：</span></span><br><span class="line">SubSystemCollections.<span class="built_in">Init</span>()</span><br><span class="line">SubSystemCollections.<span class="built_in">Uninit</span>()</span><br><span class="line">SubSystemCollections.<span class="built_in">Tick</span>()</span><br></pre></td></tr></table></figure><h2 id="SubSystemCollection">SubSystemCollection</h2><p><code>SubSystemCollection</code> 负责收集与管理所有的 <code>SubSystem</code>。</p><p>在 <code>SubSystemCollectionBase</code> 中，提供一个 <code>BaseClass</code>，这个 <code>BaseClass</code> 由对应的 <code>SubSystemCollection</code> 在初始化的时候传入：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TBaseType&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FGameplaySubSystemCollection</span> :</span> FGameplaySubSystemCollectionBase</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(TIsDerivedFrom&lt;TBaseType, UGameplaySubSystemBase&gt;::Value, <span class="string">&quot;TBaseType must inherit from UGameplaySubSystemBase&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">FGameplaySubSystemCollection</span>() : <span class="built_in">FGameplaySubSystemCollectionBase</span>(TBaseType::<span class="built_in">StaticClass</span>())</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">FGameplaySubSystemCollectionBase::<span class="built_in">FGameplaySubSystemCollectionBase</span>(UClass* InBaseType) : <span class="built_in">BaseType</span>(InBaseType)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">check</span>(BaseType);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>提供两种 <code>Init</code> 方法：</p><ol><li>根据 <code>BaseClass</code>，找到所有继承于该类的 <code>Class</code> 创建 <code>SubSystem</code> 。</li><li>指定 <code>SubSystemClasses</code> 进行创建 <code>SubSystem</code>；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Init</span><span class="params">(UObject* InOuter, <span class="keyword">bool</span> bCreatedDerivedClasses)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">SetOuter</span>(InOuter)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (bCreatedDerivedClasses)</span><br><span class="line">&#123;</span><br><span class="line">TArray&lt;UClass*&gt; SubSystemClasses;</span><br><span class="line"><span class="built_in">GetDerivedClasses</span>(BaseType, SubSystemClasses, <span class="literal">true</span>);</span><br><span class="line"><span class="built_in">AddSubSystemByClasses</span>( SubSystemClasses );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Init</span><span class="params">(UObject* InOuter, <span class="keyword">const</span> TArray&lt;UClass*&gt;&amp; InSubSystemClasses)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">SetOuter</span>(InOuter)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">AddSubSystemByClasses</span>(InSubSystemClasses);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FGameplaySubSystemCollectionBase::SetOuter</span><span class="params">(UObject* InOuter)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Outer.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// already initialized</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InOuter)) <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// invalid Outer</span></span><br><span class="line"></span><br><span class="line">Outer = InOuter;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用 <code>TMap &lt; UClass*, TStrongObjectPtr&lt;UGameplaySubSystemBase&gt; &gt; SubSystemMap</code> 将所有的 <code>SubSystem</code> 实例保存下来；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::AddSubSystem</span><span class="params">(UGameplaySubSystemBase* SubSystem)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SubSystem)) <span class="keyword">return</span>;</span><br><span class="line">SubSystemMap.<span class="built_in">Add</span>( SubSystem-&gt;<span class="built_in">GetClass</span>(), <span class="built_in">TStrongObjectPtr</span>(SubSystem)  );</span><br><span class="line">SubSystem-&gt;<span class="built_in">Init</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于期望 <code>FGameplaySubSystemCollection</code> 可以在编译期决定类型，使用了 <code>template&lt;typename TBaseType&gt;</code> ，这也导致无法走 <code>UHT</code> 的反射挂上<code>UPROPERTY</code> 标记来保证其生命周期。</p><p>所以这里需要用其它方式保证 <code>Collection</code> 内部的 <code>SubSystem</code> 的生命周期，让其不会被 <code>GC</code> ：</p><ol><li>用 <code>TStrongObjectPtr</code> ，缺点是 <code>TStrongObjectPtr</code> 的 <code>Reset</code> 校验其必须 <code>InGameThread</code>，需要注意在访问 <code>SubSystem</code> 时使用引用，不触发指针的拷贝；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">explicit</span> <span class="title">FInternalReferenceCollector</span><span class="params">(<span class="keyword">const</span> <span class="keyword">volatile</span> UObject* InObject)</span> : Object(InObject)</span></span><br><span class="line"><span class="function">&#123;</span></span><br><span class="line">    <span class="built_in">check</span>(<span class="built_in">IsInGameThread</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">FInternalReferenceCollector</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">check</span>(<span class="built_in">IsInGameThread</span>() || <span class="built_in">IsInGarbageCollectorThread</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="2"><li>使用 <code>AddToRoot / RemoveFromRoot</code> ，并用 <code>TWeakObjectPtr</code> 记录；</li></ol><p><strong>完整实现</strong>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FGameplaySubSystemCollectionBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FGameplaySubSystemCollectionBase</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">FGameplaySubSystemCollectionBase</span>(UClass* InBaseType);</span><br><span class="line">    <span class="function">UClass* <span class="title">GetBaseType</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(UObject* InOuter, <span class="keyword">bool</span> bCreatedDerivedClasses = <span class="literal">true</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(UObject* InOuter, <span class="keyword">const</span> TArray&lt;UClass*&gt;&amp; InSubSystemClasses)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">SetOuter</span><span class="params">(UObject* InOuter)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Uninit</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Tick</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">TArray&lt;UGameplaySubSystemBase*&gt; <span class="title">AddSubSystemByClasses</span><span class="params">(<span class="keyword">const</span> TArray&lt;UClass*&gt;&amp; SubSystemClasses)</span></span>;</span><br><span class="line"><span class="function">UGameplaySubSystemBase* <span class="title">AddSubSystemByClass</span><span class="params">(UClass* SubSystemClass)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">AddSubSystem</span><span class="params">(UGameplaySubSystemBase* SubSystem)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">RemoveSubSystemByClass</span><span class="params">(UClass* SubSystemClass)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">RemoveSubSystem</span><span class="params">(UGameplaySubSystemBase* SubSystem)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function">UGameplaySubSystemBase* <span class="title">GetSubSystemInternal</span><span class="params">(UClass* SubSystemClass)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">TArray&lt;UGameplaySubSystemBase*&gt; <span class="title">GetSubSystemsInternal</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">TWeakObjectPtr &lt;UObject&gt; Outer = <span class="literal">nullptr</span>;</span><br><span class="line">TMap &lt; UClass*, TWeakObjectPtr&lt;UGameplaySubSystemBase&gt; &gt; SubSystemMap;</span><br><span class="line">UClass* BaseType = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FGameplaySubSystemCollectionBase.cpp</span></span><br><span class="line"></span><br><span class="line">FGameplaySubSystemCollectionBase::<span class="built_in">FGameplaySubSystemCollectionBase</span>(UClass* InBaseType)</span><br><span class="line">: <span class="built_in">BaseType</span>(InBaseType)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">check</span>(BaseType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FGameplaySubSystemCollectionBase::<span class="built_in">GetBaseType</span>() <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> BaseType;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Init</span><span class="params">(UObject* InOuter, <span class="keyword">bool</span> bCreatedDerivedClasses)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">SetOuter</span>(InOuter)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (bCreatedDerivedClasses)</span><br><span class="line">&#123;</span><br><span class="line">TArray&lt;UClass*&gt; SubSystemClasses;</span><br><span class="line"><span class="built_in">GetDerivedClasses</span>(BaseType, SubSystemClasses, <span class="literal">true</span>);</span><br><span class="line"><span class="built_in">AddSubSystemByClasses</span>( SubSystemClasses );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Init</span><span class="params">(UObject* InOuter, <span class="keyword">const</span> TArray&lt;UClass*&gt;&amp; InSubSystemClasses)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">SetOuter</span>(InOuter)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">AddSubSystemByClasses</span>(InSubSystemClasses);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FGameplaySubSystemCollectionBase::SetOuter</span><span class="params">(UObject* InOuter)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Outer.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// already initialized</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InOuter)) <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// invalid Outer</span></span><br><span class="line"></span><br><span class="line">Outer = InOuter;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Tick</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span>&amp; Pair : SubSystemMap)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span>&amp; SubSystem = Pair.Value;</span><br><span class="line"><span class="keyword">if</span> (SubSystem.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line">SubSystem-&gt;<span class="built_in">Tick</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::Uninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TArray &lt;UClass*&gt; SubSystemClasses;</span><br><span class="line">SubSystemMap.<span class="built_in">GetKeys</span>(SubSystemClasses);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> SubSystemClass : SubSystemClasses)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">RemoveSubSystemByClass</span>(SubSystemClass);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplaySubSystemBase*&gt; <span class="title">FGameplaySubSystemCollectionBase::AddSubSystemByClasses</span><span class="params">(<span class="keyword">const</span> TArray&lt;UClass*&gt;&amp; SubSystemClasses)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TArray&lt;UGameplaySubSystemBase*&gt; SubSystems;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> SubSystemClass : SubSystemClasses)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> SubSystem = <span class="built_in">AddSubSystemByClass</span>( SubSystemClass );</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(SubSystem))</span><br><span class="line">&#123;</span><br><span class="line">SubSystems.<span class="built_in">Add</span>( SubSystem );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> SubSystems;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplaySubSystemBase* <span class="title">FGameplaySubSystemCollectionBase::AddSubSystemByClass</span><span class="params">(UClass* SubSystemClass)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (SubSystemClass == <span class="literal">nullptr</span> || !Outer.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">if</span> (SubSystemClass-&gt;<span class="built_in">HasAnyClassFlags</span>(CLASS_Abstract)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">if</span> (!SubSystemClass-&gt;<span class="built_in">IsChildOf</span>(BaseType)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (SubSystemMap.<span class="built_in">Contains</span>( SubSystemClass ))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> SubSystemMap.<span class="built_in">FindRef</span>(SubSystemClass).<span class="built_in">Get</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">UGameplaySubSystemBase* SubSystem = NewObject&lt;UGameplaySubSystemBase&gt;(Outer.<span class="built_in">Get</span>(), SubSystemClass);</span><br><span class="line"><span class="built_in">AddSubSystem</span>(SubSystem);</span><br><span class="line"><span class="keyword">return</span> SubSystem;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::AddSubSystem</span><span class="params">(UGameplaySubSystemBase* SubSystem)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SubSystem)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> CurrentSubSystem = <span class="built_in">GetSubSystemInternal</span>( SubSystem-&gt;<span class="built_in">GetClass</span>() ); <span class="built_in">IsValid</span>(CurrentSubSystem))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">RemoveSubSystem</span>( CurrentSubSystem );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">SubSystemMap.<span class="built_in">Add</span>( SubSystem-&gt;<span class="built_in">GetClass</span>(), <span class="built_in">TStrongObjectPtr</span>(SubSystem)  );</span><br><span class="line">SubSystem-&gt;<span class="built_in">Init</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::RemoveSubSystemByClass</span><span class="params">(UClass* SubSystemClass)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (SubSystemClass == <span class="literal">nullptr</span> || !Outer.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">if</span> (!SubSystemMap.<span class="built_in">Contains</span>( SubSystemClass )) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> SubSystem = <span class="built_in">GetSubSystemInternal</span>(SubSystemClass);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SubSystem)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">SubSystem-&gt;<span class="built_in">Uninit</span>();</span><br><span class="line">SubSystemMap.<span class="built_in">Remove</span>( SubSystemClass );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FGameplaySubSystemCollectionBase::RemoveSubSystem</span><span class="params">(UGameplaySubSystemBase* SubSystem)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SubSystem)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> SubSystemClass = SubSystem-&gt;<span class="built_in">GetClass</span>();</span><br><span class="line"><span class="keyword">if</span> (!SubSystemMap.<span class="built_in">Contains</span>( SubSystemClass ) ||SubSystemMap.<span class="built_in">FindRef</span>(SubSystemClass).<span class="built_in">Get</span>() != SubSystem) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">RemoveSubSystemByClass</span>( SubSystemClass );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="function">UGameplaySubSystemBase* <span class="title">FGameplaySubSystemCollectionBase::GetSubSystemInternal</span><span class="params">(UClass* SubSystemClass)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (SubSystemClass == <span class="literal">nullptr</span>) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!SubSystemMap.<span class="built_in">Contains</span>( SubSystemClass )) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span>&amp; SubSystem = SubSystemMap.<span class="built_in">FindChecked</span>( SubSystemClass ); SubSystem.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> SubSystem.<span class="built_in">Get</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UGameplaySubSystemBase*&gt; <span class="title">FGameplaySubSystemCollectionBase::GetSubSystemsInternal</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TArray &lt;UGameplaySubSystemBase*&gt; OutArray;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span>&amp; [Class, SubSystem] : SubSystemMap)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!SubSystem.<span class="built_in">IsValid</span>()) <span class="keyword">continue</span>;</span><br><span class="line">OutArray.<span class="built_in">Add</span>( SubSystem.<span class="built_in">Get</span>() );</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> OutArray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于 <code>FGameplaySubSystemCollection</code>，提供几个特化的方法，主要是 <code>GetSubSystem</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FGameplaySubSystemCollection.h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TBaseType&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FGameplaySubSystemCollection</span> :</span> FGameplaySubSystemCollectionBase</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(TIsDerivedFrom&lt;TBaseType, UGameplaySubSystemBase&gt;::Value, <span class="string">&quot;TBaseType must inherit from UGameplaySubSystemBase&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">FGameplaySubSystemCollection</span>() : <span class="built_in">FGameplaySubSystemCollectionBase</span>(TBaseType::<span class="built_in">StaticClass</span>())</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span> <span class="title">TSubSystemClass</span> =</span> TBaseType&gt;</span><br><span class="line"><span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;TSubSystemClass, TBaseType&gt;::Value, TSubSystemClass*&gt;::<span class="function">Type</span></span><br><span class="line"><span class="function"><span class="title">GetSubSystem</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">static_cast</span>&lt;TSubSystemClass*&gt;(<span class="built_in">GetSubSystemInternal</span>(TSubSystemClass::<span class="built_in">StaticClass</span>()));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span> <span class="title">TSubSystemClass</span> =</span> TBaseType&gt;</span><br><span class="line"><span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;TSubSystemClass, TBaseType&gt;::Value, TArray&lt;TSubSystemClass*&gt;&gt;::<span class="function">Type</span></span><br><span class="line"><span class="function"><span class="title">GetSubSystems</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> TArray&lt;UGameplaySubSystemBase*&gt;&amp; Array = <span class="built_in">GetSubSystemsInternal</span>();</span><br><span class="line"><span class="keyword">const</span> TArray&lt;TSubSystemClass*&gt;* SpecificArray = <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">const</span> TArray&lt;TSubSystemClass*&gt;*&gt;(&amp;Array);</span><br><span class="line"><span class="keyword">return</span> *SpecificArray;</span><br><span class="line">&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>特别地，这里的 <code>FGameplaySubSystemCollection::GetSubSystems</code>，由于 <code>FGameplaySubSystemCollection::GetSubSystemsInternal</code> 返回的也是 <code>UGameplaySubSystemBase</code> 指针，内存大小和 <code>TSubSystemClass</code> 指针一样，所以可以使用 <code>reinterpret_cast</code> 直接将整个数组的类型转化，节省遍历 <code>cast</code> 开销。</p><h3 id="SubSystemBase">SubSystemBase</h3><p>需要一个 <code>SubSystemBase</code>。</p><p>提供一些基础的方法，<code>Init</code>、<code>Uninit</code>、<code>Tick</code> 在 <code>SubSystemCollection</code> 中调用。</p><p>同时子类只需要关心：<code>OnInit</code>、<code>OnUninit</code>、<code>OnTick</code>。</p><p>对于各自的业务，各自继承自定义的 <code>SubSystemBase</code> ，然后再自定义各自的 <code>SubSytem</code> 继承于这个业务扩展出来的 <code>SubSystemBase</code> 即可。</p><p><code>SubSystemBase-&gt;GetOuter()</code> 就可以拿到对应的 <code>Manager</code>。</p><p>对于 <code>Tick</code> ，维护一个 <code>LastTickTime</code>，用于计算 <code>DeltaTime</code> 。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplaySubSystemBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(Abstract)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UGameplaySubSystemBase</span> :</span> <span class="keyword">public</span> UObject</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Init</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Uninit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInit</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Init</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Tick</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Tick</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnTick</span><span class="params">(<span class="keyword">float</span> DeltaTime)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">float</span> <span class="title">GetTickInternal</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="number">-1.0f</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">float</span> <span class="title">GetWorldTimeNow</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">float</span> LastTickTime;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Tick</span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplaySubSystemBase.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Init</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::Init</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">LastTickTime = <span class="built_in">GetWorldTimeNow</span>();</span><br><span class="line"><span class="built_in">OnInit</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::OnInit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// ------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::Uninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">OnUninit</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::OnUninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Init</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Tick</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::Tick</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">float</span> TickInternal = <span class="built_in">GetTickInternal</span>();</span><br><span class="line"><span class="keyword">if</span> (TickInternal &lt; <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">float</span> CurrentTickTime = <span class="built_in">GetWorldTimeNow</span>();</span><br><span class="line"><span class="keyword">if</span> (CurrentTickTime - LastTickTime &gt; TickInternal)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">OnTick</span>(CurrentTickTime - LastTickTime);</span><br><span class="line">LastTickTime = CurrentTickTime;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::OnTick</span><span class="params">(<span class="keyword">float</span> DeltaTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Tick</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="同步方案">同步方案</h2><p>由于各业务拆分到 <code>SubSystem</code> 中，其中</p><ol><li><code>SubSystem</code> 需要在 <code>Client</code> 有对应实例；</li><li><code>SubSystem</code> 需要支持属性同步的数据；</li><li><code>SubSystem</code> 需要支持 <code>RPC</code>；</li></ol><p>一种简单的方案是将这种需要同步的数据，存储在 <code>Manager</code> 中，<code>SubSystem</code> 也跟着 <code>Manager</code> 创建，但显然这样不够优雅；</p><p>于是可以实现一套 <code>SubSystem</code> 的同步方案，核心思路是：</p><p>将 <code>SubSystem</code> 作为 <code>Manager</code> 的 <code>SubObj</code> ，通过同步在 <code>Client</code> 创建出来，创建后注册进 <code>Manager</code> 对应的 <code>Collection</code> 中；</p><h3 id="同步基建">同步基建</h3><p>显然，这里的 <code>Manager</code> 可能是 <code>Actor</code>，也可能是 <code>ActorComponent</code>；</p><p>将 <code>SubSystem</code> 作为 <code>Manager</code> 的 <code>SubObj</code> 进行同步创建，通过 <code>ReplicateSubObjects</code> 来同步；</p><p><code>SubSystem</code> 实现 <code>IInterface_ActorSubobject</code>，在其中 <code>OnOnCreatedFromReplication</code> 的回调中，通过 <code>GetOuter</code> 获取 <code>Manager</code>；</p><p><code>Manager</code> 实现 <code>IGameplaySubSystemCollectionOwnerInterface</code>，获取 <code>Collection</code>；</p><p>这样就可以在 <code>Client</code> 将 <code>SubSystem</code> 注册进对应 <code>Manager</code> 的 <code>Collection</code>；</p><h4 id="Create-SubObject">Create SubObject</h4><pre class="mermaid">flowchart LRUActorChannel::ProcessBunch-->UActorChannel::ReadContentBlockPayload-->UActorChannel::ReadContentBlockHeaderUActorChannel::ProcessBunch-->UActorChannel::FindOrCreateReplicatorUActorChannel::ProcessBunch-->FObjectReplicator::ReceivedBunch</pre><p>在 <code>Client</code> 收到同步包时，首先进行 <code>UActorChannel::ReadContentBlockHeader</code>，</p><p>在这里会从 <code>Bunch</code> 中反序列出对应的数据，对应着 <code>UActorChannel::WriteContentBlockHeader</code> 中序列化的数据；</p><p>可以观察到，对于 <code>SubObj</code>，会在这里进行 <code>NewObject</code> 并且由 <code>ActorChannel-&gt;CreateSubObjects</code> 持有；</p><p>并且最后返回 <code>SubObj</code> 作为 <code>RepObj</code>，在 <code>UActorChannel::ProcessBunch</code> 中 <code>FindOrCreateReplicator</code> 并记录在 <code>ReplicationMap</code> 中，后续进行 <code>FObjectReplicator::ReceivedBunch</code>；</p><p>在 <code>FObjectReplicator::PostReceivedBunch</code> 中 <code>CallRepNotifies</code> 执行一系列 <code>OnRep</code>；</p><p>特别地，如果在一个 <code>Object</code> 中同步了一个 <code>SubObjectArray</code>，该 <code>Array</code> 标记有 <code>ReplicatedUsing</code> 方法，当 <code>SubObject</code> 在 <code>Client</code> 创建出时，会立马调用到该 <code>OnRep</code> 方法；（每次 <code>ProcessBunch</code> 都会对所有的 <code>ReplicationMap</code> 调用 <code>PostReceivedBunch</code>）；</p><h4 id="一致的-Outer">一致的 Outer</h4><p><code>Client</code> 创建出 <code>SubSystem</code> 时，通过 <code>GetOuter</code> 找到 <code>Manager</code>，从而找到 <code>Collection</code> 注册进该 <code>SubSystem</code>；</p><p>所以需要保证这里 <code>NewObject</code> 时，和 <code>DS</code> 保持一致的 <code>Outer</code>，这样才能找到正确的 <code>Manager</code>；</p><p>实际上，在 <code>UE4</code>  中这里 <code>NewObject</code> 时，直接将 <code>Actor</code> 作为 <code>Outer</code>；</p><p>（但实际上 <code>SubObj</code> 可能由 <code>ActorComponent</code> 等 <code>SubObj</code> 持有；错误的 <code>Actor</code> 会丢失索引路由）</p><p>参考 <code>UE5</code>，在 <code>UActorChannel::ReadContentBlockHeader</code> 时：</p><p>新写入一个 <code>Bit(0/1)</code> 表示 <code>bActorIsOuter</code>，仅当 <code>(ObjOuter == Actor) || (!Obj-&gt;IsSupportedForNetworking())</code> 时，该值为 <code>true</code>；</p><p>为了节省流量，仅当 <code>bActorIsOuter = false</code> 时候向 <code>Bunch</code> 中写入 <code>ObjOuter</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">UObject* ObjOuter = Obj-&gt;<span class="built_in">GetOuter</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">bool</span> bActorIsOuter = (ObjOuter == Actor) || (!Obj-&gt;<span class="built_in">IsSupportedForNetworking</span>());</span><br><span class="line">Bunch.<span class="built_in">WriteBit</span>(bActorIsOuter ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (!bActorIsOuter)</span><br><span class="line">&#123;</span><br><span class="line">    Bunch &lt;&lt; ObjOuter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在创建完 <code>SubObj</code> 后，会调用 <code>Actor-&gt;OnSubobjectCreatedFromReplication( SubObj )</code>，找到对应的 <code>Interface</code> 执行回调；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> ( UActorComponent * Component = Cast&lt;UActorComponent&gt;(NewSubobject) )</span><br><span class="line">&#123;</span><br><span class="line">    Component-&gt;<span class="built_in">OnCreatedFromReplication</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (IInterface_ActorSubobject* SubojectInterface = Cast&lt;IInterface_ActorSubobject&gt;(NewSubobject))</span><br><span class="line">&#123;</span><br><span class="line">    SubojectInterface-&gt;<span class="built_in">OnCreatedFromReplication</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里可以进行 <code>SubSystem</code> 的注册；</p><h3 id="具体实现">具体实现</h3><h4 id="Manager-2">Manager</h4><p>对于 <code>Manager</code>，重载 <code>Manager</code> 的 <code>ReplicateSubobjects</code>，调用 <code>Collection</code> 提供的 <code>ReplicateSubSystems</code> 方法；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Manager.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UManager::ReplicateSubobjects</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> WroteSomething = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">WroteSomething |= Super::<span class="built_in">ReplicateSubobjects</span>(Channel, Bunch, RepFlags);</span><br><span class="line">WroteSomething |= SubSystemCollections.<span class="built_in">ReplicateSubSystems</span>( Channel, Bunch, RepFlags );</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时，<code>Manager</code> 需要实现 <code>IGameplaySubSystemCollectionOwnerInterface</code> 接口，用于后续 <code>SubSystem</code> 的注册；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Manager.h</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FGameCollectionBase&amp; <span class="title">GetSubSystemCollectionBase</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> SubSystemCollections; &#125;</span><br></pre></td></tr></table></figure><h4 id="SubSystemCollection-2">SubSystemCollection</h4><p>对于 <code>SubSystemCollection</code> 实现 <code>ReplicateSubSystems</code>，将需要同步的（<code>IsSupportedForNetworking == true</code> ）<code>SubSystem</code> 写入到 <code>Bunch</code> 中；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FGameCollectionBase::ReplicateSubSystems</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Outer.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Channel)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (RepFlags-&gt;bNetInitial) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> WroteSomething = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span>&amp; SubSystems = <span class="built_in">GetSubSystemsInternal</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> SubSystem : SubSystems)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SubSystem)) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (SubSystem-&gt;<span class="built_in">IsSupportedForNetworking</span>())</span><br><span class="line">&#123;</span><br><span class="line">WroteSomething |= Channel-&gt;<span class="built_in">ReplicateSubobject</span>( SubSystem, *Bunch, *RepFlags );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>特别地，当 <code>RepFlags</code> 初始化时，先不进行 <code>SubSystem</code> 的同步；</p><p>这是因为在 <code>AActor::ReplicateSubobjects</code> 时，递归进行的 <code>ReplicateSubobjects</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (UActorComponent* ActorComp : ReplicatedComponents)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (ActorComp &amp;&amp; ActorComp-&gt;<span class="built_in">GetIsReplicated</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        WroteSomething |= ActorComp-&gt;<span class="built_in">ReplicateSubobjects</span>(Channel, Bunch, RepFlags);</span><br><span class="line">        WroteSomething |= Channel-&gt;<span class="built_in">ReplicateSubobject</span>(ActorComp, *Bunch, *RepFlags);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同步一般会先进行 <code>Actor</code> 的同步，之后进行 <code>ActorComponent</code> 的同步，也就是从上到下的设置同步参数，然后 <code>StartReplicating</code>；</p><pre class="mermaid">flowchart LRUActorChannel::ReplicateSubobject--> UActorChannel::CreateReplicator--> FObjectReplicator::StartReplicating--> UNetDriver::GetReplicationChangeListMgr--> FRepLayout::CreateReplicationChangelistMgr</pre><p>但是如果 <code>SubSystem</code> 作为一个 <code>Manager(ActorComponent)</code> 的 <code>SubSystem</code> 时，由于递归的顺序，会先调用 <code>ActorComponent</code> 的  <code>SubSystem</code> 的 <code>StartReplicating</code>，然后再调用 <code>ActorComponent</code> 的 <code>StartReplicating</code>；</p><p>可能会导致时序的问题，所以延迟一帧开始同步 <code>SubSystem</code>（判断 <code>RepFlags-&gt;bNetInitial</code>）；</p><h4 id="SubSystemBase-2">SubSystemBase</h4><p>首先对于需要同步的 <code>SubSystem</code>，重载 <code>IsSupportedForNetworking</code> 标记为 <code>true</code>；</p><p>为了在 <code>Client</code> 创建时有回调，需要实现 <code>IInterface_ActorSubobject</code> 接口；</p><p>创建后在 <code>Client</code> 正常 <code>GetOuter</code>，就可以获取到 <code>Manager</code> ，通过 <code>Manager</code> 的 <code>IGameplaySubSystemCollectionOwnerInterface</code> 实现的 <code>GetSubSystemCollectionPtr</code> 找到对应 <code>Collection</code>，将 <code>SubSystem</code> 注册进去；</p><p>同时，为了支持发送 <code>RPC</code>，需要实现 <code>GetOwnerActor</code> 以找到该 <code>SubSystem</code> 所属的 <code>Actor</code>，以作为 <code>ProcessRemoteFunction</code> 的参数；</p><p>并且需要重载 <code>CallRemoteFunction</code>、<code>GetFunctionCallspace</code>，以进行正常的 <code>RPC</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplaySubSystemBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsSupportedForNetworking</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">AActor* <span class="title">GetOwnerActor</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">CallRemoteFunction</span><span class="params">(UFunction* Function, <span class="keyword">void</span>* Parameters, FOutParmRec* OutParms, FFrame* Stack)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> int32 <span class="title">GetFunctionCallspace</span><span class="params">( UFunction* Function, FFrame* Stack )</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnCreatedFromReplication</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnDestroyedFromReplication</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br></pre></td></tr></table></figure><p>在 <code>CreatedFromReplication</code> 时，通过 <code>GetOuter</code> 获取 <code>Manager</code>，再找到 <code>Collection</code> 将自己注册进去；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GameplaySubSystemBase.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function">AActor* <span class="title">UGameplaySubSystemBase::GetOwnerActor</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UObject* Outer = <span class="built_in">GetOuter</span>();</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Outer)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> OwnerActor = Cast&lt;AActor&gt;(Outer); <span class="built_in">IsValid</span>(OwnerActor))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> OwnerActor;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> OwnerActorComponent = Cast&lt;UActorComponent&gt;(Outer); <span class="built_in">IsValid</span>(OwnerActorComponent))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> OwnerActorComponent-&gt;<span class="built_in">GetOwner</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UGameplaySubSystemBase::CallRemoteFunction</span><span class="params">(UFunction* Function, <span class="keyword">void</span>* Parameters, FOutParmRec* OutParms, FFrame* Stack)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> bProcessed = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (AActor* OwnerActor = <span class="built_in">GetOwnerActor</span>())</span><br><span class="line">&#123;</span><br><span class="line">FWorldContext* <span class="keyword">const</span> Context = GEngine-&gt;<span class="built_in">GetWorldContextFromWorld</span>(<span class="built_in">GetWorld</span>());</span><br><span class="line"><span class="keyword">if</span> (Context != <span class="literal">nullptr</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (FNamedNetDriver&amp; Driver : Context-&gt;ActiveNetDrivers)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Driver.NetDriver != <span class="literal">nullptr</span> &amp;&amp; Driver.NetDriver-&gt;<span class="built_in">ShouldReplicateFunction</span>(OwnerActor, Function))</span><br><span class="line">&#123;</span><br><span class="line">Driver.NetDriver-&gt;<span class="built_in">ProcessRemoteFunction</span>(OwnerActor, Function, Parameters, OutParms, Stack, <span class="keyword">this</span>);</span><br><span class="line">bProcessed = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> bProcessed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">int32 <span class="title">UGameplaySubSystemBase::GetFunctionCallspace</span><span class="params">(UFunction* Function, FFrame* Stack)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UObject* Outer = <span class="built_in">GetOuter</span>();</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Outer)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">return</span> Outer-&gt;<span class="built_in">GetFunctionCallspace</span>(Function, Stack);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::OnCreatedFromReplication</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> CollectionOwner = Cast&lt;IGameplaySubSystemCollectionOwnerInterface&gt;(<span class="built_in">GetOuter</span>());</span><br><span class="line"><span class="keyword">if</span> (CollectionOwner == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> CollectionPtr = CollectionOwner-&gt;<span class="built_in">GetSubSystemCollectionPtr</span>( <span class="built_in">GetClass</span>() );</span><br><span class="line"><span class="keyword">if</span> (CollectionPtr == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">CollectionPtr-&gt;<span class="built_in">SetOuter</span>(<span class="built_in">GetOuter</span>());</span><br><span class="line">CollectionPtr-&gt;<span class="built_in">AddSubSystem</span>(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UGameplaySubSystemBase::OnDestroyedFromReplication</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> CollectionOwner = Cast&lt;IGameplaySubSystemCollectionOwnerInterface&gt;(<span class="built_in">GetOuter</span>());</span><br><span class="line"><span class="keyword">if</span> (CollectionOwner == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> CollectionPtr = CollectionOwner-&gt;<span class="built_in">GetSubSystemCollectionPtr</span>( <span class="built_in">GetClass</span>() );</span><br><span class="line"><span class="keyword">if</span> (CollectionPtr == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">CollectionPtr-&gt;<span class="built_in">RemoveSubSystem</span>(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，对于一个 <code>SubSytem</code> 就可以正常的进行数据同步与 <code>RPC</code>；</p><h4 id="Note">Note</h4><h5 id="Remove">Remove</h5><p>在 <code>UE4</code> 中，没有提供直接 <code>RemoveSubObject</code> 机制，对于一个同步的 <code>SubObj</code>，会一直在 <code>ActorChannel-&gt;CreateSubObjects</code> 中被持有；</p><p>当 <code>UActorChannel::DestroyActorAndComponents</code> 时，会进行 <code>Destroy</code>；或者主动构造 <code>Bunch</code>，让其 <code>Connection-&gt;PackageMap-&gt;SerializeObject( Bunch, UObject::StaticClass(), SubObjClassObj, &amp;ClassNetGUID )</code> 获取的 <code>ClassNetGUID</code> <code>invalid</code>，从而在 <code>CreateSubObjects</code> 中 <code>Remove</code> 掉对应的 <code>SubObj</code>；</p><p>在 <code>UE5</code> 中相关 <code>SubObj</code> 的同步进行了重构，正常支持动态的 <code>AddSubObject/RemoveSubObject</code>；</p><h4 id="Destroy">Destroy</h4><p>在 <code>UObject::ConditionalBeginDestroy</code> 执行到 <code>UActorChannel::CleanUp</code> 时，会清除 <code>Channel</code> 中所有的 <code>SubObj</code>。</p><p>此时需要注意 <code>GC</code> 的时序，有可能此时 <code>Actor-&gt;GetWorld()</code> 中 <code>Actor-&gt;GetOuter()</code> 已经提前被清除，导致无法进行 <code>DestroyActorAndComponents</code>，无法正确清除 <code>CreateSubObjects</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!Actor-&gt;bNetTemporary &amp;&amp; Actor-&gt;<span class="built_in">GetWorld</span>() != <span class="literal">NULL</span> &amp;&amp; !<span class="built_in">IsEngineExitRequested</span>() &amp;&amp; Connection-&gt;Driver-&gt;<span class="built_in">ShouldClientDestroyActor</span>(Actor))</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">UE_LOG</span>(LogNetDormancy, Verbose, <span class="built_in">TEXT</span>(<span class="string">&quot;UActorChannel::CleanUp: Destroying Actor. %s&quot;</span>), *<span class="built_in">Describe</span>() );</span><br><span class="line"></span><br><span class="line">    <span class="built_in">DestroyActorAndComponents</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="RPC">RPC</h4><p>在 <code>UE4</code> 中，如果在 <code>ThisClass-&gt;UObject</code> 这一条继承链中，没有任何一个 <code>UPROPERTY</code> 属性，则相关 <code>UFUNCTION</code> 的同步数据无法写入 <code>NetFiled</code>，无法发送 <code>RPC</code></p><p>这是因为在 <code>UNetDriver::InternalProcessRemoteFunctionPrivate</code> 时调用的 <code>const FClassNetCache* ClassCache = NetCache-&gt;GetClassNetCache( TargetObj-&gt;GetClass() );</code>；其中 <code>UClass::SetUpRuntimeReplicationData</code> 判定了：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!<span class="built_in">HasAnyClassFlags</span>(CLASS_ReplicationDataIsSetUp) &amp;&amp; PropertyLink != <span class="literal">NULL</span>)</span><br></pre></td></tr></table></figure><p>这里判断了 <code>PropertyLink != nullptr</code>，不过显然是没有必要的，可以将其直接去掉；</p><p>这样就支持了没有 <code>UPROPERTY</code> 的 <code>Object</code> 可以正常 <code>SetUpRuntimeReplicationData</code>；</p><p>该问题在 <code>UE5.3</code> 中正式修复：<a href="https://github.com/EpicGames/UnrealEngine/commit/cfcec3c94d07120840df0dd108b64f502550c762#diff-5ca24143276c6adc32eb3ed8f467c4c213f73efccc9eead77044a39188c11fd1L33">Allow replication of UObjects with UFunctions but no UProperties</a>。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;GameplaySubSystem实现&lt;/h1&gt;
&lt;p&gt;维护 &lt;code&gt;SubSystem&lt;/code&gt; 的解决方案；&lt;/p&gt;
&lt;p&gt;在维护各个业务时，经常需要将 &lt;code&gt;Manager&lt;/code&gt;，下面再拆出若干个子系统，需要一种快捷的方法快速扩展出一套 &lt;co</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>2024 年度总结</title>
    <link href="https://blog.bearchild.top/2024/12/31/%E6%97%A5%E5%BF%97/2024%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/"/>
    <id>https://blog.bearchild.top/2024/12/31/%E6%97%A5%E5%BF%97/2024%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/</id>
    <published>2024-12-31T15:59:59.000Z</published>
    <updated>2024-12-30T22:07:23.828Z</updated>
    
    <content type="html"><![CDATA[<h1>2024 年度总结</h1><h2 id="一月-二月">一月~二月</h2><p>在反思中开始了新的一年，今年不断思考，经历无数的迷茫，缓慢前行。</p><p>开年经历了各种各样的事件后，在多少个深夜不断思考，剖析自我。</p><p>恰逢《我的青春恋爱物语果然有问题》完结。看着八幡从孤僻冷漠的人，经历各种困难，解决各种事件，不断反思与追问，成长为成熟又温柔的大老师。完结的不是春物，结束的只是那个所谓不需要归宿的 <em>“彷徨的孤高灵魂”</em>，他终将找到自己的真物。我的真物又在哪里？</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510783.jpg" alt="1a6842090d792e064cf14f11a671c922" style="zoom: 50%;" /><p>月底《幻兽帕鲁》正式上线，一家小公司不断成长，最终做出了名动世界的作品，这样的故事实在是令人感慨，或许小厂才能有这样的创造力，看了他们制作人的话，很受感动：</p><blockquote><p>到达这一点的道路是漫长的。<br>回想起来，我觉得自己确实走了一条弯路。<br>这是一系列不必要的失败。</p><p>我一遍又一遍地在我不应该绊倒的地方绊倒，如果我知道的话。<br>我不知道行业专家应该知道的事情。</p><p>因为我们是一群业余爱好者。<br>“我该怎么做?”——<br>开始我什么都不知道。</p><p>然而，这段弯路让我与人们建立了联系，并组建了今天的团队。<br>正是因为我们是一群业余爱好者，所以我们才能创造出一种不受行业惯例约束的方法。</p><p>如果我们一家叫 Pocket Pair 的公司是一群游戏行业的专业人士，筹集了资金并且现金充裕的话。<br>《Pal World》这款游戏就不会诞生在这个世界上。</p><p>仅仅因为你有钱并不意味着你可以制作有趣的游戏。</p></blockquote><p>或许这群 “业余爱好者” 才是真正的游戏人。“越有才华的人越早离开公司”，太多日子人，用生命玩着上班过家家，扮演着自己的角色，沉浸和迷失在这种 <em>Cosplay</em> 里。</p><p><strong>人看似在走，心却停留在原地</strong>，在人生的道路上踟蹰不前，我又何尝不是如此？</p><blockquote><p>很喜欢 doctor 说的一段话：</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510484.jpg" alt="432842ee9768d067b58150c258e29188" style="zoom:33%;" /></blockquote><p><strong>“所谓觉悟，就是在漆黑的荒野上开辟出一条理当前进的光明大道。”</strong></p><p>在成长的道路上前行，我又是否有这样的决心与觉悟呢？</p><h2 id="三月">三月</h2><p>满载着迷茫与纠结，被项目推着向前走。在思考的同时，也要先处理好眼下该做的事情，项目七月份就将要上线。</p><p>开始负责新的模式夺旗赛，从头开始实现一个玩法单局，设计了核心框架、重构了新的组队系统……除了应付基本的业务需求，也借此机会更加深入底层，不局限于单纯的实现，做了很多如今看来也很有价值的设计与实现。</p><p>从技术来看成长了许多，架构清晰了许多。虽然现在回头来看又有很多不足，但这或许说明还有继续成长与进化的空间。</p><p>迷茫中缓慢向前走，在技术沉淀的同时也常看一些游戏设计。在加班的同时寻求一些成长，虽然深知将这些积累实际应用起来才有价值，不过也得先做好积累，才有实践的那一天。</p><p>看完了《体操武士》，很喜欢评论里的一句话：</p><blockquote><p><strong>“不怕痛的毅力，是追梦人的护身符”</strong>。</p></blockquote><p>时时感慨自己心怀梦想却被困在工位，一边觉得世人皆醉我独醒，一边自卑痛恨太过弱小没有力量，踌躇满志却又经常瘫痪在床看动漫。</p><p>希望自己可以更加坚强，更加勇敢，更加勤奋一些。</p><h2 id="四月">四月</h2><p>得知了乐华收购 A-SOUL，晚晚决定不继续签合同，选择了退出。这才发现自己也很久没有看 A-SOUL 的直播了，在那个晚上疯狂刷着过去的动态。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510601.jpg" alt="ab95c4e2dda738963d9877e9a7944523" style="zoom: 33%;" /><p>自己一路匆匆忙忙寻觅远方，在某个不经意的驻足，才发现错过了多少沿途的美丽风景。</p><p>平常总是期待着发生一些奇迹，向往着一些缘分，可以把所有的偶然都实现。回过头看，才发现原来那样一个个相伴着的稀松平常的夜晚，正是奇迹发生的时刻。可惜，再也不会迎来这样的时刻了。</p><p>天下没有不散的宴席，再美丽的花朵也会枯萎，再闪耀的太阳也将落下。</p><p>或许人生就是这样，体验一次又一次的离去，从痛哭到沉默，从痛苦到漠然，从撕心裂肺再到云淡风轻。不知该庆幸还是可笑，人生的韧性与厚度却随着这些分别在不断增加。</p><p><em>“所以生命啊，它苦涩如歌”</em>，惟愿分别的人们未来各自安好，一路顺风。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510945.jpg" alt="793c6632aa420a518e3ea053b1176311" style="zoom:60%;" /><p>有离别的悲伤，也有重逢的喜悦，网易宣布了重新和暴雪展开合作。</p><p>当初高中时和朋友们在机房一起打《炉石传说》研究卡牌构筑的日子涌上心头，也回想起了那个暑假，和狗哥他们在《守望先锋》里一起开黑的时光。</p><blockquote><p>在回归留言里写下了《守望先锋》里大锤的话：</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510639.png" alt="image-20241231022811207" style="zoom:50%;" /></blockquote><p>从当初的游戏玩家，到现在的游戏开发者，或许我也始终还在做梦，自己能成为守护大家的英雄，能做出那样好玩的作品，见证每一位玩家的青春与时光，创造各种各样的记忆。</p><h2 id="五月-六月">五月~六月</h2><p>五月初和朋友们在广州相聚了一下，一起泡了泡温泉，恰了恰饭，回到科韵路圣地巡礼。</p><p>一切好像和往年一样，又有不一样的地方，这次同好会的人没有过去整齐。不过也深知在所难免，随着年纪逐渐增长，有太多的现实需要面对，越来越多的问题需要去解决。好像 <strong>时间催着每个人向前</strong>，去处理人生或者世俗的要事。</p><p>在小学时期就在游戏里结识的朋友，也在此期间摆脱了母胎 solo。翻阅起那些旧日的聊天记录，回忆起曾经分享动漫和日常琐事的时光，真是怀念那段纯真的岁月。可惜尽管曾约定见面，却总是因为忙碌而未能成行，还未见过就已经错过。</p><p>也许因为自己内心的自卑，或是不自觉的骄傲，又或许是曾经暗生的情愫，随着她迈入恋爱的新篇章，我也选择了默默减少联系。尽管如此，仍满怀真诚地希望她拥有一个充满幸福和美好的未来。</p><p>间歇中看完了《蓝色监狱》，其中绘心对于梦想的描述让人动容：</p><blockquote><p>梦想的意义在于实现，不知不觉间，追逐变成了意义所在。这种不断自我麻痹，沦为逐梦亡灵的垃圾，我一丁点兴趣都没有。</p><p>**梦想必须为实现而存在才有意义。**所以，落败是梦想的岔路口，他们必须足够坚强，能在这一刻深感自己能力不够，这是绝望的天赋。</p><p>有些可行的道路，只有放弃自己描绘的梦想之路才能看见，这将化为自己全新的可能性。</p><p>身陷绝望依然坚持战斗的人，怀有实现梦想的力量。</p></blockquote><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510653.png" alt="image-20241231032507641" style="zoom:40%;" /><p>自己的梦想，需要自己负责。不能总是用一些借口逃避，用虚假的努力安慰自己自我麻痹，假装自己在追求梦想的路上。</p><p>又看完了《钻石王牌》，以荣纯的视角，一个普通人逐渐努力成长为王牌，以队伍的视角，青道经历了各种失败，终于在秋季大赛打进了甲子园。</p><p>队伍里的每个人都有自己的梦想要追寻，这就是棒球，这就是体育，大家都有自己的奋斗故事。</p><blockquote><p>比起已经搞砸的过去，还有大概率会搞砸的未来，更重要的是现在，这个不知道会不会搞砸的瞬间。</p></blockquote><p>所有人努力毅力与坚持，共同谱写了这段美好的故事。</p><p><em>“为夏日之梦而生的人啊，为青涩灵魂点上熊熊烈火吧！”</em>，敬佩每一个为了梦想，明知不可为而为之的人。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310510151.jpg" alt="2cade7468b451e73a225ebf3ea6f1f56" style="zoom: 37%;" /><p>在某个思考着沉默着的夜晚，独自一人长跑去深圳湾边看海。</p><p>坐在岸边看着灰暗的天空，想起了三年前初来到深圳，也是在某个深夜坐在深圳湾边看着大海发呆。时间过去了这么久，我也和当初那个自己截然不同。川流不会停止，总在见证着些什么，又遗忘着什么。</p><p><strong>流水无情，除了记忆，什么也没有留下。</strong></p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310512906.png" alt="image-20241231051153689" style="zoom:45%;" /><h2 id="七月">七月</h2><p>经历了种种磨难，终于迎来了第一个参加的项目 *《极品飞车：集结》*上线的日子。</p><p>上线前花了点时间整理自己做的模块，导出了一张图表，感慨自己在这个项目里留下了这么多的内容。</p><p>还记得上线那天六点多起来，天还没亮就出发到公司，早早来等开服。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310512438.jpg" alt="Image_1720505403721" style="zoom: 20%;" /><p>可惜，故事的结局不总是那么美好。</p><blockquote><p>**努力就会得到回报，是一句狡猾的箴言。**回报的设定也许是在明天，也有可能会被拉长到永远。</p></blockquote><p>上线当天的数据并不让人满意，也因为实际福利与宣传大相径庭导致口碑崩坏。记得晚上一两点的时候，doctor 直接去和老板讨论福利相关的问题，不过并没有什么用，大家都有各自的坚持与需要考虑的问题。</p><p>晚上和他在公司楼下聊人生到凌晨四点，听着他的故事，也思考自己的道路。</p><p>天美是个大染缸，潜移默化地影响着每一位其中的从业者。日复一日的工作、加班和执行，最终将个性抹平，神经活性开始降低，停止了进化。</p><p>为了自我安慰，找升职、买房、结婚这些锚点，继续重复地生活，停止了思考。被这些 <strong>锚点</strong> 一辈子钉在原地。世界开始变小，小到只有家、工位和通勤的路。 时间开始变短，明天还未开始，就又和昨天类似。</p><p>尽管完全有能力和活力做更多的事情，却最终活成了一个程序化的人。</p><p>野狗会去追逐星星，明知不可为，也不愿停下。**追逐的路上有风雪艳阳，目尽远方是星河滚烫。**或许还真能抵达世界尽头，去传达几声犬吠。可自己又有多久没有看过天上的星星了，是否还记得自己的初心？</p><p>没有人天生就是程序员或游戏开发者，多少人被困在内心的栅栏之中无法迈出一步。</p><p>醒得太早不是件好事，或许一辈子没醒更是快乐。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310512369.jpg" alt="0f56b55d55ac9acb7253394e944d56dc" style="zoom:50%;" /><p>毕业后的第一个夏天，伴随着项目上线画上了句点。</p><p>这个夏天，结束得太快。</p><h2 id="八月-九月">八月 ~ 九月</h2><p>上线之后保持着基本的运营，也轻松了许多，更深知做游戏不易。</p><p>偶然又看到了当初写的第一个 3D 贪吃蛇 Demo，还有想做出让大家都快乐的游戏的宣言，感慨过去的活力。</p><p>项目的数据虽然不好，但还是让很多玩家体验到了自己参与的游戏，不幸却又很幸运。</p><p><strong>一路风雪，虽有遗憾却从未后悔</strong>。</p><p>趁着闲暇的时间充实了一波技术栈，逐渐把过去落下的东西给补上。了解了网络同步底层，也研究透了 GAS，输出了很多技术文章，在 Gameplay 和网络同步相关的技术上都更上一层楼。沉淀技术的同时，也时常找策划探讨各种游戏设计。</p><p>恰好黑神话上线，请假了几天通关了隐藏结局，感叹 3A 时代正在拉开帷幕，并由衷地希望自己真的有机会能参与到其中。</p><p>羡慕悟空相信 <strong>命不由天</strong> ，以肉身之死，换意念永续，最后命不由天、战胜天命。更羡慕最后的制作人员的名单里的游戏开发者们，有生之年可以留下这样一款游戏，青史留名。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310512821.png" alt="image-20241230015555150" style="zoom: 50%;" /><p>相比之下，大多数从业者看似在开发游戏，实际上只是在制造产品，加入了一个以产品为导向的公司。</p><p>真正的游戏人太少了，可能每个人入行时怀揣着开发游戏的梦想，但随着时间的推移，逐渐开始变形。日常生活下意识的努力都在向完善一张所谓的简历靠，大部分都在梦游。经历了几年的职场生活后，没有 <strong>根源的创作欲</strong> ，不是真的要创作游戏，而是要打好工。</p><blockquote><p>很喜欢肛少说的一段话：</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310512706.jpg" alt="68edcad0785722c3ca36583893c6a2cb" style="zoom:50%;" /></blockquote><p>做游戏终归是一条 <strong>逆行者之路</strong>，是真正的小众。纯粹的人太少了，或碍于现实，或囿于能力，或困于欲望。</p><p>常说 “如果……，一切会不会不一样”。人生就是这样，痛苦和魅力都在于这些无法回头的选择。徘徊于某个十字路口，选择了一条路，或左或右，就此开始了完全不一样的人生。</p><p>看《王室教师海涅》，很喜欢一句话：</p><blockquote><p>比如说，你在临死前的弥留之际，“啊啊，如果当时那样做就好了”，就算你这么想，时光也不会倒流。</p><p>属于你的珍贵人生只有一次，想要随心所欲的生活，根本不需要理由。关键是要看你的觉悟。</p><p>一旦是自己决定的事，无论一路上遇到多少挫折和绝望，我都要努力去克服。</p></blockquote><p>自己也还差得太远，仍需不断的努力。</p><h2 id="十月-十二月">十月~十二月</h2><p>国庆节家人来深圳旅游，做了周密的计划，一起逛了逛世界之窗，去看了看水族馆，爬了爬山也看了看海。</p><p>生活方面，一番折腾过后终于搬到了新家，常坐到天亮看着城市被曙光唤醒；娱乐方面，补上了很多过去没看过的老番，推理、运动、恋爱、异世界广泛涉猎；技术方面，基于学到的知识实现了一些新的工具，用新设计重构了过去的系统，也开始接触新方向的内容；游戏方面，体验了一些新作，沉迷酒馆战棋；炒股方面，经历了种种失败决定戒赌，等以后再出发；运动方面，第一次去健身房，希望可以继续坚持。就这样度过了今年的最后几个月。</p><p>在写年度总结的时候，也看到了别人的总结，看到也有人在一样挣扎痛苦着为工作所困，感慨万千。</p><p>不过希望这些失败与琐碎不会磨灭真正的热爱，希望当初写下 <em>“将此生献给游戏行业”</em> 的少年们都可以找回 <strong>初心与道路</strong>。再次找到做游戏最开始的快乐，让自己快乐，让大家快乐，让每一个玩家都快乐。</p><p><strong>“正确的道路，不会因为中途的坎坷而有所改变。”</strong></p><p>期间看到了《洛奇》的混剪，印象颇深，借鉴一下其中的话：</p><blockquote><p>这个世界并不全是阳光和彩虹，它充满温情而又冷酷无情。<br>挫折如疾风骤雨，击打我们的灵魂，磨损我们的意志。<br>真正的勇士并非从未失败，而是失败后选择依旧站起。<br>失败不是终点，放弃和绝望才是。<br>如果我们不尽力挣扎，它就会永久性地把我们揍得跪在地上。<br>强者迎风反抗，懦夫跪地投降。<br>重要的并不是出拳有多重，而是能挨多重的拳仍坚持向前，这样才叫胜利！</p></blockquote><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310524341.png" alt="GO72KU2DCHTHF1MFVPMJG}Q" style="zoom:33%;" /><h2 id="总结">总结</h2><p>毕业的第一年，在游戏、工作、人生等方面有了更多的思考。</p><p>在感情方面基本没有进展，自我总在孤独的静默中苏醒，一个人也过得很开心快乐。</p><p>参与的第一个大型项目*《极品飞车：集结》* 终于上线了，即便数据一般，但也让那么多的玩家体验到了自己做的游戏。</p><p><strong>清醒地挣扎，缓慢地前行。</strong></p><p>成长没有那么快，但坚持在路上，总能走到远方。即便过程中产生了很多负面情绪，也可以算是对热爱的事业为之动容的表现吧。</p><p>贪心地希望自己有朝一日可以做出更加满意的游戏，俘获更多的玩家的心，也收获更多的宝贵记忆。</p><p>毕竟，在这漫长又短暂的人生之旅中，总得留下几笔 <strong>浓墨重彩</strong> 吧？</p><p><em>“夜幕低垂，星辰方显璀璨。冬雪皑皑，暖阳更觉温柔。”</em></p><p>哪怕失败会是一路的主旋律，但 <strong>风霜岁月的淬炼之下，灵魂才更为坚韧。</strong></p><p>整理好行装，准备再次出发踏上新的征途吧！</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202412310524983.jpg" alt="6cc6dbde0c2675ec2ded571ecb257397"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;2024 年度总结&lt;/h1&gt;
&lt;h2 id=&quot;一月-二月&quot;&gt;一月~二月&lt;/h2&gt;
&lt;p&gt;在反思中开始了新的一年，今年不断思考，经历无数的迷茫，缓慢前行。&lt;/p&gt;
&lt;p&gt;开年经历了各种各样的事件后，在多少个深夜不断思考，剖析自我。&lt;/p&gt;
&lt;p&gt;恰逢《我的青春恋爱物语果然有</summary>
      
    
    
    
    <category term="日志" scheme="https://blog.bearchild.top/categories/%E6%97%A5%E5%BF%97/"/>
    
    
    <category term="日志" scheme="https://blog.bearchild.top/tags/%E6%97%A5%E5%BF%97/"/>
    
  </entry>
  
  <entry>
    <title>[UE]FModel资源解包流程</title>
    <link href="https://blog.bearchild.top/2024/08/30/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E9%80%86%E5%90%91/[UE]FModel%E8%B5%84%E6%BA%90%E8%A7%A3%E5%8C%85%E6%B5%81%E7%A8%8B/"/>
    <id>https://blog.bearchild.top/2024/08/30/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E9%80%86%E5%90%91/[UE]FModel%E8%B5%84%E6%BA%90%E8%A7%A3%E5%8C%85%E6%B5%81%E7%A8%8B/</id>
    <published>2024-08-29T16:00:00.000Z</published>
    <updated>2024-09-02T15:37:52.030Z</updated>
    
    <content type="html"><![CDATA[<h1>FModel资源解包流程</h1><p>基于 <code>FModel</code> 的资源解包方案</p><h2 id="USMAP-生成">USMAP 生成</h2><p>通过 <code>DLL</code> 注入工具 <a href="https://dllinjector.net/">DLL Injector</a> 将 <a href="https://github.com/TheNaeem/UnrealMappingsDumper/releases">UnrealMappingsDumper</a> 注入到对应的游戏进程中：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190332145.png" alt="image-20240827190332145"></p><p><code>Inject</code> 后，会弹框并生成对应的 <code>.usmap</code> 文件</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190448051.png" alt="image-20240827190448051"></p><h2 id="AES-Key">AES Key</h2><p>下载 <a href="https://cs.rin.ru/forum/viewtopic.php?f=10&amp;t=100672">AES_finder</a>，将 <code>AES_finder.exe</code> 放在对应游戏目录下并运行，得到 <code>key.txt</code>，其中的内容就是 <code>AES Key</code>，即 <code>Advanced Encryption Standard</code> 密钥；</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190519480.png" alt="image-20240827190519480"></p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827162259155.png" alt="image-20240827162259155"></p><h2 id="FModel">FModel</h2><p>下载 <a href="https://fmodel.app/">FModel</a>，将目录选定到需要解包的游戏目录，设置对应的 <code>UE Versions</code>：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190654658.png" alt="image-20240827190654658"></p><p>打开后，可能会弹出如下报错：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827160424229.png" alt="image-20240827160424229"></p><p>检查如下目录：<code>./FModel/Output/.data</code> ，发现存在未解压的压缩包，将其解压后 <code>Restart</code>：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190745629.png" alt="image-20240827190745629"></p><p>正常打开后设置对应的 <code>Mapping File</code> 为之前生成的 <code>.usmap</code>：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827190823448.png" alt="image-20240827190823448"></p><p>然后会发现左侧已经识别到对应的 <code>.pak</code>，但是 <code>[WRN] Could not load virtual paths, plugin manifest may not exist</code>，此时设置之前对应的 <code>AES Key</code> 即可：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827171717461.png" alt="image-20240827171717461"></p><p>然后可以看到获得了对应的 <code>.pak</code>：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240827164400003.png" alt="image-20240827164400003"></p><h2 id="参考">参考</h2><p><a href="https://www.bilibili.com/video/BV1aQWnewEGR/">AES获取与UModel解包</a></p><p><a href="https://www.bilibili.com/video/BV12NWGehE8Z/">FModel解包教程与资产检索</a></p><p><a href="https://www.bilibili.com/read/cv37491469/">《黑神话：悟空》MOD制作：模型替换教程</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;FModel资源解包流程&lt;/h1&gt;
&lt;p&gt;基于 &lt;code&gt;FModel&lt;/code&gt; 的资源解包方案&lt;/p&gt;
&lt;h2 id=&quot;USMAP-生成&quot;&gt;USMAP 生成&lt;/h2&gt;
&lt;p&gt;通过 &lt;code&gt;DLL&lt;/code&gt; 注入工具 &lt;a href=&quot;https://dll</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="逆向" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E9%80%86%E5%90%91/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="逆向" scheme="https://blog.bearchild.top/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title>[UE]网络时钟对齐解决方案</title>
    <link href="https://blog.bearchild.top/2024/08/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]%E7%BD%91%E7%BB%9C%E6%97%B6%E9%92%9F%E5%AF%B9%E9%BD%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.bearchild.top/2024/08/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]%E7%BD%91%E7%BB%9C%E6%97%B6%E9%92%9F%E5%AF%B9%E9%BD%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-08-14T16:00:00.000Z</published>
    <updated>2024-08-15T14:21:18.515Z</updated>
    
    <content type="html"><![CDATA[<h1>网络时钟对齐解决方案</h1><p>由于网络延迟等问题，<code>Client</code> 时间与 <code>DS</code> 时间可能不同。</p><p>对于 <code>Client</code> 需要有一个接近正确的 <code>GetServerWorldTime</code> 来获取当前 <code>DS</code> 的时间戳，尽可能保证在 <code>DS</code> 与各个不同的 <code>Client</code> 中，同一时刻该值唯一。</p><h2 id="GameInstance">GameInstance</h2><p>通过 <code>GameInstance</code> 来存储一些时间数据记录，最后保证所有读取数据从这里访问：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int64 StartTicks = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">float</span> WorldSeconds = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">float</span> ServerWorldSeconds = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>首先在 <code>UGameInstance::Init()</code> 时，记录 <code>StartTicks = FDateTime::Now().GetTicks()</code>；</p><p>后续在 <code>GeServerWorldTimeSeconds</code> 时，对 <code>ServerWorldSeconds</code>、<code>WorldSeconds</code> 进行更新；</p><h2 id="ServerTimeSynchronizer">ServerTimeSynchronizer</h2><h3 id="Register">Register</h3><p>在 <code>Client</code> 的 <code>PlayerController</code> 成功 <code>Received</code> 时，进行对时校验的注册，每隔 <code>SyncServerTime_TimeInterval = 5.0f</code> 进行一次对时；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::ReceivedPlayer</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsLocalController</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ClientStartSyncTime</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::ClientStartSyncTime</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">GetNetMode</span>() != NM_Client) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">GetWorld</span>()-&gt;<span class="built_in">GetTimerManager</span>().<span class="built_in">SetTimer</span>(SyncServerTime_TimeHandle, [WeakSelfPtr = TWeakObjectPtr&lt;APlayerController&gt;(<span class="keyword">this</span>)]()</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (!WeakSelfPtr.<span class="built_in">IsValid</span>() || WeakSelfPtr-&gt;<span class="built_in">GetNetMode</span>() != NM_Client) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!WeakSelfPtr-&gt;ServerTimeSynchronizer.<span class="built_in">IsTimeSynced</span>())</span><br><span class="line">&#123;</span><br><span class="line">WeakSelfPtr-&gt;<span class="built_in">C2S_ReqReportTime_Reliable</span>(FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>());</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">WeakSelfPtr-&gt;<span class="built_in">C2S_ReqReportTime_Unreliable</span>(FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>());</span><br><span class="line">&#125;</span><br><span class="line">&#125;, SyncServerTime_TimeInterval, <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Req-Res">Req &amp; Res</h3><p>通过 <code>Client</code> 定时发起对时请求 <code>C2S_ReqReportTime</code>，<code>DS</code> 接收到请求后进行回复 <code>S2C_ResReportTime</code>（如果从未进行过对时，则需要 <code>Reliable</code>）；</p><p>在 <code>Client</code> 收到 <code>Res</code> 之后，可以根据发包、收包的时间差，不断校准 <code>ServerTime</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UFUNCTION</span>(Reliable, Server)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">C2S_ReqReportTime_Reliable</span><span class="params">(int64 ClientTime)</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>(Unreliable, Server)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">C2S_ReqReportTime_Unreliable</span><span class="params">(int64 ClientTime)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OnReceivedServerTime</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>(Reliable, Client)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">S2C_ResReportTime_Reliable</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span>;</span><br><span class="line"><span class="built_in">UFUNCTION</span>(Unreliable, Client)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">S2C_ResReportTime_Unreliable</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span>;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::C2S_ReqReportTime_Reliable_Implementation</span><span class="params">(int64 ClientTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UGameInstance* GameInstance = <span class="built_in">GetGameInstance</span>();</span><br><span class="line"><span class="keyword">if</span> (GameInstance == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (GameInstance-&gt;<span class="built_in">IsDedicatedServerInstance</span>() &amp;&amp; GameInstance-&gt;StartTicks &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">S2C_ResReportTime_Reliable</span>(ClientTime, FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>() - GameInstance-&gt;StartTicks);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::C2S_ReqReportTime_Unreliable_Implementation</span><span class="params">(int64 ClientTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UGameInstance* GameInstance = <span class="built_in">GetGameInstance</span>();</span><br><span class="line"><span class="keyword">if</span> (GameInstance == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (GameInstance-&gt;<span class="built_in">IsDedicatedServerInstance</span>() &amp;&amp; GameInstance-&gt;StartTicks &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">S2C_ResReportTime_Unreliable</span>(ClientTime, FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>() - GameInstance-&gt;StartTicks);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// --------------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::OnReceivedServerTime</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">int64 ClientNow = FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>();</span><br><span class="line">int64 RTT = ClientNow - ClientTime;</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> bUpdated = ServerTimeSynchronizer.<span class="built_in">UpdateServerTime</span>(ServerTime, ClientNow, RTT);</span><br><span class="line">int64 EstimatedServerTime = ServerTimeSynchronizer.<span class="built_in">CurrentServerTime</span>(ClientNow);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!ServerTimeSynchronizer.<span class="built_in">IsTimeSynced</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">C2S_ReqReportTime_Reliable</span>(FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>());</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::S2C_ResReportTime_Reliable_Implementation</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">OnReceivedServerTime</span>(ClientTime, ServerTime);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerController::S2C_ResReportTime_Unreliable_Implementation</span><span class="params">(int64 ClientTime, int64 ServerTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">OnReceivedServerTime</span>(ClientTime, ServerTime);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Calculate">Calculate</h3><p>通过 <code>FServerTimeSynchronizer</code> 记录时间并辅助对时；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ENGINE_API</span> <span class="title">FServerTimeSynchronizer</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">FServerTimeSynchronizer</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">int64 <span class="title">CurrentServerTime</span><span class="params">(int64 TimeNow = <span class="number">0</span>)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UpdateServerTime</span><span class="params">(int64 ServerTime, int64 CurrentTime, int64 LatestRTT)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsTimeSynced</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> bTimeSynced; &#125; <span class="comment">// 进行过时钟对齐</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function">int32 <span class="title">CalculateApproximatingDeltaTime</span><span class="params">(int64 TimeDelta)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">int</span> MAX_VALID_RTT = <span class="number">500</span>; <span class="comment">// 最大能容忍的RTT时间, 超过该时间不作为有效校准数值 (单位 MS)</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">float</span> APPROXIMATING_RATE = <span class="number">0.33</span>; <span class="comment">// 逼近率 (每个单位时间客户端需要逼近服务器的值)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">int64 ClientTime = <span class="number">0</span>;</span><br><span class="line">int64 EstimatedServerTime = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">int</span> LatestRTT = INT_MAX;</span><br><span class="line"><span class="keyword">float</span> DeltaTimeClientToServer = <span class="number">0.0</span>;</span><br><span class="line"><span class="keyword">bool</span> bTimeSynced = <span class="literal">false</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><pre class="mermaid">flowchart LRClient_Req--->|RTT|DSDS--->|RTT|Client_Res</pre><p>每次收到包进行 <code>UpdateServerTime</code>，根据时间差计算出 <code>RTT (收包发包时间差 / 2)</code> 加上当时准确的 <code>ServerTime</code>（<code>RPC</code> 带下来的），可以计算出此时客户端对应的 <code>EstimatedServerTime</code>（此时预估的 <code>ServerTime</code>）；同时记录下这次的 <code>ClientTime</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FServerTimeSynchronizer::UpdateServerTime</span><span class="params">(int64 ServerTime, int64 CurrentTime, int64 RTT)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">// 丢弃过大的 RTT 时间</span></span><br><span class="line"><span class="keyword">if</span> (RTT &gt; MAX_VALID_RTT * ETimespan::TicksPerMillisecond) </span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line"></span><br><span class="line"><span class="comment">// 仅用更小的 RTT 进行当前 EstimatedServerTime 的更新</span></span><br><span class="line"><span class="keyword">if</span> (RTT &gt; LatestRTT) </span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line"></span><br><span class="line">int64 LastEstimatedServerTime = <span class="built_in">CurrentServerTime</span>(CurrentTime);</span><br><span class="line">ClientTime = CurrentTime;</span><br><span class="line">EstimatedServerTime = ServerTime + RTT / <span class="number">2</span>;</span><br><span class="line">LatestRTT = RTT;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 首次更新 ServerTime 或 APPROXIMATING_RATE &lt;= 0, 无需时钟逼近</span></span><br><span class="line"><span class="keyword">if</span> ( !bTimeSynced || APPROXIMATING_RATE &lt;= <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">DeltaTimeClientToServer = <span class="number">0.0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 之后的计算逼近到以 EstimatedServerTime 为基准的时间轴</span></span><br><span class="line">DeltaTimeClientToServer = (<span class="keyword">float</span>)(LastEstimatedServerTime - EstimatedServerTime);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> bTimeSynced = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就可以在后续任何一次查询时，根据查询时的 <code>ClientTime</code> ，与本次对时的预估 <code>ServerTime</code>、 <code>ClientTime</code> 计算出期望的 <code>ServerTime</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">int64 <span class="title">FServerTimeSynchronizer::CurrentServerTime</span><span class="params">(int64 TimeNow)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!bTimeSynced) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">int64 TimeDelta = FMath::<span class="built_in">Max</span>( TimeNow - ClientTime, <span class="number">0ll</span> );</span><br><span class="line"><span class="keyword">return</span> EstimatedServerTime + TimeDelta + <span class="built_in">CalculateApproximatingDeltaTime</span>(TimeDelta);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时，这里引入一个 <code>APPROXIMATING_RATE</code>，进行一定的时间预测逼近，在 <code>DeltaTime</code> 足够小的时候，根据 <code>DeltaTimeClientToServer</code>（客户端与服务器的预测时间差值）进行一定的时间外推 / 内收，让结果尽可能准确。这里 <code>DeltaTimeClientToServer &lt; 0</code> 说明客户端时间比服务器慢，则需要一定的加快。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">int32 <span class="title">FServerTimeSynchronizer::CalculateApproximatingDeltaTime</span><span class="params">(int64 TimeDelta)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (APPROXIMATING_RATE &lt;= <span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">float</span> fEls = <span class="built_in"><span class="keyword">float</span></span>(TimeDelta) * APPROXIMATING_RATE;</span><br><span class="line"><span class="keyword">if</span> (fEls &gt;= FMath::<span class="built_in">Abs</span>(DeltaTimeClientToServer))</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> DeltaTimeClientToServer &lt; <span class="number">0</span> ? <span class="built_in">ceil</span>(DeltaTimeClientToServer + fEls) : <span class="built_in">ceil</span>(DeltaTimeClientToServer - fEls);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="GetServerTimeTicks">GetServerTimeTicks</h3><p>最后在 <code>PlayerController</code> 暴露 <code>GetServerTimeTicks</code> 给外部访问：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">int64 <span class="title">APlayerController::GetServerTimeTicks</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> ServerTimeSynchronizer.<span class="built_in">CurrentServerTime</span>(FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="GetServerWorldTimeSeconds">GetServerWorldTimeSeconds</h2><p>对 <code>AGameStateBase::GetServerWorldTimeSeconds</code> 进行重载，最后统一通过 <code>WorldGameState</code> 进行时间访问；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">AGameStateBase::GetServerWorldTimeSeconds</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    UWorld* World = <span class="built_in">GetWorld</span>();</span><br><span class="line">    <span class="keyword">if</span> (World == <span class="literal">nullptr</span>) <span class="keyword">return</span> <span class="number">0.</span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> (UGameInstance* GameInstance = <span class="built_in">GetGameInstance</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">float</span> NowSeconds = <span class="built_in">GetWorld</span>()-&gt;TimeSeconds;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Update ServerWorldSeconds</span></span><br><span class="line"><span class="keyword">if</span> (NowSeconds != GameInstance-&gt;WorldSeconds)</span><br><span class="line">&#123;</span><br><span class="line">GameInstance-&gt;WorldSeconds = NowSeconds;</span><br><span class="line"></span><br><span class="line">int64 NowServerTicks = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (GameInstance-&gt;<span class="built_in">IsDedicatedServerInstance</span>() || <span class="built_in">HasAuthority</span>())</span><br><span class="line">&#123;</span><br><span class="line">NowServerTicks = FDateTime::<span class="built_in">Now</span>().<span class="built_in">GetTicks</span>() - GameInstance-&gt;StartTicks;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (APlayerController* PC = <span class="built_in">GetGameInstance</span>()-&gt;<span class="built_in">GetFirstLocalPlayerController</span>(<span class="built_in">GetWorld</span>()))</span><br><span class="line">&#123;</span><br><span class="line">NowServerTicks = PC-&gt;<span class="built_in">GetServerTimeTicks</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">GameInstance-&gt;ServerWorldSeconds = NowServerTicks / ETimespan::TicksPerMillisecond / <span class="number">1000.f</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> GameInstance-&gt;ServerWorldSeconds;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> World-&gt;<span class="built_in">GetTimeSeconds</span>() + ServerWorldTimeSecondsDelta;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;网络时钟对齐解决方案&lt;/h1&gt;
&lt;p&gt;由于网络延迟等问题，&lt;code&gt;Client&lt;/code&gt; 时间与 &lt;code&gt;DS&lt;/code&gt; 时间可能不同。&lt;/p&gt;
&lt;p&gt;对于 &lt;code&gt;Client&lt;/code&gt; 需要有一个接近正确的 &lt;code&gt;GetServerWor</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]ReplicationGraph应用</title>
    <link href="https://blog.bearchild.top/2024/08/07/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]ReplicationGraph%E5%BA%94%E7%94%A8/"/>
    <id>https://blog.bearchild.top/2024/08/07/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]ReplicationGraph%E5%BA%94%E7%94%A8/</id>
    <published>2024-08-06T16:00:00.000Z</published>
    <updated>2025-04-01T16:14:31.438Z</updated>
    
    <content type="html"><![CDATA[<h1>ReplicationGraph应用</h1><h2 id="简介">简介</h2><p><code>UnrealEngine</code> 基本的网络同步在进行同步时需要对每个连接判断每个 <code>Actor</code> 是否需要同步，开销较大；</p><p>可以通过实现 <code>Replication</code> ，实现一个不同的 <code>ReplicationDriver</code> 来优化性能；</p><p><code>Replication</code> 将 <code>World</code> 分为多个区域 <code>Grid</code>，把 <code>Actor</code> 根据所在 <code>Grid</code> 进行分类编组，可以快速找出需要同步的 <code>Actor</code>，同时确定哪些区域需要进行同步复制。</p><h2 id="基本原理">基本原理</h2><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240807154320652.png" alt="image-20240807154320652"></p><p>对于需要按照空间划分（<code>Spatialize</code>）的 <code>Actor</code> 来说：</p><p>首先将 <code>World</code> 按照 <code>CellSize</code> 抽象成一个个 <code>Cell</code>，一个 <code>Cell</code> 对应一个 <code>GridNode</code>，这里的 <code>GridNode</code> 只会在具体用到的时候再创建；</p><p>那么对于一个 <code>Actor</code>，根据 <code>Location</code>、<code>CellSize</code>、<code>SpatialBias</code>，就可以计算出其所在的具体 <code>Cell</code>，即 <code>GetCellInfoForActor</code>；</p><h3 id="构建">构建</h3><p>构建 <code>ReplicationGraph</code> 时，在 <code>GridNode</code> 中记录 <code>Actor</code>；</p><p>对于 <code>Actor</code>，就可以根据 <code>CellInfo</code>、以及  <code>CullDistance</code> 找到需要同步该 <code>Actor</code> 的 <code>Cell</code>，这里 <code>Cell</code> 对应的 <code>Grid</code> 中会把该 <code>Actor</code> 记录下来；</p><p>也就是上述的粉色区域部分，每个 <code>Grid</code> 都会记录下该 <code>Actor</code>；</p><p>对于 <code>Spatialize_Static</code>，即静态物体，显然只需要 <code>Add</code> 时计算一次即可；</p><p>对于 <code>Spatialize_Dynamic</code>，即动态物体，会在每次开始 <code>PrepareForReplication</code> 时，根据 <code>DynamicActor</code> 的 <code>PreviousCellInfo</code>、<code>NewCellInfo</code>，更新对应的 <code>Grid</code> 存储信息；</p><h3 id="查询">查询</h3><p>在对于一个 <code>Connection</code> 查询哪些 <code>Actor</code> 需要同步时：</p><p><code>UpdateGatherLocationsForConnection</code> 更新 <code>Connection</code> 对应的 <code>Location</code></p><p>根据该 <code>Location</code> 计算出该 <code>Connection</code> 对应的 <code>Cell</code>，把 <code>Cell</code> 对应的 <code>GridNode</code> 中的 <code>Actors</code> 同步下去即可；</p><h2 id="源码浅析">源码浅析</h2><h3 id="数据结构">数据结构</h3><pre class="mermaid">classDiagram    class UReplicationGraph</pre><ol><li><code>FGlobalActorReplicationInfo</code>：同步的 <code>Actor</code> 信息，包括</li></ol><blockquote><p><code>Settings</code>：<code>FClassReplicationInfo</code>，每个 <code>Actor Class</code> 对应的同步配置，包括 <code>CullDistance</code>、<code>ReplicationPeriodFrame</code>、<code>PriorityScale</code> 等；</p><p><code>Events</code>：<code>FGlobalActorReplicationEvents Events</code>，记录休眠状态的改变时，从休眠列表中添加或者移除；</p><p><code>FConnectionReplicationActorInfo</code>：对于 <code>Connection</code> 同步的 <code>Actor</code> 信息；</p></blockquote><ol start="3"><li><p><code>ReplicationGraphNode</code>：<code>GraphNode</code> 基类；</p></li><li><p><code>ReplicationGraphNode_ActorList</code>：记录同步的 <code>Actors</code>；在 <code>StreamingLevelCollection</code> （以 <code>SubLevelName</code> 为 <code>Key</code> 的 <code>List</code>）中记录 <code>SubLevel</code> 的 <code>Actor</code>，否则在 <code>ReplicationActorList</code> 中记录；</p></li><li><p><code>GlobalGraphNodes</code>：维护 <code>GridSpatialization2D</code>；</p></li></ol><h3 id="GraphNode">GraphNode</h3><pre class="mermaid">classDiagram    class UReplicationGraphNode {        TArray~UReplicationGraphNode*~ AllChildNodes        GatherActorListsForConnection()        RouteAddNetworkActorToNodes()        RouteRemoveNetworkActorToNodes()    }    UReplicationGraphNode <|-- UReplicationGraphNode_ActorList     class UReplicationGraphNode_ActorList {    FActorRepListRefView ReplicationActorList    }            UReplicationGraphNode <|-- UReplicationGraphNode_GridSpatialization2D    class UReplicationGraphNode_GridSpatialization2D {        TArray~TArray[UReplicationGraphNode_GridCell*]~ Grid;    }        UReplicationGraphNode <|-- UReplicationGraphNode_AlwaysRelevant    class UReplicationGraphNode_AlwaysRelevant{        TArray~UClass*~AlwaysRelevantClasses;    }        UReplicationGraphNode <|-- UReplicationGraphNode_ActorListFrequencyBuckets         UReplicationGraphNode_ActorList <|-- UReplicationGraphNode_GridCell        UReplicationGraphNode_ActorList <|-- UReplicationGraphNode_AlwaysRelevant_ForConnection</pre><ol><li><code>GridSpatalization2D</code>：将世界划分为 <code>2D</code> 网格，按位置把 <code>Actor</code> 分到不同的 <code>GridCell</code> 中，按空间管理<code>Actor</code> 是否进行同步，每帧更新 <code>GridCell</code> 内的 <code>Actor</code>；</li><li><code>GridCell</code>：<code>ReplicationActorList</code> 缓存着在该 <code>GridCell</code> 中的所有静态 <code>Actor</code>，<code>DynamicNodes</code> 里记录动态的 <code>Actor</code>，<code>DormancyNode</code> 里记录休眠的<code>Actor</code>；</li><li><code>AlwaysRelevant </code>：处理总是发送 <code>Net Updates</code> 给 所有 <code>Connections</code> 的 <code>Actors</code>；</li><li><code>AlwaysRelevant_ForConnection</code>：处理总是发送 <code>Net Updates</code> 给 特定 <code>Connection</code> 的 <code>Actors</code> ，一般是同步给 <code>PlayerController</code> 和 <code>ViewTarget</code>；</li><li><code>ActorListFrequencyBuckets </code>：记录地图格子上的 动态 <code>Actor</code> ;</li></ol><h3 id="生命周期">生命周期</h3><h4 id="Init">Init</h4><pre class="mermaid">graph TDStart(UNetDriver::InitBase) --> A("UNetDriver::SetReplicationDriver")A --> B("UReplicationGraph::InitializeActorsInWorld")B --> | 将World中的同步对象添加到对应GraphNode | C("UReplicationGraph::InitializeForWorld")C --> D("UReplicationGraph::AddNetworkActor(AActor* Actor)") A --> E(InitForNetDriver)E --> F(InitGlobalActorClassSettings)E --> G(InitGlobalGraphNodes)</pre><p><code>InitGlobalActorClassSettings</code>：设置 <code>CulltDistance</code>、<code>ReplicationPeriodFrame</code>； 等信息注册 <code>Actor</code> 对应的 <code>ClassReplicationInfo</code> 到 <code>GlobalActorReplicationInfoMap</code>；</p><p><code>InitGlobalGraphNodes</code>：创建 <code>GridSpatialization2D</code>、<code>AlwaysRelevant</code> 的 <code>GraphNode</code>；</p><p><code>InitConnectionGraphNodes</code>：创建 <code>AlwaysRelevantForConnection</code> 的 <code>GraphNode</code>；</p><p><code>RouteAddNetworkActorToNodes </code>：生成 <code>Actor</code> 时，添加 <code>NetworkActor</code>，分发 <code>Actor</code> 到 <code>GraphNode</code>；</p><p><code>RouteRemoveNetworkActorToNodes </code>：销毁 <code>Actor</code> 时，删除 <code>NetworkActor</code>，通知<code>GraphNode</code> 移除 <code>Actor</code>；</p><h4 id="Repliate">Repliate</h4><pre class="mermaid">graph TBStart(ServerReplicateActors) --> A(PrepareForReplication)A --> B(GatherActorListsForConnection)B --> C(ReplicateActorListsForConnections_Default)C --> D[ReplicateSingleActor]</pre><ol><li><p><code>PrepareForReplication</code> ： 调用 <code>GraphNode</code> 的 <code>PrepareForReplication</code>：</p><p>对于 <code>GridSpatialization2D</code>，会遍历 <code>DynamicActor</code>，更新其所在的 <code>Grid</code>；</p><p>对于 <code>AlwaysRelevant</code>，记录需要同步给所有连接的 <code>Actor</code>；</p></li><li><p><code>GatherActorListsForConnection</code> ： 遍历 <code>Connections</code> 收集 <code>ReplicationActorList</code></p><p>针对每个 <code>Connection</code> 遍历 <code>GlobalGraphNodes</code> 和 <code>Connection</code> 的<code>ConnectionGraphNodes</code>，调用其 <code>GatherActorListsForConnection</code>，收集需要同步给这个<code>Connection</code> 的 <code>Actor</code>；</p><p>对于 <code>GridSpatialization2D</code>，通过其 <code>GridCellNode</code> 根据 <code>Actor</code> 的<code>ViewLocation</code> 收集；</p><p>收集的 <code>Actor</code> 默认加到 <code>GatheredReplicationListsForConnection</code> 里的 <code>EActorRepListTypeFlags.Default List</code>；</p></li><li><p><code>ReplicateActorListsForConnections_Default</code>：进行 <code>Replicate</code> 的同步检测与排序，对 <code>GatheredReplicationListsForConnection</code> 里的 <code>Actor</code>，进行检测；</p><p>首先排除 <code>Dormancy</code>、不满足<code>ReplicateFrame</code> 的 <code>Actor</code>，然后根据优先级排序（<code>Distance</code>、<code>Starvation</code>、<code>逻辑判定</code>、<code>Owner &amp; ViewTarget</code>)，将结果缓存在 <code>PrioritizedReplicationList</code> 中；</p></li><li><p><code>ReplicateSingleActor</code>：对排好序的 <code>PrioritizedReplicationList</code> 调用 <code>ReplicateSingleActor</code> 进行同步，将对象属性序列化到流中；</p></li></ol><h2 id="具体应用">具体应用</h2><h3 id="业务划分">业务划分</h3><p>可以按照这些逻辑区分各个 <code>Actor</code>：</p><blockquote><ol><li><code>Node_AlwaysRelevant_ForConnection</code>：一直同步的 <code>Actor</code>，比如 <code>GameState</code>、全局同步对象；</li><li><code>Node_GridSpatialization2D</code>：用 <code>2D Grid</code> 划分，按照区域同步，比如 <code>Pawn</code>、<code>Character</code> 、<code>SceneItem</code> 等；</li><li><code>Node_PlayerStateFrequencyLimiter</code>：用于 <code>PlayerState</code>，控制同步频率；</li></ol></blockquote><h3 id="初始化">初始化</h3><p>在一个尽早的合适的地方注册<code>UReplicationDriver::CreateReplicationDriverDelegate()</code>；</p><p><code>Lyra</code> 在 <code>ULyraReplicationGraph</code> 的构造函数中（创建 <code>CDO</code> 时）进行；</p><p>后续 <code>UNetDriver::InitBase -&gt; SetReplicationDriver</code> 时，会使用该方法进行自定义 <code>ReplicationGraph</code> 的注册：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">UReplicationDriver* <span class="title">UReplicationDriver::CreateReplicationDriver</span><span class="params">(UNetDriver* NetDriver, <span class="keyword">const</span> FURL&amp; URL, UWorld* World)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">CreateReplicationDriverDelegate</span>().<span class="built_in">IsBound</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">CreateReplicationDriverDelegate</span>().<span class="built_in">Execute</span>(NetDriver, URL, World);</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置化">配置化</h3><p>可以参考 <code>Lyra</code> 注册 <code>ULyraReplicationGraphSettings : UDeveloperSettingsBackedByCVars</code> 在 <code>Project Settings</code> 中，也可以自定义一个 <code>Blueprint</code> 用于记录所有相关配置（在初始化时，重载为初始化该 <code>BP</code>）；</p><h4 id="InitGlobalActorClassSettings">InitGlobalActorClassSettings</h4><p>进行 <code>Class</code> 相关设置：</p><ol><li><code>SetPolicy</code></li></ol><p>记录 <code>TClassMap&lt;EClassRepNodeMapping&gt; ClassRepNodePolicies</code> 用于表示 <code>Class</code> 对应的 <code>Node Policy</code>；</p><p>在 <code>RouteAddNetworkActorToNodes</code>、<code>RouteRemoveNetworkActorToNodes</code> 时，通过 <code>GetMappingPolicy(UClass* Class)</code> 找到对应 <code>Policy</code> 的 <code>Node</code> 对应方法；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">EClassRepNodeMapping* PolicyPtr = ClassRepNodePolicies.<span class="built_in">Get</span>(Class);</span><br><span class="line">EClassRepNodeMapping Policy = PolicyPtr ? *PolicyPtr : EClassRepNodeMapping::NotRouted;</span><br><span class="line"><span class="keyword">return</span> Policy;</span><br></pre></td></tr></table></figure><p>可以提供自定义 <code>Policy</code> 的配置，同时需要通过 <code>Class</code> 的一些基础配置设置好 <code>Policy</code>，比如 <code>bAlwaysRelevant &amp;&amp; !bOnlyRelevantToOwner</code> 显然是 <code>RelevantAllConnections</code>；</p><ol start="2"><li><code>SetClassInfo</code></li></ol><p><code>FGlobalActorReplicationInfoMap GlobalActorReplicationInfoMap</code> 记录着所有 <code>Class</code> 对应的同步数据，</p><p>通过将自定义数据 <code>GlobalActorReplicationInfoMap.SetClassInfo(Class, Info)</code>，并记录 <code>ExplicitlySetClasses</code> 表示该 <code>Class</code> 为自定义类型，防止后续被重载；</p><p>可以提供配置化的 <code>Class-&gt;FClassReplicationInfo Map</code>；</p><h4 id="InitGlobalGraphNodes">InitGlobalGraphNodes</h4><p>进行 <code>ReplicationNode</code> 相关设置，<code>CreateNode</code> 并进行参数设置：</p><ol><li>设置 <code>GridNode</code> 的 <code>CellSize</code>（每个 <code>Grid</code> 的大小）、<code>SpatialBias</code>（边界值，<code>ActorLocation</code> 超出时会调用 <code>HandleActorOutOfSpatialBounds</code> 设置 <code>bNeedReubild</code> 并重构 <code>Grid</code>），是否 <code>EnableSpatialRebuilds</code>（决定 <code>AddToClassRebuildDenyList</code>）；</li><li>设置 <code>PlayerStateNode</code> 的 <code>TargetActorsPerFrame</code>；</li></ol><p>同时可以进行一些额外的参数设置，比如自定义了上层同步频率等 ；</p><h3 id="同步频率控制">同步频率控制</h3><p>控制同步频率和总量；</p><h4 id="ClassLimit">ClassLimit</h4><p>进行 <code>ReplicateActorListsForConnections_Default</code>，在 <code>ReplicateActorsForConnection</code> 时，可以针对 <code>RepItem.Actor</code> 的类型进行限制，直接限制该次同步该 <code>ActorClass</code> 可同步的总数量；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsClassLimitExceeded</span><span class="params">(AActor* Actor)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Actor)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">auto</span> CountPtr = ClassLimitMap.<span class="built_in">Find</span>( Actor-&gt;<span class="built_in">GetClass</span>() );</span><br><span class="line">    <span class="keyword">if</span> (CountPtr == <span class="literal">nullptr</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">return</span> (*CountPtr)-- &gt; <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ReplicateActorsForConnection</span><span class="params">(UNetConnection* NetConnection, FPerConnectionActorInfoMap&amp; ConnectionActorInfoMap, UNetReplicationGraphConnection* ConnectionManager, <span class="keyword">const</span> uint32 FrameNum)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="built_in">ResetClassLimit</span>(); <span class="comment">// 重置可同步总量</span></span><br><span class="line">    <span class="keyword">for</span> (int32 ActorIdx = <span class="number">0</span>; ActorIdx &lt; PrioritizedReplicationList.Items.<span class="built_in">Num</span>(); ++ActorIdx)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">const</span> FPrioritizedRepList::FItem&amp; RepItem = PrioritizedReplicationList.Items[ActorIdx];</span><br><span class="line">        AActor* Actor = RepItem.Actor;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">if</span> (ActorInfo.Channel &amp;&amp; <span class="built_in">IsClassLimitExceeded</span>(Actor) <span class="comment">/* 限制同步数量 */</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Node-PlayerStateFrequencyLimiter">Node_PlayerStateFrequencyLimiter</h4><p>引入 <code>Node_PlayerStateFrequencyLimiter</code> 用于控制 <code>PlayerState</code> 的同步频率，本质是对某一类 <code>Class</code>（可能是不被 <code>AOI</code> 管理，需要一直同步的），进行同步降频；</p><p>维护 <code>TArray&lt;FActorRepListRefView&gt; ReplicationActorLists</code>，每个 <code>FActorRepListRefView</code> 中记录最多 <code>TargetActorsPerFrame</code> 个 <code>Actor</code>，在同步时，对 <code>ReplicationActorLists[ Params.ReplicationFrameNum % ReplicationActorLists.Num() ]</code> 进行同步（帧号 % <code>ListNum</code>）；</p><p>需要特别注意：当 <code>NotifyAddNetworkActor</code> 时，进行 <code>ReplicationActorLists</code> 的扩张，当 <code>NotifyRemoveNetworkActor</code> 时，也需要补充上空位（可以将最后的 <code>Actor</code>，补充到被删去的 <code>Actor</code> 的位置）；</p><p>同时，新增的 <code>Actor</code> 应该在 <code>ForceNetUpdateReplicationActorList</code> 中；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UReplicationGraphNode_PlayerStateFrequencyLimiter::NotifyAddNetworkActor</span><span class="params">(<span class="keyword">const</span> FNewReplicatedActorInfo&amp; ActorInfo)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (ReplicationActorLists.<span class="built_in">IsEmpty</span>())</span><br><span class="line">        ReplicationActorLists.<span class="built_in">AddDefaulted</span>();</span><br><span class="line"></span><br><span class="line">FActorRepListRefView* CurrentList = &amp;ReplicationActorLists.<span class="built_in">Last</span>(); </span><br><span class="line"><span class="keyword">if</span> (CurrentList-&gt;<span class="built_in">Num</span>() &gt;= TargetActorsPerFrame)</span><br><span class="line">&#123;</span><br><span class="line">ReplicationActorLists.<span class="built_in">AddDefaulted</span>();</span><br><span class="line">CurrentList = &amp;ReplicationActorLists.<span class="built_in">Last</span>(); </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">CurrentList-&gt;<span class="built_in">Add</span>(ActorInfo.Actor);</span><br><span class="line">ForceNetUpdateReplicationActorList.<span class="built_in">Add</span>(ActorInfo.Actor);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UReplicationGraphNode_PlayerStateFrequencyLimiter::NotifyRemoveNetworkActor</span><span class="params">(<span class="keyword">const</span> FNewReplicatedActorInfo&amp; ActorInfo, <span class="keyword">bool</span> bWarnIfNotFound)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> bRemovedSomething = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">int</span> RemoveListIndex = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> Index = <span class="number">0</span>; Index &lt; ReplicationActorLists.<span class="built_in">Num</span>(); Index++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (List[Index].<span class="built_in">Remove</span>(ActorInfo.Actor))</span><br><span class="line">&#123;</span><br><span class="line">bRemovedSomething = <span class="literal">true</span>;</span><br><span class="line">RemoveListIndex = Index;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (ForceNetUpdateReplicationActorList.<span class="built_in">Num</span>())</span><br><span class="line">&#123;</span><br><span class="line">ForceNetUpdateReplicationActorList.<span class="built_in">RemoveFast</span>(ActorInfo.Actor);</span><br><span class="line">&#125;</span><br><span class="line">            </span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!bRemovedSomthing) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将 LastList 的第一个 Actor 移到 RemoveListIndex 对应的 List</span></span><br><span class="line"><span class="keyword">if</span> (ReplicationActorLists.<span class="built_in">Num</span>() &gt; <span class="number">1</span> &amp;&amp; RemoveListIndex &lt; ReplicationActorLists.<span class="built_in">Num</span>())</span><br><span class="line">&#123;</span><br><span class="line">FActorRepListRefView* CurrentList = &amp;ReplicationActorLists.<span class="built_in">Last</span>();</span><br><span class="line">        </span><br><span class="line"><span class="keyword">if</span> (!CurrentList-&gt;<span class="built_in">IsEmpty</span>())</span><br><span class="line">&#123;</span><br><span class="line">            <span class="keyword">auto</span> Actor = (*CurrentList)[<span class="number">0</span>];</span><br><span class="line">ReplicationActorLists[RemoveListIndex].<span class="built_in">Add</span>(Actor);</span><br><span class="line">CurrentList-&gt;<span class="built_in">RemoveAtSwap</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line"><span class="keyword">if</span> (CurrentList-&gt;<span class="built_in">IsEmpty</span>())</span><br><span class="line">&#123;</span><br><span class="line">ReplicationActorLists.<span class="built_in">RemoveAt</span>(ReplicationActorLists.<span class="built_in">Num</span>() - <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UReplicationGraphNode_PlayerStateFrequencyLimiter::NotifyResetAllNetworkActors</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">NotifyResetAllNetworkActors</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span>&amp; List : ReplicationActorLists)</span><br><span class="line">&#123;</span><br><span class="line">List.<span class="built_in">Reset</span>();</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">ReplicationActorLists.<span class="built_in">Reset</span>();</span><br><span class="line">ForceNetUpdateReplicationActorList.<span class="built_in">Reset</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UReplicationGraphNode_PlayerStateFrequencyLimiter::GatherActorListsForConnection</span><span class="params">(<span class="keyword">const</span> FConnectionGatherActorListParameters&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (ReplicationActorLists.<span class="built_in">Num</span>() &gt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> ListIdx = Params.ReplicationFrameNum % ReplicationActorLists.<span class="built_in">Num</span>();</span><br><span class="line">        Params.OutGatheredReplicationLists.<span class="built_in">AddReplicationActorList</span>(ReplicationActorLists[ListIdx]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (ForceNetUpdateReplicationActorList.<span class="built_in">Num</span>() &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">        Params.OutGatheredReplicationLists.<span class="built_in">AddReplicationActorList</span>(ForceNetUpdateReplicationActorList);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="网络相关性">网络相关性</h3><p>一般的 <code>ReplicationGraph</code> 在启用时，会改变 <code>NetRelevancy</code>，<code>AActor::IsNetRelevantFor</code> 不再被使用；</p><p>但是在业务需求中很有可能需要用到这种机制，可以在合适的地方将这个机制添加回去；</p><ol><li>首先在 <code>ClassReplicationInfo</code> 添加 <code>bEnableNetRelevancyCheck</code> 字段用于判断是否启用该机制：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> FORCEINLINE_DEBUGGABLE <span class="keyword">bool</span> <span class="title">IsActorRelevantToConnection</span><span class="params">( <span class="keyword">const</span> AActor* Actor, <span class="keyword">const</span> TArray&lt;FNetViewer&gt;&amp; ConnectionViewers )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">for</span> ( int32 viewerIdx = <span class="number">0</span>; viewerIdx &lt; ConnectionViewers.<span class="built_in">Num</span>(); viewerIdx++ )</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> ( Actor-&gt;<span class="built_in">IsNetRelevantFor</span>( ConnectionViewers[viewerIdx].InViewer, ConnectionViewers[viewerIdx].ViewTarget, ConnectionViewers[viewerIdx].ViewLocation ) )</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FORCEINLINE_DEBUGGABLE <span class="keyword">bool</span> <span class="title">IsActorEnableNetRelevancyCheck</span><span class="params">(<span class="keyword">const</span> FGlobalActorReplicationInfo&amp; GlobalData)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">return</span> GlobalData.Settings.bEnableNetRelevancyCheck;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>在同步 <code>Actor</code> 时进行判定：<code>UReplicationGraph::ReplicateActorListsForConnections_Default</code>：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UReplicationGraph::ReplicateActorListsForConnections_Default</span><span class="params">(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists&amp; GatheredReplicationListsForConnection, FNetViewerArray&amp; Viewers)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> FActorRepListConstView&amp; List : GatheredLists)</span><br><span class="line">    &#123;</span><br><span class="line"></span><br><span class="line">        FGlobalActorReplicationInfo&amp; GlobalData = GlobalActorReplicationInfoMap.<span class="built_in">Get</span>(Actor);</span><br><span class="line">        <span class="keyword">for</span> (AActor* Actor : List)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">            <span class="keyword">if</span> (!<span class="built_in">ReadyForNextReplication</span>(ConnectionData, GlobalData, FrameNum))</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="built_in">DO_REPGRAPH_DETAILS</span>(PrioritizedReplicationList.<span class="built_in">GetNextSkippedDebugDetails</span>(Actor)-&gt;FramesTillNextReplication = (FrameNum - ConnectionData.LastRepFrameNum));</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">        <span class="comment">// [ NetRelevant Check ]</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">IsActorEnableNetRelevancyCheck</span>(GlobalData) &amp;&amp; !<span class="built_in">IsActorRelevantToConnection</span>(Actor, Viewers))</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>在发送 <code>Multicast RPC</code> 时进行判定：<code>UReplicationGraph::ProcessRemoteFunction</code>：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UReplicationGraph::ProcessRemoteFunction</span><span class="params">(class AActor* Actor, UFunction* Function, <span class="keyword">void</span>* Parameters, FOutParmRec* OutParms, FFrame* Stack, class UObject* SubObject )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">// ----------------------------------</span></span><br><span class="line"><span class="comment">// Multicast</span></span><br><span class="line"><span class="comment">// ----------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ((Function-&gt;FunctionFlags &amp; FUNC_NetMulticast))</span><br><span class="line">&#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        FGlobalActorReplicationInfo&amp; GlobalInfo = GlobalActorReplicationInfoMap.<span class="built_in">Get</span>(Actor);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        FNetViewerArray ViewsToConsider;</span><br><span class="line">        ViewsToConsider.<span class="built_in">Emplace</span>(NetConnection, <span class="number">0.f</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (int32 ChildIdx = <span class="number">0</span>; ChildIdx &lt; NetConnection-&gt;Children.<span class="built_in">Num</span>(); ++ChildIdx)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (NetConnection-&gt;Children[ChildIdx]-&gt;ViewTarget != <span class="literal">nullptr</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                ViewsToConsider.<span class="built_in">Emplace</span>(NetConnection-&gt;Children[ChildIdx], <span class="number">0.f</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// [ NetRelevant Check ]</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">IsActorEnableNetRelevancyCheck</span>(GlobalInfo) &amp;&amp; !<span class="built_in">IsActorRelevantToConnection</span>(Actor, Viewers))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="其它应用">其它应用</h2><h3 id="快速范围查询">快速范围查询</h3><p>显然，<code>ReplicationGraphNode_GridSpatialization2D</code>  将 <code>Actor</code> 在 <code>World</code> 中按照 <code>Cell</code> 进行了划分；这样的结构可以直接借用来做一些基本的通用需求：比如快速查询某个 <code>Location</code> 一定范围内的所有 <code>Pawn</code>（正常需要对空间额外维护一棵四叉树来处理）；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取 Player 视野范围内的 Actor</span></span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;FActorRepListType&gt; <span class="title">UReplicationGraphNode_GridSpatialization2D::GetActorsByPlayer</span><span class="params">(<span class="keyword">const</span> APawn* Player)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Player)) <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    </span><br><span class="line">TArray&lt;FActorRepListType&gt; Result;</span><br><span class="line">    </span><br><span class="line">FVector ClampedViewLoc = Player-&gt;<span class="built_in">GetActorLocation</span>();</span><br><span class="line"><span class="keyword">if</span> (GridBounds.IsValid)</span><br><span class="line">&#123;</span><br><span class="line">ClampedViewLoc = GridBounds.<span class="built_in">GetClosestPointTo</span>(ClampedViewLoc);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">ClampedViewLoc = ClampedViewLoc.<span class="built_in">BoundToCube</span>(HALF_WORLD_MAX);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">int32 CellX = UE::LWC::FloatToIntCastChecked&lt;int32&gt;((ClampedViewLoc.X - SpatialBias.X) / CellSize);</span><br><span class="line"><span class="keyword">if</span> (CellX &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">CellX = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int32 CellY = UE::LWC::FloatToIntCastChecked&lt;int32&gt;((ClampedViewLoc.Y - SpatialBias.Y) / CellSize);</span><br><span class="line"><span class="keyword">if</span> (CellY &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">CellY = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (UReplicationGraphNode_GridCell* CellNode = <span class="built_in">GetCell</span>(<span class="built_in">GetGridX</span>(CellX), CellY))</span><br><span class="line">    &#123;</span><br><span class="line">CellNode-&gt;<span class="built_in">GetAllActorsInNode_Debugging</span>(Result);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">return</span> Result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取坐标范围内的 Cell 中的 Actor</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UReplicationGraphNode_GridSpatialization2D::GetGridActorsByLocationAndRange</span><span class="params">(FActorRepListType Actor, <span class="keyword">const</span> FVector&amp; Location3D, <span class="keyword">float</span> Radius, TArray&lt;UReplicationGraphNode_GridCell*&gt;&amp; OutGatheredNodes)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> FActorCellInfo&amp; CellInfo =  <span class="built_in">GetCellInfoForActor</span>(Actor, Location3D, Radius);</span><br><span class="line"><span class="built_in">GetGridNodesForActor</span>(Actor, CellInfo, OutGatheredNodes);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取 Position 范围 Radius(cm) 内的 Pawn；如果 bAccurate，则需要严格判定，否则模糊判定在 Cell 内即可；</span></span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;APawn*&gt; <span class="title">AOIUtils::GetAllPawnsInPosGrid</span><span class="params">(UWorld* World, <span class="keyword">const</span> FVector&amp; Position, <span class="keyword">float</span> Radius, <span class="keyword">bool</span> bAccurate)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(World) || !<span class="built_in">IsValid</span>(World-&gt;<span class="built_in">GetNetDriver</span>())) <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> RepGraph = World-&gt;<span class="built_in">GetNetDriver</span>()-&gt;<span class="built_in">GetReplicationDriver</span>();</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(RepGraph)) <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> GridNode = RepGraph-&gt;GridNode;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(GridNode)) <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">float</span> RadiusSquared = Radius * Radius;</span><br><span class="line">    </span><br><span class="line">    TArray&lt;UReplicationGraphNode_GridCell*&gt; OutGatheredNodes;</span><br><span class="line">    GridNode-&gt;<span class="built_in">GetGridActorsByLocationAndRange</span>(<span class="literal">nullptr</span>, Position, Radius, OutGatheredNodes);</span><br><span class="line"></span><br><span class="line">    TArray&lt;APawn*&gt; Result;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span> Cell : OutGatheredNodes)</span><br><span class="line">    &#123;</span><br><span class="line">        TArray&lt;TWeakObjectPtr&lt;AActor&gt;&gt;&amp; LocatedActors = Cell-&gt;<span class="built_in">GetLocationNode</span>();</span><br><span class="line">        <span class="keyword">if</span> (LocatedActors.<span class="built_in">Num</span>() &lt;= <span class="number">0</span>) <span class="keyword">continue</span>; </span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span> WeakActorPtr : LocatedActors)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">auto</span> Pawn = Cast&lt;APawn&gt;(WeakActorPtr);</span><br><span class="line">            <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Pawn)) <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span> (!bAccurate || FVector::<span class="built_in">DistSquared2D</span>(Pawn-&gt;<span class="built_in">GetActorLocation</span>(), Position) &lt;= RadiusSquared)</span><br><span class="line">            &#123;</span><br><span class="line">                Result.<span class="built_in">Add</span>(Pawn);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> Result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参考">参考</h2><p><a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine">UE ReplicationGraph Document</a></p><p><code>Lyra (UE5.0-5.3) ReplicationGraph 部分源码</code></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;ReplicationGraph应用&lt;/h1&gt;
&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;UnrealEngine&lt;/code&gt; 基本的网络同步在进行同步时需要对每个连接判断每个 &lt;code&gt;Actor&lt;/code&gt; 是否需要同步，开销较大；&lt;/p&gt;
&lt;p</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]Subobject同步优化</title>
    <link href="https://blog.bearchild.top/2024/08/07/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]Subobject%E5%90%8C%E6%AD%A5%E4%BC%98%E5%8C%96/"/>
    <id>https://blog.bearchild.top/2024/08/07/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]Subobject%E5%90%8C%E6%AD%A5%E4%BC%98%E5%8C%96/</id>
    <published>2024-08-06T16:00:00.000Z</published>
    <updated>2024-11-11T14:23:46.675Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Subobject同步优化">Subobject同步优化</h2><p>基于 <code>PushModel</code> 的 <code>Subobject</code> 同步优化；</p><h3 id="基本机制">基本机制</h3><p><code>UE</code> 提供了 <code>PushModel</code> 机制：</p><p>对于 <code>Property</code> 这一层，通过 <code>UEPushModelPrivate::MarkPropertyDirty</code> 主动将其标记为 <code>Dirty</code>；</p><p>在 <code>FObjectReplicator::ReplicateProperties_r</code> 进行属性同步时，通过 <code>PushModelState-&gt;IsPropertyDirty</code> 判断属性是否 <code>Dirty</code> ；</p><p>既然可以判断一个同步对象上的同步 <code>Property</code> 是否有变更，就可以筛选出没有属性变化的对象；可以在 <code>UActorChannel::ReplicateActor</code>、<code>UActorChannel::WriteSubObjectInBunch</code> 时，判断 <code>FObjectReplicator::CanSkipUpdate</code> 跳过这些对象，加快同步速度；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">bCanSkip = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Have the RepFlags changed ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; SendingRepState.RepFlags.Value == RepFlags.Value; </span><br><span class="line"></span><br><span class="line"><span class="comment">// Any Naks to handle ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; SendingRepState.NumNaks == <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Have we compared the properties twice ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; SendingRepState.LastCompareIndex &gt; <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Do we have open Ack&#x27;s ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; !(SendingRepState.bOpenAckedCalled &amp;&amp; SendingRepState.PreOpenAckHistory.<span class="built_in">Num</span>() &gt; <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Any changelists to send ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; SendingRepState.LastChangelistIndex == RepChangelistState.HistoryEnd;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Is the changelist history fully acknowledged ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; SendingRepState.HistoryStart == SendingRepState.HistoryEnd;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Are we resending data for replay ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; Connection-&gt;ResendAllDataState == EResendAllDataState::None;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Are we forcing a compare property ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; !OwningChannel-&gt;bForceCompareProperties;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Are any CustomDelta properties dirty ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; (RepChangelistState.CustomDeltaChangeIndex == SendingRepState.CustomDeltaChangeIndex &amp;&amp; !SendingRepState.<span class="built_in">HasAnyPendingRetirements</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// Are any replicated properties dirty ?</span></span><br><span class="line">bCanSkip = bCanSkip &amp;&amp; RepChangelistState.<span class="built_in">HasAnyDirtyProperties</span>() == <span class="literal">false</span>;</span><br></pre></td></tr></table></figure><h2 id="针对-Subobject-进一步优化">针对 Subobject 进一步优化</h2><p>可以观察到 <code>PushModel</code> 在 <code>AActor::ReplicateSubobjects</code> 时，内部针对每个 <code>SubObject</code> 时，进行了一系列优化（<code>CanSkip</code> 的判定）；</p><p>但是还是每帧在遍历 <code>ReplicatedComponents</code> 进行判定，并且判定的逻辑本身比较复杂，常数较大，也会造成很大的开销；</p><p>这意味着即使没有任何 <code>Component</code> 的  <code>Property</code> 被 <code>MarkDirty</code>，本身 <code>ReplicatedComponents</code> 越多，开销也会随之越大；</p><h3 id="基本思路">基本思路</h3><p>需要一个机制更加精确地进行 <code>ReplicateSubobject</code>，做到对于非 <code>Dirty</code> 的 <code>Component</code> 直接不遍历；</p><p>显然，对于一个 <code>Actor</code>，对于所有需要同步这个 <code>Actor</code> 的 <code>Connection</code>，可以直接记录下 <code>DirtyComponents</code>，即发生了属性变化但还未同步更新的 <code>Subobject</code>；在每次 <code>Dirty</code> 时加入，在同步后移除；</p><h3 id="NotifyDirty">NotifyDirty</h3><p>首先需要可以知道一个 <code>Component</code> 被 <code>MarkDirty</code>；</p><p>在 <code>PushModel</code> 中新增 <code>Delegate</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NETCORE_API</span> <span class="title">FNetPushModelDelegate</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    </span><br><span class="line"><span class="built_in">DECLARE_MULTICAST_DELEGATE_OneParam</span>(FMarkPropertyDirtyDelegate, <span class="keyword">const</span> UEPushModelPrivate::FNetPushObjectId&amp;);</span><br><span class="line"><span class="keyword">static</span> FMarkPropertyDirtyDelegate OnMarkPropertyDirty;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>修改 <code>PushModel::MarkPropertyDirty</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">MarkPropertyDirty</span><span class="params">(<span class="keyword">const</span> FNetLegacyPushObjectId ObjectId, <span class="keyword">const</span> int32 RepIndex)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> int32 ObjectIndex = ObjectId;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">LIKELY</span>(PerObjectStates.<span class="built_in">IsValidIndex</span>(ObjectIndex)))</span><br><span class="line">    &#123;</span><br><span class="line">        PerObjectStates[ObjectIndex].<span class="built_in">MarkPropertyDirty</span>(<span class="keyword">static_cast</span>&lt;uint16&gt;(RepIndex));</span><br><span class="line">        FNetPushModelDelegate::OnMarkPropertyDirty.<span class="built_in">Broadcast</span>(ObjectIndex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">MarkPropertyDirty</span><span class="params">(<span class="keyword">const</span> FNetLegacyPushObjectId ObjectId, <span class="keyword">const</span> int32 StartRepIndex, <span class="keyword">const</span> int32 EndRepIndex)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> int32 ObjectIndex = ObjectId;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">LIKELY</span>(PerObjectStates.<span class="built_in">IsValidIndex</span>(ObjectIndex)))</span><br><span class="line">    &#123;</span><br><span class="line">        FPushModelPerObjectState&amp; ObjectState = PerObjectStates[ObjectIndex];</span><br><span class="line">        <span class="keyword">for</span> (int32 RepIndex = StartRepIndex; RepIndex &lt;= EndRepIndex; ++RepIndex)</span><br><span class="line">        &#123;</span><br><span class="line">            ObjectState.<span class="built_in">MarkPropertyDirty</span>(<span class="keyword">static_cast</span>&lt;uint16&gt;(RepIndex));</span><br><span class="line">            FNetPushModelDelegate::OnMarkPropertyDirty.<span class="built_in">Broadcast</span>(ObjectIndex);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Connection">Connection</h3><p>对于每个 <code>Actor</code>，维护一份 <code>Data</code>，其中维护对其可见的 <code>Connection</code>；</p><p>显然可以在 <code>AActor::ReplicateSubobjects</code> 时，将 <code>Connection</code> 加入存储列表；</p><p>在 <code>RemoveClientConnection</code> 时，将 <code>Connection</code> 从所有的 <code>Actor</code> 的 <code>Data</code> 中移除；</p><h3 id="DirtyComponents">DirtyComponents</h3><p>在每次收到 <code>FNetPushModelDelegate::OnMarkPropertyDirty</code> 时，记录该 <code>Subobject</code> 到其 <code>Parent</code> 维护的 <code>DirtyComponents</code> 数据中；</p><p>同时，当 <code>ActorComponent</code> 进行 <code>Register</code>、<code>Unregister</code> 时，也需要将其加入 <code>DirtyComponents</code>；</p><p>特别地，为了考虑丢包的情况，判断取出 <code>FSendingRepState* SendingRepState = Channel-&gt;ActorReplicator-&gt;RepState-&gt;GetSendingRepState()</code>，然后判定 <code>SendingRepState-&gt;NumNaks &gt; 0</code> 时（即存在丢包时），将所有数据重新传一遍（即所有的 <code>ReplicatedComponents</code> 都加入 <code>DirtyComponents</code>）；</p><p>每次 <code>ReplicateSubobjects</code> 后，清空 <code>DirtyComponents</code>；</p><p>这样就可以保证 <code>DirtyComponents</code> 的个数限制，未 <code>MarkDirty</code> 的不参与 <code>Replicate</code>；</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Subobject同步优化&quot;&gt;Subobject同步优化&lt;/h2&gt;
&lt;p&gt;基于 &lt;code&gt;PushModel&lt;/code&gt; 的 &lt;code&gt;Subobject&lt;/code&gt; 同步优化；&lt;/p&gt;
&lt;h3 id=&quot;基本机制&quot;&gt;基本机制&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;U</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]《极品飞车：集结》</title>
    <link href="https://blog.bearchild.top/2024/07/11/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Project/[UE]%E3%80%8A%E6%9E%81%E5%93%81%E9%A3%9E%E8%BD%A6%EF%BC%9A%E9%9B%86%E7%BB%93%E3%80%8B/"/>
    <id>https://blog.bearchild.top/2024/07/11/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Project/[UE]%E3%80%8A%E6%9E%81%E5%93%81%E9%A3%9E%E8%BD%A6%EF%BC%9A%E9%9B%86%E7%BB%93%E3%80%8B/</id>
    <published>2024-07-10T16:00:00.000Z</published>
    <updated>2024-07-13T08:25:52.473Z</updated>
    
    <content type="html"><![CDATA[<h1>《极品飞车：集结》</h1><h1>Intro</h1><p><em><strong>“SPEED IT UP, RACE TO THE TOP.”</strong></em></p><ul><li><p>Gameplay &amp; AI.</p></li><li><p>MostWanted &amp; CaptureTheFlag</p></li></ul><p><em>22.03.17 - 24.07.11</em></p><p><a href="https://nfsm.qq.com/">Download</a></p><h1>MostWanted</h1><p>热力追踪</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202407122219466.jpg" alt="Image_1717786967102"></p><h1>CaptureTheFlag</h1><p>夺旗</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202407122219389.jpg" alt="Image_1717581520601"></p><h1>Dev Part</h1><p>负责的模块</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202407122219965.jpg" alt="Image_1720505403721"></p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202407122219291.png" alt="Image_1720597831269"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;《极品飞车：集结》&lt;/h1&gt;
&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;“SPEED IT UP, RACE TO THE TOP.”&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Gameplay &amp;amp; AI.&lt;/p&gt;
&lt;/li&gt;</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Project" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Project/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="Project" scheme="https://blog.bearchild.top/tags/Project/"/>
    
  </entry>
  
  <entry>
    <title>[UE]Command解决方案</title>
    <link href="https://blog.bearchild.top/2024/06/25/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]Command%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.bearchild.top/2024/06/25/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]Command%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-06-24T16:00:00.000Z</published>
    <updated>2024-09-12T17:18:49.859Z</updated>
    
    <content type="html"><![CDATA[<h1>Command解决方案</h1><p>基本的命令框架，由 <code>Command</code>、<code>CommandHistory</code>、<code>CommandManager</code> 组成；</p><h2 id="Command">Command</h2><pre class="mermaid">classDiagramdirection LRclass TCommand {CommandType : TCommandTypeCommandParams : FCommonVariantParamsCommandTargets : TArray~TStrongObjectPtr[UObject]~}TCommand<|--FCommandclass FCommandFCommand*--ECommandTypeclass ECommandTypeFCommandWrapper*--FCommandclass FCommandWrapper {Command : const FCommand *}</pre><p><code>TCommand</code> ：维护 <code>Command</code>，其中 <code>CommandType</code> 表示该 <code>Command</code> 的类型，该类型与具体业务相关；<code>CommandParams</code>、<code>CommandTargets</code> 用于记录 <code>Command</code> 的参数，<code>Params</code> 记录基础类型、<code>Targets</code> 记录相关 <code>UObject</code> 指针；</p><p><code>FCommand</code>：针对特殊的 <code>CommandType : ECommandType</code> 的 <code>Command</code> 特化；</p><p><code>FCommandWrapper</code>：对 <code>FCommand</code> 的一个封装，可用于打包数据到 <code>lua</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Command</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommandType&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TCommand</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(TIsEnumClass&lt;TCommandType&gt;::Value, <span class="string">&quot;TCommandType must be a enum type&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">TCommand</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="built_in">TCommand</span>(TCommandType Type, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;) : <span class="built_in">CommandCategory</span>(Category), <span class="built_in">CommandType</span>(Type), <span class="built_in">CommandParams</span>(Params) &#123;&#125;</span><br><span class="line"><span class="built_in">TCommand</span>(TCommandType Type, TStrongObjectPtr&lt;UObject&gt; Target, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;) : <span class="built_in">CommandCategory</span>(Category), <span class="built_in">CommandType</span>(Type), <span class="built_in">CommandParams</span>(Params), <span class="built_in">CommandTargets</span>(&#123; Target &#125;) &#123;&#125;</span><br><span class="line"><span class="built_in">TCommand</span>(TCommandType Type, <span class="keyword">const</span> TArray&lt;TStrongObjectPtr&lt;UObject&gt;&gt;&amp; Targets, <span class="keyword">const</span> FCommonVariantParams&amp; Params = &#123;&#125;) : <span class="built_in">CommandCategory</span>(Category), <span class="built_in">CommandType</span>(Type), <span class="built_in">CommandParams</span>(Params), <span class="built_in">CommandTargets</span>(Targets) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Clear</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">TCommandType <span class="title">GetType</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> CommandType; &#125;</span><br><span class="line"><span class="function"><span class="keyword">const</span> FCommonVariantParams&amp; <span class="title">GetParams</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> CommandParams; &#125;</span><br><span class="line"><span class="keyword">const</span> TArray&lt;TStrongObjectPtr&lt;UObject&gt;&gt;&amp; <span class="built_in">GetTargets</span>() <span class="keyword">const</span> &#123; <span class="keyword">return</span> CommandTargets; &#125;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsValid</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> CommandType != TCommandType::None; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">MarkInvalid</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; CommandType = TCommandType::None; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">mutable</span> TCommandType CommandType = TCommandType::None;</span><br><span class="line">FCommonVariantParams CommandParams &#123;&#125;;</span><br><span class="line">TArray&lt;TStrongObjectPtr&lt;UObject&gt;&gt; CommandTargets&#123;&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommandType&gt;</span><br><span class="line"><span class="keyword">void</span> TCommand&lt;TCommandType&gt;::<span class="built_in">Clear</span>()</span><br><span class="line">&#123;</span><br><span class="line">CommandType = TCommandType::None;</span><br><span class="line">CommandParams = &#123;&#125;;</span><br><span class="line">CommandTargets.<span class="built_in">Empty</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FCommand</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UENUM</span>()</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">ECommandType</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    None = <span class="number">0</span>,</span><br><span class="line"></span><br><span class="line">    Test_1,</span><br><span class="line">    Test_2,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">struct FCommand : TCommand&lt;ECommandType&gt; </span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">using</span> Super = TCommand&lt;ECommandType&gt;;</span><br><span class="line">    <span class="keyword">using</span> Super::Super;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// FCommandWrapper</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">USTRUCT</span>(BlueprintType)</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FCommandWrapper</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">BindCommand</span><span class="params">(<span class="keyword">const</span> FCommand* InCommand)</span> </span>&#123; Command = InCommand; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">uint32 <span class="title">GetType</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> (uint32)Command-&gt;<span class="built_in">GetType</span>(); &#125;</span><br><span class="line">    <span class="function"><span class="keyword">const</span> FCommonVariantParams&amp; <span class="title">GetParams</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> Command-&gt;<span class="built_in">GetParams</span>(); &#125;</span><br><span class="line">    <span class="function">TArray&lt;UObject*&gt; <span class="title">GetTargets</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">IsValid</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> Command-&gt;<span class="built_in">IsValid</span>(); &#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">MarkInvalid</span><span class="params">()</span> </span>&#123; Command-&gt;<span class="built_in">MarkInvalid</span>(); &#125;</span><br><span class="line">    <span class="function">FString <span class="title">ToString</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> Command-&gt;<span class="built_in">ToString</span>(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">const</span> FCommand* Command = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function">TArray&lt;UObject*&gt; <span class="title">FCommandWrapper::GetTargets</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TArray&lt;UObject*&gt; Res&#123;&#125;;</span><br><span class="line"></span><br><span class="line">Algo::<span class="built_in">Transform</span>(Command-&gt;<span class="built_in">GetTargets</span>(), Res, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Value) &#123; <span class="keyword">return</span> Value.<span class="built_in">Get</span>(); &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> Res;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">BEGIN_EXPORT_REFLECTED_CLASS</span>(FCommandWrapper)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(GetType)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(GetParams)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(GetTargets)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(IsValid)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(MarkInvalid)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(ToString)</span><br><span class="line"><span class="built_in">END_EXPORT_CLASS</span>()</span><br><span class="line"><span class="built_in">IMPLEMENT_EXPORTED_CLASS</span>(FCommandWrapper)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="CommandHistory">CommandHistory</h2><pre class="mermaid">classDiagramdirection LRclass TCommandHistory {MaxHistoryCount : int32CurrIndex : int32TailIndex : int32CurrHistoryCount : int32UndoCommandCount : int32CommandHistory : TArray~TUniquePtr[TCommand]~OnProcessCommand : TOnProcessCommandDelegateExecuteCommand(Command, Type)Record(Command)Undo()Redo()}TCommandHistory<--ECommandOperationTypeTCommandHistory<|--FCommandHistoryFCommandHistory*--FOnProcessCommandInternalFCommandHistory*--FCommand</pre><p><code>TCommandHistory</code>：用于记录 <code>Command</code> 序列集合，<code>ExecuteCommand</code> 的 <code>Record</code>、<code>Undo</code>、<code>Redo</code> 等；</p><p><code>FCommandHistory</code>：<code>TCommandHistory</code> 针对 <code>TCommand</code>、<code>TOnProcessCommandDelegate</code> 的特化；</p><p>通过 <code>OnProcessCommandDelegate&lt;void(const Command&amp;, OperationType)&gt;</code>  来通知与分发 <code>Command</code> 的执行，其中 <code>OperationType</code> 与业务无关，主要有：</p><blockquote><ol><li><code>Record</code>：记录新的 <code>Command</code>；</li><li><code>Undo</code>：回退 <code>Command</code>；</li><li><code>Redo</code>：重做 <code>Command</code>；</li><li><code>Discard</code>：在 <code>Truncate</code> 截断 <code>CommandList</code>（回退后有新 <code>Command</code> ）时，丢弃之前回退的 <code>Command</code>；</li></ol></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Operation</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UENUM</span>()</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">ECommandOperationType</span> :</span> uint8</span><br><span class="line">&#123;</span><br><span class="line">BeforeRecord, <span class="comment">// 在命令记录到历史队列前，用于标记命令是否需要进入历史队列</span></span><br><span class="line">Record,       <span class="comment">// 记录命令</span></span><br><span class="line"></span><br><span class="line">BeforeUndo,   <span class="comment">// 回退前</span></span><br><span class="line">Undo,         <span class="comment">// 回退</span></span><br><span class="line">AfterUndo,    <span class="comment">// 回退后</span></span><br><span class="line"></span><br><span class="line">BeforeRedo,   <span class="comment">// 重做前</span></span><br><span class="line">Redo,         <span class="comment">// 重做</span></span><br><span class="line">AfterRedo,    <span class="comment">// 重做后</span></span><br><span class="line"></span><br><span class="line">Discard,      <span class="comment">// 回退之后有新的命令，丢弃已回退的命令</span></span><br><span class="line">Overwrite,    <span class="comment">// 历史队列已满，覆盖最旧的命令</span></span><br><span class="line">Clear,        <span class="comment">// 历史队列清空</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TCommandHistory</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TCommandHistory</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(TIsSame&lt;TOnProcessCommandDelegate, TDelegate&lt;<span class="built_in"><span class="keyword">void</span></span>(<span class="keyword">const</span> TCommand&amp;, ECommandOperationType)&gt;&gt;::Value, <span class="string">&quot;TOnProcessCommandDelegate must be a TDelegate&lt;void(const TCommand&amp;, ECommandOperationType)&gt;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(int32 InMaxHistoryCount)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Clear</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Destroy</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Record</span><span class="params">(TUniquePtr&lt;TCommand&gt; Command)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Undo</span><span class="params">(int32 StepCount)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Redo</span><span class="params">(int32 StepCount)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Truncate</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">TOnProcessCommandDelegate OnProcessCommand;</span><br><span class="line"></span><br><span class="line"><span class="function">int32 <span class="title">GetCurrHistoryCount</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> CurrHistoryCount; &#125;</span><br><span class="line"><span class="function">int32 <span class="title">GetTotalHistoryCount</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> CurrHistoryCount + UndoCommandCount; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function">int32 <span class="title">CalcIndex</span><span class="params">(int32 Val)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ExecuteCommand</span><span class="params">(int32 Index, ECommandOperationType OperationType)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ExecuteCommand</span><span class="params">(<span class="keyword">const</span> TCommand* Command, ECommandOperationType OperationType)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">int32 MaxHistoryCount = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">TArray&lt;TUniquePtr&lt;TCommand&gt;&gt; CommandHistory&#123;&#125;;</span><br><span class="line"></span><br><span class="line">int32 CurrIndex = <span class="number">-1</span>;</span><br><span class="line">int32 TailIndex = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">int32 CurrHistoryCount = <span class="number">0</span>;</span><br><span class="line">int32 UndoCommandCount = <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCommandHistory Implementation</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Init</span>(int32 InMaxHistoryCount)</span><br><span class="line">&#123;</span><br><span class="line">MaxHistoryCount = InMaxHistoryCount;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Clear</span>()</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">Truncate</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">0</span>; i &lt; CurrHistoryCount; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Index = <span class="built_in">CalcIndex</span>(i);</span><br><span class="line"><span class="keyword">if</span> (CommandHistory.<span class="built_in">IsValidIndex</span>(Index) &amp;&amp; CommandHistory[Index])</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Clear);</span><br><span class="line">CommandHistory[Index]-&gt;<span class="built_in">Clear</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">CommandHistory.<span class="built_in">Empty</span>();</span><br><span class="line"></span><br><span class="line">CurrIndex = <span class="number">-1</span>;</span><br><span class="line">TailIndex = <span class="number">-1</span>;</span><br><span class="line">CurrHistoryCount = <span class="number">0</span>;</span><br><span class="line">UndoCommandCount = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Destroy</span>()</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">Clear</span>();</span><br><span class="line">OnProcessCommand.<span class="built_in">Unbind</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Record</span>(TUniquePtr&lt;TCommand&gt; Command)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (MaxHistoryCount == <span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!Command) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Command.<span class="built_in">Get</span>(), ECommandOperationType::BeforeRecord);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无效命令不进入队列</span></span><br><span class="line"><span class="keyword">if</span> (!Command-&gt;<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Truncate</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Index = <span class="built_in">CalcIndex</span>(CurrIndex + <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (!CommandHistory.<span class="built_in">IsValidIndex</span>(Index))</span><br><span class="line">&#123;</span><br><span class="line">CommandHistory.<span class="built_in">Add</span>(<span class="built_in">MoveTemp</span>(Command));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (CommandHistory[Index] &amp;&amp; CommandHistory[Index]-&gt;<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Overwrite);</span><br><span class="line">&#125;</span><br><span class="line">CommandHistory[Index] = <span class="built_in">MoveTemp</span>(Command);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">CurrIndex = TailIndex = Index;</span><br><span class="line"><span class="keyword">if</span> (MaxHistoryCount &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">CurrHistoryCount = FMath::<span class="built_in">Min</span>(CurrHistoryCount + <span class="number">1</span>, MaxHistoryCount);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">CurrHistoryCount++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Record);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Undo</span>(int32 StepCount)</span><br><span class="line">&#123;</span><br><span class="line">StepCount = FMath::<span class="built_in">Clamp</span>(StepCount, <span class="number">0</span>, CurrHistoryCount);</span><br><span class="line"><span class="keyword">if</span> (StepCount == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> LastCurrIndex = CurrIndex;</span><br><span class="line">CurrIndex = <span class="built_in">CalcIndex</span>(LastCurrIndex - StepCount);</span><br><span class="line">UndoCommandCount += StepCount;</span><br><span class="line">CurrHistoryCount -= StepCount;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">0</span>; i &lt; StepCount; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Index = <span class="built_in">CalcIndex</span>(LastCurrIndex - i);</span><br><span class="line"><span class="keyword">if</span> (CommandHistory.<span class="built_in">IsValidIndex</span>(Index) &amp;&amp; CommandHistory[Index])</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::BeforeUndo);</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Undo);</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::AfterUndo);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Redo</span>(int32 StepCount)</span><br><span class="line">&#123;</span><br><span class="line">StepCount = FMath::<span class="built_in">Clamp</span>(StepCount, <span class="number">0</span>, UndoCommandCount);</span><br><span class="line"><span class="keyword">if</span> (StepCount == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> LastCurrIndex = CurrIndex;</span><br><span class="line">CurrIndex = <span class="built_in">CalcIndex</span>(LastCurrIndex + StepCount);</span><br><span class="line">UndoCommandCount -= StepCount;</span><br><span class="line">CurrHistoryCount += StepCount;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">1</span>; i &lt;= StepCount; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Index = <span class="built_in">CalcIndex</span>(LastCurrIndex + i);</span><br><span class="line"><span class="keyword">if</span> (CommandHistory.<span class="built_in">IsValidIndex</span>(Index) &amp;&amp; CommandHistory[Index])</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::BeforeRedo);</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Redo);</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::AfterRedo);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">Truncate</span>()</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Count = UndoCommandCount;</span><br><span class="line"><span class="keyword">if</span> (Count == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">TailIndex = CurrIndex;</span><br><span class="line">UndoCommandCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">1</span>; i &lt;= Count; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Index = <span class="built_in">CalcIndex</span>(CurrIndex + i);</span><br><span class="line"><span class="keyword">if</span> (CommandHistory.<span class="built_in">IsValidIndex</span>(Index) &amp;&amp; CommandHistory[Index])</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ExecuteCommand</span>(Index, ECommandOperationType::Discard);</span><br><span class="line">CommandHistory[Index]-&gt;<span class="built_in">Clear</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line">int32 TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">CalcIndex</span>(int32 Val) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (MaxHistoryCount &lt;= <span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span> Val;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Res = Val % MaxHistoryCount;</span><br><span class="line"><span class="keyword">if</span> (Res &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">Res += MaxHistoryCount;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">return</span> Res;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">ExecuteCommand</span>(int32 Index, ECommandOperationType OperationType)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> &lt;= Index &amp;&amp; Index &lt; CommandHistory.<span class="built_in">Num</span>())</span><br><span class="line">    &#123;</span><br><span class="line">OnProcessCommand.<span class="built_in">ExecuteIfBound</span>(*CommandHistory[Index].<span class="built_in">Get</span>(), OperationType);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TCommand, <span class="keyword">typename</span> TOnProcessCommandDelegate&gt;</span><br><span class="line"><span class="keyword">void</span> TCommandHistory&lt;TCommand, TOnProcessCommandDelegate&gt;::<span class="built_in">ExecuteCommand</span>(<span class="keyword">const</span> TCommand* Command, ECommandOperationType OperationType)</span><br><span class="line">&#123;</span><br><span class="line">OnProcessCommand.<span class="built_in">ExecuteIfBound</span>(*Command, OperationType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TestCommandHistory</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">DECLARE_DELEGATE_TwoParams</span>(FOnProcessCommandInternal, <span class="keyword">const</span> FCommand&amp; <span class="comment">/*Command*/</span>, ECommandOperationType <span class="comment">/*OperationType*/</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FCommandHistory</span> :</span> TCommandHistory&lt;FCommand, FOnProcessCommandInternal&gt; &#123;&#125;;</span><br></pre></td></tr></table></figure><h2 id="CommandManager">CommandManager</h2><pre class="mermaid">classDiagramdirection LRCommandUtils-->CommandManagerclass CommandManager {ProcessCommandEvents : TMap~ECommandType, FOnProcessCommand~ChangeCurrCommandHistoryType(CommandHistoryType)RegisterProcessCommandEventListener(CommandType, Object, Callback)UnregisterProcessCommandEventListener(CommandType, Object)DispatchProcessCommandEvent(Command, OperationType)DispatchProcessCommandEventToLua(CommandWrapper, OperationType)}CommandManager*--ECommandHistoryType</pre><p><code>CommandManager</code>：</p><p>主要是为了维护多个 <code>CommandHistory</code>（可能有多个业务各自相关的 <code>CommandHistory</code>），进行相关的初始化；</p><p>通过 <code>ChangeCurrCommandHistoryType</code> 来切换当前执行的 <code>Command</code> 所属的 <code>CommandHistoryType</code>；有些 <code>Command</code> 在多个 <code>CommandHistoryType</code> 的情景下都会有（比如当前切到一个新的 <code>Editor</code>，但是 <code>PropertyCommand</code> 通用模块在任意一个 <code>Editor</code> 都会使用到），所以不选择执行 <code>Command</code> 时指定 <code>CommandHistoryType</code> 的方案，选择主动切换；业务可以根据自己的情况选择维护数据结构来管理 <code>CommandHistoryType</code> 的切换；</p><p>提供对应 <code>Event</code> 的注册与分发、提供 <code>Record</code>、<code>Undo</code> 等接口给 <code>CommandUtils</code> 调用（期望外部仅调用 <code>CommandUtils</code> 相关方法），同时进行一些 <code>CommonCommandType</code> 的注册以及与 <code>lua</code> 的通信；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommandManager</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommandManager::InitCommandHistoryMap</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> Type : TEnumRange&lt;ECommandHistoryType&gt;())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> CommandHistory = MakeShared&lt;FCommandHistory&gt;();</span><br><span class="line">CommandHistory-&gt;<span class="built_in">Init</span>(MAX_HISTORY_COUNT);</span><br><span class="line">CommandHistory-&gt;OnProcessCommand.<span class="built_in">BindUObject</span>(<span class="keyword">this</span>, &amp;UCommandManager::DispatchProcessCommandEvent);</span><br><span class="line"></span><br><span class="line">CommandHistoryMap.<span class="built_in">Add</span>(Type, CommandHistory);</span><br><span class="line">&#125;</span><br><span class="line">CurrCommandHistoryType = ECommandHistoryType::None;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommandManager::ChangeCurrCommandHistoryType</span><span class="params">(ECommandHistoryType Type)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">CurrCommandHistoryType = Type;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TSharedPtr&lt;FCommandHistory&gt; <span class="title">UCommandManager::GetCurrCommandHistory</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> CommandHistoryPtr = CommandHistoryMap.<span class="built_in">Find</span>(CurrCommandHistoryType))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> *CommandHistoryPtr;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommandManager::DispatchProcessCommandEvent</span><span class="params">(<span class="keyword">const</span> FCommand&amp; Command, ECommandOperationType OperationType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Type = Command.<span class="built_in">GetType</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> EventPtr = ProcessCommandEvents.<span class="built_in">Find</span>(Type))</span><br><span class="line">&#123;</span><br><span class="line">EventPtr-&gt;<span class="built_in">Broadcast</span>(Command, OperationType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FCommandWrapper CommandWrapper&#123;&#125;;</span><br><span class="line">CommandWrapper.<span class="built_in">BindCommand</span>(&amp;Command);</span><br><span class="line"><span class="built_in">DispatchProcessCommandEventToLua</span>(CommandWrapper, OperationType);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ------------</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> UserClass&gt;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCommandManager::RegisterProcessCommandEventListener</span><span class="params">(ECommandType Type, UserClass* InUserObject, <span class="keyword">typename</span> TMemFunPtrType&lt;TIsConst&lt;UserClass&gt;::Value, UserClass, FOnProcessCommandCallbackType&gt;::Type InFunc)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!ProcessCommandEvents.<span class="built_in">Contains</span>(Type))</span><br><span class="line">    &#123;</span><br><span class="line">        ProcessCommandEvents.<span class="built_in">Add</span>(Type, &#123;&#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span>&amp; Event = ProcessCommandEvents[Type];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Event.<span class="built_in">IsBoundToObject</span>(InUserObject))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Event.<span class="built_in">AddUObject</span>(InUserObject, InFunc);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> UserClass&gt;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCommandManager::UnregisterProcessCommandEventListener</span><span class="params">(ECommandType Type, UserClass* InUserObject)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!ProcessCommandEvents.<span class="built_in">Contains</span>(Type))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span>&amp; Event = ProcessCommandEvents[Type];</span><br><span class="line">    Event.<span class="built_in">RemoveAll</span>(InUserObject);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;Command解决方案&lt;/h1&gt;
&lt;p&gt;基本的命令框架，由 &lt;code&gt;Command&lt;/code&gt;、&lt;code&gt;CommandHistory&lt;/code&gt;、&lt;code&gt;CommandManager&lt;/code&gt; 组成；&lt;/p&gt;
&lt;h2 id=&quot;Command&quot;&gt;Comm</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]InputSystem基础框架</title>
    <link href="https://blog.bearchild.top/2024/06/18/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/[UE]InputSystem%E5%9F%BA%E7%A1%80%E6%A1%86%E6%9E%B6/"/>
    <id>https://blog.bearchild.top/2024/06/18/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/[UE]InputSystem%E5%9F%BA%E7%A1%80%E6%A1%86%E6%9E%B6/</id>
    <published>2024-06-17T16:00:00.000Z</published>
    <updated>2024-11-11T14:24:31.340Z</updated>
    
    <content type="html"><![CDATA[<h1>InputSystem基础框架</h1><h2 id="Key">Key</h2><pre class="mermaid">flowchart LRInput(PlayerController::UpdateInput)Input-->InputProcesssor::TickInput-->InputHandle::TickInput-->InputCollector::Tick</pre><h3 id="InputProcessor">InputProcessor</h3><p>对于 <code>InputProcessor</code>，进行按键的原始输入处理，调用 <code>InputKey</code> 将将数据传递到 <code>InputCollector</code>，继续后续的操作；</p><ol><li>通过监听引擎原始的按键数据 <code>APlayerController::IsInputKeyDown(const FKey Key)</code>；</li><li>允许外部调用输入，执行 <code>InputKey</code> （比如某个 <code>UI</code> 点击触发输入）；</li></ol><p>特别地，在 <code>Tick</code> 中进行原始数据处理；</p><p>由于一般按键输入支持 <code>Ctrl / Shit / Alt / Cmd</code> + <code>Key</code>，需要对这种情况适配，同时防止按键冲突：对于<code>KeySetting(KeyType - 具体按键配置 映射)</code> 额外维护 <code>bCtrl (需要与 ctrl 组合)</code>、<code>MustNoCtrl (需要不按下 Ctrl)</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputProcessor</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputProcessor::ProcessInputKey</span><span class="params">()</span> <span class="comment">// Tick</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> bShift = PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::LeftShift) || PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::RightShift);</span><br><span class="line">    <span class="keyword">bool</span> bCtrl = PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::LeftControl) || PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::RightControl);</span><br><span class="line">    <span class="keyword">bool</span> bAlt = PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::LeftAlt) || PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::RightAlt);</span><br><span class="line">    <span class="keyword">bool</span> bCmd = PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::LeftCommand) || PC-&gt;<span class="built_in">IsInputKeyDown</span>(EKeys::RightCommand);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">auto</span> IsKeyDown = [&amp;](EInputKeyType KeyType) -&gt; <span class="keyword">bool</span></span><br><span class="line">    &#123;</span><br><span class="line">         <span class="keyword">for</span> (<span class="keyword">auto</span> KeySetting : KeySettings[KeyType]) <span class="comment">// 一个 KeyType 可能有多种触发方式</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">bool</span> bKeyDown = PC-&gt;<span class="built_in">IsInputKeyDown</span>(<span class="built_in">FKey</span>(<span class="built_in">FName</span>(KeySetting.<span class="built_in">KeyName</span>())));</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> ((KeySetting.<span class="built_in">Shift</span>() &amp;&amp; !bShift) || (KeySetting.<span class="built_in">MustNoShift</span>() &amp;&amp; bShift))</span><br><span class="line">                bKeyDown = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> ((KeySetting.<span class="built_in">Ctrl</span>() &amp;&amp; !bCtrl) || (KeySetting.<span class="built_in">MustNoCtrl</span>() &amp;&amp; bCtrl))</span><br><span class="line">                bKeyDown = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> ((KeySetting.<span class="built_in">Alt</span>() &amp;&amp; !bAlt) || (KeySetting.<span class="built_in">MustNoAlt</span>() &amp;&amp; bAlt))</span><br><span class="line">                bKeyDown = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> ((KeySetting.<span class="built_in">Cmd</span>() &amp;&amp; !bCmd) || (KeySetting.<span class="built_in">MustNoCmd</span>() &amp;&amp; bCmd))</span><br><span class="line">                bKeyDown = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (bKeyDown)</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> KeyType : KeyTypes)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">IsKeyDown</span>(KeyType))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">InputKey</span>(KeyType);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputProcessor::InputKey</span><span class="params">(EInputKeyType KeyType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InputCollector)) <span class="keyword">return</span>;</span><br><span class="line">    InputCollector-&gt;<span class="built_in">SetKey</span>(KeyType, <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="InputCollector">InputCollector</h3><pre class="mermaid">classDiagramdirection LRInputCollector --> FInputKeyInfoclass InputCollector {KeyInfoMap: TMap~KeyType, FInputKeyInfo~}FInputKeyInfo *-- EKeyStateclass FInputKeyInfo {bEnable : boolbPressed : boolKeyState : EKeyState}class EKeyState {IKS_Idle : 未按下IKS_Press : 当帧按下IKS_Release : 当帧释放IKS_Holding : 持续按下}</pre><p>对于 <code>InputCollector</code>，维护 <code>Key - Press</code> 相关的原始输入数据，将数据从 <code>Press</code> 加工到 <code>KeyState</code>，并每帧检测是否未按下，这样外部只需要进行 <code>Press</code>，而不需要 <code>Release</code>；有几个主要的功能：</p><ol><li>提供 <code>SetKey</code> 进行按键设置，提供 <code>GetKey</code> 查询按键状态；</li><li>维护每个 <code>Key</code> 的  <code>KeyInfo</code>，数据用于后续处理；</li><li>提供 <code>EnableKey</code>，设置按键是否可以输入（<code>Disable</code> 时需要取消输入）；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputKeyInfo</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">USTRUCT</span>(BlueprintType)</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FInputKeyInfo</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    <span class="keyword">bool</span> bPressed = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    EInputKeyState KeyState = IKS_Idle;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    <span class="keyword">bool</span> bEnable = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用于记录该帧内是否已经处理过按键</span></span><br><span class="line">    <span class="keyword">bool</span> bProcessedThisFrame = <span class="literal">false</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputCollector</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputCollector::SetKey</span><span class="params">(EInputKeyType KeyType, <span class="keyword">bool</span> bPressed, <span class="keyword">bool</span> bForce)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (bForce || <span class="built_in">IsEnableKey</span>(KeyType))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">SetKey_Internal</span>(KeyType, bPressed);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FInputKeyInfo <span class="title">UInputCollector::GetKeyInfo</span><span class="params">(EInputKeyType KeyType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> KeyInfoMap.<span class="built_in">Contains</span>(KeyType) ? KeyInfoMap[KeyType] : &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Internal -----</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputCollector::Tick</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 每次 Tick 进行按键信息重置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> KeyType : KeyTypes)</span><br><span class="line">    &#123;</span><br><span class="line">        FInputKeyInfo&amp; KeyInfo = KeyInfoMap[KeyType];</span><br><span class="line">        <span class="keyword">if</span> (!KeyInfo.bProcessedThisFrame)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">SetKey_Internal</span>(KeyType, <span class="literal">false</span>);</span><br><span class="line">           KeyInfo.bProcessedThisFrame = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;   </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputCollector::SetKey_Internal</span><span class="params">(EInputKeyType KeyType, <span class="keyword">bool</span> bPressed)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    FInputKeyInfo&amp; KeyInfo = KeyInfoMap[KeyType];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (bPressed == KeyInfo.bPressed)</span><br><span class="line">    &#123;</span><br><span class="line">        KeyInfo.KeyState = bPressed ? IKS_Holding : IKS_Idle;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        KeyInfo.KeyState = bPressed ? IKS_Press : IKS_Release;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    KeyInfo.bPressed = bPressed;</span><br><span class="line">    KeyInfo.bProcessedThisFrame = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="InputHandle">InputHandle</h3><pre class="mermaid">classDiagramclass InputHandleBase {KeyInfo : TArray~EInputKeyType~SetInput(KeyType, bPressed)CollectInput()ProcessInput()NotifyInput()}InputHandleBase <|-- InputHandld_DefaultInputHandleBase <|-- InputHandle_CharacterInputHandleBase <|-- InputHandle_Vehicle</pre><p>业务自定义逻辑的位置，一般仅对业务开放这部分的代码修改；</p><p>根据不同的输入模式，创建不同的 <code>InputHandle</code>，在这里对输入进行业务级别的加工，同时处理基本的输入操作回调（内部基本的业务逻辑，以及允许其它模块向内部注册的事件），并将输入操作通知给其它模块；</p><p>业务自定义其输入逻辑，比如：</p><ol><li>该输入模式下，当输入某个操作时，系统自动取消其它操作的输入等；</li><li>处理按键是否需要自动抬起；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputHandle</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputHandleBase::Tick</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">RecordInput</span>();   <span class="comment">// 记录上次输入结果</span></span><br><span class="line">    <span class="built_in">CollectInput</span>();  <span class="comment">// 收集本帧输入结果（先默认清空，后根据具体情况收集）</span></span><br><span class="line">    <span class="built_in">ProcessInput</span>();  <span class="comment">// 根据输入结果修改 KeyState</span></span><br><span class="line">    <span class="built_in">DispatchInput</span>(); <span class="comment">// 分发输入结果并处理</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Process -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputHandleBase::ProcessInput</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> Key : ActiveKeyTypes)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">bool</span> bLastKeyDown = RecordKeyInfoMap[Key].bPressed;</span><br><span class="line">        <span class="keyword">bool</span> bCurrentKeyDown = KeyInfoMap[Key].bPressed;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (bCurrentKeyDown != bLastKeyDown)</span><br><span class="line">        &#123;</span><br><span class="line">            bCurrentKeyDown ? <span class="built_in">OnGameKeyPress</span>(Key) : <span class="built_in">OnGameKeyRelease</span>(Key);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            bCurrentKeyDown ? <span class="built_in">OnGameKeyHolding</span>(Key) : <span class="built_in">OnGameKeyIdle</span>(Key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Register -----</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">DECLARE_MULTICAST_DELEGATE_TwoParams</span>(FInputEvent, KeyType, KeyState);</span><br><span class="line"><span class="keyword">using</span> FInputEventCallbackParamTypes = FInputEventCallbackType::FDelegate::TFuncType;</span><br><span class="line"></span><br><span class="line">TMap &lt;KeyType, FInputEvent&gt; ProcessInputEvents;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;</span><br><span class="line"><span class="keyword">typename</span> UserClass,</span><br><span class="line"><span class="keyword">typename</span> FuncType = TMemFunPtrType&lt;TIsConst&lt;UserClass&gt;::Value, UserClass, FInputEventCallbackParamTypes&gt;::Type</span><br><span class="line">&gt;</span><br><span class="line"><span class="keyword">bool</span> UInputHandleBase::<span class="built_in">RegisterInputEvent</span>(EInputKeyType KeyType, UserClass* InUserObject, FuncType InFunc)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!ProcessInputEvents.<span class="built_in">Contains</span>(KeyType))</span><br><span class="line">        ProcessInputEvents.<span class="built_in">Add</span>(KeyType, &#123;&#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span>&amp; Event = Processvents[KeyType];</span><br><span class="line">    <span class="keyword">if</span> (Event.<span class="built_in">IsBoundToObject</span>(InUserObject))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Event.<span class="built_in">AddUObject</span>(InObject, InFunc);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> UserClass&gt;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UnregisterInputEvent</span><span class="params">(EInputKeyType Type, UserClass* InUserObject)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!ProcessInputEvents.<span class="built_in">Contains</span>(Type))</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span>&amp; Event = ProcessInputEvents[Type];</span><br><span class="line">    Event.<span class="built_in">RemoveAll</span>(InUserObject);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line">                                </span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputHandleBase::DispatchInput</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 内部处理基础操作</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">        </span><br><span class="line">    <span class="comment">// 执行外部注册的操作</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> KeyType : ActiveKeyTypes)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">auto</span> EventPtr = ProcessInputEvents.<span class="built_in">Find</span>(KeyType))</span><br><span class="line">        &#123;</span><br><span class="line">            EventPtr-&gt;<span class="built_in">Broadcast</span>(KeyType, <span class="built_in">GetKeyState</span>(KeyType));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Helper -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputHandleBase::SetInput</span><span class="params">(EKeyType KeyType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    KeyInfo[KeyType].bPressed = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UInputHandleBase::UpdateKeyFromCollector</span><span class="params">(EInputKeyType KeyType)</span> <span class="comment">// 在 CollectInput 中调用</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">SetInput</span>(KeyType, InputCollector-&gt;<span class="built_in">GetKeyInfo</span>(KeyType).bPressed);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Mouse">Mouse</h2><h3 id="InputProcessor-2">InputProcessor</h3><p><code>Interface : IInputProcessor</code> 由 <code>FSlateApplication</code> 驱动，也有 <code>Key</code>、<code>Mouse</code> 相关操作的回调：</p><pre class="mermaid">flowchart LRFSlateApplication::ProcessMouseButtonDownEvent-->FSlateApplication::InputPreProcessorsHelper::HandleMouseButtonDownEvent</pre><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IInputProcessor</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SLATE_API</span> <span class="title">IInputProcessor</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">IInputProcessor</span>()&#123;&#125;;</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">IInputProcessor</span>()&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Tick</span><span class="params">(<span class="keyword">const</span> <span class="keyword">float</span> DeltaTime, FSlateApplication&amp; SlateApp, TSharedRef&lt;ICursor&gt; Cursor)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Key...</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleKeyDownEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FKeyEvent&amp; InKeyEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleKeyUpEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FKeyEvent&amp; InKeyEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleAnalogInputEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FAnalogInputEvent&amp; InAnalogInputEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Mouse...</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMouseMoveEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMouseButtonDownEvent</span><span class="params">( FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMouseButtonUpEvent</span><span class="params">( FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMouseButtonDoubleClickEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMouseWheelOrGestureEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; InWheelEvent, <span class="keyword">const</span> FPointerEvent* InGestureEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">HandleMotionDetectedEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FMotionEvent&amp; MotionEvent)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Debug name for logging purposes */</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">const</span> TCHAR* <span class="title">GetDebugName</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;&quot;</span>); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以在合适的地方（比如 <code>InputSubSystem / InputManager</code> ）进行创建：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">InputProcessor = <span class="built_in">MakeShareable</span>(<span class="keyword">new</span> <span class="built_in">FInputProcessor</span>());</span><br><span class="line">FSlateApplication::<span class="built_in">Get</span>().<span class="built_in">RegisterInputPreProcessor</span>(InputProcessor);</span><br></pre></td></tr></table></figure><p>其次是与 <code>UI</code> 的交互， <code>UserWidget</code> 中也提供了 <code>Mouse</code> 的回调：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchStarted</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchMoved</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchEnded</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnMouseWheel</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InMouseEvent)</span> <span class="keyword">override</span></span>;</span><br></pre></td></tr></table></figure><p>可以将接口收束在一个 <code>InputManager</code> 中，向外暴露相关事件；</p><h3 id="DragDrop">DragDrop</h3><p><code>DragArea</code> 用于维护可拖动的范围，以及封装引擎基本接口、提供注册方法：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- DragArea</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--region ----- Events -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:OnMouseButtonDown</span><span class="params">(Geometry, MouseEvent)</span></span></span><br><span class="line"><span class="keyword">return</span> UE.UWidgetBlueprintLibrary.DetectDragIfPressed(MouseEvent, <span class="built_in">self</span>, <span class="string">&quot;LeftMouseButton&quot;</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 开始拖动</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:OnDragDetected</span><span class="params">(Geometry, PointerEvent)</span></span></span><br><span class="line"><span class="keyword">local</span> OperationClass = <span class="built_in">self</span>.DragDropOperationClass</span><br><span class="line"><span class="keyword">local</span> Operation = NewObject(OperationClass)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">self</span>.DragDetectedDelegate ~= <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line"><span class="keyword">for</span> Object, Callback <span class="keyword">in</span> <span class="built_in">pairs</span>(<span class="built_in">self</span>.DragDetectedDelegate) <span class="keyword">do</span></span><br><span class="line"><span class="built_in">xpcall</span>(Callback, error_handler, Object, Geometry, PointerEvent, Operation)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> Operation</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 取消拖动</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:OnDragCancelled</span><span class="params">(Geometry, PointerEvent, Operation)</span></span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">self</span>.DragCancelledDelegate ~= <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line"><span class="keyword">for</span> Object, Callback <span class="keyword">in</span> <span class="built_in">pairs</span>(<span class="built_in">self</span>.DragCancelledDelegate) <span class="keyword">do</span></span><br><span class="line"><span class="built_in">xpcall</span>(Event, error_handler, Object, Geometry, PointerEvent, Operation)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- Other...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--endregion Events</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--reigon ----- Bind -----</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:BindEvent</span><span class="params">(EventName, Object, Callback)</span></span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">self</span>[EventName] == <span class="literal">nil</span> <span class="keyword">then</span> <span class="built_in">self</span>[EventName] = &#123;&#125; <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">self</span>[EventName][Object] == <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">self</span>[EventName][Object] = Callback</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:UnbindEvent</span><span class="params">(EventName, Object)</span></span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">self</span>[EventName] == <span class="literal">nil</span> <span class="keyword">then</span> <span class="built_in">self</span>[EventName] = &#123;&#125; <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">self</span>[EventName][Object] ~= <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">self</span>[EventName][Object] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:BindDragDetected</span><span class="params">(Object,Func)</span></span> <span class="built_in">self</span>:BindEvent(<span class="string">&quot;DragDetectedDelegate&quot;</span>, Object, Func) <span class="keyword">end</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:UnbindDragDetected</span><span class="params">(Object)</span></span> <span class="built_in">self</span>:UnbindEvent(<span class="string">&quot;DragDetectedDelegate&quot;</span>, Object) <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:BindDragCancelled</span><span class="params">(Object,Func)</span></span> <span class="built_in">self</span>:BindEvent(<span class="string">&quot;DragCancelledDelegate&quot;</span>, Object, Func) <span class="keyword">end</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragAreaView:UnbindDragCancelled</span><span class="params">(Object)</span></span> <span class="built_in">self</span>:UnbindEvent(<span class="string">&quot;DragCancelledDelegate&quot;</span>, Object) <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--endregion Bind</span></span><br></pre></td></tr></table></figure><p><code>DragItem</code> 用于维护拖动的实际 <code>Item</code>；</p><p><code>PreviewItem</code> 用于维护拖动时的 <code>Preview</code>（也就是拖动时显示的 <code>Item</code> 预览）；</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- DragItem</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragItemView:Construct</span><span class="params">()</span></span></span><br><span class="line"><span class="built_in">self</span>.DragArea:BindDragDetected(<span class="built_in">self</span>, <span class="built_in">self</span>.OnDragDetectedCallback)</span><br><span class="line"><span class="built_in">self</span>.DragArea:BindDragCancelled(<span class="built_in">self</span>, <span class="built_in">self</span>.OnDragCancelledCallback)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragItemView:Destruct</span><span class="params">()</span></span></span><br><span class="line"><span class="built_in">self</span>.DragArea:UnbindDragDetected(<span class="built_in">self</span>)</span><br><span class="line"><span class="built_in">self</span>.DragArea:UnbindDragCancelled(<span class="built_in">self</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragItemView:OnDragDetectedCallback</span><span class="params">(Geometry,PointerEvent,Operation)</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> BPClass = UE.UGameplayStatics.GetObjectClass(<span class="built_in">self</span>)</span><br><span class="line">Operation.PreviewItem = UE.UCommonWidgetUtils.CreateWidget(BPClass)</span><br><span class="line"></span><br><span class="line">Operation.DefaultDragVisual = Operation.PreviewItem</span><br><span class="line"><span class="keyword">if</span> Operation.PreviewItem <span class="keyword">and</span> Operation.PreviewItem.OnDragDetected <span class="keyword">then</span></span><br><span class="line">Operation.PreviewItem:OnDragDetected()</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">Operation.Pivot = UE.EDragPivot.TopLeft</span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> LocalPosition = UE.USlateBlueprintLibrary.AbsoluteToLocal(<span class="built_in">self</span>:GetCachedGeometry(), UE.UKismetInputLibrary.PointerEvent_GetScreenSpacePosition(PointerEvent))</span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> Offset = UE.FVector2D()</span><br><span class="line"><span class="keyword">local</span> Size = UE.USlateBlueprintLibrary.GetLocalSize(<span class="built_in">self</span>:GetCachedGeometry())</span><br><span class="line">Offset.X = LocalPosition.X / Size.X * <span class="number">-1</span></span><br><span class="line">Offset.Y = LocalPosition.Y / Size.Y * <span class="number">-1</span></span><br><span class="line">Operation.Offset = Offset</span><br><span class="line"></span><br><span class="line">Operation.UseInAnimation = <span class="literal">false</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">DragItemView:OnDragCancelledCallback</span><span class="params">(Geometry, PointerEvent, Operation)</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> Operation.PreviewItem <span class="keyword">and</span> Operation.PreviewItem.OnDragCancelled <span class="keyword">then</span></span><br><span class="line">Operation.PreviewItem:OnDragCancelled()</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">UE.UCommonWidgetUtils.DestroyWidget(Operation.PreviewItem)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommonWidgetUtils</span></span><br><span class="line"></span><br><span class="line"><span class="function">UUserWidget* <span class="title">UCommonWidgetUtils::CreateWidget</span><span class="params">(UWorld* InWorld, TSubclassOf&lt;UUserWidget&gt; WidgetType)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UUserWidget* Widget = UWidgetBlueprintLibrary::<span class="built_in">Create</span>(InWorld, WidgetType, <span class="literal">nullptr</span>);</span><br><span class="line"><span class="built_in">AddObjectRef</span>(Widget);</span><br><span class="line"><span class="keyword">return</span> Widget;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommonWidgetUtils::DestroyWidget</span><span class="params">(UUserWidget* Widget)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">RemoveObjectRef</span>(Widget);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// --------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommonWidgetUtils::AddObjectRef</span><span class="params">(UObject* Object)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Object &amp;&amp; <span class="built_in">IsUObjectValid</span>(Object))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> L = UnLua::<span class="built_in">GetState</span>();</span><br><span class="line"><span class="keyword">auto</span>&amp; Env = UnLua::FLuaEnv::<span class="built_in">FindEnvChecked</span>(L);</span><br><span class="line">Env.<span class="built_in">AddManualObjectReference</span>(Object);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCommonWidgetUtils::RemoveObjectRef</span><span class="params">(UObject* Object)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Object &amp;&amp; <span class="built_in">IsUObjectValid</span>(Object))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> L = UnLua::<span class="built_in">GetState</span>();</span><br><span class="line"><span class="keyword">auto</span>&amp; Env = UnLua::FLuaEnv::<span class="built_in">FindEnvChecked</span>(L);</span><br><span class="line">Env.<span class="built_in">RemoveManualObjectReference</span>(Object);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>特别地：</p><ol><li>通过 <code>Operation</code> 的 <code>DefaultDragVisual</code> 来设置拖动时的预览 <code>Widget</code>，在 <code>FUMGDragDropOp::New</code> 时，将其设置到 <code>Operation-&gt;DecoratorWidget</code>，并在 <code>FUMGDragDropOp::OnDragged</code> 时更新 <code>Position</code>；</li><li><code>CreateWidget</code> / <code>DestroyWidget</code> 针对 <code>PreviewItem</code>，这里可以额外维护一个 <code>Pool</code> 进行 <code>Widget</code> 复用；</li><li>在合适的地方增加额外 <code>Callback</code>；</li></ol><h3 id="Touch">Touch</h3><p>引擎对于 <code>Touch</code> 的相关实现主要在：<code>UPlayerInput::InputTouch(uint32 Handle, ETouchType::Type Type, const FVector2D&amp; TouchLocation, float Force, FDateTime DeviceTimestamp, uint32 TouchpadIndex)</code> 中；</p><p>实际上，可以根据 <code>IInputProcessor</code> 提供的接口，在 <code>InputManager</code> 中维护 <code>TArray &lt;FInputTouchData&gt; InputTouchDatas</code> 以及相关信息，统一管理并分发给各个业务模块，同时方便自定义与扩展。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputTouchData</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FInputTouchData</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">ETouchIndex::Type TouchIndex;</span><br><span class="line">FVector2D StartLocation;</span><br><span class="line">FVector2D Location;</span><br><span class="line">FVector2D DeltaMove;</span><br><span class="line"><span class="keyword">float</span> BeginTime;</span><br><span class="line"><span class="keyword">float</span> UpdateTime;</span><br><span class="line"><span class="keyword">float</span> DeltaTime;</span><br><span class="line"><span class="keyword">float</span> ForceValue;</span><br><span class="line"><span class="keyword">bool</span> bPendingKill;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要和引擎一样维护所有的 <code>TouchData</code>，这样才可以通过 <code>TouchIndex</code> 与其它的接口关联，对应到其它回调的 <code>PointerEvent</code> 中的 <code>(ETouchIndex::Type)PointerEvent.GetPointerIndex()</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputProcessor</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FInputProcessor</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    TArray &lt;FInputTouchData&gt; InputTouchDatas;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="function">FInputTouchData <span class="title">FInputProcessor::GetInputTouchData</span><span class="params">(ETouchIndex::Type TouchIndex)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    FInputTouchData* DataPtr = InputTouchDatas.<span class="built_in">FindByKey</span>(TouchIndex);</span><br><span class="line">    <span class="keyword">return</span> DataPtr != <span class="literal">nullptr</span> ? *DataPtr : &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Process -----</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FInputProcessor::ProcessMouseEvent</span><span class="params">(ETouchType TouchType, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    FVector2D OutPixelPosition = FVector2D::ZeroVector;</span><br><span class="line">    FVector2D OutViewportPosition = FVector2D::ZeroVector;</span><br><span class="line">    USlateBlueprintLibrary::<span class="built_in">AbsoluteToViewport</span>(<span class="built_in">GetWorld</span>(), MouseEvent.<span class="built_in">GetScreenSpacePosition</span>(), OutPixelPosition, OutViewportPosition);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">InputTouch</span>(MouseEvent.<span class="built_in">GetPointerIndex</span>(), TouchType, OutPixelPosition, MouseEvent.<span class="built_in">GetTouchForce</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FInputProcessor::HandleMouseButtonDownEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">ProcessMouseEvent</span>(ETouchType::Began, MouseEvent);</span><br><span class="line">    <span class="comment">// BrodcastEvents..</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FInputProcessor::HandleMouseMoveEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">ProcessMouseEvent</span>(ETouchType::Moved, MouseEvent);</span><br><span class="line">    <span class="comment">// BrodcastEvents..</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FInputProcessor::HandleMouseButtonUpEvent</span><span class="params">(FSlateApplication&amp; SlateApp, <span class="keyword">const</span> FPointerEvent&amp; MouseEvent)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">ProcessMouseEvent</span>(ETouchType::Ended, MouseEvent);</span><br><span class="line">    <span class="comment">// BrodcastEvents..</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Clear PendingKill Datas</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> Index = InputTouchDatas.<span class="built_in">Num</span>() - <span class="number">1</span>; Index &gt;= <span class="number">0</span>; Index--)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (InputTouchDatas[Index].bPendingKill == <span class="literal">true</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            InputTouchDatas.<span class="built_in">RemoveAt</span>(Index);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InputProcessor</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FInputProcessor::InputTouch</span><span class="params">(uint32 Handle, ETouchType::Type Type, <span class="keyword">const</span> FVector2D&amp; TouchLocation, <span class="keyword">float</span> Force)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="function">FVector <span class="title">Location</span><span class="params">(TouchLocation, Force)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in"><span class="keyword">switch</span></span> (Type)</span><br><span class="line">    &#123;</span><br><span class="line">    <span class="keyword">case</span> ETouchType::Began:</span><br><span class="line">        <span class="built_in">BeginTouch</span>((ETouchIndex::Type)Handle, Location);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> ETouchType::Ended:</span><br><span class="line">        <span class="built_in">EndTouch</span>((ETouchIndex::Type)Handle, Location);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="built_in">MoveTouch</span>((ETouchIndex::Type)Handle, Location);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FInputProcessor::BeginTouch</span><span class="params">(<span class="keyword">const</span> ETouchIndex::Type TouchIndex, <span class="keyword">const</span> FVector Location)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    FInputTouchData* DataPtr = InputTouchDatas.<span class="built_in">FindByKey</span>(TouchIndex);</span><br><span class="line">    <span class="keyword">if</span> (DataPtr == <span class="literal">nullptr</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        InputTouchDatas.<span class="built_in">AddDefaulted</span>(<span class="number">1</span>);</span><br><span class="line">        DataPtr = &amp;InputTouchDatas.<span class="built_in">Last</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    FInputTouchData&amp; InputTouchData = *DataPtr;</span><br><span class="line">    InputTouchData.TouchIndex = TouchIndex;</span><br><span class="line">    InputTouchData.StartLocation = InputTouchData.Location = <span class="built_in">FVector2D</span>(Location.X, Location.Y);</span><br><span class="line">    InputTouchData.ForceValue = Location.Z;</span><br><span class="line">    InputTouchData.BeginTime = InputTouchData.UpdateTime = <span class="built_in">GetWorld</span>()-&gt;<span class="built_in">GetTimeSeconds</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FInputProcessor::MoveTouch</span><span class="params">(<span class="keyword">const</span> ETouchIndex::Type TouchIndex, <span class="keyword">const</span> FVector Location)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    FInputTouchData* DataPtr = InputTouchDatas.<span class="built_in">FindByKey</span>(TouchIndex);</span><br><span class="line">    <span class="keyword">if</span> (DataPtr == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    FInputTouchData&amp; InputTouchData = *DataPtr;</span><br><span class="line">    FVector2D ScreenLocation = <span class="built_in">FVector2D</span>(Location.X, Location.Y);</span><br><span class="line">    InputTouchData.DeltaMove = ScreenLocation - InputTouchData.Location;</span><br><span class="line">    InputTouchData.Location = ScreenLocation;</span><br><span class="line">    InputTouchData.ForceValue = Location.Z;</span><br><span class="line">    InputTouchData.DeltaTime = <span class="built_in">GetWorld</span>()-&gt;<span class="built_in">GetDeltaSeconds</span>();</span><br><span class="line">    InputTouchData.UpdateTime = <span class="built_in">GetWorld</span>()-&gt;<span class="built_in">GetTimeSeconds</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FInputProcessor::EndTouch</span><span class="params">(<span class="keyword">const</span> ETouchIndex::Type TouchIndex, <span class="keyword">const</span> FVector Location)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; InputTouchData : InputTouchDatas)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (InputTouchData.TouchIndex == TouchIndex)</span><br><span class="line">        &#123;</span><br><span class="line">            InputTouchData.bPendingKill = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就在 <code>InputProcessor</code> 中维护了所有的 <code>TouchDatas</code>，可以分发事件给外部监听，以及提供数据查询。</p><h3 id="Gesture">Gesture</h3><p>根据 <code>Touch</code> 的信息，通过 <code>FGestureRecognizer</code> 维护 <code>Gesture_Pinch</code>、<code>Gesture_Flick</code>、<code>Gesture_Rotate</code> 等；</p><p>在 <code> UPlayerInput::Tick</code> 中会进行 <code>GestureRecognizer.DetectGestures(Touches, this, DeltaTime)</code> ：</p><ol><li>对于 <code>Pinch</code>：如果 <code>float* CurrentAlpha = CurrentGestureValues.Find(EKeys::Gesture_Pinch)</code> 即 <code>Pinch</code> 还未开启，并且本次 <code>TouchCount &gt; 2</code>，则认为 <code>Pinch</code> 刚刚开始，此时根据 <code>CurrentPinchPoint_Start</code>、<code>CurrentPinchPoint_End</code> 计算出 <code>AnchorDistanceSq</code> 并存储，将 <code>1.0f</code> 作为基础缩放值存储在 <code>GestureValue</code>；在下一次根据新的 <code>NewDistanceSq / AnchorDistanceSq</code> 作为缩放值记录在 <code>CurrentGestureValues[EKeys::Gesture_Pinch]</code>；  <code>TouchCount</code> 减少到 <code>&lt; 2</code> 时， <code>Pinch</code> 结束；</li><li>对于 <code>Flick</code>：要求 <code>FlickTime &lt; 0.25f &amp;&amp; (FlickCurrent - AnchorPoints[0]).SizeSquared() &gt; 10000.f</code>，经过的时间在 0.25s 内，并且滑动一定的距离；计算和存储 <code>Angle</code> 于 <code>CurrentGestureValues[EKeys::Gesture_Flick]</code>；</li><li>对于 <code>Rotate</code>：如果 <code>float* CurrentAngle = CurrentGestureValues.Find(EKeys::Gesture_Rotate)</code> 即 <code>Rotate</code> 还未开启，当 <code>TouchCount &gt; 2</code> 时，则认为 <code>Rotate</code> 刚刚开始，此时计算出 <code>StartAngle</code> 记录，将 <code>0.0f</code> 作为基础旋转值存储在 <code>GestureValue</code>；在后续更新 <code>GestureValue</code> 为 <code>NewAngle - StartAngle</code>，表示相对角度；  <code>TouchCount</code> 减少到 <code>&lt; 2</code> 时， <code>Rotate</code> 结束；</li></ol><p>最后：</p><table><thead><tr><th>Gesture</th><th>CurrentGestureValue : Type</th></tr></thead><tbody><tr><td>Pinch</td><td>Scale （相对开始的比例缩放）</td></tr><tr><td>Flick</td><td>Angle（本次滑动的角度）</td></tr><tr><td>Rotate</td><td>Angle （相对开始的角度）</td></tr></tbody></table><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UPlayerInput::InputGesture</span><span class="params">(<span class="keyword">const</span> FKey Gesture, <span class="keyword">const</span> EInputEvent Event, <span class="keyword">const</span> <span class="keyword">float</span> Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">FKeyState&amp; KeyState = KeyStateMap.<span class="built_in">FindOrAdd</span>(Gesture);</span><br><span class="line"></span><br><span class="line">KeyState.Value.X = KeyState.RawValue.X = KeyState.RawValueAccumulator.X = Value;</span><br><span class="line">KeyState.EventAccumulator[Event].<span class="built_in">Add</span>(++EventCount);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这些信息会存储在 <code>KeyState</code> 里，通过 <code>UPlayerInput::GetKeyState(FKey InKey)</code> 获取；</p><p>当然，在 <code>Mouse</code> 的回调中：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchStarted</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchMoved</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FReply <span class="title">NativeOnTouchEnded</span><span class="params">(<span class="keyword">const</span> FGeometry&amp; InGeometry, <span class="keyword">const</span> FPointerEvent&amp; InGestureEvent)</span> <span class="keyword">override</span></span>;</span><br></pre></td></tr></table></figure><p>可以看到这里有 <code>GestureEvent</code>，也可以获取对应主要信息：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">FVector2D ScreenSpacePosition;</span><br><span class="line">FVector2D LastScreenSpacePosition;</span><br><span class="line">FVector2D CursorDelta;</span><br><span class="line"><span class="keyword">const</span> TSet&lt;FKey&gt;* PressedButtons;</span><br><span class="line">FKey EffectingButton;</span><br><span class="line">uint32 PointerIndex;</span><br><span class="line">uint32 TouchpadIndex;</span><br><span class="line"><span class="keyword">float</span> Force;</span><br><span class="line"><span class="keyword">bool</span> bIsTouchEvent;</span><br><span class="line">EGestureEvent GestureType;</span><br><span class="line">FVector2D WheelOrGestureDelta;</span><br><span class="line"><span class="keyword">bool</span> bIsDirectionInvertedFromDevice;</span><br><span class="line"><span class="keyword">bool</span> bIsTouchForceChanged;</span><br><span class="line"><span class="keyword">bool</span> bIsTouchFirstMove;</span><br></pre></td></tr></table></figure><p>实际上，可以将这里的 <code>GestureRecognizer</code> 移到统一维护的 <code>InputManager</code> 中，将相关信息提供给各个业务查询，方便自定义与扩展：比如增加 <code>Tag</code> 限定提供给外部设置，当且仅当 <code>Tag</code> 一致时，进行对应 <code>Gesture</code> 更新等。</p><h2 id="参考">参考</h2><p><em><strong>UE 5.4 源码</strong></em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;InputSystem基础框架&lt;/h1&gt;
&lt;h2 id=&quot;Key&quot;&gt;Key&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;flowchart LR

Input(PlayerController::UpdateInput)

Input
--&gt;InputProces</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="3C" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="3C" scheme="https://blog.bearchild.top/tags/3C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]CharacterMovement源码浅析</title>
    <link href="https://blog.bearchild.top/2024/06/10/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/[UE]CharacterMovement%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/"/>
    <id>https://blog.bearchild.top/2024/06/10/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/[UE]CharacterMovement%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/</id>
    <published>2024-06-09T16:00:00.000Z</published>
    <updated>2024-08-02T14:30:53.327Z</updated>
    
    <content type="html"><![CDATA[<h1>CharacterMovement源码浅析</h1><h2 id="Base">Base</h2><pre class="mermaid">classDiagramdirection LRclass UMovementComponent {UpdatedComponent : TObjectPtr~USceneComponent~ (处理空间位置)UpdatedPrimitive : TObjectPtr~UPrimitiveComponent~ (处理渲染物理)}%% -------------UMovementComponent<|--UProjectileMovementComponentclass UProjectileMovementComponent {支持发射体（子弹等）}%% -------------UMovementComponent<|--UNavMovementComponentclass UNavMovementComponent {支持 Agent 寻路NavAgentProps : FNavAgentProperties}UNavMovementComponent<|--UPawnMovementComponentclass UPawnMovementComponent {支持输入控制AddInputVector()}UPawnMovementComponent<|--UCharacterMovementComponent</pre><p><code>Move</code> 一般是先进行基础运动（<code>PerformMovent</code>），然后处理基于物理的模拟（<code>Collision</code>、<code>Simulation</code>）；</p><pre class="mermaid">flowchart LRUCharacterMovementComponentTickComponent-->ConsumeInputVectorTickComponent-->ControlledCharacterMoveTickComponent-->Other...</pre><ol><li><code>ComsumeInputVector</code>：</li></ol><p>从 <code>PawnOwner</code> 取出累积的 <code>ControlInputVector</code>，该值监听输入并调用 <code>Pawn::AddMovementInput</code> 得来；</p><ol start="2"><li><code>ControlledCharacterMove</code>：</li></ol><p>进行 <code>Character</code> 移动的输入处理、物理模拟、同步；</p><ol start="3"><li><code>Other...</code></li></ol><h2 id="ControlledCharacterMove">ControlledCharacterMove</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCharacterMovementComponent::ControlledCharacterMove</span><span class="params">(<span class="keyword">const</span> FVector&amp; InputVector, <span class="keyword">float</span> DeltaSeconds)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">SCOPE_CYCLE_COUNTER</span>(STAT_CharUpdateAcceleration);</span><br><span class="line"></span><br><span class="line"><span class="comment">// We need to check the jump state before adjusting input acceleration, to minimize latency</span></span><br><span class="line"><span class="comment">// and to make sure acceleration respects our potentially new falling state.</span></span><br><span class="line">CharacterOwner-&gt;<span class="built_in">CheckJumpInput</span>(DeltaSeconds);</span><br><span class="line"></span><br><span class="line"><span class="comment">// apply input to acceleration</span></span><br><span class="line">Acceleration = <span class="built_in">ScaleInputAcceleration</span>(<span class="built_in">ConstrainInputAcceleration</span>(InputVector));</span><br><span class="line">AnalogInputModifier = <span class="built_in">ComputeAnalogInputModifier</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (CharacterOwner-&gt;<span class="built_in">GetLocalRole</span>() == ROLE_Authority)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">PerformMovement</span>(DeltaSeconds);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (CharacterOwner-&gt;<span class="built_in">GetLocalRole</span>() == ROLE_AutonomousProxy &amp;&amp; <span class="built_in">IsNetMode</span>(NM_Client))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">ReplicateMoveToServer</span>(DeltaSeconds, Acceleration);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><pre class="mermaid">flowchart LRControlledCharacterMove-->CheckJumpInput-.->ScaleInputAcceleration-.->ComputeAnalogInputModifierControlledCharacterMove-->|ROLE_Authority|PerformMovement-->Start(StartNewPhysics)ControlledCharacterMove-->|ROLE_AutonomousProxy & IsClient|ReplicateMoveToServerStart-->MOVE_NoneStart-->MOVE_WalkingStart-->MOVE_FallingStart-->MOVE_FlyingStart-->MOVE_SwimmingStart-->MOVE_Custom</pre><h3 id="Input">Input</h3><p>解析输入相关的数据；</p><ol><li><code>CheckJumpInput</code>：根据 <code>bPressedJump</code>，计算 <code>JumpCurrentCount</code>、<code>JumpForceTimeRemaining</code>；</li><li><code>ScaleInputAcceleration</code>：根据玩家的输入 <code>InputVector</code>，计算出当前的初始加速度值；</li><li><code>ComputeAnalogInputModifier</code>：模拟输入修正值，将 <code>Acceleration /= MaxAcceleration</code>，限制在 <code>0-1</code> 内；</li></ol><h3 id="PerfomeMovement">PerfomeMovement</h3><p>进行基础运动模拟，设置位移。</p><h4 id="Kinetic-Walking">Kinetic : Walking</h4><pre class="mermaid">flowchart LRUCharacterMovementComponent%%-------------------------------------PerformMovement-->StartNewPhysics-->PhysWalking%%-------------------------------------PhysWalking-->GetSimulationTimeStepPhysWalking-->CalcVelocityPhysWalking-->MoveAlongFloorPhysWalking-->FindFloorPhysWalking-->CheckLedgesPhysWalking-->MaintainHorizontalGroundVelocity%%-------------------------------------MoveAlongFloor-->ComputeGroundMovementDeltaMoveAlongFloor-->SafeMoveUpdatedComponent%%-------------------------------------CheckLedges-->|true|GetLedgeMoveGetLedgeMove-->|true|RevertMove-->TryLedgeMoveGetLedgeMove-->|false|bMustJump-->|true|RevertMove-->FallCheckLedges-->|false|FloorCheck%%-------------------------------------FloorCheck-->IsWalkableFloorIsWalkableFloor-->|true|AdjustFloorHeight-->SetBaseIsWalkableFloor-->|false|GetPenetrationAdjustment-->ResolvePenetration%%-------------------------------------</pre><p><code>GetSimulationTimeStep</code>：将 <code>TickDeltaTime</code> 按照 <code>MaxSimulationTimeStep</code> 分割为若干段（为了保证平滑），处理每一段的信息；</p><p><code>CalcVelocity</code>：根据 <code>Friction</code>、<code>bFluid</code>、<code>BrakingDeceleration</code> 修改 <code>Acceleration</code>，并计算出 <code>Velocity</code> 水平速度；</p><p><code>MoveAlongFloor</code>：根据 <code>MoveVelocity</code> 信息，先调用 <code>ComputeGroundMovementDelta</code>，根据 <code>Velocity</code> 计算出 <code>RampVector</code> 平行于斜面的移动距离，同时根据 <code>bMaintainHorizontalGroundVelocity</code> 处理沿斜面速度减慢的情况；然后调用 <code>SafeMoveUpdatedComponent</code>，进行 <code>MoveUpdatedComponent</code>，更新位置；在 <code>UPrimitiveComponent::MoveComponentImpl</code> 中，还会进行 <code>World-&gt;ComponentSweepMulti</code> 判断是否遇到障碍物；</p><p><code>FindFloor</code>：更新 <code>CurrentFloor : FFindFloorResult</code> 信息；用 <code>CharacterOwner-&gt;GetCapsuleComponent()</code> 进行 <code>FloorSweepTest</code>，计算出 <code>ValidPerchRadius</code> 等信息；</p><p><code>CheckLedges</code>：检测是否在 <code>Ledge</code> 附近，如果是，则先尝试寻找新的移动方向（通过 <code>GetLedgeMove</code> 进行 <code>SweepSingleByChannel</code> 计算出边缘法线返回新的反向）；如果找不到新的方向，则尝试跳跃，检测 <code>bMustJump</code>，如果不能跳跃则取消移动；</p><p><code>FloorCheck</code>：校验 <code>Floor</code> 相关数据，如果 <code>Character</code> 处于<code>IsWalkableFloor</code> 的 <code>Floor</code>，<code>AdjustFloorHeight</code> 来调整 <code>Character</code> 的高度，如果在 <code>Floor</code> 中则 <code>GetPenetrationAdjustment</code> 计算需要弹出 <code>Character</code> 的距离并 <code>ResolvePenetration</code>，防止 <code>Floor</code> 和 <code>Character</code> 有冲突卡住；</p><p><code>MaintainHorizontalGroundVelocity</code>：调用之前判断是否依然 <code>IsMovingOnGround</code>，如果是则根据 <code>bMaintainHorizontalGroundVelocity</code>，计算 <code>GravityRelativeVelocity</code> 进而更新 <code>Velocity</code>；</p><h4 id="Kinetic-Falling">Kinetic : Falling</h4><pre class="mermaid">flowchart LRPerformMovement-->StartNewPhysics-->PhysFallingPhysFalling-->GetFallingLateralAccelerationPhysFalling-->ShouldLimitAirControlPhysFalling-->RestorePreAdditiveRootMotionVelocityPhysFalling-->CalcVelocityPhysFalling-->ApplyRootMotionToVelocityPhysFalling-->NotifyJumpApexPhysFalling-->SafeMoveUpdatedComponentPhysFalling-->|IsSwimming|StartSwimmingPhysFalling-->BlockingHit%%---------------------BlockingHit-->|IsValidLandingPoint|ProcessLandedBlockingHit-->HandleImpact%%---------------------ProcessLanded-->|IsFalling|SetPostLandedPhysicsProcessLanded-->StartNewPhysics_2%%---------------------HandleImpact-.-> CalcVelocity_2-.-> BlockingHit_2</pre><p><code>GetFallingLateralAcceleration</code>：计算 <code>Character</code> 在水平方向上的加速度；重点是将 <code>WorldAcceleration</code> 通过<code>RotateWorldToGravity</code> 转为 <code>Gravity</code> 相关坐标系，然后将 <code>Z</code> 的方向设为 <code>0</code>，再转回 <code>World</code> 坐标系，这样以移除垂直方向上的加速度（因为垂直方向的加速度需要由 <code>Gravity</code> 决定，而不是 <code>InputVector</code>）；</p><p><code>RestorePreAdditiveRootMotionVelocity</code>：<code>Apply AdditiveRootMotion</code> 的情况下将 <code>Velocity</code> 设置为 <code>LastPreAdditiveVelocity</code>（ <code>AdditiveRootMotion</code> 表示 <code>RootMotioinVelocity</code> 将与 <code>Character</code> 的原始速度 <code>LastPreAdditiveVelocity</code>，即计算 <code>RootMotionVelocity</code> 前的速度叠加），防止 <code>RootMotion Velocity</code> 被累加；</p><p><code>CalcVelocity</code>：根据 <code>FallAcceleration</code>、<code>Gravity</code>、<code>JumpForce</code> 等数据，计算出 <code>NewFallVelocity</code>；</p><p><code>ApplyRootMotionToVelocity</code>：应用 <code>RootMotion Velocity</code>，根据 <code>HasOverrideVelocity / HasAdditiveVelocity</code> 两种应用速度方式，计算 <code>Velocity</code>；</p><p><code>NotifyJumpApex</code>：当 <code>RotateWorldToGravity(Velocity).Z &lt; 0</code> 时，说明到达了 <code>JumpApex</code> 跳跃顶点，进行通知；</p><p><code>SafeMoveUpdatedComponent</code>：进行位移设置；</p><p><code>BlokingHit</code>：在碰到障碍物时的处理；</p><p><code>ProcessLanded</code>：判断是否 <code>IsValidLandingPoint</code>，如果是，进行着陆；进行通知 <code>Landed</code> 与设置相关物理状态 <code>SetPostLandedPhysics</code>，然后开始新的物理模拟 <code>StartNewPhysics</code>；</p><p><code>HandleImpact</code>：无法着陆时，<code>AddImpactPhysicsForces</code> ，用于后续计算碰撞后的 <code>Velocity</code>与位移；</p><p><code>BlockingHit_2</code>：碰撞移动后再次计算是否再次 <code>BlockingHit</code>，如果无 <code>Hit</code>，则尝试 <code>FindFloor</code>，找到 <code>Floor</code>则尝试着陆；如果是，说明 <code>Character</code> 被卡在了两个障碍物中间；检测是否 <code>IsValidLandingPoint</code> 是着陆点，是则 <code>ProcessLand</code>；如果不是着陆点，特殊处理被卡住 <code>bDitch</code> 的情况（检查 <code>OldHitImpactNormal</code>、<code>Hit.ImpactNormal</code> 是否都具有 <code>Z</code> 即斜坡朝上，且夹角 <code>&gt;90°</code> 即斜坡朝向不同，同时 <code>Character</code> 的 <code>Delta.Z</code> 接近 <code>0</code> 即在垂直方向无移动），如果是，尝试增加 <code>Velocity</code> 与位移，摆脱被卡住的情况；</p><h4 id="Kinetic-Other">Kinetic : Other</h4><p>TODO…</p><h3 id="ReplicateMoveToServer">ReplicateMoveToServer</h3><p>对于 <code>AutonomousProxy Character</code> 将移动同步到服务器，同时进行 <code>Client</code> 本地的预表现；</p><p>主要维护三种 <code>Move</code> 数据：</p><blockquote><ol><li><code>Old Move</code>：当前还未被 <code>DS</code> <code>ACK</code> 的 <code>Move</code> 数据中，最早的一次 <code>Move</code>；</li><li><code>New Move</code>：本次执行（即 <code>Client</code> 进行 <code>Perform</code>）的 <code>Move</code>；</li><li><code>Pending Move</code>：若某次 <code>New Move</code> 还未进行同步（等待并包），将其存储在 <code>Pending Move</code> 中，等待下次同步带上该数据；</li></ol></blockquote><p>在 <code>CallServerMovePacked</code> 时，打包三种 <code>Move</code> 同步；</p><p>首先需要了解：<code>FNetworkPredictionData</code>；</p><h4 id="FNetworkPredictionData">FNetworkPredictionData</h4><p><code>PredictionData_Client_Character</code> 维护 <code>Client</code> 的 <code>Move</code> 相关数据，同时用于合并、丢弃、比较、标记更新等操作；</p><pre class="mermaid">classDiagramclass FNetworkPredictionData_Client_Character {SavedMoves : TArray~FSavedMovePtr~FreeMoves : TArray~FSavedMovePtr~PendingMove : FSavedMovePtrLastAckedMove : FSavedMovePtrClientUpdateRealTime : floatbUpdatePosition : uint32...}FNetworkPredictionData_Client_Character-->FSavedMove_Characterclass FSavedMove_Character {        TimeStamp : float        DeltaTime : floatAcceleration : FVectorMaxSpeed : floatStart / End / Saved : Location / ReletiveLocation / Rotation / Velocity / Floor / CapsuleRadius / CapsuleHalfHeight / Base / ActorOverlapCounter ......}</pre><p>其中：</p><p><code>FSavedMovePtr</code> 是 <code>TSharedPtr&lt;FSavedMove_Character&gt;</code>；</p><p><code>SavedMoves</code> 保存 <code>Client</code> 执行的 <code>Move</code>，在 <code>CleintAck</code> 后， <code>LastAckedMove</code> 将会被 <code>Free</code> 并从 <code>SavedMoves</code> 中移除；</p><p><code>PendingMove</code> 记录 <code>Client</code> 最新执行的，还未 <code>CallServer</code> 的 <code>Move</code>，每次 <code>Push</code> 到 <code>SavedMoves</code> 中，同时可能会作为 <code>OldMove</code> 被  <code>Combine</code>；</p><p><code>FreeMoves</code> 记录已经被标记 <code>Free</code> 的 <code>Move</code>，后续释放；</p><pre class="mermaid">classDiagram    class FNetworkPredictionData_Server_Character {    PendingAdjustment : FClientAdjustment}FNetworkPredictionData_Server_Character-->FClientAdjustmentclass FClientAdjustment {TimeStamp : floatDeltaTime : floatbAckGoodMove : boolNew Loc / Vel / Rot / Base ......}</pre><p><code>PredictionData_Server_Character</code> 记录在 <code>Server</code> 上的 <code>Move</code> 数据，用于校验、修正等；</p><p>其中：</p><p><code>PendingAdjustment</code> 维护了一系列 <code>ClientAdjust</code> 所需的数据；</p><h4 id="ReplicateMoveToServer-Logic">ReplicateMoveToServer - Logic</h4><pre class="mermaid">flowchart LRReplicateMoveToServer-->GetPredictionData_Client_CharacterReplicateMoveToServer-->ClientData_UpdateTimeStampAndDeltaTimeReplicateMoveToServer-->FindImportantMoveReplicateMoveToServer-->ClientData_CreateSavedMove-.->Move_SetMoveFor-.->Move_CombineReplicateMoveToServer-->PerformMovement-.->Move_PostUpdate-.->ClientData_SaveMoveReplicateMoveToServer-->CallServerMove-.->ClearPending</pre><p><code>GetPredictionData_Client_Character</code>：获取 <code>Client</code> 的预测数据 <code>ClientData : FNetworkPredictionData_Client_Character*</code>；</p><p><code>ClientData</code> 会在各个地方被更新，比如</p><blockquote><ol><li><code>ReplicateMoveToServer</code> 中：<br>更新物理模拟的 <code>TimeStamp</code>与 <code>DeltaTime</code>：<code>ClientData-&gt;UpdateTimeStampAndDeltaTime</code>；<br>创建新的 <code>SavedMove</code> ：<code>ClientData-&gt;CreateSavedMove()</code>；<br><code>PerformMovement</code> 之后更新 <code>Location</code>、<code>Rotation</code>、<code>Velocity</code> 等数据：<code>NewMove-&gt;PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record)</code>;</li><li><code>CallServerMove / CallServerMovePacked</code> 前更新时间：<code>ClientData-&gt;ClientUpdateRealTime = MyWorld-&gt;GetRealTimeSeconds();</code></li><li><code>ClientAckGoodMove</code>：<code>Client</code> 收到 <code>Server</code> 的移动确认时，更新最后的移动 <code>ClientData-&gt;LastAckedMove</code>：</li></ol></blockquote><p><code>FindImportantMove</code>：找到最早的未 <code>Ack</code> 的 <code>ImportantMove</code> 数据，<code>IsImportantMove</code> 指与上一个 <code>Ack</code> 的移动有差异的移动；判定是否 <code>Important</code> 时，会检查 <code>CompressedFlags</code> （压缩了 <code>FLAG_JumpPressed</code>、<code>FLAG_WantsToCrouch</code> 等信息）、<code>Start/End PackedMovementMode</code>、<code>Acceleration</code> 的大小、方向差异是否超过阈值；找到 <code>Unack ImportMove</code> 后，存储在 <code>OldMove</code> 中，后续将其与新的 <code>Move</code> 一起 <code>CallServerMove</code>，确保 <code>Server</code> 可以正确处理。</p><p><code>CreateSavedMove</code>：创建新的 <code>FSavedMove</code> 数据，也就是定义一个新的 <code>Move</code>；</p><p><code>Move_SetMoveFor</code>：根据 <code>CharacterOwner</code>、<code>DeltaTime</code>、<code>NewAcceleration</code> 等数据设置 <code>Move</code> 基本信息；</p><p><code>Move_Combine</code>：尝试将这个新的 <code>Move</code> 与 待处理的移动 <code>PendingMove</code> 合并，如果 <code>CanCombine</code>，更新 <code>Rotation</code>、<code>Position</code> 等信息；<code>CanCombine</code> 会校验 <code>TimeStamp</code>、<code>RootMotion</code>、<code>Acceleration</code>、<code>StartVelocity</code>、<code>MaxSpeed</code>、<code>Jump</code>、<code>CompressedFlags</code>、<code>MovementMode</code>、<code>StartCapsule Radius/HalfHeight</code>、<code>AttachParent</code>、<code>TimeDilation</code>、<code>ActorOverlapCounter</code> 这些数据；<code>Combine</code> 时更新 <code>Location</code>、<code>Rotation</code>、<code>Velocity</code>、<code>Floor</code>、<code>Jump</code> 等数据；</p><p><code>PerformMovement</code>：在本地执行移动；</p><p><code>Move_PostUpdate</code>：在 <code>PerformMovement</code> 更新了移动相关数据之后，设置这些状态数据到 <code>Move</code> 中；</p><p><code>ClientData_SaveMove</code>：将 <code>NewMove</code> 保存到移动列表 <code>ClientData-&gt;SavedMoves</code> 中；</p><p><code>CallServerMove</code>：根据角色是否正在复制移动 <code>bSendServerMove</code> 将新的移动发动到 <code>Server</code>，根据 <code>ShouldUsePackedMovementRPCs</code> 决定发送的方式 <code>CallServerMovePacked / CallServerMove</code>；</p><p><code>ClearPendingMove</code>：清空 <code>PendingMove</code>，表示没有待处理的移动；</p><h2 id="AutonomousProxy">AutonomousProxy</h2><p>从 <code>ReplicateMoveToServer -&gt; CallServerMovePacked</code> 继续出发：</p><pre class="mermaid">flowchart LRCallServerMovePacked-->ServerMovePacked_ClientSend-->|DS|ServerMovePacked_Implementation-->ServerMovePacked_ServerReceiveServerMovePacked_ServerReceive-->ServerMove_HandleMoveDataServerMove_HandleMoveData-->SetCurrentNetworkMoveDataServerMove_HandleMoveData-->ServerMove_PerformMovementServerMove_PerformMovement-->MoveAutonomousServerMove_PerformMovement-->ServerMoveHandleClientError-->ServerCheckClientErrorMoveAutonomous-->PerformMovement</pre><p>通过 <code>CallServerMovedPacked (UnreliableRPC)</code> 将打包的 <code>SaveMoves</code> 数据发送到 <code>DS</code>，<code>DS</code> 根据 <code>Client</code> 发送到的数据应用 <code>Move</code> 数据，进行<code>SetCurrentNetworkMoveData</code>、<code>ServerMove_PerformMovent</code>，同时在 <code>ServerMove_PerformMovent</code> 中 <code>MoveAutonomous</code> （内部还是 <code>PerformMovement</code>）与 校验数据合法性 <code>CheckClientError</code>；如果数据差异过大，则 <code>ServerData-&gt;PendingAdjustment.bAckGoodMove = false</code>；</p><p><code>Move</code> 数据变化后，通过 <code>Replicate</code> 将其复制到 <code>Client</code>，主要涉及的数据有：</p><blockquote><ol><li><code>bReplicateMovement</code>：标记是否要进行 <code>Move</code> 的同步；</li><li><code>ReplicatedMovement</code>：移动数据；</li><li><code>ReplicatedBasedMovement</code>：<code>Base</code> 的移动数据；</li><li><code>ReplicatedMovementMode</code>：移动模式（<code>Walk</code>、<code>Fall</code> 等）</li><li>其它数据：<code>Transform</code>、<code>RootMotion</code> 等；</li></ol></blockquote><pre class="mermaid">flowchart LRUNetDriver::TickFlush-->UNetDriver::ServerReplicateActors-->SendClientAdjustmentSendClientAdjustment-->bAckGoodMovebAckGoodMove-->|true|ServerLastClientGoodMoveAckTime-->ShouldUsePackedMovementRPCs_GoodbAckGoodMove-->|false|ServerLastClientAdjustmentTime-->ShouldUsePackedMovementRPCs_NoGoodShouldUsePackedMovementRPCs_Good-->|false|ClientAckGoodMoveShouldUsePackedMovementRPCs_NoGood-->|false|ClientAdjustPositionShouldUsePackedMovementRPCs_Good-->|true|ServerSendMoveResponseShouldUsePackedMovementRPCs_NoGood-->|true|ServerSendMoveResponseServerSendMoveResponse-->MoveResponsePacked_ServerSend-->ClientMoveResponsePacked-->|Client|MoveResponsePacked_ClientReceive</pre><p>同步时候，也向 <code>Client</code> 进行 <code>SendClientAdjust</code>，通知 <code>Client</code> 每次 <code>NewMove</code> 的结果；</p><p>根据 <code>ShouldUsePackedMovemtnRPCs</code> 决定是否需要 <code>ServerSendMoveResponse</code>；</p><pre class="mermaid">flowchart LRMoveResponsePacked_ClientReceive-->ClientHandleMoveResponse-->IsGoodMoveIsGoodMove-->|true|ClientAckGoodMove_ImplementationIsGoodMove-->|false|ClientAdjustPosition_Implementation-->SetbUpdatePosition_trueTickComponent-->ClientUpdatePositionAfterServerUpdate</pre><p><code>Client</code> 收到 <code>DS</code> 的 <code>SendClientAdjust</code> 后，判定 <code>MoveResponse</code> 是否 <code>IsGoodMove</code>，如果是，<code>Client</code> 进行 <code>Ack</code>，被确认的 <code>Move</code> 将会立刻从 <code>SavedMoves</code> 中移除；否则 <code>Client</code> 需要更新 <code>bUpdatePosition</code> 为 <code>true</code>，后续在 <code>ClientUpdatePositionAfterServerUpdate</code> 中进行修正；</p><p><code>ClientUpdatePositionAfterServerUpdate</code>：判定 <code>bUpdatePosition</code> 是否是 <code>true</code>，如果是则回放 <code>DS</code> 未 <code>Ack</code> 的 <code>ClientData-&gt;SavedMoves.Num()</code>，进行 <code>SetCurrentReplayedSavedMove</code> 并 <code>MoveFor</code>；</p><h2 id="SimulateProxy">SimulateProxy</h2><pre class="mermaid">flowchart LRACharacter::OnRep_ReplicatedMovement-->AActor::PostNetReceiveVelocityACharacter::OnRep_ReplicatedMovement-->ACharacter::PostNetReceiveLocationAndRotationAActor::PostNetReceiveVelocity-->UPrimitiveComponent::SetPhysicsLinearVelocityACharacter::PostNetReceiveLocationAndRotation-->SmoothCorrectionACharacter::PostNetReceiveLocationAndRotation-->SetbNetworkUpdateReceived_true</pre><p>数据同步后，<code>Client</code> 通过 <code>OnRep_ReplicatedBasedMovement</code>、<code>OnRep_ReplicatedMovement</code> 将坐标设置给 <code>Actor</code>；</p><pre class="mermaid">flowchart LRTickComponent-->SimulatedTick-->SimulateMovementSimulatedTick-->|!bNetworkSmoothingComplete|SmoothClientPosition%% -----------SimulateMovement-->ScopedUpdatesScopedUpdates-->bIsSimulatedProxybIsSimulatedProxy-->bNetworkUpdateReceived_truebNetworkUpdateReceived_true-->|bNetworkGravityDirectionChanged|SetGravityDirectionbNetworkUpdateReceived_true-->|bNetworkMovementModeChanged| ApplyNetworkMovementModebNetworkUpdateReceived_true-->|bJustTeleported OR bForceNextFloorCheck|UpdateFloorFromAdjustmentbNetworkUpdateReceived_false-->|bForceNextFloorCheck|UpdateFloorFromAdjustment%% -----------ScopedUpdates-->UpdateCharacterStateBeforeMovementScopedUpdates-->MaybeUpdateBasedMovementScopedUpdates-->UpdateProxyAccelerationScopedUpdates-->|!bHandledNetUpdate OR !bNetworkSkipProxyPredictionOnNetUpdate|MoveSmooth-->IsMovingOnGroundIsMovingOnGround-->|true|MoveAlongFloorIsMovingOnGround-->|false|SafeMoveUpdatedComponent-->|!bSteppedUp|SlideAlongSurfaceScopedUpdates-->UpdateCharacterStateAfterMovementScopedUpdates-->OnMovementUpdatedSimulateMovement-->CallMovementUpdateDelegateSimulateMovement-->UpdateComponentVelocity%% -----------SmoothClientPosition-->SmoothClientPosition_InterpolateSmoothClientPosition-->SmoothClientPosition_UpdateVisuals</pre><p><code>Smooth</code>：</p><p><code>SmoothingServerTimeStamp</code> 表示 <code>Character</code> 在  <code>DS</code> 当前移动时间戳，由 <code>ACharacter::PreReplication</code> 时，同步的 <code>ReplicatedServerLastTransformUpdateTimeStamp</code> 得来；</p><p><code>SmoothingClientTimeStamp</code> 表示 <code>Character</code> 在这个  <code>Client</code> 当前平滑到的移动时间戳；</p><p>每次进行 <code>SmoothClientPosition_Interpolate</code> 时；</p><p>在 <code>SmoothingMode = ENetworkSmoothingMode::Linear</code> 的情况下：</p><ol><li>计算 <code>TargetDelta = LastCorrectionDelta</code>，这里的 <code>LastCorrectionDelta = 上一次( SmoothingServerTimeStamp - SmoothingClientTimeStamp)</code> ，表示实际上相比于 <code>DS</code> 上的数据，<code>Client</code> 在这一次平滑开始前，还剩余多少时间还未执行平滑操作；</li><li>更新 <code>SmoothingClientTimeStamp = Min(SmoothingClientTimeStamp + DeltaSeconds, SmoothingServerTimeStamp + MaxTimeAhead);</code><br>这里的 <code>DeltaSeconds</code> 表示当帧过去的实际时间；<br><code>MaxTimeAhead = TargetDelta * 0.15f</code>，表示允许多往前外插的时间，<code>0.15f</code> 是允许多预测的时间比例；<br>现在这个新的 <code>SmoothingClientTimeStamp</code>，就表示这一帧 <code>Client</code> 需要平滑到的时间戳；</li><li>计算 <code>RemainingTime = SmoothingServerTimeStamp - SmoothingClientTimeStamp</code>， 表示在这一帧平滑过后，还剩下多少时间没有平滑；然后 <code>CurrentSmoothTime = TargetDelta - RemainingTime</code>，得到这一帧需要平滑多少时间；</li><li>计算 <code>LerpPercent = FMath::Clamp(CurrentSmoothTime / TargetDelta, 0.0f, LerpLimit)</code>，按照 <code>本次平滑多少时间 / 剩余的总共需要平滑的时间</code>，得到这个 <code>LerpPercent</code>；<br>其中 <code>LerpLimit = 1.15f</code>，也是允许多平滑的比例；</li><li>得到 <code>LerpPercent</code> 后，更新 <code>MeshTranslationOffset</code>、<code>MeshRotationOffset</code>；</li></ol><p>特别地，如果不是通过 <code>DS</code> 模式来进行同步（比如自定义协议），可以打包 <code>FRepMovement</code> 数据，然后手动进行 <code>Replicate</code> 模拟：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CharacterActor-&gt;<span class="built_in">PreNetReceive</span>();</span><br><span class="line">CharacterActor-&gt;<span class="built_in">SetReplicatedMovementMode</span>();</span><br><span class="line">CharacterActor-&gt;<span class="built_in">PostNetReceive</span>();</span><br><span class="line"></span><br><span class="line">CharacterActor-&gt;<span class="built_in">SetReplicatedMovement</span>();</span><br><span class="line">CharacterActor-&gt;<span class="built_in">OnRep_ReplicatedMovement</span>();</span><br></pre></td></tr></table></figure><h2 id="参考">参考</h2><p><em><strong>UE 5.4 源码</strong></em></p><p><em><strong>大体框架：</strong></em><a href="https://zhuanlan.zhihu.com/p/114341957">UE4 移动的网络同步</a></p><p><em><strong>核心代码：</strong></em><a href="https://zhuanlan.zhihu.com/p/34257208">《Exploring in UE4》移动组件详解[原理分析]</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;CharacterMovement源码浅析&lt;/h1&gt;
&lt;h2 id=&quot;Base&quot;&gt;Base&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;classDiagram

	direction LR
	
	class UMovementComponent {
		Upda</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="3C" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="3C" scheme="https://blog.bearchild.top/tags/3C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]ToString解决方案</title>
    <link href="https://blog.bearchild.top/2024/06/01/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]ToString%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.bearchild.top/2024/06/01/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]ToString%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-05-31T16:00:00.000Z</published>
    <updated>2024-06-03T13:40:26.813Z</updated>
    
    <content type="html"><![CDATA[<h1>ToString解决方案</h1><h2 id="背景">背景</h2><p>提供一个通用机制 <code>UStringUtils::ToString(Value)</code> 将其 <code>String</code> 化。</p><p>其中 <code>Value</code> 包括：</p><ol><li><code>Base</code>：基础类型（<code>int</code>、<code>bool</code>、<code>float</code>、<code>FString</code> 等）；</li><li><code>Enum</code>；</li><li>包含 <code>ToString</code> 或 <code>GetUID</code> 方法的类型、指针；</li><li><code>Pointer</code>：<code>TStrongPtr</code>、<code>TShaerdPtr</code>、<code>TWeakPtr</code>、<code>TUniquePtr</code>；</li><li><code>Variant</code>；</li><li><code>Container</code>：包括 <code>Array</code>、<code>Set</code>、<code>Map</code> 等；</li></ol><h2 id="实现">实现</h2><h3 id="基础类型">基础类型</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsIntegral&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">FromInt</span>(<span class="keyword">static_cast</span>&lt;int32&gt;(Value));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> uint32&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%u&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> int64&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%lld&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> uint64&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%llu&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsFloatingPoint&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%.3f&quot;</span>), <span class="keyword">static_cast</span>&lt;<span class="keyword">float</span>&gt;(Value));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> <span class="keyword">bool</span>&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s&quot;</span>), Value ? <span class="built_in">TEXT</span>(<span class="string">&quot;true&quot;</span>) : <span class="built_in">TEXT</span>(<span class="string">&quot;false&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> FString&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Enum">Enum</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsEnum&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(google::protobuf::is_proto_enum&lt;T&gt;::value)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">auto</span> String = ::google::protobuf::internal::<span class="built_in">NameOfEnum</span>(::google::protobuf::<span class="keyword">template</span> GetEnumDescriptor&lt;T&gt;(), <span class="keyword">static_cast</span>&lt;<span class="keyword">int</span>&gt;(Value));</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">UTF8_TO_TCHAR</span>(String.<span class="built_in">c_str</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> <span class="built_in"><span class="keyword">constexpr</span></span> (TIsUEnumClass&lt;T&gt;::Value)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">auto</span> EnumClass = StaticEnum&lt;T&gt;();</span><br><span class="line">        <span class="keyword">return</span> EnumClass-&gt;<span class="built_in">GetNameStringByValue</span>(<span class="keyword">static_cast</span>&lt;int64&gt;(Value));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;RawEnum_%s&quot;</span>), *<span class="built_in">ToString</span>(<span class="keyword">static_cast</span>&lt;__underlying_type(T)&gt;(Value)));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsEnum&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TEnumAsByte&lt;T&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">ToString</span>(Value.<span class="built_in">GetValue</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，这里用到了 <code>TIsUEnumClass</code> 判断是否是 <code>UEnum</code>，这在 <code>UE5</code> 才正式提供，具体代码在 <code>UhtHeaderCodeGeneratorHFile.cs : AppendEnum</code> 中生成；</p><p>如果是 <code>UE4</code>，可以自定义该 <code>concept</code>，修改 <code>UHT</code> 的 <code>CodeGenerator</code> ，将其 <code>UEnum : TIsUEnumClass = true</code>生成在 <code>.generated.h</code> 中：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 新增 TIsUEnumClass</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TIsUEnumClass</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> Value = <span class="literal">false</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 修改 UEnum 的 Genrator</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FNativeClassHeaderGenerator::ExportEnum</span><span class="params">(FOutputDevice&amp; Out, UEnum* Enum)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">const</span> EUnderlyingEnumType* EnumPropType = GEnumUnderlyingTypes.<span class="built_in">Find</span>(Enum))</span><br><span class="line">&#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">Out.<span class="built_in">Logf</span>( <span class="built_in">TEXT</span>(<span class="string">&quot;\r\n&quot;</span>) );</span><br><span class="line">Out.<span class="built_in">Logf</span>( <span class="built_in">TEXT</span>(<span class="string">&quot;enum class %s%s;\r\n&quot;</span>), *Enum-&gt;CppType, *UnderlyingTypeString );</span><br><span class="line">Out.<span class="built_in">Logf</span>( <span class="built_in">TEXT</span>(<span class="string">&quot;template&lt;&gt; %sUEnum* StaticEnum&lt;%s&gt;();\r\n&quot;</span>), *<span class="built_in">GetAPIString</span>(), *Enum-&gt;CppType );</span><br><span class="line">Out.<span class="built_in">Logf</span>( <span class="built_in">TEXT</span>(<span class="string">&quot;\r\n&quot;</span>) );</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 新增 TIsUEnumClass 判断</span></span><br><span class="line">Out.<span class="built_in">Logf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;template&lt;&gt; struct TIsUEnumClass&lt;%s&gt; &#123; static constexpr bool Value = true; &#125;;\r\n&quot;</span>), *Enum-&gt;CppType);</span><br><span class="line">Out.<span class="built_in">Logf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;\r\n&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="包含-ToString-或-GetUID-方法的类型、指针">包含 <code>ToString</code> 或 <code>GetUID</code> 方法的类型、指针</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value.<span class="built_in">ToString</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T* Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value ? Value-&gt;<span class="built_in">ToString</span>() : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;T, UObject&gt;::Value &amp;&amp; !THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(THasGetUIDFunc&lt;T&gt;::Value)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">ToString</span>(Value.<span class="built_in">GetUID</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Value.<span class="built_in">GetName</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;T, UObject&gt;::Value &amp;&amp; !THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T* Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Value)) <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(THasGetUIDFunc&lt;T&gt;::Value)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">ToString</span>(Value-&gt;<span class="built_in">GetUID</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Value-&gt;<span class="built_in">GetName</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里用了 <code>THasToStringFunc</code>、<code>TGetUIDFunc</code>，需要新增 <code>TypeTraits</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TrueType</span> &#123;</span> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> Value = <span class="literal">true</span>; &#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FalseType</span> &#123;</span> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> Value = <span class="literal">false</span>; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// THas[FunctionName]Func</span></span><br><span class="line"><span class="comment">// Declare type traits to detect if type T has a function named FunctionName with params.</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DECLARE_HASFUNCTION_TYPETRAITS(FunctionName, ReturnType, ...) \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename T&gt;</span> \</span></span><br><span class="line"><span class="meta">struct THas##FunctionName##Func\</span></span><br><span class="line"><span class="meta">&#123; \</span></span><br><span class="line"><span class="meta">private: \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename, typename... TArgs&gt;</span> \</span></span><br><span class="line"><span class="meta">    struct Internal &#123; \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename U, typename = void&gt;</span> \</span></span><br><span class="line"><span class="meta">struct THas##FunctionName##FuncInternal : FalseType &#123;&#125;; \</span></span><br><span class="line"><span class="meta">    template<span class="meta-string">&lt;typename U&gt;</span> \</span></span><br><span class="line"><span class="meta">    struct THas##FunctionName##FuncInternal<span class="meta-string">&lt;U, typename TEnableIf&lt;TIsSame&lt;ReturnType, decltype(DeclVal&lt;U&gt;</span>().FunctionName(DeclVal<span class="meta-string">&lt;TArgs&gt;</span>()...))&gt;::Value&gt;::Type&gt; : TrueType &#123;&#125;; \</span></span><br><span class="line"><span class="meta">    &#125;; \</span></span><br><span class="line"><span class="meta">public: \</span></span><br><span class="line"><span class="meta">static constexpr bool Value = Internal<span class="meta-string">&lt;void, ##__VA_ARGS__&gt;</span>::template THas##FunctionName##FuncInternal<span class="meta-string">&lt;T&gt;</span>::Value; \</span></span><br><span class="line"><span class="meta">&#125;;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Declare -----</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// THasToStringFunc: FString T::ToString()</span></span><br><span class="line"><span class="built_in">DECLARE_HASFUNCTION_TYPETRAITS</span>(ToString, FString)</span><br><span class="line"><span class="comment">// THasGetUIDFunc: uint64 T::GetUID()</span></span><br><span class="line"><span class="built_in">DECLARE_HASFUNCTION_TYPETRAITS</span>(GetUID, uint64)</span><br></pre></td></tr></table></figure><h3 id="Pointer">Pointer</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsPointer&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value ? UStringUtils::<span class="built_in">ToString</span>(*Value) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TStrongObjectPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TWeakPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Pin</span>().<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TSharedPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TUniquePtr&lt;T&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Variant">Variant</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsSame&lt;T, FVariant&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> VariantType = Value.<span class="built_in">GetType</span>();</span><br><span class="line">    <span class="built_in"><span class="keyword">switch</span></span> (VariantType)</span><br><span class="line">    &#123;</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Bool:     <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;<span class="keyword">bool</span>&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Float:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;<span class="keyword">float</span>&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::String:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;FString&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Int8:     <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int8&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Int16:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int16&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Int32:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int32&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::Int64:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int64&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::UInt8:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint8&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::UInt16:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint16&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::UInt32:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint32&gt;());</span><br><span class="line">    <span class="keyword">case</span> EVariantTypes::UInt64:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint64&gt;());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Unknown&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Container">Container</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TContainer, <span class="keyword">typename</span> TFunctor, <span class="keyword">typename</span> = <span class="keyword">typename</span> TEnableIf&lt;TIsContainer&lt;TContainer&gt;::Value&gt;::Type&gt;</span><br><span class="line"><span class="keyword">static</span> FString <span class="built_in">ContainerToStringHelper</span>(<span class="keyword">const</span> TContainer&amp; Container, <span class="keyword">const</span> FString&amp; StartToken, <span class="keyword">const</span> FString&amp; EndToken, <span class="keyword">bool</span> Multilines, TFunctor&amp;&amp; ElementToStringFunctor)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (Container.<span class="built_in">IsEmpty</span>()) <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Empty&quot;</span>);</span><br><span class="line"></span><br><span class="line">    TArray&lt;FString&gt; ElementStrings&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element : Container)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">auto</span> String = <span class="built_in">ElementToStringFunctor</span>(Element);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (String.<span class="built_in">IsEmpty</span>())</span><br><span class="line">        &#123;</span><br><span class="line">            ElementStrings.<span class="built_in">Emplace</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;None&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            ElementStrings.<span class="built_in">Emplace</span>(<span class="built_in">MoveTemp</span>(String));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> Res = FString::<span class="built_in">Join</span>(ElementStrings, Multilines ? <span class="built_in">TEXT</span>(<span class="string">&quot;,\n&quot;</span>) : <span class="built_in">TEXT</span>(<span class="string">&quot;, &quot;</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Multilines)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s\n%s\n%s&quot;</span>), *StartToken, *Res, *EndToken);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s%s%s&quot;</span>), *StartToken, *Res, *EndToken);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TArray&lt;T&gt;&amp; Array, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Array, <span class="built_in">TEXT</span>(<span class="string">&quot;[&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;]&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line">        <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Element); </span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TSet&lt;T&gt;&amp; Set, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Set, <span class="built_in">TEXT</span>(<span class="string">&quot;(&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;)&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line">        <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Element); </span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TKey, <span class="keyword">typename</span> TValue&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TMap&lt;TKey, TValue&gt;&amp; Map, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Map, <span class="built_in">TEXT</span>(<span class="string">&quot;&#123;&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;&#125;&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line">        <span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s: %s&quot;</span>), *UStringUtils::<span class="built_in">ToString</span>(Element.Key), *UStringUtils::<span class="built_in">ToString</span>(Element.Value));</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里用到了 <code>TIsContainer</code> ，可以通过是否有 <code>.begin()</code>、<code>.end()</code>、<code>.IsEmpty()</code> 方法简单判断：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TMakeVoid</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TMakeVoid</span> &#123;</span> <span class="keyword">using</span> Type = <span class="keyword">void</span>; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TIsContainer</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, <span class="keyword">typename</span> = <span class="keyword">void</span>&gt;</span><br><span class="line">struct TIsContainer : FalseType &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TIsContainer</span>&lt;</span>T, <span class="keyword">typename</span> TMakeVoid&lt;</span><br><span class="line">    <span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">begin</span>()), </span><br><span class="line">    <span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">end</span>()), </span><br><span class="line">    <span class="keyword">typename</span> TEnableIf&lt;TIsSame&lt;<span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">IsEmpty</span>()), <span class="keyword">bool</span>&gt;::Value&gt;::Type</span><br><span class="line">&gt;::Type&gt; : TrueType &#123;&#125;;</span><br></pre></td></tr></table></figure><h2 id="完整实现">完整实现</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// StringUtils.h</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;CoreMinimal.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;Kismet/BlueprintFunctionLibrary.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;UObject/StrongObjectPtr.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TypeTraits.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;StringUtils.generated.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UStringUtils</span> :</span> <span class="keyword">public</span> UBlueprintFunctionLibrary</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsIntegral&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">FromInt</span>(<span class="keyword">static_cast</span>&lt;int32&gt;(Value));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> uint32&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%u&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> int64&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%lld&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> uint64&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%llu&quot;</span>), Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsFloatingPoint&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%.3f&quot;</span>), <span class="keyword">static_cast</span>&lt;<span class="keyword">float</span>&gt;(Value));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> <span class="keyword">bool</span>&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s&quot;</span>), Value ? <span class="built_in">TEXT</span>(<span class="string">&quot;true&quot;</span>) : <span class="built_in">TEXT</span>(<span class="string">&quot;false&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> FString&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Enum</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsEnum&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(google::protobuf::is_proto_enum&lt;T&gt;::value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> String = ::google::protobuf::internal::<span class="built_in">NameOfEnum</span>(::google::protobuf::<span class="keyword">template</span> GetEnumDescriptor&lt;T&gt;(), <span class="keyword">static_cast</span>&lt;<span class="keyword">int</span>&gt;(Value));</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">UTF8_TO_TCHAR</span>(String.<span class="built_in">c_str</span>());</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> <span class="built_in"><span class="keyword">constexpr</span></span> (TIsUEnumClass&lt;T&gt;::Value)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> EnumClass = StaticEnum&lt;T&gt;();</span><br><span class="line"><span class="keyword">return</span> EnumClass-&gt;<span class="built_in">GetNameStringByValue</span>(<span class="keyword">static_cast</span>&lt;int64&gt;(Value));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;RawEnum_%s&quot;</span>), *<span class="built_in">ToString</span>(<span class="keyword">static_cast</span>&lt;__underlying_type(T)&gt;(Value)));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsEnum&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TEnumAsByte&lt;T&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">ToString</span>(Value.<span class="built_in">GetValue</span>());</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Enum</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region ToString</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">ToString</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T* Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value ? Value-&gt;<span class="built_in">ToString</span>() : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;T, UObject&gt;::Value &amp;&amp; !THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(THasGetUIDFunc&lt;T&gt;::Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">ToString</span>(Value.<span class="built_in">GetUID</span>());</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">GetName</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;T, UObject&gt;::Value &amp;&amp; !THasToStringFunc&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T* Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Value)) <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(THasGetUIDFunc&lt;T&gt;::Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">ToString</span>(Value-&gt;<span class="built_in">GetUID</span>());</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> Value-&gt;<span class="built_in">GetName</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion ToString</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Pointer</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsPointer&lt;T&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value ? UStringUtils::<span class="built_in">ToString</span>(*Value) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TStrongObjectPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TWeakPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Pin</span>().<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(TSharedPtr&lt;T&gt; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TUniquePtr&lt;T&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Value.<span class="built_in">IsValid</span>() ? UStringUtils::<span class="built_in">ToString</span>(Value.<span class="built_in">Get</span>()) : <span class="built_in">TEXT</span>(<span class="string">&quot;Invalid&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Pointer</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region FVariant</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsSame&lt;T, FVariant&gt;::Value, FString&gt;::<span class="function">Type <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> T&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> VariantType = Value.<span class="built_in">GetType</span>();</span><br><span class="line"><span class="built_in"><span class="keyword">switch</span></span> (VariantType)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Bool:     <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;<span class="keyword">bool</span>&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Float:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;<span class="keyword">float</span>&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::String:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;FString&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Int8:     <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int8&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Int16:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int16&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Int32:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int32&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::Int64:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;int64&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::UInt8:    <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint8&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::UInt16:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint16&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::UInt32:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint32&gt;());</span><br><span class="line"><span class="keyword">case</span> EVariantTypes::UInt64:   <span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Value.<span class="keyword">template</span> GetValue&lt;uint64&gt;());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Unknown&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion FVariant</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Container</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TContainer, <span class="keyword">typename</span> TFunctor, <span class="keyword">typename</span> = <span class="keyword">typename</span> TEnableIf&lt;TIsContainer&lt;TContainer&gt;::Value&gt;::Type&gt;</span><br><span class="line"><span class="keyword">static</span> FString <span class="built_in">ContainerToStringHelper</span>(<span class="keyword">const</span> TContainer&amp; Container, <span class="keyword">const</span> FString&amp; StartToken, <span class="keyword">const</span> FString&amp; EndToken, <span class="keyword">bool</span> Multilines, TFunctor&amp;&amp; ElementToStringFunctor)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Container.<span class="built_in">IsEmpty</span>()) <span class="keyword">return</span> <span class="built_in">TEXT</span>(<span class="string">&quot;Empty&quot;</span>);</span><br><span class="line"></span><br><span class="line">TArray&lt;FString&gt; ElementStrings&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element : Container)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> String = <span class="built_in">ElementToStringFunctor</span>(Element);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (String.<span class="built_in">IsEmpty</span>())</span><br><span class="line">&#123;</span><br><span class="line">ElementStrings.<span class="built_in">Emplace</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;None&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">ElementStrings.<span class="built_in">Emplace</span>(<span class="built_in">MoveTemp</span>(String));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Res = FString::<span class="built_in">Join</span>(ElementStrings, Multilines ? <span class="built_in">TEXT</span>(<span class="string">&quot;,\n&quot;</span>) : <span class="built_in">TEXT</span>(<span class="string">&quot;, &quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (Multilines)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s\n%s\n%s&quot;</span>), *StartToken, *Res, *EndToken);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s%s%s&quot;</span>), *StartToken, *Res, *EndToken);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TArray&lt;T&gt;&amp; Array, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Array, <span class="built_in">TEXT</span>(<span class="string">&quot;[&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;]&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line"><span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Element); </span><br><span class="line">&#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TSet&lt;T&gt;&amp; Set, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Set, <span class="built_in">TEXT</span>(<span class="string">&quot;(&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;)&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line"><span class="keyword">return</span> UStringUtils::<span class="built_in">ToString</span>(Element); </span><br><span class="line">&#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> TKey, <span class="keyword">typename</span> TValue&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> FString <span class="title">ToString</span><span class="params">(<span class="keyword">const</span> TMap&lt;TKey, TValue&gt;&amp; Map, <span class="keyword">bool</span> Multilines = <span class="literal">false</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UStringUtils::<span class="built_in">ContainerToStringHelper</span>(Map, <span class="built_in">TEXT</span>(<span class="string">&quot;&#123;&quot;</span>), <span class="built_in">TEXT</span>(<span class="string">&quot;&#125;&quot;</span>), Multilines, [](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Element) -&gt; FString &#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;%s: %s&quot;</span>), *UStringUtils::<span class="built_in">ToString</span>(Element.Key), *UStringUtils::<span class="built_in">ToString</span>(Element.Value));</span><br><span class="line">&#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Container</span></span><br><span class="line">    </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TypeTraits.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;CoreMinimal.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TrueType</span> &#123;</span> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> Value = <span class="literal">true</span>; &#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FalseType</span> &#123;</span> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> Value = <span class="literal">false</span>; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TMakeVoid</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TMakeVoid</span> &#123;</span> <span class="keyword">using</span> Type = <span class="keyword">void</span>; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Container</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TIsContainer</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, <span class="keyword">typename</span> = <span class="keyword">void</span>&gt;</span><br><span class="line">struct TIsContainer : FalseType &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TIsContainer</span>&lt;</span>T, <span class="keyword">typename</span> TMakeVoid&lt;</span><br><span class="line">    <span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">begin</span>()), </span><br><span class="line">    <span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">end</span>()), </span><br><span class="line">    <span class="keyword">typename</span> TEnableIf&lt;TIsSame&lt;<span class="keyword">decltype</span>(DeclVal&lt;T&gt;().<span class="built_in">IsEmpty</span>()), <span class="keyword">bool</span>&gt;::Value&gt;::Type</span><br><span class="line">&gt;::Type&gt; : TrueType &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Container</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region HasMemberFunction</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// THas[FunctionName]Func</span></span><br><span class="line"><span class="comment">// Declare type traits to detect if type T has a function named FunctionName with params.</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DECLARE_HASFUNCTION_TYPETRAITS(FunctionName, ReturnType, ...) \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename T&gt;</span> \</span></span><br><span class="line"><span class="meta">struct THas##FunctionName##Func\</span></span><br><span class="line"><span class="meta">&#123; \</span></span><br><span class="line"><span class="meta">private: \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename, typename... TArgs&gt;</span> \</span></span><br><span class="line"><span class="meta">    struct Internal &#123; \</span></span><br><span class="line"><span class="meta">template<span class="meta-string">&lt;typename U, typename = void&gt;</span> \</span></span><br><span class="line"><span class="meta">struct THas##FunctionName##FuncInternal : FalseType &#123;&#125;; \</span></span><br><span class="line"><span class="meta">    template<span class="meta-string">&lt;typename U&gt;</span> \</span></span><br><span class="line"><span class="meta">    struct THas##FunctionName##FuncInternal<span class="meta-string">&lt;U, typename TEnableIf&lt;TIsSame&lt;ReturnType, decltype(DeclVal&lt;U&gt;</span>().FunctionName(DeclVal<span class="meta-string">&lt;TArgs&gt;</span>()...))&gt;::Value&gt;::Type&gt; : TrueType &#123;&#125;; \</span></span><br><span class="line"><span class="meta">    &#125;; \</span></span><br><span class="line"><span class="meta">public: \</span></span><br><span class="line"><span class="meta">static constexpr bool Value = Internal<span class="meta-string">&lt;void, ##__VA_ARGS__&gt;</span>::template THas##FunctionName##FuncInternal<span class="meta-string">&lt;T&gt;</span>::Value; \</span></span><br><span class="line"><span class="meta">&#125;;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ----- Declare -----</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// THasToStringFunc: FString T::ToString()</span></span><br><span class="line"><span class="built_in">DECLARE_HASFUNCTION_TYPETRAITS</span>(ToString, FString)</span><br><span class="line"><span class="comment">// THasGetUIDFunc: uint64 T::GetUID()</span></span><br><span class="line"><span class="built_in">DECLARE_HASFUNCTION_TYPETRAITS</span>(GetUID, uint64)</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion HasMemberFunction</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;ToString解决方案&lt;/h1&gt;
&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;
&lt;p&gt;提供一个通用机制 &lt;code&gt;UStringUtils::ToString(Value)&lt;/code&gt; 将其 &lt;code&gt;String&lt;/code&gt; 化。&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;V</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]UnLua &amp; C++ 交互</title>
    <link href="https://blog.bearchild.top/2024/05/27/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]UnLua&amp;C++%E4%BA%A4%E4%BA%92/"/>
    <id>https://blog.bearchild.top/2024/05/27/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]UnLua&amp;C++%E4%BA%A4%E4%BA%92/</id>
    <published>2024-05-26T16:00:00.000Z</published>
    <updated>2025-06-25T15:11:05.090Z</updated>
    
    <content type="html"><![CDATA[<h1>UnLua &amp; C++ 交互</h1><p>在 <code>Lua</code> 中调用一般的 <code>UFUNCTION</code> 反射比较简单直接，这里以从 <code>Lua</code> 中调用 <code>CFUNCTION</code> 为例简单介绍两端交互。</p><p>具体为在 <code>Lua</code> 中的调用：<code>local Table = UE.UTestUtils.LuaCFunction(Val0, Val1)</code>，并返回一个 <code>Lua Table : &#123; &quot;Val0&quot; = Val0, &quot;Val1&quot; = Va1 &#125;</code>。</p><h3 id="定义">定义</h3><p>首先通过 <code>ADD_STATIC_CFUNCTION</code> 静态导出这个原生的 <code>FGlueFunction</code> ；</p><p>具体的实现可以在 <a href="https://github.com/Tencent/UnLua/blob/master/Plugins/UnLua/Source/UnLua/Public/UnLuaEx.h">UnLuaEx.h</a> 中找到。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Test.h</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">lua_State</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UTestUtils</span> :</span> <span class="keyword">public</span> UBlueprintFunctionLibrary</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">LuaCFunction</span><span class="params">(lua_State* L)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Test.cpp</span></span><br><span class="line"><span class="built_in">BEGIN_EXPORT_REFLECTED_CLASS</span>(UTestUtils)</span><br><span class="line"><span class="built_in">ADD_STATIC_CFUNTION</span>(LuaCFunction)</span><br><span class="line"><span class="built_in">END_EXPORT_CLASS</span>(UTestUtils)</span><br><span class="line"><span class="built_in">IMPLEMENT_EXPORTED_CLASS</span>(UTestUtils)</span><br></pre></td></tr></table></figure><h3 id="实现">实现</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">UTestUtils::LuaCFunction</span><span class="params">(lua_State* L)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Top = <span class="built_in">lua_gettop</span>(L);</span><br><span class="line"><span class="keyword">if</span> (Top == <span class="number">2</span>)</span><br><span class="line">&#123;</span><br><span class="line">int32 Val0 = <span class="built_in">lua_tointeger</span>(L, <span class="number">1</span>);</span><br><span class="line">int32 Val1 = <span class="built_in">lua_tointeger</span>(L, <span class="number">2</span>);</span><br><span class="line">UUnLuaFunctionLibrary::<span class="built_in">CreateLuaTable</span>(L, <span class="string">&quot;Val0&quot;</span>, Val0, <span class="string">&quot;Val1&quot;</span>, Val1);</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">luaL_error</span>(L, <span class="string">&quot;Call UTestUtils::LuaCFunction error! argc = %d&quot;</span>, Top);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CFUNCTION</code> 在 <code>Lua</code> 中调用时，会传入当前的 <code>lua_State* L</code> ，可以通过这个<code>L</code> 对 <code>LuaStack</code> 进行访问与写入。</p><p>首先这里的 <code>Top = lua_gettop(L)</code> 会根据 <code>cast_int(L-&gt;top - (L-&gt;ci-&gt;func + 1))</code> 计算出返回参数的个数，比如这里的 <code>(Val0, Val1)</code> 就是 <code>2</code> 个参数。</p><p>接着通过 <code>lua_tointeger(L, i)</code> 将第 <code>i</code> 个参数取出。</p><p>然后通过 <code>UUnLuaFunctionLibrary::CreateLuaTable</code>，创建一个 <code>Lua Table</code>，进行赋值与写入，这个方法是实现的重点。</p><p>最后如果合法，<code>return 1</code>，返回写入 <code>LuaStack</code> 中返回值的个数，也就是有 <code>1</code> 个 <code>LuaTable</code> 被写入了栈。</p><p>具体的与 <code>lua</code> 层交互的原始方法实现可以在 <a href="https://github.com/Tencent/UnLua/blob/master/Plugins/UnLua/Source/ThirdParty/Lua/lua-5.4.4/src/lapi.c">lapi.c</a> 中找到。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function">UnLua::FLuaTable <span class="title">UUnLuaFunctionLibrary::CreateLuaTable</span><span class="params">(lua_State* L, Types&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> LuaEnv = UnLua::FLuaEnv::<span class="built_in">FindEnv</span>(L);</span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"></span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> UnLua::<span class="built_in">FLuaTable</span>(LuaEnv, <span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K, <span class="keyword">typename</span> V, <span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, V&amp;&amp; Value, Types&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;K&gt;(Key), Forward&lt;V&gt;(Value));</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K, <span class="keyword">typename</span> V&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, V&amp;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;K&gt;(Key));</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;V&gt;(Value));</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个 <code>UUnLuaFunctionLibrary::CreateLuaTable</code> 向 <code>L</code> 对应的 <code>LuaStack</code> 中写入了一个赋值好的 <code>LuaTable</code>。</p><p>首先通过 <code>lua_newtable(L)</code> 创建了一个空的 <code>LuaTable</code>，然后进行 <code>PushKeyValue</code> 的递归调用。</p><p>针对一次 <code>PushKeyValue</code>，首先会往 <code>LuaStack</code> 中压入 <code>Key</code>、<code>Value</code>，压入后先前的 <code>LuaTable</code> 处于在 <code>StatckIndex=-3</code> 的位置。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/image-20240527120544564.png" alt="image-20240527120544564" style="zoom:33%;" /><p>然后针对  <code>StackIndex = -3</code>位置（也就是当前栈里的 <code>LuaTable</code>）调用 <code>lua_rawset</code> 方法，将 <code>Key</code> 和 <code>Value</code> 弹出并打包成参数塞进 <code>LuaTable</code>。</p><p>这里的 <code>lua_rawset</code> 实际上类似 <code>lua_settable</code>。对于 <code>lua_settable</code>，会找到元方法 <code>__newindex</code> 并调用，对于 <code>lua_rawset</code>，则会调用默认的 <code>__newindex</code> 方法：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">__newindex</span> = <span class="function"><span class="keyword">function</span><span class="params">(table, key, value)</span></span></span><br><span class="line">    <span class="built_in">rawset</span>(<span class="built_in">table</span>, key, value)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>这样，就完成了一个这样的方法实现。</p><p>给出 <code>UUnluaFunctionLibrary</code> 的更多扩展：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LuaTable</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function">UnLua::FLuaTable <span class="title">UUnLuaFunctionLibrary::CreateLuaTable</span><span class="params">(lua_State* L, Types&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> LuaEnv = UnLua::FLuaEnv::<span class="built_in">FindEnv</span>(L);</span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"></span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> UnLua::<span class="built_in">FLuaTable</span>(LuaEnv, <span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function">UnLua::FLuaTable <span class="title">UUnLuaFunctionLibrary::CreateLuaArray</span><span class="params">(lua_State* L, <span class="keyword">const</span> Types&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> LuaEnv = UnLua::FLuaEnv::<span class="built_in">FindEnv</span>(L);</span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"></span><br><span class="line">PushKeyValue&lt;<span class="number">1</span>&gt;(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> UnLua::<span class="built_in">FLuaTable</span>(LuaEnv, <span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K, <span class="keyword">typename</span> V&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, V&amp;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;K&gt;(Key));</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;V&gt;(Value));</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K, <span class="keyword">typename</span> V, <span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, V&amp;&amp; Value, Types&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;K&gt;(Key), Forward&lt;V&gt;(Value));</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">int</span> N, <span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, T&amp;&amp; Arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, N);</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;T&gt;(Arg));</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">int</span> N, <span class="keyword">typename</span> T, <span class="keyword">typename</span>... Types&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, T&amp;&amp; Arg0, Types&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">PushKeyValue&lt;N&gt;(L, Forward&lt;T&gt;(Arg0));</span><br><span class="line">PushKeyValue&lt;N + <span class="number">1</span>&gt;(L, Forward&lt;Types&gt;(Args)...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K, <span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, TArray&lt;T&gt;&amp; Array)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;K&gt;(Key));</span><br><span class="line"></span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> Index = <span class="number">0</span>; Index &lt; Array.<span class="built_in">Num</span>(); Index++)</span><br><span class="line">&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Index + <span class="number">1</span>);</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Array[Index]);</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Struct</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">typename</span> TEnableIf&lt;TIsUStruct&lt;T&gt;::Value, T*&gt;::<span class="function">Type</span></span><br><span class="line"><span class="function"><span class="title">UUnLuaFunctionLibrary::GetValue</span><span class="params">(lua_State* L, int32 Index)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span>&amp; Env = UnLua::FLuaEnv::<span class="built_in">FindEnvChecked</span>(L);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> StructType = Env.<span class="built_in">GetPropertyRegistry</span>()-&gt;<span class="built_in">CreateTypeInterface</span>(L, Index);</span><br><span class="line"><span class="keyword">if</span> (!StructType)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">auto</span> StructProperty = CastField&lt;FStructProperty&gt;(StructType-&gt;<span class="built_in">GetUProperty</span>());</span><br><span class="line"><span class="keyword">if</span> (StructProperty == <span class="literal">nullptr</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!StructProperty-&gt;Struct-&gt;<span class="built_in">IsChildOf</span>(TBaseStructure&lt;T&gt;::<span class="built_in">Get</span>()))</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="keyword">static_cast</span>&lt;T*&gt;(<span class="built_in">GetCppInstanceFast</span>(L, Index));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Json</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> K&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushKeyValue</span><span class="params">(lua_State* L, K&amp;&amp; Key, <span class="keyword">const</span> TSharedPtr&lt;FJsonValue&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Forward&lt;K&gt;(Key));</span><br><span class="line"><span class="built_in">PushValue</span>(L, Value);</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushValue</span><span class="params">(lua_State* L, <span class="keyword">const</span> TSharedPtr&lt;FJsonObject&gt;&amp; JsonObject)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; KeyValue : JsonObject-&gt;Values)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, KeyValue.Key, KeyValue.Value);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushValue</span><span class="params">(lua_State* L, <span class="keyword">const</span> TArray&lt;TSharedPtr&lt;FJsonValue&gt;&gt;&amp; JsonValues)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">lua_newtable</span>(L);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">0</span>; i &lt; JsonValues.<span class="built_in">Num</span>(); i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">PushKeyValue</span>(L, i + <span class="number">1</span>, JsonValues[i]);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UUnLuaFunctionLibrary::PushValue</span><span class="params">(lua_State* L, <span class="keyword">const</span> TSharedPtr&lt;FJsonValue&gt;&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in"><span class="keyword">switch</span></span> (Value-&gt;Type)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">case</span> EJson::String: UnLua::<span class="built_in">Push</span>(L, Value-&gt;<span class="built_in">AsString</span>()); <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> EJson::Number: UnLua::<span class="built_in">Push</span>(L, Value-&gt;<span class="built_in">AsNumber</span>()); <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> EJson::Boolean: UnLua::<span class="built_in">Push</span>(L, Value-&gt;<span class="built_in">AsBool</span>()); <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> EJson::Array: <span class="built_in">PushValue</span>(L, Value-&gt;<span class="built_in">AsArray</span>()); <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> EJson::Object: <span class="built_in">PushValue</span>(L, Value-&gt;<span class="built_in">AsObject</span>()); <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>: <span class="built_in">lua_pushnil</span>(L); <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">UUnLuaFunctionLibrary::GetLuaTableFromJsonPath</span><span class="params">(lua_State* L)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Top = <span class="built_in">lua_gettop</span>(L);</span><br><span class="line"><span class="keyword">if</span> (Top != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogWarning</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;Args count error. argc = %d&quot;</span>), Top);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> JsonFilePath = <span class="built_in">FString</span>(<span class="built_in">UTF8_TO_TCHAR</span>(<span class="built_in">lua_tostring</span>(L, <span class="number">1</span>)));</span><br><span class="line"><span class="keyword">if</span> (!FPaths::<span class="built_in">FileExists</span>(JsonFilePath))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogWarning</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;Json file path &#x27;%s&#x27; not found.&quot;</span>), *JsonFilePath);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FString FileContent;</span><br><span class="line"><span class="keyword">if</span> (FFileHelper::<span class="built_in">LoadFileToString</span>(FileContent, *JsonFilePath))</span><br><span class="line">&#123;</span><br><span class="line">TSharedPtr&lt;FJsonObject&gt; JsonObject;</span><br><span class="line"><span class="keyword">auto</span> JsonReader = TJsonReaderFactory&lt;&gt;::<span class="built_in">Create</span>(FileContent);</span><br><span class="line"><span class="keyword">if</span> (FJsonSerializer::<span class="built_in">Deserialize</span>(JsonReader, JsonObject))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">PushValue</span>(L, JsonObject);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogWarning</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;Parse json from file &#x27;%s&#x27; failed.&quot;</span>), *JsonFilePath);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogWarning</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;Load file path &#x27;%s&#x27; failed.&quot;</span>), *JsonFilePath);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Call</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span>... T&gt;</span><br><span class="line"><span class="function">UnLua::FLuaRetValues <span class="title">UUnLuaFunctionLibrary::InnerCall</span><span class="params">(UObjectBaseUtility* Obj, lua_State *L, <span class="keyword">const</span> <span class="keyword">char</span> *FuncName, T&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">auto</span> LuaEnv = UnLua::FLuaEnv::<span class="built_in">FindEnv</span>(L);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (UnLua::<span class="built_in">GetObjectLuaInstance</span>(Obj))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="function"><span class="keyword">const</span> UnLua::FLuaTable <span class="title">LuaInstance</span><span class="params">(UnLua::FLuaValue(LuaEnv, <span class="number">-1</span>))</span></span>;</span><br><span class="line">        <span class="function"><span class="keyword">const</span> UnLua::FLuaFunction <span class="title">LuaFunc</span><span class="params">(LuaEnv, LuaInstance, FuncName)</span></span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (LuaFunc.<span class="built_in">IsValid</span>())</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">auto</span> Ret = LuaFunc.<span class="built_in">Call</span>(LuaInstance, Forward&lt;T&gt;(Args)...);</span><br><span class="line">            <span class="built_in">lua_pop</span>(L, <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span> Ret;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> UnLua::<span class="built_in">FLuaRetValues</span>(LuaEnv, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Class:</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span>... T&gt;</span><br><span class="line"><span class="function">UnLua::FLuaRetValues <span class="title">InnerCall</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *FuncName, T&amp;&amp;... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> UUnLuaFunctionLibrary::<span class="built_in">InnerCall</span>(<span class="keyword">this</span>, UnLua::<span class="built_in">GetState</span>(), FuncName, Args);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Helper</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UUnLuaFunctionLibrary::CheckParamsCount</span><span class="params">(lua_State* L, int32 Num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">lua_gettop</span>(L) == Num;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">UUnLuaFunctionLibrary::ToString</span><span class="params">(lua_State* L, int32 Index, <span class="keyword">bool</span> PushValue)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> Res = <span class="built_in">FString</span>(<span class="built_in">UTF8_TO_TCHAR</span>(<span class="built_in">luaL_tolstring</span>(L, Index, <span class="literal">nullptr</span>)));</span><br><span class="line"><span class="keyword">if</span> (!PushValue)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">lua_pop</span>(L, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> Res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Delegate</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FUnLuaTableCallback</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">FUnLuaTableCallback</span>(TSharedPtr&lt;UnLua::FLuaTableRef&gt; Table, TSharedPtr&lt;UnLua::FLuaFunction&gt; Func) : <span class="built_in">Table</span>(Table), <span class="built_in">Func</span>(Func) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">operator</span><span class="params">()</span> <span class="params">(T... Args)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Func-&gt;<span class="built_in">Call</span>(*Table.<span class="built_in">Get</span>(), Forward&lt;T&gt;(Args)...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">TSharedPtr&lt;UnLua::FLuaTableRef&gt; Table;</span><br><span class="line">TSharedPtr&lt;UnLua::FLuaFunction&gt; Func;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DECLARE_MULTICAST_DELEGATE_LUA_HELPER(OwnerType, DelegateName, ...)                                                            \</span></span><br><span class="line"><span class="meta">private:                                                                                                                               \</span></span><br><span class="line"><span class="meta">TMap<span class="meta-string">&lt;FString, FDelegateHandle&gt;</span> DelegateName##LuaHandles;                                                                           \</span></span><br><span class="line"><span class="meta">public:                                                                                                                                \</span></span><br><span class="line"><span class="meta">static int Bind##DelegateName(lua_State* L)                                                                                        \</span></span><br><span class="line"><span class="meta">&#123;                                                                                                                                  \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (!UUnLuaFunctionLibrary::CheckParamsCount(L, 3)) &#123; return luaL_error(L, <span class="meta-string">&quot;Invalid parameters count.&quot;</span>); &#125;                   \</span></span><br><span class="line"><span class="meta">auto Self = static_cast<span class="meta-string">&lt;OwnerType*&gt;</span>(GetCppInstanceFast(L, 1));                                                                 \</span></span><br><span class="line"><span class="meta">auto TableValue = UnLua::FLuaValue(L, 2);                                                                                      \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (TableValue.GetType() != LUA_TTABLE) &#123; return luaL_error(L, <span class="meta-string">&quot;Parameter 2 is invalid; it should be a table.&quot;</span>); &#125;             \</span></span><br><span class="line"><span class="meta">auto Key = UUnLuaFunctionLibrary::ToString(L, 2);                                                                            \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (Self-&gt;DelegateName##LuaHandles.Contains(Key)) &#123; return 0; &#125;                                                                \</span></span><br><span class="line"><span class="meta">auto FuncValue =  UnLua::FLuaValue(L, 3);                                                                                      \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (FuncValue.GetType() != LUA_TFUNCTION) &#123; return luaL_error(L, <span class="meta-string">&quot;Parameter 3 is invalid; it should be a function.&quot;</span>); &#125;        \</span></span><br><span class="line"><span class="meta">TSharedPtr<span class="meta-string">&lt;UnLua::FLuaTableRef&gt;</span> Table = MakeShareable(new UnLua::FLuaTableRef(L, TableValue));                                 \</span></span><br><span class="line"><span class="meta">TSharedPtr<span class="meta-string">&lt;UnLua::FLuaFunction&gt;</span> Func = MakeShareable(new UnLua::FLuaFunction(L, FuncValue));                                   \</span></span><br><span class="line"><span class="meta">Self-&gt;DelegateName##LuaHandles.Emplace(Key, Self-&gt;DelegateName.AddLambda(FUnLuaTableCallback<span class="meta-string">&lt;__VA_ARGS__&gt;</span>(Table, Func)));    \</span></span><br><span class="line"><span class="meta">return 0;                                                                                                                      \</span></span><br><span class="line"><span class="meta">&#125;                                                                                                                                  \</span></span><br><span class="line"><span class="meta">static int Unbind##DelegateName(lua_State* L)                                                                                      \</span></span><br><span class="line"><span class="meta">&#123;                                                                                                                                  \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (!UUnLuaFunctionLibrary::CheckParamsCount(L, 2)) &#123; return luaL_error(L, <span class="meta-string">&quot;Invalid parameters count.&quot;</span>); &#125;                   \</span></span><br><span class="line"><span class="meta">auto Self = static_cast<span class="meta-string">&lt;OwnerType*&gt;</span>(GetCppInstanceFast(L, 1));                                                                 \</span></span><br><span class="line"><span class="meta">auto TableValue = UnLua::FLuaValue(L, 2);                                                                                      \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (TableValue.GetType() != LUA_TTABLE) &#123; return luaL_error(L, <span class="meta-string">&quot;Parameter 2 is invalid; it should be a table.&quot;</span>); &#125;             \</span></span><br><span class="line"><span class="meta">auto Key = UUnLuaFunctionLibrary::ToString(L, 2);                                                                            \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (!Self-&gt;DelegateName##LuaHandles.Contains(Key)) &#123; return 0; &#125;                                                               \</span></span><br><span class="line"><span class="meta">Self-&gt;DelegateName.Remove(Self-&gt;DelegateName##LuaHandles[Key]);                                                                \</span></span><br><span class="line"><span class="meta">Self-&gt;DelegateName##LuaHandles.Remove(Key);                                                                                    \</span></span><br><span class="line"><span class="meta">return 0;                                                                                                                      \</span></span><br><span class="line"><span class="meta">&#125; </span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADD_MULTICAST_DELEGATE_LUA_HELPER_FUNC(DelegateName)   \</span></span><br><span class="line"><span class="meta">ADD_STATIC_CFUNTION(Bind##DelegateName)                    \</span></span><br><span class="line"><span class="meta">ADD_STATIC_CFUNTION(Unbind##DelegateName)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Delegate Lua Helper</span></span><br></pre></td></tr></table></figure><h2 id="参考">参考</h2><p><a href="https://www.lua.org/manual/5.4/manual.html">Lua 5.4 Reference</a>： <em>4.6 – Functions and Types</em></p><p><a href="https://chenanbao.github.io/2018/07/28/Lua%E8%99%9A%E6%8B%9F%E6%A0%88%E4%BA%A4%E4%BA%92%E6%B5%81%E7%A8%8B/">Lua 虚拟栈交互流程</a></p><p><a href="https://zhuanlan.zhihu.com/p/105356332">Unlua 解析</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;UnLua &amp;amp; C++ 交互&lt;/h1&gt;
&lt;p&gt;在 &lt;code&gt;Lua&lt;/code&gt; 中调用一般的 &lt;code&gt;UFUNCTION&lt;/code&gt; 反射比较简单直接，这里以从 &lt;code&gt;Lua&lt;/code&gt; 中调用 &lt;code&gt;CFUNCTION&lt;/code&gt; 为例</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]PlayerInfo高频同步解决方案</title>
    <link href="https://blog.bearchild.top/2024/04/30/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]PlayerInfo%E9%AB%98%E9%A2%91%E5%90%8C%E6%AD%A5%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.bearchild.top/2024/04/30/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]PlayerInfo%E9%AB%98%E9%A2%91%E5%90%8C%E6%AD%A5%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-04-29T16:00:00.000Z</published>
    <updated>2024-07-21T14:38:40.165Z</updated>
    
    <content type="html"><![CDATA[<h1>PlayerInfo高频同步解决方案</h1><h2 id="描述">描述</h2><h3 id="需求目的">需求目的</h3><p>分析一个常见的需求： “在 <code>1P</code> 客户端显示 <code>3P</code> 的 <code>Transform</code> ”。</p><p>显然，在客户端存在 <code>3P</code> 的 <code>Pawn</code> 时，可以直接取 <code>Pawn</code> 的 <code>Transform</code>；但出于性能考虑，会进行各种 <code>AOI</code> 机制，在较远距离时客户端会将 <code>3P</code> 的 <code>Pawn</code> 裁剪掉，只留下 <code>PlayerState</code>（或者某个不被剪裁的数据 <code>Channel</code>） 用于同步。</p><p>一个直观的想法是将 <code>Transform</code> 直接通过对应的 <code>PlayerState</code> 属性同步给所有客户端；但出于性能考虑，对于同步一般会开启 <code>PushModel</code>；这种高频字段会频繁将 <code>PlayerState</code> 对应 <code>ActorChannel</code> 给 <code>MarkDirty</code>，导致 <code>PushModel</code> 功能基本失效，频繁进行同步的 <code>Diff</code> 等大开销的操作；</p><p>所以需要一个机制对这种情况进行优化。</p><h3 id="核心思路">核心思路</h3><p>对于 <code>DS</code>，创建一个 <code>Channel</code> 专门用于同步 <code>Player</code> 的高频变化信息，如 <code>Location</code>、<code>Rotation</code> 等；</p><p>对于同步的信息，进行适当的同步降频（不需要每帧同步）、字节压缩（舍弃部分精度，精确到 <code>float</code> 没有意义）；</p><p>同时为了保证 <code>Client</code> 的信息相对正确（同步降频会导致 <code>Location</code> 不连续），在 <code>1P</code> <code>Client</code> 进行信息的预测插值；</p><h2 id="实现">实现</h2><h3 id="Replicator">Replicator</h3><p>创建一个 <code>Actor</code> - <code>PlayerSyncInfoReplicator</code> 专门用于打包所有 <code>Player</code> 高频 <code>Info</code>（这里特指 <code>Location</code>、<code>Rotation</code>） 进行数据同步，在合适的地方进行初始化（比如 <code>ReplicationGraph</code> 初始化后）；</p><p><code>Replicator</code> 包括一个 <code>FPlayerSyncInfoContainer</code> 结构体用于打包数据、进行数据的收集、自定义序列化；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerSyncInfoReplicator::GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">GetLifetimeReplicatedProps</span>(OutLifetimeProps);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不使用 PushModel，始终进行同步操作</span></span><br><span class="line">FDoRepLifetimeParams Params;</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS</span>(APlayerSyncInfoReplicator, PlayerSyncInfoContainer, Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerSyncInfoReplicator::PreReplication</span><span class="params">(IRepChangedPropertyTracker&amp; ChangedPropertyTracker)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 收集数据</span></span><br><span class="line">PlayerSyncInfoContainer.<span class="built_in">PreReplication</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Define-Data-Byte-Compression">Define Data &amp; Byte Compression</h3><p>对于每一个 <code>Player</code>，需要收集 <code>FSyncInfo</code>，这里记录了一个 <code>SyncLocation : FVector_NetQuantize</code> 与 <code>Yaw</code>；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FSyncInfo</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="built_in">FSyncInfo</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="built_in">FSyncInfo</span>(<span class="keyword">const</span> FVector_NetQuantize&amp; InSyncLocation) : <span class="built_in">SyncLocation</span>(InSyncLocation) &#123; &#125;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">FVector_NetQuantize SyncLocation;</span><br><span class="line">    <span class="keyword">float</span> Yaw;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TStructOpsTypeTraits</span>&lt;</span> FSyncInfo &gt; : <span class="keyword">public</span> TStructOpsTypeTraitsBase2&lt; FSyncInfo &gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">WithNetSerializer = <span class="literal">true</span>,</span><br><span class="line">WithNetSharedSerialization = <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>FVector_NetQuantize</code> 是一个自定义的类型，进行了对 <code>FVector</code> 的数据压缩。</p><p>由于表达一个 <code>float</code> 所需要的 <code>Bit</code> 比较多，但大部分情况不需要同步到 <code>float</code> 这么高精度的数据），比如一个有限范围的大世界， <code>Location</code> 显然不需要那么高的精度表示：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FVector_NetQuantize</span> :</span> <span class="keyword">public</span> FVector</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_USTRUCT_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="function">FORCEINLINE <span class="title">FVector_NetQuantize</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">explicit</span> FORCEINLINE <span class="title">FVector_NetQuantize</span><span class="params">(EForceInit E)</span> : FVector(E) </span></span><br><span class="line"><span class="function">FORCEINLINE FVector_NetQuantize( float InX, float InY, float InZ ) : FVector(InX, InY, InZ) &#123;</span>&#125;</span><br><span class="line"><span class="function">FORCEINLINE <span class="title">FVector_NetQuantize</span><span class="params">( <span class="keyword">const</span> FVector &amp;InVec )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">FVector::<span class="keyword">operator</span>=(InVec);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">bOutSuccess = SerializePackedVector&lt;<span class="number">1</span>, <span class="number">20</span>&gt;(*<span class="keyword">this</span>, Ar);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TStructOpsTypeTraits</span>&lt;</span> FVector_NetQuantize &gt; : <span class="keyword">public</span> TStructOpsTypeTraitsBase2&lt; FVector_NetQuantize &gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">WithNetSerializer = <span class="literal">true</span>,</span><br><span class="line">WithNetSharedSerialization = <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这里除了将 <code>FVector</code> 压缩为一个 <code>20bit</code> 的 <code>FVector_NetQuantize</code>，还可以对 <code>Yaw</code> 这一旋转角度进行压缩：</p><ol><li>定义一个 <code>uint8</code> 的 <code>ByteYaw</code>，存储 <code>Yaw</code> 的压缩版本，通过 <code>FRotator::CompressAxisToByte</code> 将其压缩为一个字节；</li><li>新增一个标志位 <code>NotZero</code> 判断 <code>ByteRaw</code> 是否是 <code>0</code>（不需要同步 <code>Yaw</code> 时候，该值就为 <code>0</code>，可能清空较多），如果为 <code>0</code>，则只需要存储 <code>NotZero</code> 的值（1位），而不是整个 <code>ByteYaw</code>（8位）；</li></ol><p>对于 <code>FSyncInfo</code>，自定义其 <code>NetSerialize</code> 逻辑：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FSyncInfo::NetSerialize</span><span class="params">(FArchive&amp; Ar, UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">SyncLocation.<span class="built_in">NetSerialize</span>(Ar, Map, bOutSuccess);</span><br><span class="line"></span><br><span class="line">uint8 ByteYaw = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> ( Ar.<span class="built_in">IsSaving</span>() )</span><br><span class="line">ByteYaw = FRotator::<span class="built_in">CompressAxisToByte</span>(Yaw);</span><br><span class="line"></span><br><span class="line">uint8 NotZero = ByteYaw != <span class="number">0</span>;</span><br><span class="line">Ar.<span class="built_in">SerializeBits</span>( &amp;NotZero, <span class="number">1</span> );</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> ( NotZero ) Ar &lt;&lt; ByteYaw;</span><br><span class="line">    <span class="keyword">else</span> ByteYaw = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>( Ar.<span class="built_in">IsLoading</span>() )</span><br><span class="line">&#123;</span><br><span class="line">Yaw= FRotator::<span class="built_in">DecompressAxisFromByte</span>(ByteYaw);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里就定义完毕了 <code>FSyncInfo</code>，显然需要一个 <code>FSyncInfo</code> 对 <code>Player</code> <code>UID</code> 的映射，打包成一个结构体 <code>FPlayerSyncInfo</code>。</p><p>一般使用 <code>uint64</code> 来保存 <code>UID</code>，可以简单的用额外的一个 <code>bit</code> 判断 <code>UID</code> 是否 <code>&gt; MAX_uint32</code>，用来节省一些流量（也可以将 <code>uint64</code> 分为 4 个 <code>16bit</code>，用额外的 <code>2bit</code> 来判断在哪一段范围）</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FPlayerSyncInfo</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"><span class="built_in">FPlayerSyncInfo</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="built_in">FPlayerSyncInfo</span>(uint64 UID, <span class="keyword">const</span> FSyncInfo&amp; SyncInfo)</span><br><span class="line">: <span class="built_in">UID</span>(UID), <span class="built_in">SyncInfo</span>(SyncInfo) &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">uint64 UID;</span><br><span class="line">FSyncInfo SyncInfo;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TStructOpsTypeTraits</span>&lt;</span> FPlayerSyncInfo &gt; : <span class="keyword">public</span> TStructOpsTypeTraitsBase2&lt; FPlayerSyncInfo &gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> </span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">WithNetSerializer = <span class="literal">true</span>,</span><br><span class="line">WithNetSharedSerialization = <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----------------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FPlayerSyncInfo::NetSerialize</span><span class="params">(FArchive&amp; Ar, UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">SyncInfo.<span class="built_in">NetSerialize</span>(Ar, Map, bOutSuccess);</span><br><span class="line"></span><br><span class="line">uint8 UID_IsHigh = UID &gt; MAX_uint32;</span><br><span class="line">Ar.<span class="built_in">SerializeBits</span>(&amp;IsHigh, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (UID_IsHigh)</span><br><span class="line">&#123;</span><br><span class="line">Ar &lt;&lt; UID;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">uint32 UID_LowBits = (uint32)UID;</span><br><span class="line">Ar &lt;&lt; UID_LowBits;</span><br><span class="line">UID = (uint64)UID_LowBits;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Collect-Data">Collect Data</h3><p><code>FPlayerSyncInfoContainer</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FPlayerSyncInfoContainer</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">FPlayerSyncInfoContainer</span>() &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">PreReplication</span><span class="params">()</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>==(<span class="keyword">const</span> FPlayerSyncInfoContainer&amp; Other) <span class="keyword">const</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> FrameCounter == Other.FrameCounter;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>!=(<span class="keyword">const</span> FPlayerSyncInfoContainer&amp; Other) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> !(*<span class="keyword">this</span> == Other);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">CollectReplicateData</span><span class="params">()</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 控制同步频率</span></span><br><span class="line">uint64 FrameCounter = <span class="number">0</span>;</span><br><span class="line">uint32 PastFrameCounter = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> bHasCollectDataThisFrame = <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 同步信息的数量（对应玩家个数）</span></span><br><span class="line">uint32 InfoCount = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// Bit 流</span></span><br><span class="line">TSharedPtr &lt;FNetBitWriter&gt; WriterPtr;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Replicator</span></span><br><span class="line">    TWeakObjectPtr &lt;<span class="class"><span class="keyword">class</span> <span class="title">APlayerSyncInfoReplicator</span>&gt;</span> Replicator;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TStructOpsTypeTraits</span>&lt;</span>FPlayerSyncInfoContainer&gt; : <span class="keyword">public</span> TStructOpsTypeTraitsBase2&lt;FPlayerSyncInfoContainer&gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">enum</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">WithNetSerializer = <span class="literal">true</span>,</span><br><span class="line">WithIdenticalViaEquality = <span class="literal">true</span>,</span><br><span class="line">WithCopy = <span class="literal">false</span>,</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在 <code>PreReplication</code> 中进行 <code>CollectReplicateData</code>；</p><p>通过 <code>CVarPlayerSyncInfoReplicateFrameInternal</code> 来控制同步的频率，记录 <code>PastFrameCounter</code> 用于记录过去了多少帧，用于后续判断；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FPlayerSyncInfoContainer::PreReplication</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (FrameCounter == GFrameCounter) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    PastFrameCounter = FMath::Clamp&lt;uint32&gt;(GFrameCounter - FrameCounter, <span class="number">1</span>, CVarPlayerSyncInfoReplicateFrameInternal + <span class="number">1</span>);</span><br><span class="line">FrameCounter = GFrameCounter;</span><br><span class="line">bHasCollectDataThisFrame = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">CollectReplicateData</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>CollectReplicate</code> 中进行数据收集与统计，将收集到的数据字节流写入 <code>Writer</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FPlayerSyncInfoContainer::CollectReplicateData</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Replicator.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> (bHasCollectDataThisFrame) <span class="keyword">return</span>;</span><br><span class="line">bHasCollectDataThisFrame = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!WriterPtr.<span class="built_in">IsValid</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        WriterPtr = MakeShared&lt;FNetBitWriter&gt;();</span><br><span class="line">        WriterPtr-&gt;<span class="built_in">SetAllowResize</span>(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        WriterPtr.<span class="built_in">Get</span>()-&gt;<span class="built_in">Reset</span>();</span><br><span class="line">        InfoCount = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    FNetBitWriter* Writer = WriterPtr.<span class="built_in">Get</span>();</span><br><span class="line">    <span class="keyword">bool</span> bOutSuccess = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">auto</span> SerializePlayerSyncInfo = [&amp;](<span class="keyword">const</span> <span class="keyword">auto</span>&amp; PlayerState)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!PlayerState.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">        </span><br><span class="line">        uint64 UID = PlayerState-&gt;<span class="built_in">GetUID</span>();</span><br><span class="line">        FVector Location = INVALID_LOCATION</span><br><span class="line">        <span class="keyword">float</span> Yaw = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (APawn* PlayerPawn = PlayerState-&gt;<span class="built_in">GetPawn</span>(); <span class="built_in">IsValid</span>(PlayerPawn))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">const</span> FTransform&amp; Transform = PlayerPawn-&gt;<span class="built_in">GetTransform</span>();</span><br><span class="line">            Location = Transform.<span class="built_in">GetLocation</span>();</span><br><span class="line">            Yaw = Transform.<span class="built_in">Rotator</span>().Yaw;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function">FVector_NetQuantize <span class="title">SyncLocation</span><span class="params">(Location.X, Location.Y, Location.Z)</span></span>;</span><br><span class="line">        <span class="function">FPlayerSyncInfo <span class="title">PlayerSyncInfo</span><span class="params">(UID, &#123;SyncLocation, Yaw&#125;)</span></span>;</span><br><span class="line">        PlayerSyncInfo.<span class="built_in">NetSerialize</span>(*Writer, <span class="literal">nullptr</span>, bOutSuccess);</span><br><span class="line">        InfoCount++;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; PlayerState : PlayerArray)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">SerializePlayerSyncInfo</span>(PlayerState);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Serialize">Serialize</h3><p>对于每一个 <code>Player</code> 的 <code>Connection</code>，判断其是否满足同步帧数限制，每次 <code>NetSerialize</code> ，将 <code>ConnectionDriver-&gt;NextReplicateFrameCount -= PlayerSyncInfoContainer.PastFameCounter</code>，若 <code>&lt;= 0</code> 则重置其 <code>NextReplicateFrameCount</code> 为 <code>CVarPlayerSyncInfoReplicateFrameInternal</code>，并认为本次需要对该 <code>Connection</code> 进行同步；若需要同步，将 <code>Writer</code> 中的数据进行序列化；</p><p>在反序列化时，找到对应的 <code>Player</code>，将数据设置到其 <code>PlayerState</code> 中；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FPlayerSyncInfoContainer::NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (Ar.<span class="built_in">IsSaving</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> PackageMapClient = Cast&lt;UPackageMapClient&gt;(Map);</span><br><span class="line"><span class="keyword">auto</span> Connection = PackageMapClient-&gt;<span class="built_in">GetConnection</span>();</span><br><span class="line"><span class="keyword">auto</span> ConnectionDriver = Connection-&gt;<span class="built_in">GetReplicationConnectionDriver</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">bool</span> bNeedReplicate = <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (ConnectionDriver-&gt;NextReplicateFrameCount &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            bNeedReplicate = <span class="literal">true</span>;</span><br><span class="line">            ConnectionDriver-&gt;NextReplicateFrameCount = CVarPlayerSyncInfoReplicateFrameInternal</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (bNeedReplicate)</span><br><span class="line">        &#123;</span><br><span class="line">            Ar.<span class="built_in">SerializeIntPacked</span>(InfoCount);</span><br><span class="line">            Ar.<span class="built_in">SerializeBits</span>(WriterPtr-&gt;<span class="built_in">GetData</span>(), WriterPtr-&gt;<span class="built_in">GetNumBits</span>());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            uint32 Count = <span class="number">0</span>;</span><br><span class="line">            Ar.<span class="built_in">SerializeIntPacked</span>(Count);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        bOutSuccess = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">        <span class="comment">// 将收集到的数据同步到客户端，在客户端进行数据的反序列化，</span></span><br><span class="line">uint32 Count = <span class="number">0</span>;</span><br><span class="line">Ar.<span class="built_in">SerializeIntPacked</span>(Count);</span><br><span class="line"><span class="keyword">if</span> (Count == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 记录当前收包累计次数，用于预测等</span></span><br><span class="line"><span class="keyword">const</span> uint64 FrameNum = ++GDSPlayerSyncInfoReplicateFrameNum;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> Index = <span class="number">0</span>; Index &lt; Count; Index++)</span><br><span class="line">&#123;</span><br><span class="line">FPlayerSyncInfo PlayerSyncInfo;</span><br><span class="line">PlayerSyncInfo.<span class="built_in">NetSerialize</span>( Ar, Map, bOutSuccess );</span><br><span class="line"></span><br><span class="line">uint64 UID = PlayerSyncInfo.UID;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> PlayerState = <span class="built_in">GET_PLAYERSTATE_BY_UID</span>(UID))</span><br><span class="line">&#123;</span><br><span class="line">PlayerState-&gt;<span class="built_in">SetPlayerSyncInfo</span>(PlayerSyncInfo);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Client-Predict">Client Predict</h3><p>对于 <code>Client</code>，会间隔收到 <code>3P</code> 的  <code>PlayerSyncInfo</code>，同时在收包的时候记录了帧号。</p><p>对于比如 <code>Location</code> 这样的数据，显然需要保证其连续性。</p><p>于是可以根据这些信息做一个简单的预测：</p><ol><li>在 <code>Client</code> 存在 <code>Pawn</code> 时，直接使用 <code>Pawn</code> 的位置，并且强制更新预测与期望；</li><li>在收到一个新包 <code>CurrentBack</code> 时，使用 <code>PrePack -&gt; CurrentBack</code> 的 <code>LocationDiff / PreFrameInternal</code> 计算出一个新的预测速度 <code>PredictCalcDeltaSyncLocationVelocity</code>，同时可以根据实际情况  <code>* SpeedFactor</code> 来进行适当修改；</li><li>每次 <code>GetSyncLocation</code> 时尝试更新预测信息，在当前帧 <code>CurrentFrame</code>，利用 <code>PredictCalcDeltaSyncLocationVelocity * FrameInternal</code> 来外推当前所在 <code>Location</code>，这里的 <code>FrameInternal</code> 是从 上一次预测帧 <code>LastPredictFrame</code> 开始经过的帧数，预测完毕更新 <code>LastPredictFrame</code>；</li><li>如果当前位置和预测期望位置太远，则直接设置为期望位置，不进行插值；</li></ol><pre class="mermaid">flowchart LRA(PrePack)B(CurrentBack)C(NextBack)Last([LastPredictFrame])Now([CurrentFrame])A---->|PreFrameInternal| BB-->|Internal|LastLast-->|Internal|NowNow-..->C</pre><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerState::SetSyncLocation</span><span class="params">(FVector InSyncLocation, int32 CurrentFrameNum)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> bEnableSet = <span class="built_in">EnableSetSyncLocation</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (bEnableSet)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// --- Predict</span></span><br><span class="line">        int64 DeltaFrameCount = UKismetSystemLibrary::<span class="built_in">GetFrameCount</span>() - SyncLocationFrameCount;</span><br><span class="line">        <span class="keyword">float</span> SpeedUpFactor = <span class="number">1.05f</span>;</span><br><span class="line">        PredictCalcDeltaSyncLocationVelocity = DeltaFrameCount &lt;= <span class="number">0</span> ? <span class="number">0.0f</span> : FVector::<span class="built_in">Dist</span>(InSyncLocation, ActorSyncLocation) / DeltaFrameCount * SpeedUpFactor;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// --- UpdateLocation</span></span><br><span class="line">        ActorSyncLocation = InSyncLocation;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        PredictCalcDeltaSyncLocationVelocity = <span class="number">0.0f</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// --- UpdateFrame</span></span><br><span class="line">SyncLocationUpdateFrameNum = CurrentFrameNum;</span><br><span class="line">SyncLocationFrameCount = UKismetSystemLibrary::<span class="built_in">GetFrameCount</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">APlayerState::EnableSetSyncLocation</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// if WeakNet then Disable...</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">FVector <span class="title">APlayerState::GetSyncLocation</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (APawn* Pawn = <span class="built_in">GetPawn</span>(); <span class="built_in">IsValid</span>(Pawn))</span><br><span class="line">&#123;</span><br><span class="line">        FVector Location = Pawn-&gt;<span class="built_in">GetTransform</span>().<span class="built_in">GetLocation</span>();</span><br><span class="line">        ActorSyncLocation.<span class="built_in">Set</span>(Loc.X, Loc.Y, Loc.Z);</span><br><span class="line">        <span class="built_in">UpdatePredictSyncLocation</span>(ActorSyncLocation, <span class="literal">true</span>);</span><br><span class="line">        <span class="keyword">return</span> ActorSyncLocation;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断是否正常发包，若新的 PlayerSyncInfoPack 中无该 PlayerState 则不采用预测信息</span></span><br><span class="line"><span class="keyword">if</span> (GDSPlayerSyncInfoReplicateFrameNum == SyncLocationUpdateFrameNum &amp;&amp; SyncLocationUpdateFrameNum != <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">UpdatePredictSyncLocation</span>(ActorSyncLocation);</span><br><span class="line"><span class="keyword">return</span> PredictSyncLocation;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> InvalidSyncLocation;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">APlayerState::UpdatePredictSyncLocation</span><span class="params">(FVector InLocation, <span class="keyword">bool</span> bForce)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">int64 CurrentFrame = UKismetSystemLibrary::<span class="built_in">GetFrameCount</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 强制更新预测信息</span></span><br><span class="line"><span class="keyword">if</span> (bForce == <span class="literal">true</span>)</span><br><span class="line">&#123;</span><br><span class="line">LastPredictCalcLocationFrameCount = CurrentFrame;</span><br><span class="line">PredictSyncLocation = InLocation;</span><br><span class="line">        PredictCalcDeltaSyncLocationVelocity = <span class="number">0.0f</span>;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int64 DeltaFrame = CurrentFrame - LastPredictCalcLocationFrameCount;</span><br><span class="line"><span class="keyword">if</span> (DeltaFrame &lt;= <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 预测速度为 0 / 距离超过 DistanceLimit，直接传送</span></span><br><span class="line"><span class="keyword">if</span> ( FMath::<span class="built_in">IsNearlyEqual</span>(PredictCalcDeltaSyncLocationVelocity, <span class="number">0.0f</span>) || FVector::<span class="built_in">DistSquared</span>(PredictSyncLocation, InLocation) &gt;= PredictSyncLocationDistanceLimitSquared)</span><br><span class="line">&#123;</span><br><span class="line">PredictSyncLocation = InLocation;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">PredictSyncLocation += DeltaFrame * (InLocation - PredictSyncLocation).<span class="built_in">GetSafeNormal</span>() * PredictCalcDeltaSyncLocationVelocity;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">LastPredictCalcLocationFrameCount = CurrentFrame;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，就可以得到一个相对丝滑的 <code>Location</code> 信息。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;PlayerInfo高频同步解决方案&lt;/h1&gt;
&lt;h2 id=&quot;描述&quot;&gt;描述&lt;/h2&gt;
&lt;h3 id=&quot;需求目的&quot;&gt;需求目的&lt;/h3&gt;
&lt;p&gt;分析一个常见的需求： “在 &lt;code&gt;1P&lt;/code&gt; 客户端显示 &lt;code&gt;3P&lt;/code&gt; 的 &lt;code&gt;Trans</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>ACT浅思</title>
    <link href="https://blog.bearchild.top/2024/04/24/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/ACT%E6%B5%85%E6%80%9D/"/>
    <id>https://blog.bearchild.top/2024/04/24/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/ACT%E6%B5%85%E6%80%9D/</id>
    <published>2024-04-23T16:00:00.000Z</published>
    <updated>2024-07-25T17:11:36.981Z</updated>
    
    <content type="html"><![CDATA[<h1>ACT浅思</h1><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404262219322.png" alt="image-20240426221904635"></p><h2 id="ACT-表现">ACT 表现</h2><h3 id="动作">动作</h3><ol><li>动作节奏：</li></ol><pre class="mermaid">flowchart LR前摇--->攻击判定--->后摇-->后摇-可打断</pre><p>需要控制好每个阶段的时间分配，表现不同的节奏；</p><blockquote><p>较长的后摇可以方便玩家明确下一步操作（不攻击、打断后摇继续攻击），判定更明确；</p><p>较短的后摇可以增加动作连续性连续，提升手感；</p></blockquote><ol start="2"><li>动画配合：</li></ol><p>动画曲线配合：从动画中提取曲线，配合曲线调整位移；比如移动，需要在脚落下的那一刻位移速度最大，其余阶段几乎不位移；</p><p>攻击方、受击方动画、位移需要匹配；</p><ol start="3"><li>动作设计：</li></ol><p><code>Anticipation &amp; Follow Through</code>：预期、跟随；在确定性的时间做更多奖励性的动作演出，此时玩家会更加专注，比如支援突击等玩家主动触发的明确时刻；利用跟随增强动画流畅感和力量感，比如格挡时的强化慢镜头，利用一些跟随，同时提供反应时间；再比如局外人物展示界面，在结束阶段利用 衣物飘带、尾巴 等动力学变化强化演出效果；</p><p><code>Slow In / Slow Out</code>：慢入、快速表演、慢出；同时可以增加蓄力抖动等加强压迫感；</p><p><code>Exaggeration &amp; Strong Posing</code>：夸张和强烈的动作；比如跳跃使用后空翻，提升视觉效果；受击动作，也可以有夸张的表现，具体在 硬直、击退、击飞 等；同时可以添加骨骼抖动强化反馈；</p><ol start="4"><li>爽感优化</li></ol><p>顿帧：伤害越大顿帧越久，一般发生在 攻击方接触受击方、技能或连招开始与结束时；</p><p>连招：较长的连招（意味着更大的风险）过后，可以添加夸张的收尾表演，释放压力；</p><p>终结技：目标强僵直、残血时可以添加慢镜头终结技；</p><h3 id="镜头">镜头</h3><ol><li>运镜逻辑：</li></ol><p>镜头内容：需要保证全程看清目标，更多以目标为核心；需要保证目标完整在镜头内（尤其是使用大位移技能时）；尽量避免主角与目标重叠；</p><p>镜头构图：可以稍微错位，可以看清目标动作；</p><ol start="2"><li>镜头效果：</li></ol><p>屏幕震动：普攻可以增加左右偏移、重击增加上下纵深偏移；</p><p>子弹时间：在合适的时候增加子弹时间，可以表现出力量感；</p><p>镜头模糊：径向模糊、动态模糊、空气扭曲等效果；可以表现出速度感；</p><h3 id="UI">UI</h3><ol><li><code>HUD</code> 的缩放、偏移、光效；</li><li>屏幕闪屏：在使用技能或击杀目标时，可以增加闪屏或者黑屏淡入淡出等效果；</li><li>准心变化：颜色、大小、特殊形态（骷髅表示击杀、打叉表示命中或爆头）；</li><li>跳字：分为普攻、暴击、恢复；主要调整：轨迹（抛物线、弹出、上浮）、缩放、透明度、重力、字体、颜色；比如暴击一般离散范围小、上浮位移大、爆发力可以更强；</li><li>连击提示：与击打节奏契合；</li><li>受击指向：被攻击的方向；</li></ol><h3 id="特殊效果">特殊效果</h3><ol><li>特效：</li></ol><blockquote><ol><li>普通特效：技能本身效果（武器发光等），保证和场景颜色差异够大；</li><li>粒子特效：与目标解除时出现 命中/防御/暴击 等粒子表现；场景破碎效果等；</li><li>环境特效：对环境的氛围影响，场景灯光、场景变色；</li><li>角色特效：角色自身的发光等表现；</li></ol></blockquote><ol start="2"><li>音效：在进行指定行为时，需要有特定音效反馈，比如冲击、发射、受击等；</li><li>背景音乐：战斗和漫游需要有不同的音乐变化；</li><li>物理震动：手柄、手机的震动反馈；</li></ol><h2 id="ACT-数值">ACT 数值</h2><h3 id="攻击方">攻击方</h3><ol><li><p>基本属性：攻击力、暴击率、属性加成；</p></li><li><p>攻击范围：</p><blockquote><ol><li>自身：关注攻击碰撞盒，方向、距离、范围等；</li><li>抛射物：关注触发方式（时间、瞄准方式）、轨迹等；</li></ol></blockquote></li><li><p>攻击强度：</p><blockquote><ol><li>进攻强度：与对方防御强度对比，决定对对方造成的伤害与效果；</li><li>维持强度：保证攻击完整的能力，与敌方进攻强度对比，决定攻击是否会被对方攻击打断；</li><li>命中效果：硬直时间（防御、命中），特殊表现（击飞、倒地）</li></ol></blockquote></li><li><p>攻击节奏：前摇、生效（判定时间、持续时间）、后摇</p></li><li><p>攻击位移：水平位移、空中位移</p></li></ol><h3 id="防守方">防守方</h3><ol><li><p>基本属性：防御值、护甲、抗性、伤害减免；</p></li><li><p>防守范围：</p><blockquote><ol><li>受击判定：是否进行判定（硬直）、无敌帧</li><li>受击范围：受击碰撞盒、防守碰撞盒</li></ol></blockquote></li><li><p>防守强度：</p><blockquote><ol><li>格挡：格挡强度、格挡范围、移动距离</li><li>闪避：移动距离、无敌时间、后摇硬直时间</li><li>反击：允许反击的时间与状态、无敌时间、硬直时间</li></ol></blockquote></li><li><p>防守体力：</p><blockquote><ol><li>体力上限：最大体力值，受到攻击、进行防守等行为会降低体力，体力为 0 会进入硬直</li><li>体力恢复：脱战恢复体力</li></ol></blockquote></li></ol><h2 id="参考">参考</h2><p><a href="https://zhuanlan.zhihu.com/p/360843202">拳打冥王脚踢地府——《黑帝斯》战斗设计分析</a></p><p><a href="https://game.academy.163.com/course/careerArticle?course=324">最畅爽的打击感从何而来？</a></p><p><a href="https://zhuanlan.zhihu.com/p/342697831">《战双帕弥什》的动作打击感是怎么做出来的</a></p><p><a href="https://indienova.com/indie-game-development/gdc-dreamscaper-killer-combat-on-an-indie-budget/">《层层梦境》：小型独立团队如何打造杀手级战斗系统</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;ACT浅思&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404262219322.png&quot; alt=&quot;image-20</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="3C" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/3C/"/>
    
    
    <category term="3C" scheme="https://blog.bearchild.top/tags/3C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]Networking源码浅析</title>
    <link href="https://blog.bearchild.top/2024/04/20/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]Networking%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/"/>
    <id>https://blog.bearchild.top/2024/04/20/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]Networking%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/</id>
    <published>2024-04-19T16:00:00.000Z</published>
    <updated>2024-04-26T15:30:09.646Z</updated>
    
    <content type="html"><![CDATA[<h1>Networking源码浅析</h1><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404262212892.png" alt="image-20240426221216657"></p><h2 id="定义">定义</h2><p><code>Bunch</code>：表示一组相关的网络数据，包含 <code>RPC / 属性同步</code> 等信息，<code>UE</code> 中 <code>DS/Client</code> 通信的基本单位。</p><p><code>Packet</code>：<code>UDP</code> 的基本传输单位，可以打包一个或多个 <code>Bunch</code>，在接受端进行解析。</p><p><code>NetDriver</code>：管理网络通信，比如管理连接、处理数据包的发送和接收、处理网络事件。</p><p><code>NetConnection</code>：表示一个网络连接，与一个 <code>NetDriver</code> 关联，并通过 <code>NetDriver</code> 进行实际的网络通信。</p><p><code>Channel</code>：管理网络传输，进行数据分类，提供  <code>SendBunch</code>、<code>ReceivedRawBunch</code> 方法，由 <code>NetConnection</code> 管理，一个 <code>NetConnection</code> 可以包含多种 <code>Channel</code>。</p><p><code>FObjectReplicator</code>：处理对象的网络同步，保存属性快照，进行状态改变发送、更新同步状态、接受与发送消息。</p><p><code>ActorChannel</code>：关联特定的 <code>Actor</code> 负责其同步，包含多个 <code>FObjectReplicator</code> 用于复制其 <code>Actor</code>、<code>ActorComponent</code> 。</p><p><code>FRepLayout</code>：管理复制的属性，定义底层数据内存布局，数据比较、序列化与反序列化等。</p><h2 id="RPC">RPC</h2><p><strong>Server RPC</strong></p><table><thead><tr><th>Calling Machine</th><th>Owning Connection</th><th>Executing Machine</th></tr></thead><tbody><tr><td>Server</td><td>Client</td><td>Server</td></tr><tr><td>Server</td><td>Server</td><td>Server</td></tr><tr><td>Server</td><td>None</td><td>Server</td></tr><tr><td>Client</td><td>Invoking Client</td><td>Server</td></tr><tr><td>Client</td><td>Different Client</td><td>Dropped</td></tr><tr><td>Client</td><td>Server</td><td>Dropped</td></tr><tr><td>Client</td><td>None</td><td>Dropped</td></tr></tbody></table><p><strong>Client RPC</strong></p><table><thead><tr><th>Calling Machine</th><th>Owning Connection</th><th>Executing Machine</th></tr></thead><tbody><tr><td>Server</td><td>Owning Client</td><td>Owning Client</td></tr><tr><td>Server</td><td>Server</td><td>Server</td></tr><tr><td>Server</td><td>None</td><td>Server</td></tr><tr><td>Client</td><td>Invoking Client</td><td>Invoking Client</td></tr><tr><td>Client</td><td>Different Client</td><td>Invoking Client</td></tr><tr><td>Client</td><td>Server</td><td>Invoking Client</td></tr><tr><td>Client</td><td>None</td><td>Invoking Client</td></tr></tbody></table><p><strong>NetMulticast RPC</strong></p><table><thead><tr><th>Calling Machine</th><th>Owning Connection</th><th>Executing Machine</th></tr></thead><tbody><tr><td>Server</td><td>Client</td><td>Server and all Clients the invoking actor is relevant for</td></tr><tr><td>Server</td><td>Server</td><td>Server and all Clients the invoking actor is relevant for</td></tr><tr><td>Server</td><td>None</td><td>Server and all Clients the invoking actor is relevant for</td></tr><tr><td>Client</td><td>Invoking Client</td><td>Invoking Client</td></tr><tr><td>Client</td><td>Different Client</td><td>Invoking Client</td></tr><tr><td>Client</td><td>Server</td><td>Invoking Client</td></tr><tr><td>Client</td><td>None</td><td>Invoking Client</td></tr></tbody></table><p><strong>Send RPC</strong></p><pre class="mermaid">flowchart TBUObject::ProcessEvent-- FunctionCallspace::Remote --> AActor::CallRemoteFunction--> UNetDriver::ProcessRemoteFunctionServerMulticast(bIsServerMulticast)UNetDriver::ProcessRemoteFunction--> ServerMulticastServerMulticast--TRUE-->UNetDriver::GetFunctionRepLayout--Actor IsNetRelevant || FUNC_NetReliable --> FRepLayout::BuildSharedSerializationForRPC--> UNetDriver::InternalProcessRemoteFunctionPrivate--> UNetDriver::ProcessRemoteFunctionForChannelPrivateServerMulticast--FALSE--> UReplicationGraph::ProcessRemoteFunction--> UNetDriver::ProcessRemoteFunctionForChannel--> UNetDriver::ProcessRemoteFunctionForChannelPrivate-- FRepLayout::SendPropertiesForRPC --> QueueBunchQueueBunch(!bReliable & NetMulticast)QueueBunch--TRUE-->UChannel::SendBunchQueueBunch--FALSE-->UActorChannel::QueueRemoteFunctionBunch</pre><p><strong>Receive RPC</strong></p><pre class="mermaid">flowchart TBUIpNetDriver::TickDispatch--> UNetConnection::ReceivedRawPacket--> UNetConnection::ReceivedPacket--> UNetConnection::DispatchPacket--> UChannel::ReceivedRawBunch--> UChannel::ReceivedNextBunch--> UChannel::ReceivedSequencedBunch--> UActorChannel::ReceivedBunch--> UActorChannel::ProcessBunch--> FObjectReplicator::ReceivedBunch-- FObjectReplicator::ReceivedRPC--> UObject::ProcessEvent</pre><h2 id="属性同步">属性同步</h2><p><strong>Replicate Diff</strong></p><pre class="mermaid">flowchart TBUNetDriver::TickFlush--> UNetDriver::ServerReplicateActors--> UActorChannel::ReplicateActor--> FObjectReplicator::ReplicateProperties--> FNetSerializeCB::UpdateChangelistMgr--> FRepLayout::UpdateChangelistMgr--> FRepLayout::CompareProperties</pre><p><strong>Replicate Send</strong></p><pre class="mermaid">flowchart TBUNetDriver::TickFlush--> UNetDriver::ServerReplicateActors--> UActorChannel::ReplicateActor--> FObjectReplicator::ReplicateProperties--> FRepLayout::ReplicateProiperties--> FRepLayout::SendProperties--> FProperty::NetSerializeItem--> UChannel::SendBunch</pre><p><strong>Replicate Receive</strong></p><pre class="mermaid">flowchart TBUIpNetDriver::TickDispatch--> UNetConnection::ReceivedRawPacket--> UNetConnection::ReceivedPacket--> UNetConnection::DispatchPacket--> UChannel::ReceivedRawBunch--> UChannel::ReceivedNextBunch--> UChannel::ReceivedSequencedBunch--> UActorChannel::ReceivedBunch--> UActorChannel::ProcessBunch--> FObjectReplicator::ReceivedBunch--> FRepLayout::ReceiveProperties--> FRepLayout::ReceivePropertyHelper--> FProperty::NetSerializeItem--> FObjectReplicator::PostReceivedBunch--> FObjectReplicator::CallRepNotifies</pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;Networking源码浅析&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404262212892.png&quot; alt=</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]弱网工具Clumsy的使用</title>
    <link href="https://blog.bearchild.top/2024/04/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]%E5%BC%B1%E7%BD%91%E5%B7%A5%E5%85%B7Clumsy%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
    <id>https://blog.bearchild.top/2024/04/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/[UE]%E5%BC%B1%E7%BD%91%E5%B7%A5%E5%85%B7Clumsy%E7%9A%84%E4%BD%BF%E7%94%A8/</id>
    <published>2024-04-13T16:00:00.000Z</published>
    <updated>2024-04-14T11:41:55.634Z</updated>
    
    <content type="html"><![CDATA[<h1>弱网工具Clumsy的使用</h1><p><strong>Clumsy</strong> 用于弱网测试时，进行本地弱网环境的模拟，基于 <a href="https://github.com/basil00/Divert">WinDivert</a> 实现，可以出模拟延迟、丢包等网络状态。</p><h2 id="Downloads">Downloads</h2><p>下载地址：<a href="https://github.com/jagt/clumsy">Clumsy</a>；官方文档：<a href="https://jagt.github.io/clumsy/cn/index.html">Clumsy Manual</a></p><p>下载后的几个重点文件：</p><p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404141744461.png" alt="%~UYF6SWT2EC28Q%6Z4N_EM"></p><p>打开 <strong>Clumsy.exe</strong> 可以看到以下内容：</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404141744627.png" alt="image-20240414172540109" style="zoom:50%;" /><p>可以看出这里有几个重点的 <code>Function</code>，一般重点使用 <strong>Lag 延迟</strong>、<strong>Drop 丢包</strong>。</p><p>在 <code>Filtering</code> 填入对应的 <code>Command</code> 后，设置 <code>Functions</code>，点击 <code>Start</code> 即可开始模拟。</p><p>同时可以在 <code>Config.txt</code> 中，预设一些 <code>Presets</code> 方便下次使用。</p><h2 id="UE-DS-中的使用">UE DS 中的使用</h2><p>对于 <code>UE</code>的 <code>DS</code> 环境，首先为了具体到某一个 <code>Client</code> / <code>DS</code>，采用 <code>Filter Port</code> 的机制，对某个具体的端口号进行操作。</p><p>用 <code>netstat -aon\Findstr 进程ID</code>，查询 <code>DS</code>/<code>Client</code> 进程，通过 <code>UDP</code> 进行链接。</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404141745888.png" alt="image-20240414173745071" style="zoom:67%;" /><p><code>DS</code> 的 <code>port</code> 一般可以在启动命令中找到 <code>-port=17777</code>；</p><p><code>Client</code> 的 <code>port</code> 可以在 <code>DS</code> 的 <code>Log</code> 中，快速查询到对应与指定 <code>Client</code> 连接的对应端口号：</p><img src="https://bearchildbucket-1300061763.cos.ap-guangzhou.myqcloud.com/img/articles/202404141745297.png" alt="image-20240414173856028" style="zoom: 50%;" /><blockquote><p>// DS.log</p><p>LogNet: NotifyAcceptedConnection: Name: TestMap, TimeStamp: 04/14/24 00:00:00, [UNetConnection] <strong>RemoteAddr</strong>: 127.0.0.1:<strong>58459</strong>, Name: IpConnection_0, Driver: GameNetDriver NetDriver_1, IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID</p><p>LogNet: AddClientConnection: Added client connection: [UNetConnection] RemoteAddr: 127.0.0.1:58459, Name: IpConnection_0, Driver: GameNetDriver NetDriver_1, IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID</p></blockquote><pre><code>这里的 **58459** 也就是该 `Client` 的 `Connetcion` 对应的端口号。在 `Filtering` 中填入对应的设置即可：1. 关闭上行包：`udp and udp.SrcPort == 58459` ，设置 `Drop = 100%`2. 关闭下行包：`udp and udp.DstPort == 58459`，设置 `Drop = 100%`</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;弱网工具Clumsy的使用&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Clumsy&lt;/strong&gt; 用于弱网测试时，进行本地弱网环境的模拟，基于 &lt;a href=&quot;https://github.com/basil00/Divert&quot;&gt;WinDivert&lt;/a&gt; 实现，可以出模拟延</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
    <category term="网络" scheme="https://blog.bearchild.top/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[UE]夺旗模式框架</title>
    <link href="https://blog.bearchild.top/2024/03/20/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]%E5%A4%BA%E6%97%97%E6%A8%A1%E5%BC%8F%E6%A1%86%E6%9E%B6/"/>
    <id>https://blog.bearchild.top/2024/03/20/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]%E5%A4%BA%E6%97%97%E6%A8%A1%E5%BC%8F%E6%A1%86%E6%9E%B6/</id>
    <published>2024-03-19T16:00:00.000Z</published>
    <updated>2025-03-24T15:18:17.602Z</updated>
    
    <content type="html"><![CDATA[<h1>夺旗模式框架</h1><h2 id="描述">描述</h2><p><strong>夺旗模式：</strong></p><p>场景里会按照一定的规则刷新出不同类型的旗帜（不同类型的旗帜有不同的得分、血量、表现等）；</p><p>玩家会被分成若干个队伍，抢夺刷新出来的旗帜，将旗帜带到自己队伍目标点进行销毁，则可以获得分数；</p><p>玩家持有旗帜时，旗帜可能会掉落或被抢夺：被其它玩家撞击到则旗帜被抢夺到其它玩家身上；旗帜的血量归零则会掉落回场景；</p><p>有正式赛与加时赛两种类型的比赛，首先会开启正式赛；对于正式赛，率先达到目标分的队伍获胜；如果平局则开启加时赛，否则进行结算；对于加时赛，率先得分的队伍获胜，如果平局则以两队平局进行结算；</p><p><strong>要点总结：</strong></p><blockquote><ol><li>旗帜实体：旗帜本身有自己的类型、得分、血量；</li><li>旗帜刷新：场景内根据一定规则（倒计时刷新、旗子上缴）会刷出不同类型旗帜；</li><li>旗帜传递：玩家可以拾取场景里的旗帜、相互抢夺旗帜、掉落旗帜（持有旗帜血量归零）、将旗帜送给终点得分等；玩家可以组队，共享队伍得分；同队伍之间的玩家可以传递旗帜；</li><li>单局流程：所有玩家就绪分队后，传送到起始点准备开赛；如果一局比赛平分，一定条件下可以触发下一场比赛继续；</li></ol></blockquote><h2 id="模式基础">模式基础</h2><h3 id="单局流程">单局流程</h3><p>首先，一个完整的夺旗赛，由若干个子比赛构成，子比赛逐渐推进；</p><pre class="mermaid">flowchart LRM[MainProcess]R0[RaceProcess_0]R1[RaceProcess_1]R2[RaceProcess_2]M-->R0M-->R1M-->R2R0-.->R1R1-.->R2</pre><p>比赛胜负规则：</p><blockquote><p>单场比赛结束时，判定是否有一方队伍得分更高：</p><p>如果有，则全局结束；</p><p>如果没有，尝试进入下一场比赛（如果有配置的话），若没有下一场则全局结束；</p><ol><li>正式赛：比赛结束条件：有一队达到目标分 / 倒计时结束</li><li>加时赛：比赛结束条件：有一队得到分数 / 倒计时结束</li><li>其它类型比赛……</li></ol></blockquote><h3 id="局内玩法">局内玩法</h3><p>从业务出发，先明确有哪些相关逻辑：</p><p>最基本的交互显然是：</p><pre class="mermaid">flowchart LRF0[场景旗帜_0]F1[场景旗帜_1]G0([目标点_0])G1([目标点_1])P0[[Player_0]]P1[[Player_1]]F0--PickUp--->P0--Drop-->F0P0--Reach--->G0P0<--Strike--->P1</pre><blockquote><p><code>PickUp</code>：<code>Player</code> 触碰到 <code>场景旗帜</code> ，就可以拾取该旗帜到自己身上；</p><p><code>Drop</code> ：<code>Player</code> 持有的旗帜存在血量，其血量归零（可能是被其它 <code>Player</code> 攻击导致扣血），就会触发旗帜的掉落，在对应位置生成场景旗帜；</p><p><code>Reach</code>：<code>Player</code> 将持有的旗帜护送到 <code>目标点</code>，获得分数，同时该旗帜会被销毁；</p><p><code>Strike</code>：<code>Player</code> 撞击另一个 <code>Player</code>，可以抢夺对方的旗帜；</p></blockquote><h3 id="业务拆分">业务拆分</h3><p>首先，先给出基本的一些元素定义；</p><blockquote><p>游戏玩法的本质是由刷新点刷新出旗帜，旗帜在各个部分之间交互传递，旗帜本身定义为 <code>FlagItem</code>。</p><p>旗帜在场景（不在 <code>Player</code> 身上）里的表现为，存在对应的 <code>FlagSceneItem</code>。</p><p>场景里存在若干个终点，定义该终点为 <code>GoalPointSceneItem</code></p></blockquote><p>这里说的 <strong>交互</strong> 的本质实际上是一个 <code>Flag</code> 的 <code>Instance</code> 在 <em><strong>各个部分</strong></em> 之间的 <strong>传递</strong> 。想一下所谓的 <em>各个部分</em>，到底是什么？</p><p>本质可以抽象为一个个持有 <code>Flag</code> 的容器，将其定义为 <code>FlagContainer</code>。</p><p>这里显然有三种 <code>Container</code>：</p><blockquote><p><code>Container_WorldFlag</code>：定义 <code>WorldFlag</code> 为 <strong>没有被 <code>Player</code> 持有的旗帜</strong></p><p><code>Container_GoalPoint</code>：定义 <code>GoalPoint</code> 为目标点</p><p><code>Container_Player</code>：每个 <code>Player</code> 持有一个 <code>Container</code>；</p></blockquote><p>细想一下， <code>WorldFlag</code> 、<code>GoalPoint</code> 的具体业务逻辑和具体的某个 <code>Instance</code> 没有太大关系，只需要通过 <code>ID</code> 串联即可，所以对于 <code>WorldFlag</code>、<code>GoalPoint</code>，不需要真的在每一个 <code>Instance</code> 都创建对应 <code>Container</code>，只需要一个单局管理的唯一 <code>Manager</code> 用来代替管理即可。但显然对于每个 <code>Player</code> 都需要有自己的 <code>Container</code>。</p><p>可以在单局内创建全局唯一的  <code>Container_WorldFlag</code> 、<code>Container_GoalPoint</code> 实例。</p><pre class="mermaid">flowchart TDF0[FlagSceneItem_0]F1[FlagSceneItem_1]F2[FlagSceneItem_2]subgraph WorldFlag[Container_WorldFlag]    direction LR    F0-.-F1-.-F2endG0([GoalPoint_0])G1([GoalPoint_1])G2([GoalPoint_2])subgraph GoalPoint[Container_GoalPoint]    direction LR    G0-.-G1-.-G2endP0[[Container_Player_0]]P1[[Container_Player_1]]WorldFlag--PickUp--->P0--Drop--->WorldFlagP0--Reach--->GoalPointP0<--Strike--->P1</pre><p>这样就可以将 <strong>交互</strong> 的逻辑拆得非常简单了：</p><blockquote><p><code>PickUp</code> ：由 <code>FlagSceneItem</code> 和 <code>Player</code> 的 <code>Overlap</code> 触发，将 <code>Container_WorldFlag</code> 持有的对应该 <code>FlagSceneItem</code> 的 <code>FlagItem</code> ，传递给这个 <code>Player</code> 的 <code>Conatiner_Player</code></p><p><code>Drop</code> ： 由 <code>Player</code> 的血量变化归零触发，将 <code>Container_Player</code> 上的所有 <code>FlagItem</code> 转移给 <code>Cotainer_WorldFlag</code></p><p><code>Reach</code>：由 <code>Player</code> 和 <code>GoalPointSceneItem</code> 的 <code>Overlap</code> 触发，将 <code>Container_Player</code> 上的所有 <code>FlagItem</code> 转移给 <code>Cotainer_GoalPonit</code></p><p><code>Strike</code>：由 <code>Player0</code> 和 <code>Player1</code> 的撞击触发，将 <code>Container_Player</code> 上的所有 <code>FlagItem</code> 转移给 <code>Cotainer_Player1</code></p></blockquote><p>这里的 <code>PickUp</code>、<code>Drop</code>、<code>Reach</code>、<code>Strike</code> 是这张图的 <strong>边</strong>，将其定义为 <code>TransferPolicy</code>。</p><p>对于 <code>Container</code>，有各自的 <code>Add(FlagItem)</code>、<code>Remove(FlagItem)</code>，一些基本逻辑：</p><blockquote><p><code>Container_WorldFlag</code>：</p><ol><li><p><code>AddItem</code> ：从 <code>FlagItem</code> 上取出位置，将 <code>Item</code> 血量设置为满血，通过调用 <code>FlagSceneItemManager</code> 创建出 <code>FlagItem</code> 对应的 <code>FlagSceneItem</code>；</p></li><li><p><code>RemoveItem</code>：调用 <code>FlagSceneItemManager</code> 删除对应的  <code>FlagItem</code>  对应的 <code>FlagSceneItem</code>；</p></li></ol></blockquote><blockquote><p><code>Container_GoalPoint</code>：</p><ol><li><p><code>AddItem</code>：判断是从哪个 <code>Container</code> 来的，给对应 <code>Player</code> 加分，并且 <code>RemoveItem</code>；</p></li><li><p><code>RemoveItem</code> ：销毁对应的 <code>FlagItem</code>；</p></li></ol></blockquote><blockquote><p><code>Container_Player</code>：</p><ol><li><p><code>AddItem</code>：从 <code>FlagItem</code> 获得 <code>FlagSyncData</code> 同步给自己，并设置 <code>Player</code> 血量为 <code>Item</code> 的血量等逻辑；</p></li><li><p><code>RemoveItem</code>：将 <code>Player</code> 血量设置给 <code>Item</code> 血量，清空同步信息等逻辑；</p></li></ol></blockquote><p>完整的业务拆分完毕，这下需要哪些系统显而易见：</p><blockquote><p><code>ProcessSystem</code>：负责管理单局流程，创建比赛与推动玩法进程；</p><p><code>TeamSystem</code> ：负责 <code>Player</code> 的组队，管理 <code>Memebr</code>、<code>Score</code> 等；</p><p><code>FlagItemSystem</code>：负责管理所有 <code>FlagItem</code> 实例，提供 <code>Create</code>、<code>Destroy</code> 等方法，维护 <code>Item</code> 基本的生命周期；</p><p><code>ContainerSystem</code>：负责提供基本的 <code>CreateContainer</code>、<code>DestoryContainer</code> 方法，对于具体的 <code>Container</code>，需要支持 <code>AddItem</code> 、<code>RemoveItem</code>、<code>GetItem</code> 等；</p><p><code>TransferSystem</code>：负责管理所有的 <code>TransferPolicy</code> ，提供 <code>TransferItem</code> 方法；</p><p><code>FlagSpawnerSystem</code>：旗帜的刷新点，负责 <code>WorldFlag</code> 的生成，生成 <code>FlagItem</code> 并 <code>Add</code> 到 <code>Container_WorldFlag</code>；</p><p><code>SceneItemSystem</code> ：维护场景里 <code>SceneItem</code> 的生成，提供 <code>Create</code>、<code>Destroy</code> 对应的 <code>SceneItem</code> 的方法；对于某个具体的 <code>SceneItem</code>，需要实现基础的 <code>Overlap</code>、<code>Sync</code>；</p></blockquote><h2 id="具体实现">具体实现</h2><h3 id="ProcesSystem"><code>ProcesSystem</code></h3><p>首先需要一个挂在 <code>GameState</code> 上的  <code>MainProcess</code> 负责管理全局的流程，串联多个 <code>RaceProcess</code> 推进玩法；</p><p>显然，一个最简单的流程可以这样：</p><pre class="mermaid">flowchart LRM[MainProcess]R0[RaceProcess_0]R1[RaceProcess_1]F[Finish]M---->|1. 开赛|R0R0-.->|2. Result_0|MM-->|3. Check_Result_0|R1R1-.->|4. Result_1|MM-->|5. Check_Result_1|FR0-.->R1R1-.->F</pre><p>但是这样循环流程需要在 <code>Main</code> 里面关注 <code>RaceProcess</code> 给出的 <code>Result</code> 事件，显然有点不够优雅。</p><p>发现这里的主要由两部分组成：</p><blockquote><ol><li><code>RaceProcess</code> 管理子比赛流程，给出 <code>Result</code></li><li><code>MainProcess</code> 接受子比赛 <code>Result</code>，校验并判断开启下一场 <code>Race</code> 或者结束比赛</li></ol></blockquote><p>所以可以将其拆成两个 <code>Controller</code>：</p><p><code>MainProcess</code> 负责提供 <code>Dispatch</code> 的功能，</p><blockquote><ol><li><code>FinishRace_Controller</code>：监听 <code>Dispatcher</code> 的 <code>开赛</code> 事件，并向 <code>Dispatcher</code> 分发 <code>单局结束</code>事件；</li><li><code>CreateRace_Controller</code>：监听 <code>单局结束</code> 事件，进行处理并向 <code>Dispatcher</code> 分发<code>开赛</code> 事件；</li></ol></blockquote><pre class="mermaid">flowchart TBM[MainProcess]D[Dispatcher]F[FinishRace_Controller]C[CreateRace_Controller]M-->DF-->|Send_Finsih|DD-.->|Register_Create|FC-->|Send_Create|DD-.->|Register_Finish|C</pre><h3 id="TeamSystem"><code>TeamSystem</code></h3><p>参考 <a href="https://www.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DTeamSystem%E6%A1%86%E6%9E%B6/">[UE] TeamSystem 框架</a></p><p>支持玩家的 <code>加入</code>、<code>退出</code> 队伍，以及维护队伍的各种数据（比如 <code>Members</code>、<code>Score</code>），并维护数据，进行同步。</p><h3 id="SceneItemSystem"><code>SceneItemSystem</code></h3><pre class="mermaid">classDiagram    UCFSceneItemUtils..>UCFSceneItemManager        UCFSceneItemManager..>UCFScecneItemBase    class UCFSceneItemManager {    frien UCFSceneItemUtils    - CreateItem(Type, UID, Params)    - DestroyItem(UID)    }        UCFScecneItemBase..*UCFScecneItemSyncComponent    UCFScecneItemBase..*UCFScecneItemDisplayComponent    class UCFScecneItemBase {    SyncComponent : UCFScecneItemSyncComponent    OverlapDelegate    # OnInit()    # OnUninit()    # BeginDetect()    # EndDetect()    + CollectSyncData()    }        UCFScecneItem_WorldFlag--|>UCFScecneItemBase    UCFScecneItem_GoalPoint--|>UCFScecneItemBase</pre><p>首先，需要 <code>UCFSceneItemManager</code> 负责管理所有的 <code>SceneItem</code>，同时通过 <code>UCFSceneItemUtils</code> 提供方法给外部调用。</p><p>对于一个具体的 <code>SceneItem</code>，额外对其提供两个 <code>Component</code>，</p><blockquote><ol><li><code>SyncComponent</code>：在 <code>DS/Client</code> 生成，维护 <code>SceneItem</code> 的同步数据，这一部分数据不会被 <code>AOI</code> 裁剪，保证远距离的数据同步；</li><li><code>DisplayComponent</code>：仅在 <code>Client</code> 生成，随着 <code>SceneItem</code> 被 <code>AOI</code>，用于实现客户端表现。</li></ol></blockquote><h3 id="FlagItemSystem、ContainerSystem"><code>FlagItemSystem</code>、<code>ContainerSystem</code></h3><pre class="mermaid">classDiagramdirection LRUCaptureFlagItemSystem..>FCaptureFlagItem    class UCaptureFlagItemSystem {    ItemInstances : TMap~uint64|TSharedPtr~FCaptureFlagItem~~    + CreateItem(ItemID)    + DestroyItem(ItemUID)    + GetItem(ItemUID)    - GenerateUID()    - RegisterClearTimer()    - UnregisterClearTimer()    - ClearAllItems(bCheckUnused)    }        %% ----- FlagItem -----        namespace FlagItem {        class FCaptureFlagItem        class FCaptureFlagItemSyncData    }        FCaptureFlagItem..FCaptureFlagItemSyncData    FCaptureFlagItem..UCaptureFlagContainerBase    class FCaptureFlagItem {    + InitItem(InUID, ItemID)    + GetSyncData() : FCaptureFlagItemSyncData    + UID / ItemID / Type / Health / MaxHealth...    + Container : TWeakObjectPtr~UCaptureFlagContainerBase~    + TransferReason : ECFTransferReason    }        class FCaptureFlagItemSyncData {    + UID / ItemID / Type / Health / MaxHealth / TransferReason    }        %% ----- FlagContainer -----        namespace FlagContainer {    class UCaptureFlagContainerBase    class UCaptureFlagContainer_WorldFlag    class UCaptureFlagContainer_GoalPoint    class UCaptureFlagContainer_Player    }        class UCaptureFlagContainerBase {    Items : TMap ~uint64|TWeakPtr~FCaptureFlagItem~~    Owner : TWeakObjectPtr~UObject~    + Init(InOwner)    + Uninit()    + AddItem(TWeakPtr~FCaptureFlagItem~Item, Params)    + RemoveItem(TWeakPtr~FCaptureFlagItem~ Item)    + VerifyCanAddItem()    + GetType() : ECFContainerType    # OnInit()    # OnUninit()    # OnAddItem(TWeakPtr~FCaptureFlagItem~Item, Params)    # OnRemoveItem(TWeakPtr~FCaptureFlagItem~ Item)    }        UCaptureFlagContainer_WorldFlag--|>UCaptureFlagContainerBase    UCaptureFlagContainer_GoalPoint--|>UCaptureFlagContainerBase        UCaptureFlagContainer_Player--|>UCaptureFlagContainerBase    class UCaptureFlagContainer_Player{    # GetType() : ECFContainerType        }        %% ----- ContainerOwner -----        namespace ContainerOwner {    class UCaptureFlagManager    class UCaptureFlagComponent    class ICaptureFlagContainerOwnerInterface    }            UCaptureFlagManager..>UCaptureFlagContainer_GoalPoint    UCaptureFlagManager..>UCaptureFlagContainer_WorldFlag    UCaptureFlagManager..|>ICaptureFlagContainerOwnerInterface    class UCaptureFlagManager{    - WorldFlagContainer    - GoalPointContainer    + GetWorldFlagContainer()    + GetGoalPointContainer()    }        UCaptureFlagComponent..|>ICaptureFlagContainerOwnerInterface    UCaptureFlagComponent..>UCaptureFlagContainer_Player    UCaptureFlagComponent..>FCaptureFlagItemSyncData    class UCaptureFlagComponent{    - FlagContainer_Player    - FlagItemSyncData : FCaptureFlagItemSyncData    + GetCaptureFlagContainer()    + UpdateFlagItemSyncData(SyncData)    }        ICaptureFlagContainerOwnerInterface..>UCaptureFlagContainerBase    class ICaptureFlagContainerOwnerInterface{    CreateFlagContainer(InOwner)    DestroyFlagContainer(Container)    }</pre><p>首先，需要一个 <code>FlagItemSystem</code> 负责管理所有的 <code>FlagItem</code> 实例。提供基本的创建、销毁、查询功能的同时，这个 <code>FlagItemSystem</code>需要 <code>RegisterClearTimer</code>，每隔一段时间，<code>Clear</code> 不被任何一个 <code>Container</code> 持有的 <code>Item</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UCaptureFlagItemManager</span></span><br><span class="line"></span><br><span class="line"><span class="function">TWeakPtr&lt;FCaptureFlagItem&gt; <span class="title">UCaptureFlagItemManager::CreateItem</span><span class="params">(<span class="keyword">int</span> ItemID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> NewItem = MakeShared&lt;FCaptureFlagItem&gt;();</span><br><span class="line">NewItem-&gt;<span class="built_in">Init</span>( <span class="built_in">GenerateUID</span>(),  ItemID );</span><br><span class="line">ItemInstances.<span class="built_in">Add</span>( NewItem-&gt;<span class="built_in">GetUID</span>(), NewItem );</span><br><span class="line"></span><br><span class="line"><span class="built_in">NotifyItemCreated</span>(NewItem);</span><br><span class="line"><span class="keyword">return</span> NewItem;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCaptureFlagItemManager::DestoryItem</span><span class="params">(uint64 ItemUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!ItemInstances.<span class="built_in">Contains</span>(ItemUID)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Item = ItemInstances.<span class="built_in">FindRef</span>(ItemUID);</span><br><span class="line"><span class="keyword">if</span> (Item.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Item-&gt;<span class="built_in">GetContainer</span>().<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 先从 Container 上移除</span></span><br><span class="line">Item-&gt;<span class="built_in">GetContainer</span>()-&gt;<span class="built_in">RemoveItem</span>(Item);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">ItemInstances.<span class="built_in">Remove</span>(ItemUID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCaptureFlagItemManager::ClearAllItems</span><span class="params">(<span class="keyword">bool</span> bUnused)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TArray &lt;uint64&gt; ItemUIDs;</span><br><span class="line">ItemInstances.<span class="built_in">GetKeys</span>(ItemUIDs);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> ItemUID : ItemUIDs)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Item = ItemInstances.<span class="built_in">FindRef</span>(ItemUID);</span><br><span class="line"><span class="keyword">if</span> (!Item.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ( bUnused == <span class="literal">false</span></span><br><span class="line">|| <span class="comment">/*未被任何 Container 持有*/</span> Item-&gt;<span class="built_in">GetContainer</span>().<span class="built_in">IsValid</span>() == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">DestoryItem</span>(ItemUID);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于 <code>FlagItem</code>，保存 <code>Flag</code> 最基本的信息，同时提供一个 <code>CollectSyncData</code>，用于生成对应的 <code>FlagSyncData</code>，表示需要同步的数据，在 <code>ContainerOwner</code> 需要的时候进行数据同步。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FlagItem.h</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> GENERATE_CAPTUREFLAG_ITEM_PROPERTY(Visibility, Type, Name) \</span></span><br><span class="line"><span class="meta">public: Type Get##Name() const &#123; return Name; &#125; \</span></span><br><span class="line"><span class="meta">public: void Set##Name(const Type&amp; Value) &#123; Name = Value; &#125; \</span></span><br><span class="line"><span class="meta">Visibility: Type Name</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 需要在 FCaptureFlagItem::GetSyncData 中打包数据</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FCaptureFlagItemSyncData</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="built_in">FCaptureFlagItemSyncData</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>==(<span class="keyword">const</span> FCaptureFlagItemSyncData&amp; Other) <span class="keyword">const</span>;</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>!=(<span class="keyword">const</span> FCaptureFlagItemSyncData&amp; Other) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> !(*<span class="keyword">this</span> == Other);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line">uint64 UID = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line"><span class="keyword">int</span> ItemID = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line"><span class="keyword">int</span> Type = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line"><span class="keyword">float</span> Health = <span class="number">0.0f</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line"><span class="keyword">float</span> MaxHealth = <span class="number">0.0f</span>;</span><br><span class="line"><span class="built_in">UPROPERTY</span>()</span><br><span class="line">ECFTransferReason TransferReason;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FCaptureFlagItem</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">FCaptureFlagItem</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(uint64 InUID, <span class="keyword">int</span> ItemID)</span></span>;</span><br><span class="line"><span class="function">FCaptureFlagItemSyncData <span class="title">GetSyncData</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, uint64, UID);</span><br><span class="line"></span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, <span class="keyword">int</span>, ItemID);</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, <span class="keyword">int</span>, Type);</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, <span class="keyword">float</span>, Health);</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, <span class="keyword">float</span>, MaxHealth);</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, <span class="keyword">int</span>, Score);</span><br><span class="line"></span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, ECFTransferReason, TransferReason);</span><br><span class="line"><span class="built_in">GENERATE_CAPTUREFLAG_ITEM_PROPERTY</span>(<span class="keyword">private</span>, TWeakObjectPtr&lt;class UCaptureFlagContainerBase&gt;, Container);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>需要 <code>Container</code> 来持有 <code>FlagItem</code>，在 <code>AddItem</code> 和 <code>RemoveItem</code> 内写具体的逻辑。</p><p>每种 <code>Container</code> 通过重载 <code>UCaptureFlagContainerBase</code> 的 <code>PURE_VIRTUAL</code> 方法  <code>GetType</code>  来定义其类型。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ContainerBase.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(Abstract)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UCaptureFlagContainerBase</span> :</span> <span class="keyword">public</span> UObject</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">friend</span> <span class="class"><span class="keyword">class</span> <span class="title">UCaptureFlagItemUtils</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> ECFContainerType <span class="title">GetType</span><span class="params">()</span> <span class="title">PURE_VIRTUAL</span><span class="params">( UCaptureFlagContainerBase::GetType, <span class="keyword">return</span> ECFContainerType::None; )</span> <span class="comment">// 设置 ContainerType</span></span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">VerifyCanAddItem</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">CollectParams</span><span class="params">(FCaptureFlagParams&amp; Params)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckCapcityValid</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(UObject* InOwner)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Uninit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">AddItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, <span class="keyword">const</span> FCaptureFlagParams&amp; Params = &#123;&#125;)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">RemoveItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item)</span></span>;</span><br><span class="line">TArray &lt;TWeakPtr&lt;FCaptureFlagItem&gt;&gt; <span class="built_in">GetAllItems</span>();</span><br><span class="line"><span class="function">TWeakPtr&lt;FCaptureFlagItem&gt; <span class="title">GetItem</span><span class="params">(uint64 ItemUID)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ClearItems</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="comment">// 各个 Container 自定义逻辑</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInit</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnAddItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, <span class="keyword">const</span> FCaptureFlagParams&amp; Params)</span> </span>&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRemoveItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item)</span> </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">TMap &lt;uint64, TWeakPtr&lt;FCaptureFlagItem&gt;&gt; Items &#123;&#125;;</span><br><span class="line">TWeakObjectPtr &lt;UObject&gt; Owner = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ContainerBase.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCaptureFlagContainerBase::AddItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, <span class="keyword">const</span> FCaptureFlagParams&amp; Params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Owner.<span class="built_in">IsValid</span>() || !Item.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">uint64 ItemUID = Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">GetUID</span>();</span><br><span class="line"><span class="keyword">if</span> (Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">GetContainer</span>().<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// Item 只能被放在一个 Container 内</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查是否可以获得</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">VerifyCanAddItem</span>() == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">SetContainer</span>( <span class="keyword">this</span> );</span><br><span class="line">Items.<span class="built_in">Add</span>( ItemUID, Item );</span><br><span class="line"></span><br><span class="line"><span class="built_in">OnAddItem</span>(Item, Params);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCaptureFlagContainerBase::RemoveItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Owner.<span class="built_in">IsValid</span>() || !Item.<span class="built_in">IsValid</span>()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">uint64 ItemUID = Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">GetUID</span>();</span><br><span class="line"><span class="keyword">if</span> (!Items.<span class="built_in">Contains</span>(ItemUID))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">SetContainer</span>(<span class="literal">nullptr</span>);</span><br><span class="line">Items.<span class="built_in">Remove</span>(ItemUID);</span><br><span class="line"><span class="built_in">OnRemoveItem</span>(Item);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每个 <code>Container</code> 需要一个 <code>ContainerOwner</code> 来持有，这里有三种 <code>Container</code>。</p><blockquote><ol><li><code>Container_WorldFlag</code>： 由 <code>CaptureFlagManager(GS)</code> 持有，一场子比赛持有一个；</li><li><code>Container_GoalPoint</code>：由 <code>CaptureFlagManager(GS)</code> 持有，一场子比赛持有一个；</li><li><code>Container_Player</code>：由 <code>CaptureFlagComponent(PS)</code> 持有，每个 <code>Player</code> 持有一个；</li></ol></blockquote><p>提供一个 <code>ICaptureFlagContainerOwnerInterface</code> 用于支持 <code>Create</code>、<code>Destroy</code> 对应的 <code>Container</code> 方法；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ContainerOwner.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UINTERFACE</span>(BlueprintType, Blueprintable)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UCaptureFlagContainerOwnerInterface</span> :</span> <span class="keyword">public</span> UInterface</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ICaptureFlagContainerOwnerInterface</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span> <span class="title">T</span>&gt;</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">typename</span> TEnableIf&lt;TIsDerivedFrom&lt;T, UCaptureFlagContainerBase&gt;::Value, T*&gt;::<span class="function">Type</span></span><br><span class="line"><span class="function"><span class="title">CreateFlagContainer</span><span class="params">(UObject* InOwner)</span> <span class="comment">// 传入 this</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InOwner)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">T* Container = NewObject&lt;T&gt;(InOwner);</span><br><span class="line">Container-&gt;<span class="built_in">Init</span>(InOwner);</span><br><span class="line"><span class="keyword">return</span> Container;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">DestroyFlagContainer</span><span class="params">(UCaptureFlagContainerBase* Container)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Container)) <span class="keyword">return</span>;</span><br><span class="line">Container-&gt;<span class="built_in">Uninit</span>();</span><br><span class="line">Container = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="TransferSystem"><code>TransferSystem</code></h3><pre class="mermaid">classDiagram    direction LR        UCFTransferSystem..>UCFTransferPolicyBase    UCFTransferSystem..UCaptureFlagContainerBase    class UCFTransferSystem {        Policies : FGameplaySubSystemCollection~UCFTransferPolicyBase~        friend UCFTransferPolicyBase        - TransferItem(Item, TargetContainer, TransferReason)    }            UCFTransferPolicyBase..UCaptureFlagContainerBase    class UCFTransferPolicyBase{    # OnInit()    # OnUninit()    # TransferItem(Item, TargetContainer)    # GetTransferReason() : ECFTransferReason    }        UCFTansferPolicy_PickUp--|>UCFTransferPolicyBase    UCFTansferPolicy_Drop--|>UCFTransferPolicyBase    UCFTansferPolicy_Reach--|>UCFTransferPolicyBase    UCFTansferPolicy_Strike--|>UCFTransferPolicyBase</pre><p>首先需要一个 <code>UCFTransferSystem</code> 持有 <code>Polices</code>，管理并维护所有 <code>TransferPolicy</code> 的生命周期，具体可以参考 <a href="https://www.bearchild.top/2025/03/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DGameplaySubSystem%E5%AE%9E%E7%8E%B0/">[UE] GameplaySubSystem 简单实现</a>。</p><p>对于一个具体的 <code>TransferPolicy</code>，通过重载 <code>TransferPolicyBase</code> 的 <code>PURE_VIRTUAL</code> 方法  <code>GetTransferReason</code> ，明确其对应的 <code>TransferReason</code>，在其 <code>OnInit</code>、<code>OnUninit</code>，监听该类型需要的事件，通过对应的 <code>Callback</code> 调用到 <code>Base</code> 提供的 <code>TransferItem</code> 方法，执行 <code>Item</code> 的转移。</p><p>对于一次成功的 <code>Transfer</code>，显然会调用到 <code>SourceContainer</code> 的 <code>Remove</code> ，以及 <code>TargetContainer</code> 的 <code>Add</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TransferManager</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCaptureFlagTransferManager::TransferItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, UCaptureFlagContainerBase* TargetContainer, ECFTransferReason TransferReason)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!Item.<span class="built_in">IsValid</span>() || !<span class="built_in">IsValid</span>(TargetContainer)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> SharedItem = Item.<span class="built_in">Pin</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查是否可以获得</span></span><br><span class="line"><span class="keyword">if</span> (TargetContainer-&gt;<span class="built_in">VerifyCanAddItem</span>() == <span class="literal">false</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="comment">// Source 可以为空</span></span><br><span class="line"><span class="keyword">auto</span> SourceContainer = SharedItem-&gt;<span class="built_in">GetContainer</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (SourceContainer == TargetContainer) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">ECFTransferReason LastTransferReason = Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">GetTransferReason</span>();</span><br><span class="line">Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">SetTransferReason</span>(TransferReason);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 若有 Source，则尝试移除</span></span><br><span class="line"><span class="keyword">if</span> (SourceContainer.<span class="built_in">IsValid</span>() &amp;&amp; SourceContainer-&gt;<span class="built_in">RemoveItem</span>(Item) == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line">Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">SetTransferReason</span>(LastTransferReason);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FCaptureFlagParams Params&#123;&#125;;</span><br><span class="line"><span class="keyword">if</span> (SourceContainer.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line">SourceContainer-&gt;<span class="built_in">CollectParams</span>(Params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 尝试添加</span></span><br><span class="line"><span class="keyword">if</span> (TargetContainer-&gt;<span class="built_in">AddItem</span>(Item, Params) == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line">Item.<span class="built_in">Pin</span>()-&gt;<span class="built_in">SetTransferReason</span>(LastTransferReason);</span><br><span class="line"><span class="comment">// 失败则还给 Source</span></span><br><span class="line"><span class="keyword">if</span> (SourceContainer.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line">SourceContainer-&gt;<span class="built_in">AddItem</span>(Item);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TransferPolicyBase.h</span></span><br><span class="line"><span class="built_in">UCLASS</span>(Abstract)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UCFTransferPolicyBase</span> :</span> <span class="keyword">public</span> UGameModeSubSystemBase</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> ECFTransferReason <span class="title">GetTransferReason</span><span class="params">()</span> <span class="keyword">const</span> <span class="title">PURE_VIRTUAL</span><span class="params">(UCFTransferPolicyBase::GetTransferReason, <span class="keyword">return</span> ECFTransferReason::None;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnInit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnUninit</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">TransferItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, UCaptureFlagContainerBase* TargetContainer)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">TWeakObjectPtr &lt;<span class="class"><span class="keyword">class</span> <span class="title">UCaptureFlagTransferManager</span>&gt;</span> System = <span class="literal">nullptr</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TransferPolicy.cpp</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UCFTransferPolicyBase::OnInit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">System = Cast&lt;UCaptureFlagTransferManager&gt;(<span class="built_in">GetOuter</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UCFTransferPolicyBase::TransferItem</span><span class="params">(TWeakPtr&lt;FCaptureFlagItem&gt; Item, UCaptureFlagContainerBase* TargetContainer)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!System.<span class="built_in">IsValid</span>() || !Item.<span class="built_in">IsValid</span>() || !<span class="built_in">IsValid</span>(TargetContainer)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> bSucceed = <span class="literal">false</span>;</span><br><span class="line">bSucceed = System-&gt;<span class="built_in">TransferItem</span>( Item, TargetContainer, <span class="built_in">GetTransferReason</span>() );</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> bSucceed;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;夺旗模式框架&lt;/h1&gt;
&lt;h2 id=&quot;描述&quot;&gt;描述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;夺旗模式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;场景里会按照一定的规则刷新出不同类型的旗帜（不同类型的旗帜有不同的得分、血量、表现等）；&lt;/p&gt;
&lt;p&gt;玩家会被分成若干个队伍，抢夺刷新出来的</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]CommonParams解决方案</title>
    <link href="https://blog.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]CommonParams%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]CommonParams%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-03-13T16:00:00.000Z</published>
    <updated>2025-06-25T15:11:02.102Z</updated>
    
    <content type="html"><![CDATA[<h1>CommonParams解决方案</h1><p>在实现业务框架的时候，经常需要支持传参的功能（比如 <code>Buff</code> 系统中，<code>AddBuff</code> 的时候，通常需要支持外部传入一个 <code>BuffParams</code> ，在内部解析出各种 <code>Param</code> 使用。）</p><p>这个参数需要支持基本的数据保存、同步等。</p><h2 id="初始的想法">初始的想法</h2><p>一个最简单的想法，是打包一个结构体，支持各种类型的传入，比如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Params</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    uint64 uVar0;</span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    <span class="keyword">int</span> iVar0;</span><br><span class="line">    <span class="built_in">UPROPERTY</span>()</span><br><span class="line">    <span class="keyword">float</span> fVar0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样虽然很简单，同时可以通过反射来同步。</p><p>但是有一个巨大的问题，那就是在解析参数的时候，只知道 <code>Var0</code> 这种没有名字的抽象的概念，使用者需要记住 <code>Var0</code> 对应的是什么数据，很不直观。</p><h2 id="CommonVariantParams">CommonVariantParams</h2><p>维护一个 <code>CommonVariantParams</code>，通过 <code>TMap &lt;FString, FVariant&gt; ValueMap</code> 来保存数据。</p><p>为了方便使用，还可以自定义一些构造函数，比如 <code>std::initializer_list&lt;TPairInitializer&lt;const FString&amp;, FVariant&gt;&gt; ValuePairs</code> ，这样就可以支持 <code>&#123; &#123;Key0, Val0&#125;, &#123;Key1, Val1&#125; &#125;</code> 形式的构造。</p><p>由于 <code>TMap</code> 以及 <code>FVariant</code> 实际上在引擎内部都已经有了合适的重载 <code>operator &lt;&lt; Archive</code>，所以自定义 <code>NetSerialize</code> 也很简单。</p><p>同时，标记 <code>WithNetSerializer</code> 来自定义 <code>NetSerialize</code> 以支持网络同步；标记 <code>WithIdenticalViaEquality</code> ，在进行同步计算 <code>RepLayout</code> 时，通过自定义的 <code>operator ==</code> 来进行 <code>Diff</code>，这样当 <code>CommonVariantParams</code> 作为属性同步时，才能正常判断其发生了变化以进行属性同步。</p><h3 id="使用方法">使用方法</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建Params：</span></span><br><span class="line">FCommonVariantParams Params = &#123; &#123;<span class="string">&quot;ParamA&quot;</span>, (<span class="keyword">float</span>)A&#125;, &#123;<span class="string">&quot;ParamsB&quot;</span>, (<span class="keyword">int</span>)B &#125;, &#123;<span class="string">&quot;ParamsC&quot;</span>, (FString)C &#125; <span class="comment">/* ... */</span> &#125;);</span><br><span class="line"><span class="comment">// 解析Params:</span></span><br><span class="line"><span class="keyword">float</span> A = BuffParams.GetValue&lt;<span class="keyword">float</span>&gt;(<span class="string">&quot;ParamA&quot;</span>);</span><br><span class="line"><span class="keyword">int</span> B = BuffParams.GetValue&lt;<span class="keyword">int</span>&gt;(<span class="string">&quot;ParamB&quot;</span>);</span><br><span class="line">FString C = BuffParams.GetValue&lt;FString&gt;(<span class="string">&quot;ParamC&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="具体实现">具体实现</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommonVariantParams.h</span></span><br><span class="line"><span class="built_in">USTRUCT</span>(BlueprintType)</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FCommonVariantParams</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="built_in">FCommonVariantParams</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="built_in">FCommonVariantParams</span>(<span class="keyword">const</span> FString&amp; Key, FVariant Value);</span><br><span class="line"><span class="built_in">FCommonVariantParams</span>(std::initializer_list&lt;TPairInitializer&lt;<span class="keyword">const</span> FString&amp;, FVariant&gt;&gt; ValuePairs);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, FVariant Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">Contains</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsEmpty</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="function">FVariant <span class="title">GetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> ValueType&gt; </span></span><br><span class="line"><span class="function">ValueType <span class="title">GetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, ValueType Default = &#123;&#125;)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!ValueMap.<span class="built_in">Contains</span>(FieldName)) <span class="keyword">return</span> Default;</span><br><span class="line"><span class="keyword">if</span> (TVariantTraits&lt;ValueType&gt;::<span class="built_in">GetType</span>() != ValueMap[FieldName].<span class="built_in">GetType</span>()) <span class="keyword">return</span> Default;</span><br><span class="line"><span class="keyword">return</span> ValueMap[FieldName].GetValue&lt;ValueType&gt;();</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">template</span> &lt;<span class="keyword">typename</span> ValueType&gt; </span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">TryGetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, ValueType&amp; OutValue)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!ValueMap.<span class="built_in">Contains</span>(FieldName)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (TVariantTraits&lt;ValueType&gt;::<span class="built_in">GetType</span>() != ValueMap[FieldName].<span class="built_in">GetType</span>()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">OutValue = ValueMap[FieldName].GetValue&lt;ValueType&gt;();</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">const</span> TMap&lt;FString, FVariant&gt;&amp; <span class="title">GetValueMap</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span>;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>==(<span class="keyword">const</span> FCommonVariantParams&amp; Other) <span class="keyword">const</span>;</span><br><span class="line"><span class="keyword">bool</span> <span class="keyword">operator</span>!=(<span class="keyword">const</span> FCommonVariantParams&amp; Other) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> !(*<span class="keyword">this</span> == Other);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FCommonVariantParams <span class="keyword">operator</span>+(<span class="keyword">const</span> FCommonVariantParams&amp; OtherParams);</span><br><span class="line">FCommonVariantParams&amp; <span class="keyword">operator</span>=(<span class="keyword">const</span> FCommonVariantParams&amp; OtherParams)</span><br><span class="line">&#123;</span><br><span class="line">ValueMap = OtherParams.<span class="built_in">GetValueMap</span>();</span><br><span class="line"><span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Merge</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; OtherParams)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Clear</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">begin</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> ValueMap.<span class="built_in">begin</span>(); &#125;</span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">begin</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> ValueMap.<span class="built_in">begin</span>(); &#125;</span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">end</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> ValueMap.<span class="built_in">end</span>(); &#125;</span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">end</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> ValueMap.<span class="built_in">end</span>(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">TMap&lt;FString, FVariant&gt; ValueMap;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TStructOpsTypeTraits</span>&lt;</span>FCommonVariantParams&gt; : TStructOpsTypeTraitsBase2&lt;FCommonVariantParams&gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">enum</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">WithNetSerializer = <span class="literal">true</span>,</span><br><span class="line">        WithIdenticalViaEquality = <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommonVariantParams.cpp</span></span><br><span class="line">FCommonVariantParams::<span class="built_in">FCommonVariantParams</span>(<span class="keyword">const</span> FString&amp; Key, FVariant Value)</span><br><span class="line">&#123;</span><br><span class="line">ValueMap.<span class="built_in">Add</span>(Key, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FCommonVariantParams::<span class="built_in">FCommonVariantParams</span>(std::initializer_list&lt;TPairInitializer&lt;<span class="keyword">const</span> FString&amp;, FVariant&gt;&gt; ValuePairs)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; Pair : ValuePairs)</span><br><span class="line">&#123;</span><br><span class="line">ValueMap.<span class="built_in">Add</span>(Pair.Key, Pair.Value);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, FVariant Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">ValueMap.<span class="built_in">Add</span>(FieldName, <span class="built_in">FVariant</span>(Value));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FCommonVariantParams::Contains</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> ValueMap.<span class="built_in">Contains</span>(FieldName);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FCommonVariantParams::IsEmpty</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> ValueMap.<span class="built_in">IsEmpty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FVariant <span class="title">FCommonVariantParams::GetValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> Value = ValueMap.<span class="built_in">Find</span>(FieldName))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> *Value;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> TMap&lt;FString, FVariant&gt;&amp; <span class="title">FCommonVariantParams::GetValueMap</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> ValueMap;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FCommonVariantParams::NetSerialize</span><span class="params">(FArchive&amp; Ar, class UPackageMap* Map, <span class="keyword">bool</span>&amp; bOutSuccess)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">bOutSuccess = <span class="literal">true</span>;</span><br><span class="line">Ar &lt;&lt; ValueMap;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> FCommonVariantParams::<span class="keyword">operator</span>==(<span class="keyword">const</span> FCommonVariantParams&amp; Other) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line">TArray&lt;FString&gt; Keys, OtherKeys;</span><br><span class="line">ValueMap.<span class="built_in">GenerateKeyArray</span>(Keys);</span><br><span class="line">Other.ValueMap.<span class="built_in">GenerateKeyArray</span>(OtherKeys);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (Keys != OtherKeys) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> Key : Keys)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (ValueMap[Key] != Other.ValueMap[Key])</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FCommonVariantParams FCommonVariantParams::<span class="keyword">operator</span>+(<span class="keyword">const</span> FCommonVariantParams&amp; OtherParams)</span><br><span class="line">&#123;</span><br><span class="line">FCommonVariantParams CombinedParams = *<span class="keyword">this</span>;</span><br><span class="line">CombinedParams.<span class="built_in">Merge</span>(OtherParams);</span><br><span class="line"><span class="keyword">return</span> CombinedParams;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::Merge</span><span class="params">(<span class="keyword">const</span> FCommonVariantParams&amp; OtherParams)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; [Key, Value] : OtherParams.<span class="built_in">GetValueMap</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 覆盖</span></span><br><span class="line"><span class="built_in">SetValue</span>(Key, Value);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::Clear</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">ValueMap.<span class="built_in">Empty</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Lua">Lua</h3><p>对于这个 <code>CommonVariantParams</code>，还可以扩展一些方法，让这些参数可以通过 <code>Unlua</code> 在 <code>Lua</code> 脚本中设置与访问。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetIntValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, int32 Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetUInt64Value</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, uint64 Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetFloatValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">float</span> Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetBoolValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">bool</span> Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetStringValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FString&amp; Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetTransformValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FTransform&amp; Value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetVectorValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FVector&amp; Value)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">int32 <span class="title">GetIntValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, int32 Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">uint64 <span class="title">GetUInt64Value</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, uint64 Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">GetFloatValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">float</span> Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">GetBoolValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">bool</span> Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">FString <span class="title">GetStringValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FString&amp; Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">FTransform <span class="title">GetTransformValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FTransform&amp; Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="function">FVector <span class="title">GetVectorValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FVector&amp; Default = &#123;&#125;)</span> <span class="keyword">const</span></span>;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetIntValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, int32 Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetUInt64Value</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, uint64 Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetFloatValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">float</span> Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetBoolValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">bool</span> Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetStringValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FString&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetTransformValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FTransform&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FCommonVariantParams::SetVectorValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FVector&amp; Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">SetValue</span>(FieldName, Value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">int32 <span class="title">FCommonVariantParams::GetIntValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, int32 Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;int32&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">uint64 <span class="title">FCommonVariantParams::GetUInt64Value</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, uint64 Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;uint64&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">FCommonVariantParams::GetFloatValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">float</span> Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;<span class="keyword">float</span>&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">FCommonVariantParams::GetBoolValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">bool</span> Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;<span class="keyword">bool</span>&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">FCommonVariantParams::GetStringValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FString&amp; Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;FString&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function">FTransform <span class="title">FCommonVariantParams::GetTransformValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FTransform&amp; Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;FTransform&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FVector <span class="title">FCommonVariantParams::GetVectorValue</span><span class="params">(<span class="keyword">const</span> FString&amp; FieldName, <span class="keyword">const</span> FVector&amp; Default <span class="comment">/*= &#123;&#125;*/</span>)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> GetValue&lt;FVector&gt;(FieldName, Default);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>同时，可以添加自定义的 <code>Lua</code> 方法，让其在 <code>Lua</code> 中可以支持 <code>ToTable</code> 等，参考 <a href="https://www.bearchild.top/2024/05/27/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DUnLua&amp;C++%E4%BA%A4%E4%BA%92/">UnLua &amp; C++ 交互</a> ；</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> FCommonVariantParams* <span class="title">GetCommonVariantParams</span><span class="params">(lua_State* L, int32 Index)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> UUnLuaFunctionLibrary::GetValue&lt;FCommonVariantParams&gt;(L, Index);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> int32 <span class="title">FCommonVariantParams_Index</span><span class="params">(lua_State* L)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!UUnLuaFunctionLibrary::<span class="built_in">CheckParamsCount</span>(L, <span class="number">2</span>))</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">luaL_error</span>(L, <span class="string">&quot;invalid parameters&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> CommonVariantParams = <span class="built_in">GetCommonVariantParams</span>(L, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (CommonVariantParams == <span class="literal">nullptr</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">luaL_error</span>(L, <span class="string">&quot;invalid struct type&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span>* KeyStringPtr = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">lua_isstring</span>(L, <span class="number">2</span>))</span><br><span class="line">    &#123;</span><br><span class="line">KeyStringPtr = <span class="built_in">lua_tostring</span>(L, <span class="number">2</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (KeyStringPtr == <span class="literal">nullptr</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">lua_pushnil</span>(L);</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Key = <span class="built_in">FString</span>(<span class="built_in">UTF8_TO_TCHAR</span>(KeyStringPtr));</span><br><span class="line"><span class="keyword">if</span> (CommonVariantParams-&gt;<span class="built_in">Contains</span>(Key))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> Value = CommonVariantParams-&gt;<span class="built_in">GetValue</span>(Key);</span><br><span class="line">UUnLuaFunctionLibrary::<span class="built_in">PushValue</span>(L, Value);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">lua_getmetatable</span>(L, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">lua_compare</span>(L, <span class="number">1</span>, <span class="number">-1</span>, LUA_OPEQ))</span><br><span class="line"><span class="built_in">lua_pushnil</span>(L);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">lua_pushvalue</span>(L, <span class="number">2</span>);</span><br><span class="line"><span class="built_in">lua_rawget</span>(L, <span class="number">-2</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">lua_remove</span>(L, <span class="number">-2</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">FCommonVariantParams_ToTable</span><span class="params">(lua_State* L)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!UUnLuaFunctionLibrary::<span class="built_in">CheckParamsCount</span>(L, <span class="number">1</span>))</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">luaL_error</span>(L, <span class="string">&quot;invalid parameters&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> CommonVariantParams = <span class="built_in">GetCommonVariantParams</span>(L, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (CommonVariantParams == <span class="literal">nullptr</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">luaL_error</span>(L, <span class="string">&quot;invalid struct type&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">lua_newtable</span>(L);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; [Key, Value] : *CommonVariantParams)</span><br><span class="line">&#123;</span><br><span class="line">UnLua::<span class="built_in">Push</span>(L, Key);</span><br><span class="line">UUnLuaFunctionLibrary::<span class="built_in">PushValue</span>(L, Value);</span><br><span class="line"><span class="built_in">lua_rawset</span>(L, <span class="number">-3</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> luaL_Reg FCommonVariantParamsLib[] =</span><br><span class="line">&#123;</span><br><span class="line">    &#123;<span class="string">&quot;ToTable&quot;</span>, FCommonVariantParams_ToTable&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;__index&quot;</span>, FCommonVariantParams_Index&#125;,</span><br><span class="line">    &#123;<span class="literal">nullptr</span>, <span class="literal">nullptr</span>&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>最后进行导出：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导出：</span></span><br><span class="line"><span class="comment">// define 在 UnluaEx.h 中</span></span><br><span class="line"><span class="built_in">BEGIN_EXPORT_REFLECTED_CLASS</span>(FCommonVariantParams)</span><br><span class="line"><span class="built_in">ADD_LIB</span>(FCommonVariantParamsLib)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(Contains)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(IsEmpty)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(ToString)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(SetIntValue)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(SetUInt64Value)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(SetFloatValue)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(SetBoolValue)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(SetStringValue)</span><br><span class="line">    <span class="built_in">ADD_FUNCTION</span>(SetTransformValue)</span><br><span class="line"></span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetIntValue)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetUInt64Value)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetFloatValue)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetBoolValue)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetStringValue)</span><br><span class="line"><span class="built_in">ADD_FUNCTION</span>(GetTransformValue)</span><br><span class="line"><span class="built_in">END_EXPORT_CLASS</span>()</span><br><span class="line"><span class="built_in">IMPLEMENT_EXPORTED_CLASS</span>(FCommonVariantParams)</span><br></pre></td></tr></table></figure><h3 id="具体业务">具体业务</h3><p>特别地，具体业务使用的时候，可以继承一个自己的 <code>Params</code> 来使用，以 <code>FGameplayBuffParams</code> 为例：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">USTRUCT</span>()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FGameplayBuffParams</span> :</span> <span class="keyword">public</span> FCommonVariantParams</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"><span class="keyword">using</span> FCommonVariantParams::FCommonVariantParams;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;CommonParams解决方案&lt;/h1&gt;
&lt;p&gt;在实现业务框架的时候，经常需要支持传参的功能（比如 &lt;code&gt;Buff&lt;/code&gt; 系统中，&lt;code&gt;AddBuff&lt;/code&gt; 的时候，通常需要支持外部传入一个 &lt;code&gt;BuffParams&lt;/code&gt; ，</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
  <entry>
    <title>[UE]TeamSystem框架</title>
    <link href="https://blog.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]TeamSystem%E6%A1%86%E6%9E%B6/"/>
    <id>https://blog.bearchild.top/2024/03/14/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/[UE]TeamSystem%E6%A1%86%E6%9E%B6/</id>
    <published>2024-03-13T16:00:00.000Z</published>
    <updated>2025-06-25T15:22:09.500Z</updated>
    
    <content type="html"><![CDATA[<h1>TeamSystem框架</h1><h2 id="基本架构">基本架构</h2><p>支持基本的加入、退出 队伍，以及维护队伍的基本数据（<code>TeamID</code>、 <code>MemberUIDs</code>）</p><p>提供继承扩展，维护自定义业务逻辑与队伍数据；</p><p>对于挂载组件：由 <code>Manager</code> 级别的  <code>TeamManager</code> ，与 <code>Player</code> 身上的 <code>TeamComponent</code> 组成；</p><p>以及队伍数据同步，自定义不同方式的数据同步；</p><pre class="mermaid">classDiagram    UTeamManager..>UTeamBase    UTeamManager..>UTeamSyncDataBase    class UTeamManager {        SubSystemCollections : FGameplaySubSystemCollection~UTeamCommonSubSystemBase~        Teams : TMap~uint64|UTeamBase*~         TeamSyncDatas : TArray~UTeamSyncDataBase*~                CreateTeam(TeamID)        DestoryTeam(TeamID)        GetTeam(TeamID)                JoinTeam(TeamID, PlayerUID)        LeaveTeam(PlayerUID)    }        UTeamComponent..>UTeamSyncDataBaseclass UTeamComponent {        TeamID : uint64        SubSystemCollection : FGameplaySubSystemCollection~UTeamPlayerSubSystemBase~        UpdateTeamID()        CreateSyncData()        DestroySyncData()    }           class UTeamBase {    TeamID : uint64    MemberUIDs : TArray~uint64~    OnDataDirty        Init(TeamID)    Uninit()    AddMember(PlayerUID)    RemoveMember(PlayerUID)        MarkDirty()    }        class UTeamSyncDataBase {    BoundTeam : TWeakObjectPtr~UTeamBase~    TeamID : uint64    MemberUIDs : TArray~uint64~        BindTeam(InTeam)    Unbind()        OnDataDirty()    CollectSyncData(InTeam)    }</pre><h2 id="TeamManager">TeamManager</h2><blockquote><p>持有 <code>Teams</code> 保存队伍的原始指针，提供 <code>CreateTeam</code>、<code>DestroyTeam</code> 方法；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamManager.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Team</span></span><br><span class="line"></span><br><span class="line"><span class="function">UTeamBase* <span class="title">UTeamManager::GetTeam</span><span class="params">(uint64 TeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Teams.<span class="built_in">FindRef</span>(TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> TMap&lt;uint64, UTeamBase*&gt;&amp; <span class="title">UTeamManager::GetTeams</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> Teams;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ----------</span></span><br><span class="line"></span><br><span class="line"><span class="function">uint64 <span class="title">UTeamManager::GetHashValue</span><span class="params">(uint64 Value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">uint64 HashValue = <span class="built_in">HashCombine</span>( <span class="built_in">GetHashCode</span>(), <span class="built_in">GetTypeHash</span>(Value)));</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::GetHashValue, HashValue=%llu, [Value=%llu | HashCode=%llu]&quot;</span>), HashValue, Value, <span class="built_in">GetHashCode</span>());</span><br><span class="line"><span class="keyword">return</span> HashValue;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UTeamBase* <span class="title">UTeamManager::GetOrCreateTeam</span><span class="params">(uint64 TeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> Team = <span class="built_in">GetTeam</span>(TeamID); !<span class="built_in">IsValid</span>(Team))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">CreateTeam</span>(TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">GetTeam</span>(TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">UTeamBase* <span class="title">UTeamManager::CreateTeam</span><span class="params">(uint64 TeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">GetTeam</span>(TeamID) != <span class="literal">nullptr</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogW</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, Team is existed! TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, Begin, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line"></span><br><span class="line"><span class="comment">// CreateTeam</span></span><br><span class="line"><span class="keyword">auto</span> TeamClass = <span class="built_in">GetTeamClass</span>();</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(TeamClass) || !TeamClass-&gt;<span class="built_in">IsChildOf</span>(UTeamBase::<span class="built_in">StaticClass</span>()))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogE</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, TeamClass is invalid! TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">UTeamBase* NewTeam = NewObject&lt;UTeamBase&gt;(<span class="keyword">this</span>, <span class="built_in">GetTeamClass</span>());</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(NewTeam))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogE</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, NewTeam is invalid! TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, CreateTeam, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line">NewTeam-&gt;<span class="built_in">Init</span>(TeamID);</span><br><span class="line">Teams.<span class="built_in">Add</span>( TeamID, NewTeam );</span><br><span class="line"></span><br><span class="line"><span class="comment">// SyncData</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> NewTeamSyncData = UTeamUtils::<span class="built_in">CreateTeamSyncData</span>( <span class="keyword">this</span>, NewTeam, <span class="built_in">GetTeamSyncDataClass</span>() );</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(NewTeamSyncData))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::CreateTeam, CreateTeamSyncData, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line">TeamSyncDatas.<span class="built_in">Add</span>( NewTeamSyncData );</span><br><span class="line"><span class="built_in">MARK_PROPERTY_DIRTY_FROM_NAME</span>(UTeamManager, TeamSyncDatas, <span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> NewTeam;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::DestoryTeam</span><span class="params">(uint64 TeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">auto</span> Team = <span class="built_in">GetTeam</span>(TeamID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Team)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::DestoryTeam, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line"></span><br><span class="line"><span class="comment">// SyncData</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> Iter = TeamSyncDatas.<span class="built_in">CreateIterator</span>(); Iter; ++Iter)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">auto</span> SyncData = *Iter;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SyncData) || SyncData-&gt;<span class="built_in">GetTeamID</span>() == TeamID)</span><br><span class="line">&#123;</span><br><span class="line">UTeamUtils::<span class="built_in">DestoryTeamSyncData</span>(SyncData);</span><br><span class="line">Iter.<span class="built_in">RemoveCurrent</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">MARK_PROPERTY_DIRTY_FROM_NAME</span>(UTeamManager, TeamSyncDatas, <span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">Team-&gt;<span class="built_in">Uninit</span>();</span><br><span class="line">Teams.<span class="built_in">Remove</span>(TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Team</span></span><br></pre></td></tr></table></figure><blockquote><p>提供 <code>Player</code> 的 <code>JoinTeam</code>、<code>LeaveTeam</code>；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamManager.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Join/Leave</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::JoinTeam</span><span class="params">(uint64 TeamID, uint64 PlayerUID, <span class="keyword">bool</span> bNeedHash)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (TeamID == <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, TeamID is 0! PlayerUID=%llu, TeamID=%llu, bNeedHash=%d&quot;</span>), PlayerUID, TeamID, bNeedHash);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> TeamComponent = UTeamUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), PlayerUID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(TeamComponent)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, PlayerUID=%llu, TeamID=%llu, bNeedHash=%d&quot;</span>), PlayerUID, TeamID, bNeedHash);</span><br><span class="line"><span class="keyword">if</span> (bNeedHash)</span><br><span class="line">&#123;</span><br><span class="line">TeamID = <span class="built_in">GetHashValue</span>(TeamID);</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, ProcessID, PlayerUID=%llu, HashTeamID=%llu&quot;</span>), PlayerUID, TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果玩家已经存在于某个队伍则无法再次加入</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> ExistTeam = <span class="built_in">GetTeam</span>(TeamComponent-&gt;<span class="built_in">GetTeamID</span>()); <span class="built_in">IsValid</span>(ExistTeam))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogE</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, Player already exist Team! PlayerUID=%llu, ExistTeamID=%llu&quot;</span>), PlayerUID, ExistTeam-&gt;<span class="built_in">GetTeamID</span>());</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Team = <span class="built_in">GetOrCreateTeam</span>(TeamID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Team))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogE</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, Team is invalid! PlayerUID=%llu, TeamID=%llu&quot;</span>), PlayerUID, TeamID);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::JoinTeam, Begin, PlayerUID=%llu, TeamID=%llu&quot;</span>), PlayerUID, TeamID);</span><br><span class="line">TeamComponent-&gt;<span class="built_in">UpdateTeamID</span>(Team-&gt;<span class="built_in">GetTeamID</span>());</span><br><span class="line">TeamComponent-&gt;<span class="built_in">CreateSyncData</span>(Team);</span><br><span class="line"></span><br><span class="line">Team-&gt;<span class="built_in">AddMember</span>(PlayerUID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::LeaveTeam</span><span class="params">(uint64 PlayerUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> TeamComponent = UTeamUtils::<span class="built_in">GetComponentByUID</span>(<span class="built_in">GetWorld</span>(), PlayerUID);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(TeamComponent)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> Team = <span class="built_in">GetTeam</span>(TeamComponent-&gt;<span class="built_in">GetTeamID</span>());</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Team))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">LogE</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::LeaveTeam, Player isn&#x27;t in any team! PlayerUID=%llu&quot;</span>), PlayerUID);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">uint64 TeamID = Team-&gt;<span class="built_in">GetTeamID</span>();</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::LeaveTeam, Begin, PlayerUID=%llu, TeamID=%llu&quot;</span>), PlayerUID, TeamID);</span><br><span class="line">Team-&gt;<span class="built_in">RemoveMember</span>(PlayerUID);</span><br><span class="line">TeamComponent-&gt;<span class="built_in">UpdateTeamID</span>(<span class="number">0</span>);</span><br><span class="line">TeamComponent-&gt;<span class="built_in">DestroySyncData</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (Team-&gt;<span class="built_in">GetMemberUIDs</span>().<span class="built_in">Num</span>() == <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">DestoryTeam</span>( TeamID );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Join/Leave</span></span><br></pre></td></tr></table></figure><blockquote><p>持有 <code>TeamCommonSubSystem</code>，支持业务扩展拆分为若干个子系统，参考：<a href="https://www.bearchild.top/2025/03/15/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/%5BUE%5DGameplaySubSystem%E5%AE%9E%E7%8E%B0/">[UE]GameplaySubSystem 实现</a></p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamManager.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::RegisterSubSystems</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::RegisterSubSystems&quot;</span>));</span><br><span class="line"></span><br><span class="line">SubSystemCollection.<span class="built_in">Init</span>( <span class="keyword">this</span>, <span class="built_in">GetSubSystemClasses</span>() );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::UnregisterSubSystems</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::UnregisterSubSystems&quot;</span>));</span><br><span class="line"></span><br><span class="line">SubSystemCollection.<span class="built_in">Uninit</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>同步 <code>TeamSyncDatas</code>，其中类型为 <code>TeamSyncData_Common</code>，记录所有人都需要关心的队伍同步数据；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamManager.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Replicate</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamManager::OnRep_TeamSyncDatas</span><span class="params">(<span class="keyword">const</span> TArray&lt;UTeamSyncDataBase*&gt;&amp; LastTeamSyncDatas)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamManager::OnRep_TeamSyncDatas, TeamSyncDatas=%s, LastTeamSyncDatas=%s&quot;</span>), *UStringUtils::<span class="built_in">ToString</span>(TeamSyncDatas), *UStringUtils::<span class="built_in">ToString</span>(LastTeamSyncDatas));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UTeamManager::ReplicateSubobjects</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> WroteSomething = Super::<span class="built_in">ReplicateSubobjects</span>(Channel, Bunch, RepFlags);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(Channel) &amp;&amp; RepFlags-&gt;bNetInitial == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; SyncData : TeamSyncDatas)</span><br><span class="line">&#123;</span><br><span class="line">WroteSomething |= Channel-&gt;<span class="built_in">ReplicateSubobject</span>( SyncData, *Bunch, *RepFlags);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Replicate</span></span><br></pre></td></tr></table></figure><h2 id="TeamComponent">TeamComponent</h2><blockquote><p>维护 <code>TeamID</code>；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamComponent.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region TeamID</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::UpdateTeamID</span><span class="params">(uint64 InTeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (InTeamID == TeamID) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::UpdateTeamID, UID=%llu, TeamID=%llu&quot;</span>), <span class="built_in">GetUID</span>(), InTeamID);</span><br><span class="line"><span class="built_in">SetTeamID</span>(InTeamID);</span><br><span class="line"><span class="built_in">OnRep_TeamID</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsDS</span>(<span class="keyword">this</span>))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">S2C_NotifyTeamIDChanged</span>(InTeamID);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::S2C_NotifyTeamIDChanged_Implementation</span><span class="params">(uint64 InTeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsClient</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">if</span> (InTeamID == TeamID) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::S2C_NotifyTeamIDChanged_Implementation, UID=%llu, TeamID=%llu&quot;</span>), <span class="built_in">GetUID</span>(), TeamID);</span><br><span class="line"><span class="built_in">SetTeamID</span>(InTeamID);</span><br><span class="line"><span class="built_in">OnRep_TeamID</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::OnRep_TeamID</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::OnRep_TeamID, UID=%llu, TeamID=%llu&quot;</span>), <span class="built_in">GetUID</span>(), TeamID);</span><br><span class="line">OnPlayerTeamIDChanged.<span class="built_in">Broadcast</span>(TeamID);</span><br><span class="line">UTeamDelegate::<span class="built_in">GetDelegate</span>()-&gt;OnPlayerTeamIDChanged.<span class="built_in">Broadcast</span>(<span class="built_in">GetUID</span>(), TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion TeamID</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>持有 <code>TeamPlayerSubSystem</code>，支持业务扩展拆分为若干个子系统；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamComponent.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region SubSystem</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::RegisterSubSystems</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::RegisterSubSystems, UID=%llu&quot;</span>), <span class="built_in">GetUID</span>());</span><br><span class="line"></span><br><span class="line">SubSystemCollection.<span class="built_in">Init</span>( <span class="keyword">this</span>, <span class="built_in">GetSubSystemClasses</span>() );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::UnregisterSubSystems</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsStandaloneOrDS</span>(<span class="keyword">this</span>)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::UnregisterSubSystems, UID=%llu&quot;</span>), <span class="built_in">GetUID</span>());</span><br><span class="line"></span><br><span class="line">SubSystemCollection.<span class="built_in">Uninit</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="function">UTeamPlayerSubSystemBase* <span class="title">UTeamComponent::GetSubSystem</span><span class="params">(UClass* InClass)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InClass)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">return</span> SubSystemCollection.<span class="built_in">GetSubSystem</span>(InClass);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion SubSystem</span></span><br></pre></td></tr></table></figure><blockquote><p>同步 <code>TeamSyncData</code>，其中类型为 <code>TeamSyncData_OwnerOnly</code>，记录仅自己队伍成员关心的队伍同步数据；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ComponentBase.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UComponentBase::ReplicateSubobjects</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> WroteSomething = Super::<span class="built_in">ReplicateSubobjects</span>(Channel, Bunch, RepFlags);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(Channel) &amp;&amp; <span class="built_in">IsValid</span>(Channel-&gt;Connection))</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (Channel-&gt;Connection-&gt;PlayerController == <span class="built_in">GetPlayerController</span>())</span><br><span class="line">&#123;</span><br><span class="line">WroteSomething |= <span class="built_in">ReplicateSubobjects_OwnerOnly</span>(Channel, Bunch, RepFlags);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamComponent.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region SyncData</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">UTeamComponent::ReplicateSubobjects_OwnerOnly</span><span class="params">(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">bool</span> WroteSomething = Super::<span class="built_in">ReplicateSubobjects_OwnerOnly</span>(Channel, Bunch, RepFlags);</span><br><span class="line"></span><br><span class="line">WroteSomething |= SubSystemCollection.<span class="built_in">ReplicateSubSystems</span>(Channel, Bunch, RepFlags);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(Channel) &amp;&amp; RepFlags-&gt;bNetInitial == <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line">WroteSomething |= Channel-&gt;<span class="built_in">ReplicateSubobject</span>( SyncData, *Bunch, *RepFlags);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> WroteSomething;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::CreateSyncData</span><span class="params">(UTeamBase* InTeam)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InTeam)) <span class="keyword">return</span>;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::CreateSyncData, UID=%llu, InTeamID=%llu, TeamID=%llu&quot;</span>), <span class="built_in">GetUID</span>(), InTeam-&gt;<span class="built_in">GetTeamID</span>(), TeamID);</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> NewSyncData = UTeamUtils::<span class="built_in">CreateTeamSyncData</span>( <span class="keyword">this</span>, InTeam, <span class="built_in">GetSyncDataClass</span>() );</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">IsValid</span>(NewSyncData))</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">SetSyncData</span>(NewSyncData);</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::CreateSyncData, UID=%llu, SyncData=%s&quot;</span>), <span class="built_in">GetUID</span>(), *SyncData-&gt;<span class="built_in">ToString</span>());</span><br><span class="line"><span class="built_in">OnRep_SyncData</span>();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::DestroySyncData</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::DestroySyncData, UID=%llu, TeamID=%llu&quot;</span>), <span class="built_in">GetUID</span>(), TeamID);</span><br><span class="line">UTeamUtils::<span class="built_in">DestoryTeamSyncData</span>(SyncData);</span><br><span class="line"><span class="built_in">SetSyncData</span>( <span class="literal">nullptr</span> );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamComponent::OnRep_SyncData</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamComponent::OnRep_SyncData, UID=%llu, SyncData=%s&quot;</span>), <span class="built_in">GetUID</span>(), *UStringUtils::<span class="built_in">ToString</span>(SyncData));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Team">Team</h2><ol><li>维护队伍信息： <code>TeamID</code>、<code>MemberUIDs</code>，以及可能继承的属性；</li><li>当属性变化时，进行 <code>MarkDirty</code> 通知 <code>OnDataDirty</code>；</li></ol><blockquote><p><strong>完整实现</strong></p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamMacro.h</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Property</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY(Visibility, Type, Name) \</span></span><br><span class="line"><span class="meta">Visibility: Type Name&#123;&#125;; \</span></span><br><span class="line"><span class="meta">TEAM_GENERATE_PROPERTY_FUNCTIONS_INTERNAL(Name)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ----</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY_MARKDIRTY(Visibility, Type, Name) \</span></span><br><span class="line"><span class="meta">Visibility: Type Name&#123;&#125;; \</span></span><br><span class="line"><span class="meta">TEAM_GENERATE_PROPERTY_MARKDIRTY_FUNCTIONS(Name)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Property</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Function</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY_FUNCTIONS(Name) \</span></span><br><span class="line"><span class="meta">TEAM_EXPAND(TEAM_GENERATE_PROPERTY_FUNCTIONS_INTERNAL(decltype(Name), Name))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY_FUNCTIONS_INTERNAL(Type, Name) \</span></span><br><span class="line"><span class="meta">public: const Type&amp; Get##Name() const &#123; return Name; &#125; \</span></span><br><span class="line"><span class="meta">public: void Set##Name(const Type&amp; Value) &#123; \</span></span><br><span class="line"><span class="meta">Name = Value; \</span></span><br><span class="line"><span class="meta">&#125;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// -----</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY_MARKDIRTY_FUNCTIONS(Name) \</span></span><br><span class="line"><span class="meta">TEAM_EXPAND(TEAM_GENERATE_PROPERTY_MARKDIRTY_FUNCTIONS_INTERNAL(decltype(Name), Name))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEAM_GENERATE_PROPERTY_MARKDIRTY_FUNCTIONS_INTERNAL(Type, Name) \</span></span><br><span class="line"><span class="meta">public: const Type&amp; Get##Name() const &#123; return Name; &#125; \</span></span><br><span class="line"><span class="meta">public: void Set##Name(const Type&amp; Value) &#123; \</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">if</span> (Name == Value) return; \</span></span><br><span class="line"><span class="meta">Name = Value; \</span></span><br><span class="line"><span class="meta">this-&gt;MarkDirty(); \</span></span><br><span class="line"><span class="meta">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Function</span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Team.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>(Blueprintable, BlueprintType)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UTeamBase</span> :</span> <span class="keyword">public</span> UObject</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Init</span><span class="params">(uint64 InTeamID)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Uninit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Dirty</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">DECLARE_MULTICAST_DELEGATE</span>(FDataDirty)</span><br><span class="line">FDataDirty OnDataDirty;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">MarkDirty</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Dirty</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Member</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">AddMember</span><span class="params">(uint64 MemberUID)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">RemoveMember</span><span class="params">(uint64 MemberUID)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Member</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="built_in">TEAM_GENERATE_PROPERTY_MARKDIRTY</span>(<span class="keyword">protected</span>, uint64, TeamID);</span><br><span class="line"><span class="built_in">TEAM_GENERATE_PROPERTY_MARKDIRTY</span>(<span class="keyword">protected</span>, TArray&lt;uint64&gt;, MemberUIDs);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Team.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamBase::Init</span><span class="params">(uint64 InTeamID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">TeamID = InTeamID;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamBase::Init, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line">MemberUIDs.<span class="built_in">Empty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamBase::Uninit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamBase::Uninit, TeamID=%llu&quot;</span>), TeamID);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Dirty</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamBase::MarkDirty</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">OnDataDirty.<span class="built_in">Broadcast</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Dirty</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Member</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamBase::AddMember</span><span class="params">(uint64 MemberUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (MemberUIDs.<span class="built_in">Contains</span>( MemberUID )) <span class="keyword">return</span>;</span><br><span class="line">MemberUIDs.<span class="built_in">Add</span>( MemberUID );</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamBase::AddMember, TeamID=%llu, MemberUID=%llu, MemberUIDs=%s&quot;</span>), TeamID, MemberUID, *UStringUtils::<span class="built_in">ToString</span>(MemberUIDs));</span><br><span class="line"><span class="built_in">MarkDirty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamBase::RemoveMember</span><span class="params">(uint64 MemberUID)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!MemberUIDs.<span class="built_in">Contains</span>( MemberUID )) <span class="keyword">return</span>;</span><br><span class="line">MemberUIDs.<span class="built_in">Remove</span>( MemberUID ); </span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamBase::RemoveMember, TeamID=%llu, MemberUID=%llu, MemberUIDs=%s&quot;</span>), TeamID, MemberUID, *UStringUtils::<span class="built_in">ToString</span>(MemberUIDs));</span><br><span class="line"><span class="built_in">MarkDirty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Member</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="TeamSyncData">TeamSyncData</h2><blockquote><p>与对应的 <code>Team</code> 进行绑定；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamSyncData.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Bind</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::BindTeam</span><span class="params">(UTeamBase* InTeam)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InTeam)) <span class="keyword">return</span>;</span><br><span class="line">BoundTeam = InTeam;</span><br><span class="line">InTeam-&gt;OnDataDirty.<span class="built_in">AddUObject</span>(<span class="keyword">this</span>, &amp;ThisClass::OnDataDirty);</span><br><span class="line"><span class="built_in">CollectSyncData</span>(InTeam);</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::BindTeam, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::Unbind</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!BoundTeam.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">BoundTeam-&gt;OnDataDirty.<span class="built_in">RemoveAll</span>(<span class="keyword">this</span>);</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::Unbind, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Bind</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>当 <code>Team</code> 进行 <code>OnDataDirty</code> 时，重新对 <code>Team</code> 进行 <code>CollectSyncData</code> 收集并设置同步数据；</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamSyncData.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region CollectData</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::OnDataDirty</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (BoundTeam.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">CollectSyncData</span>( BoundTeam.<span class="built_in">Get</span>() );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::CollectSyncData</span><span class="params">(UTeamBase* InTeam)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InTeam)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetTeamID</span>( InTeam-&gt;<span class="built_in">GetTeamID</span>() );</span><br><span class="line"><span class="built_in">SetMemberUIDs</span>( InTeam-&gt;<span class="built_in">GetMemberUIDs</span>() );</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::CollectSyncData, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion CollectData</span></span><br></pre></td></tr></table></figure><p>提供 <code>Create</code> / <code>Destroy</code> <code>TeamSyncData</code> 的方法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamUtils.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function">UTeamSyncDataBase* <span class="title">UTeamUtils::CreateTeamSyncData</span><span class="params">(UObject* Outer, UTeamBase* InTeam, UClass* InClass)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(Outer)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InClass) || !InClass-&gt;<span class="built_in">IsChildOf</span>(UTeamSyncDataBase::<span class="built_in">StaticClass</span>())) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line">UTeamSyncDataBase* NewSyncData = NewObject&lt;UTeamSyncDataBase&gt;(Outer, InClass);</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(NewSyncData)) <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line">NewSyncData-&gt;<span class="built_in">BindTeam</span>( InTeam );</span><br><span class="line"><span class="keyword">return</span> NewSyncData;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamUtils::DestoryTeamSyncData</span><span class="params">(UTeamSyncDataBase* SyncData)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(SyncData)) <span class="keyword">return</span>;</span><br><span class="line">SyncData-&gt;<span class="built_in">Unbind</span>();</span><br><span class="line">SyncData = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>完整实现</strong></p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamSyncData.h</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">UCLASS</span>()</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UTeamSyncDataBase</span> :</span> <span class="keyword">public</span> UObject</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">GENERATED_BODY</span>()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">IsSupportedForNetworking</span><span class="params">()</span> <span class="keyword">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="literal">true</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Bind</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">BindTeam</span><span class="params">(UTeamBase* InTeam)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Unbind</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">TWeakObjectPtr &lt;UTeamBase&gt; BoundTeam;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Bind</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region CollectData</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OnDataDirty</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">CollectSyncData</span><span class="params">(UTeamBase* InTeam)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion CollectData</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Data</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> FString <span class="title">ToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRep_TeamID</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UFUNCTION</span>()</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">OnRep_MemberUIDs</span><span class="params">(<span class="keyword">const</span> TArray&lt;uint64&gt;&amp; LastMemberUIDs)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>(ReplicatedUsing=OnRep_TeamID)</span><br><span class="line">uint64 TeamID;</span><br><span class="line"></span><br><span class="line"><span class="built_in">UPROPERTY</span>(ReplicatedUsing=OnRep_MemberUIDs)</span><br><span class="line">TArray &lt;uint64&gt; MemberUIDs;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Data</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TeamSyncData.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Base</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::GetLifetimeReplicatedProps</span><span class="params">(TArray&lt;FLifetimeProperty&gt;&amp; OutLifetimeProps)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Super::<span class="built_in">GetLifetimeReplicatedProps</span>(OutLifetimeProps);</span><br><span class="line"></span><br><span class="line">FDoRepLifetimeParams SharedParams;</span><br><span class="line">SharedParams.bIsPushBased = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UTeamSyncDataBase, TeamID, SharedParams);</span><br><span class="line"><span class="built_in">DOREPLIFETIME_WITH_PARAMS_FAST</span>(UTeamSyncDataBase, MemberUIDs, SharedParams);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Base</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Bind</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::BindTeam</span><span class="params">(UTeamBase* InTeam)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InTeam)) <span class="keyword">return</span>;</span><br><span class="line">BoundTeam = InTeam;</span><br><span class="line">InTeam-&gt;OnDataDirty.<span class="built_in">AddUObject</span>(<span class="keyword">this</span>, &amp;ThisClass::OnDataDirty);</span><br><span class="line"><span class="built_in">CollectSyncData</span>(InTeam);</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::BindTeam, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::Unbind</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!BoundTeam.<span class="built_in">IsValid</span>()) <span class="keyword">return</span>;</span><br><span class="line">BoundTeam-&gt;OnDataDirty.<span class="built_in">RemoveAll</span>(<span class="keyword">this</span>);</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::Unbind, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Bind</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region CollectData</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::OnDataDirty</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (BoundTeam.<span class="built_in">IsValid</span>())</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">CollectSyncData</span>( BoundTeam.<span class="built_in">Get</span>() );</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::CollectSyncData</span><span class="params">(UTeamBase* InTeam)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">IsValid</span>(InTeam)) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">SetTeamID</span>( InTeam-&gt;<span class="built_in">GetTeamID</span>() );</span><br><span class="line"><span class="built_in">SetMemberUIDs</span>( InTeam-&gt;<span class="built_in">GetMemberUIDs</span>() );</span><br><span class="line"></span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::CollectSyncData, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion CollectData</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> region Data</span></span><br><span class="line"></span><br><span class="line"><span class="function">FString <span class="title">UTeamSyncDataBase::ToString</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">return</span> FString::<span class="built_in">Printf</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UID=%llu, TeamID=%llu, Members=%s&quot;</span>), <span class="built_in">GetUID</span>(), TeamID, *UStringUtils::<span class="built_in">ToString</span>(MemberUIDs));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::OnRep_TeamID</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::OnRep_TeamID, %s&quot;</span>), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UTeamSyncDataBase::OnRep_MemberUIDs</span><span class="params">(<span class="keyword">const</span> TArray&lt;uint64&gt;&amp; LastMemberUIDs)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">LogD</span>(<span class="built_in">TEXT</span>(<span class="string">&quot;UTeamSyncDataBase::OnRep_MemberUIDs, LastMemberUIDs=%s, %s&quot;</span>), *UStringUtils::<span class="built_in">ToString</span>(LastMemberUIDs), *<span class="built_in">ToString</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> endregion Data</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;TeamSystem框架&lt;/h1&gt;
&lt;h2 id=&quot;基本架构&quot;&gt;基本架构&lt;/h2&gt;
&lt;p&gt;支持基本的加入、退出 队伍，以及维护队伍的基本数据（&lt;code&gt;TeamID&lt;/code&gt;、 &lt;code&gt;MemberUIDs&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;提供继承扩展，维护自定义</summary>
      
    
    
    
    <category term="游戏开发" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Gameplay/"/>
    
    
    <category term="Gameplay" scheme="https://blog.bearchild.top/tags/Gameplay/"/>
    
    <category term="UE" scheme="https://blog.bearchild.top/tags/UE/"/>
    
  </entry>
  
</feed>
