<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>AnimationPlayer on 山高月小</title><link>https://blog.dart.xin/tags/animationplayer/</link><description>Recent content in AnimationPlayer on 山高月小</description><generator>Hugo</generator><language>zh-cn</language><copyright>zwdart</copyright><lastBuildDate>Wed, 04 Nov 2026 09:53:13 +0800</lastBuildDate><atom:link href="https://blog.dart.xin/tags/animationplayer/index.xml" rel="self" type="application/rss+xml"/><item><title>Godot AnimationPlayer</title><link>https://blog.dart.xin/godot/gd103-animationplayer/</link><pubDate>Wed, 04 Nov 2026 09:53:13 +0800</pubDate><guid>https://blog.dart.xin/godot/gd103-animationplayer/</guid><description>&lt;p&gt;&lt;code&gt;AnimationPlayer&lt;/code&gt; 是 Godot 动画系统的&lt;strong&gt;数据核心&lt;/strong&gt;。与“即用即弃”的 &lt;code&gt;Tween&lt;/code&gt; 不同，它把动画作为&lt;strong&gt;资源&lt;/strong&gt;保存在节点里，通过可视化的时间轴进行编排，可以在编辑器中精细调整节奏，再用代码决定何时播放哪一段。如果你的动画涉及多个属性、需要精确到帧的时序，或者包含音效、函数调用等非属性事件，&lt;code&gt;AnimationPlayer&lt;/code&gt; 是比 &lt;code&gt;Tween&lt;/code&gt; 更合适的选择。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="-先建立正确的场景结构"&gt;🧱 先建立正确的场景结构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AnimationPlayer&lt;/code&gt; 继承自&lt;strong&gt;裸 &lt;code&gt;Node&lt;/code&gt;&lt;/strong&gt;，而不是 &lt;code&gt;Node2D&lt;/code&gt; 或 &lt;code&gt;Node3D&lt;/code&gt;。这意味着它&lt;strong&gt;不传递变换&lt;/strong&gt;——如果把带有 2D/3D 变换的节点设为它的子节点，子节点不会继承父级的位移、旋转或缩放，视觉上会出现位置偏移或异常。&lt;/p&gt;
&lt;p&gt;正确的结构是让 &lt;code&gt;AnimationPlayer&lt;/code&gt; 与被动画的节点&lt;strong&gt;平级&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Player (CharacterBody2D)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Sprite2D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── AnimationPlayer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;AnimationPlayer&lt;/code&gt; 可以通过&lt;strong&gt;轨道路径&lt;/strong&gt;驱动任意兄弟或子节点的属性：位置、旋转、缩放、&lt;code&gt;modulate&lt;/code&gt;、&lt;code&gt;visible&lt;/code&gt;、碰撞形状的 &lt;code&gt;disabled&lt;/code&gt; 状态，甚至方法调用和音频播放。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="-轨道类型不止是属性"&gt;🗂️ 轨道类型：不止是属性&lt;/h3&gt;
&lt;p&gt;在动画面板中点击“添加轨道”，你会看到多种类型。常用的是以下四种：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;轨道类型&lt;/th&gt;
&lt;th style="text-align: left"&gt;作用&lt;/th&gt;
&lt;th style="text-align: left"&gt;典型用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;属性轨道&lt;/strong&gt; (Property / Value)&lt;/td&gt;
&lt;td style="text-align: left"&gt;插值任意属性&lt;/td&gt;
&lt;td style="text-align: left"&gt;位置、旋转、透明度、着色器参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;方法轨道&lt;/strong&gt; (Call Method)&lt;/td&gt;
&lt;td style="text-align: left"&gt;在精确时间点调用函数&lt;/td&gt;
&lt;td style="text-align: left"&gt;生成伤害判定、播放音效、切换状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;音频轨道&lt;/strong&gt; (Audio Playback)&lt;/td&gt;
&lt;td style="text-align: left"&gt;将音频锁定在时间轴上&lt;/td&gt;
&lt;td style="text-align: left"&gt;脚步声与落脚点精确同步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;贝塞尔轨道&lt;/strong&gt; (Bezier)&lt;/td&gt;
&lt;td style="text-align: left"&gt;用曲线精细控制数值节奏&lt;/td&gt;
&lt;td style="text-align: left"&gt;非线性的 UI 动效、特殊缓动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法轨道的关键配置&lt;/strong&gt;：默认的调用模式是 &lt;code&gt;CALL_MODE_CONTINUOUS&lt;/code&gt;，在关键帧前后拖拽播放头时可能&lt;strong&gt;每帧重复触发&lt;/strong&gt;。对于“伤害判定只该发生一次”的逻辑，必须在代码中将调用模式设为 &lt;code&gt;CALL_MODE_DISCRETE&lt;/code&gt;，确保函数在越过关键帧时&lt;strong&gt;只执行一次&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="-代码控制播放混合与队列"&gt;▶️ 代码控制：播放、混合与队列&lt;/h3&gt;
&lt;h4 id="基础播放与状态驱动"&gt;基础播放与状态驱动&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript" data-lang="gdscript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@onready&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;anim_player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AnimationPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$AnimationPlayer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_physics_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input_dir&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ui_left&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ui_right&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ui_up&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ui_down&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input_dir&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;Vector2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZERO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;anim_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;walk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;anim_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;idle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;一个重要的细节：&lt;strong&gt;对正在播放的同一动画重复调用 &lt;code&gt;play()&lt;/code&gt; 不会重新开始&lt;/strong&gt;，也不会报错。因此可以在 &lt;code&gt;_physics_process&lt;/code&gt; 中放心地每帧调用 &lt;code&gt;play()&lt;/code&gt;，根据状态切换动画。&lt;/p&gt;</description><content:encoded><![CDATA[<p><code>AnimationPlayer</code> 是 Godot 动画系统的<strong>数据核心</strong>。与“即用即弃”的 <code>Tween</code> 不同，它把动画作为<strong>资源</strong>保存在节点里，通过可视化的时间轴进行编排，可以在编辑器中精细调整节奏，再用代码决定何时播放哪一段。如果你的动画涉及多个属性、需要精确到帧的时序，或者包含音效、函数调用等非属性事件，<code>AnimationPlayer</code> 是比 <code>Tween</code> 更合适的选择。</p>
<hr>
<h3 id="-先建立正确的场景结构">🧱 先建立正确的场景结构</h3>
<p><code>AnimationPlayer</code> 继承自<strong>裸 <code>Node</code></strong>，而不是 <code>Node2D</code> 或 <code>Node3D</code>。这意味着它<strong>不传递变换</strong>——如果把带有 2D/3D 变换的节点设为它的子节点，子节点不会继承父级的位移、旋转或缩放，视觉上会出现位置偏移或异常。</p>
<p>正确的结构是让 <code>AnimationPlayer</code> 与被动画的节点<strong>平级</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Player (CharacterBody2D)
</span></span><span class="line"><span class="cl">├── Sprite2D
</span></span><span class="line"><span class="cl">└── AnimationPlayer
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>AnimationPlayer</code> 可以通过<strong>轨道路径</strong>驱动任意兄弟或子节点的属性：位置、旋转、缩放、<code>modulate</code>、<code>visible</code>、碰撞形状的 <code>disabled</code> 状态，甚至方法调用和音频播放。</p>
<hr>
<h3 id="-轨道类型不止是属性">🗂️ 轨道类型：不止是属性</h3>
<p>在动画面板中点击“添加轨道”，你会看到多种类型。常用的是以下四种：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: left">轨道类型</th>
					<th style="text-align: left">作用</th>
					<th style="text-align: left">典型用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><strong>属性轨道</strong> (Property / Value)</td>
					<td style="text-align: left">插值任意属性</td>
					<td style="text-align: left">位置、旋转、透明度、着色器参数</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>方法轨道</strong> (Call Method)</td>
					<td style="text-align: left">在精确时间点调用函数</td>
					<td style="text-align: left">生成伤害判定、播放音效、切换状态</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>音频轨道</strong> (Audio Playback)</td>
					<td style="text-align: left">将音频锁定在时间轴上</td>
					<td style="text-align: left">脚步声与落脚点精确同步</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>贝塞尔轨道</strong> (Bezier)</td>
					<td style="text-align: left">用曲线精细控制数值节奏</td>
					<td style="text-align: left">非线性的 UI 动效、特殊缓动</td>
			</tr>
	</tbody>
</table>
<p><strong>方法轨道的关键配置</strong>：默认的调用模式是 <code>CALL_MODE_CONTINUOUS</code>，在关键帧前后拖拽播放头时可能<strong>每帧重复触发</strong>。对于“伤害判定只该发生一次”的逻辑，必须在代码中将调用模式设为 <code>CALL_MODE_DISCRETE</code>，确保函数在越过关键帧时<strong>只执行一次</strong>。</p>
<hr>
<h3 id="-代码控制播放混合与队列">▶️ 代码控制：播放、混合与队列</h3>
<h4 id="基础播放与状态驱动">基础播放与状态驱动</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">anim_player</span><span class="p">:</span> <span class="nc">AnimationPlayer</span> <span class="o">=</span> <span class="nx">$AnimationPlayer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">_physics_process</span><span class="p">(</span><span class="n">_delta</span><span class="p">:</span> <span class="kt">float</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">void</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="n">input_dir</span> <span class="o">:=</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">get_vector</span><span class="p">(</span><span class="s2">&#34;ui_left&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_right&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_up&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_down&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">input_dir</span> <span class="o">!=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;walk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;idle&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>一个重要的细节：<strong>对正在播放的同一动画重复调用 <code>play()</code> 不会重新开始</strong>，也不会报错。因此可以在 <code>_physics_process</code> 中放心地每帧调用 <code>play()</code>，根据状态切换动画。</p>
<h4 id="常用控制方法速查">常用控制方法速查</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;attack&#34;</span><span class="p">)</span>                <span class="c1"># 正向播放</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">play_backwards</span><span class="p">(</span><span class="s2">&#34;attack&#34;</span><span class="p">)</span>      <span class="c1"># 反向播放</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">queue</span><span class="p">(</span><span class="s2">&#34;idle&#34;</span><span class="p">)</span>                 <span class="c1"># 当前动画结束后自动播放下一个</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">stop</span><span class="p">()</span>                        <span class="c1"># 立即停止</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">pause</span><span class="p">()</span>                       <span class="c1"># 暂停在当前帧</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">()</span>                        <span class="c1"># 从暂停位置恢复</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="n">speed_scale</span> <span class="o">=</span> <span class="mf">2.0</span>             <span class="c1"># 加速播放</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">seek</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>                     <span class="c1"># 跳转到 0.5 秒处</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>queue()</code> 是串联动画的轻量方式：先播放 <code>attack</code>，<code>attack</code> 播完后自动进入 <code>idle</code>，不需要手动等待信号。</p>
<h4 id="平滑过渡利用混合时间">平滑过渡：利用混合时间</h4>
<p><code>play()</code> 的第二个参数控制混合时间，让两段动画之间<strong>交叉淡入淡出</strong>，而不是硬切换：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="c1"># 在 0.2 秒内从当前动画混合过渡到 run</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;run&#34;</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果不传这个参数，Godot 会使用动画面板中“编辑过渡”（Edit Transitions）里为每对动画预设的混合时间。对于 <code>idle ↔ walk</code> 这类频繁切换的状态，设置 <strong>0.1–0.2 秒</strong>的混合时间能显著提升手感。</p>
<hr>
<h3 id="-方法轨道把逻辑事件焊在时间线上">🎬 方法轨道：把逻辑事件焊在时间线上</h3>
<p>属性轨道管“视觉”，方法轨道管“事件”。例如在攻击动画的第 0.3 秒生成伤害判定：</p>
<p>在编辑器中添加一条<strong>方法轨道</strong>，路径指向挂有目标脚本的节点，在第 0.3 秒插入关键帧并填入方法名 <code>spawn_hitbox</code>。然后在代码中确保调用模式为单次触发：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="c1"># 在 _ready() 或动画准备阶段，将方法轨道设为 DISCRETE</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">void</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="n">anim</span> <span class="o">:=</span> <span class="n">anim_player</span><span class="o">.</span><span class="nf">get_animation</span><span class="p">(</span><span class="s2">&#34;attack&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">anim</span><span class="o">.</span><span class="nf">get_track_count</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">anim</span><span class="o">.</span><span class="nf">track_get_type</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Animation</span><span class="o">.</span><span class="n">TYPE_METHOD</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">anim</span><span class="o">.</span><span class="nf">track_set_call_mode</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="nc">Animation</span><span class="o">.</span><span class="n">CALL_MODE_DISCRETE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>被调用的函数必须存在于目标节点上：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">spawn_hitbox</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">void</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 生成伤害区域、播放特效等，只会执行一次</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="n">hitbox</span> <span class="o">:=</span> <span class="nb">preload</span><span class="p">(</span><span class="s2">&#34;res://hitbox.tscn&#34;</span><span class="p">)</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nf">add_child</span><span class="p">(</span><span class="n">hitbox</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果需要在播放中途<strong>动态改变调用的方法或参数</strong>，方法轨道的关键帧值本身是一个字典 <code>{ &quot;method&quot;: &quot;函数名&quot;, &quot;args&quot;: [...] }</code>，可以在运行时通过 <code>track_insert_key</code> 替换。</p>
<hr>
<h3 id="-动画库组织多个动画">📚 动画库：组织多个动画</h3>
<p>一个 <code>AnimationPlayer</code> 可以保存多条动画。出于复用性考虑，动画被注册在 <strong><code>AnimationLibrary</code></strong> 资源中。如果你直接新建动画而不指定库，它会进入默认的 <code>[Global]</code> 库，直接 <code>play(&quot;idle&quot;)</code> 即可。</p>
<p>当动画数量增多时，按用途分库会更清晰：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="c1"># 运行时加载额外的动画库</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="n">combat_lib</span><span class="p">:</span> <span class="nc">AnimationLibrary</span> <span class="o">=</span> <span class="nb">load</span><span class="p">(</span><span class="s2">&#34;res://animations/combat_lib.tres&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">add_animation_library</span><span class="p">(</span><span class="s2">&#34;combat&#34;</span><span class="p">,</span> <span class="n">combat_lib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 播放指定库中的动画</span>
</span></span><span class="line"><span class="cl"><span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;combat/slash&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>分库的好处是<strong>按需加载</strong>：基础移动动画可以常驻，战斗动画库只在进入战斗时加载，减少内存占用。</p>
<hr>
<h3 id="-两个必须避开的坑">⚠️ 两个必须避开的坑</h3>
<p><strong>① 不要给根节点直接添加动画</strong></p>
<p>动画中的 <code>position</code> 是<strong>相对于父节点</strong>的坐标。如果直接给场景根节点打位置关键帧，当这个场景被实例化到另一个场景中时，动画会以<strong>新的父节点位置</strong>作为原点，导致节点“跳”到错误的位置。解决办法是给场景加一个空白根节点，把原来的内容包进去，动画只作用于内部的子节点。</p>
<p><strong>② 移动或重命名节点后，轨道可能失效</strong></p>
<p><code>AnimationPlayer</code> 的轨道路径是<strong>节点路径字符串</strong>。如果你在创建动画后移动或重命名了被动画的节点，轨道路径不会自动更新，播放时会出现“Node not found”错误，属性不再被驱动。双击轨道可以手动编辑路径，或者在调整场景结构后重新绑定轨道。</p>
<hr>
<h3 id="-完整可运行示例带过渡的移动状态机">🧩 完整可运行示例：带过渡的移动状态机</h3>
<p>将以下脚本挂在一个 <code>CharacterBody2D</code> 上，确保场景中有一个 <code>AnimationPlayer</code> 包含 <code>idle</code> 和 <code>walk</code> 两条动画：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-gdscript" data-lang="gdscript"><span class="line"><span class="cl"><span class="kd">extends</span> <span class="nc">CharacterBody2D</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">anim_player</span><span class="p">:</span> <span class="nc">AnimationPlayer</span> <span class="o">=</span> <span class="nx">$AnimationPlayer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="n">SPEED</span> <span class="o">:=</span> <span class="mf">200.0</span>
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="n">BLEND_TIME</span> <span class="o">:=</span> <span class="mf">0.15</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">_physics_process</span><span class="p">(</span><span class="n">_delta</span><span class="p">:</span> <span class="kt">float</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">void</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="n">input_dir</span> <span class="o">:=</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">get_vector</span><span class="p">(</span><span class="s2">&#34;ui_left&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_right&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_up&#34;</span><span class="p">,</span> <span class="s2">&#34;ui_down&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">input_dir</span> <span class="o">!=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">velocity</span> <span class="o">=</span> <span class="n">input_dir</span> <span class="o">*</span> <span class="n">SPEED</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 如果当前不在 walk，用 0.15 秒混合进入</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">anim_player</span><span class="o">.</span><span class="n">current_animation</span> <span class="o">!=</span> <span class="s2">&#34;walk&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;walk&#34;</span><span class="p">,</span> <span class="n">BLEND_TIME</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">velocity</span> <span class="o">=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">anim_player</span><span class="o">.</span><span class="n">current_animation</span> <span class="o">!=</span> <span class="s2">&#34;idle&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">anim_player</span><span class="o">.</span><span class="nf">play</span><span class="p">(</span><span class="s2">&#34;idle&#34;</span><span class="p">,</span> <span class="n">BLEND_TIME</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">move_and_slide</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>这个示例的关键在于<strong>用 <code>current_animation</code> 判断是否需要切换</strong>，避免每帧重复调用 <code>play()</code> 导致混合反复重置。<code>BLEND_TIME</code> 让 <code>idle</code> 和 <code>walk</code> 之间平滑交叉淡化，而不是生硬跳变。</p>
<p><code>AnimationPlayer</code> 的强项是<strong>时间轴编排</strong>——一旦你把关键帧、方法轨道和混合时间配置好，代码端只需要关心“何时切换到哪条动画”，决策逻辑可以保持得非常薄。当你需要状态机或方向混合空间时，<code>AnimationTree</code> 才是下一步的升级，而 <code>AnimationPlayer</code> 始终是它背后的数据底座。</p>
]]></content:encoded></item></channel></rss>