# 编写自定义动作（C#）

除了使用 [内置动作](https://docs.febucci.com/text-animator-unity/typewriter/wait-actions-when-typing)之外，你可以通过脚本（C#）编写自己的动作。

{% hint style="info" %}
务必阅读 [gao-ji-gai-nian](https://docs.febucci.com/text-animator-unity/3.x-zh/bian-xie-zi-ding-yi-lei/gao-ji-gai-nian "mention") 页面。
{% endhint %}

***

## 创建自定义动作的不同方式 <a href="#actions-base-class" id="actions-base-class"></a>

自 Text Animator 3.0 起，你可以通过多种方式创建动作，根据项目需求提供更多灵活性。

### 将动作作为组件创建

{% hint style="success" %}
作为组件创建的动作允许你更容易地引用场景对象
{% endhint %}

```csharp
[System.Serializable]
class ExampleActionComponent : TypewriterActionScriptable
{
    [SerializeField] float timeToWait;
    
    // 主逻辑在这里， 
    
    // ...可以是无状态的
    protected override IActionState CreateCustomState(ActionMarker marker, object typewriter)
        => new ExampleState(timeToWait);
        
    // ...或作为协程
    protected override IEnumerator PerformAction(TypingInfo typingInfo)
    {
        // yield return ...
    }
}
```

### 将动作作为 ScriptableObject 创建

{% hint style="success" %}
作为 ScriptableObject 的动作可以重复使用并在无需加载场景的情况下被引用
{% endhint %}

```csharp
[System.Serializable]
[CreateAssetMenu(menuName = "Create Example Action")]
class ExampleActionScriptable : TypewriterActionScriptable
{
    [SerializeField] float timeToWait;
    
    // 主逻辑在这里...
    
    // ...可以是无状态的
    protected override IActionState CreateCustomState(ActionMarker marker, object typewriter)
        => new ExampleState(timeToWait);
        
    // ...或作为协程
    protected override IEnumerator PerformAction(TypingInfo typingInfo)
    {
        // yield return ...
    }
}
```

{% hint style="warning" %}
附言：别忘了在项目视图中创建你的动作 ScriptableObject，并将其添加到动作数据库中。
{% endhint %}

***

## 实现动作逻辑的不同方式 <a href="#actions-base-class" id="actions-base-class"></a>

你可以决定如何编写动作的核心逻辑。&#x20;

* 在协程（IEnumerator）内部，或
* 通过单独的“tick”方法（该方法返回动作是否应继续运行或已完成）。

首先，导入正确的命名空间：

<pre class="language-csharp"><code class="lang-csharp"><strong>using Febucci.TextAnimatorForUnity.Actions;
</strong><strong>using Febucci.TextAnimatorCore.Typing;
</strong>using UnityEngine;
</code></pre>

### 创建协程 <a href="#actions-base-class" id="actions-base-class"></a>

编写协程非常简单！&#x20;

例如，在你的 TypewriterAction 类（无论是组件还是 Scriptable）中，只需重写 PerformAction 方法：

```csharp
[SerializeField] AudioSource source;

protected override IEnumerator PerformAction(TypingInfo typingInfo)
{
    if (source != null && source.clip != null)
    {
        source.Play();
        yield return new WaitForSeconds(source.clip.length);
    }
}
```

### 创建无状态动作 <a href="#actions-base-class" id="actions-base-class"></a>

另一方面，创建无状态动作需要你创建一个继承于自定义结构体， **IActionState** 并且该结构体将执行动作（在此示例中：在继续打字器之前等待几秒），例如：

```csharp
struct ExampleState : IActionState // <--- 必须继承自此
{
    float timePassed;
    readonly float timeToWait;
    public ExampleState(float timeToWait)
    {
        timePassed = 0;
        this.timeToWait = timeToWait;
    }
    
    public ActionStatus Progress(float deltaTime, ref TypingInfo typingInfo)
    {
        // 增加已过时间
        timePassed += deltaTime;
        
        // 根据时间决定继续还是停止
        return timePassed >= timeToWait
            ? ActionStatus.Finished
            : ActionStatus.Running;
    }
    
    public void Cancel()
    {
        // 在此用于修改 
    }
}
```

然后你可以通过在你的动作类中重写 CreateCustomState 方法来实例化此结构体（我们在这里看到的那个 [#actions-base-class](#actions-base-class "mention")).

```csharp
protected override IActionState CreateCustomState(ActionMarker marker, object typewriter)
        => new ExampleState(timeToWait);
```

### 属性 <a href="#attributes" id="attributes"></a>

* 该 `标记` 参数包含关于你的标签的有用信息，例如 ID 或是否有随之而来的任何参数（例如 `<playSound=02>`).
* 该 `typewriter` 引用当前正在执行该动作的 Typewriter 组件或 AnimatedLabel
* 该 `typingInfo` 包含诸如当前打字速度（你可以修改）和打字器内已过时间等信息。

***

{% hint style="success" %}
完成！通过这个简单的步骤，你可以添加任何你想要的自定义动作。
{% endhint %}
