> For the complete documentation index, see [llms.txt](https://malbersanimations.gitbook.io/animal-controller/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://malbersanimations.gitbook.io/animal-controller/utilities/weapon-trail.md).

# Weapon Trail

Last updated AC v1.5.3

## Overview

The **Weapon Trail** draws a procedural mesh ribbon behind a melee weapon while it swings. Two child transforms on the weapon, `Tip` and `Base`, mark the outer and inner edges of the blade. Every frame the component records where those two points are, keeps the rows that are younger than the Profile's `Duration`, and builds a ribbon mesh through them. Catmull-Rom smoothing is applied between the recorded rows so fast swings still read as a round arc.

All the visual tuning lives on a **Weapon Trail Profile** asset. One profile can be shared by many weapons, and one component can hold several profiles (a normal attack and a charged attack, for example) and switch between them by index.

The trail can be started and stopped from the **Weapon Trail Behaviour** on an Animator state, from the **Weapon Trail Reaction**, from the `MAttackTrigger` events, or from code.

\[Insert Image - Sword swing with a weapon trail in Play Mode]

Add it from **Add Component → Malbers → Utilities → Effects - Audio → Weapon Trail**.

## Requirements

* The component goes on the weapon GameObject (the root of the weapon prefab, or any object that has the `Tip` and `Base` transforms as children).
* Two transforms on the weapon: `Tip` (outer edge of the ribbon) and `Base` (inner edge). The inspector can create them for you.
* A **Weapon Trail Profile** asset assigned in the `Profiles` list. Create one from **Assets → Create → Malbers Animations → Effects → Weapon Trail Profile**.
* A material on the Profile. The shader must be transparent, two sided (Cull Off) and multiply by vertex color. If the material is empty the trail falls back to `Sprites/Default` (or `Unlit/Transparent`), which works on URP, HDRP and Built-in.
* For the **Weapon Trail Behaviour** to find the trail, the weapon must be parented under the character's `Animator` (equipped in the hand, for example).

The shipped weapon prefabs already have it. `Sword.prefab` in **Common/Prefabs/Weapons** uses the `Weapon Trail [Melee]` profile, and the Super Item weapons (`Axe (SI)`, `Dagger (SI)`, `Spear (SI)`, `Two_Hand Axe (SI)`, `Throw_Axe (SI)`) carry one too. Ready-made profiles are in **Common/Scriptable Assets/Weapon Trail**: `[Melee]`, `[Heavy Axe]`, `[Fire]`, `[Ice]`, `[Magic]` and `[Dark]`.

## How it Works

1. **Register.** On enable the component finds the closest `Animator` above it and registers on it. When the weapon is re-parented (equipped, holstered, swapped hands) it leaves the old Animator and joins the new one, so a Weapon Trail Behaviour always finds the weapons that are currently in the character's hands.
2. **Play.** `Play()` clears the history, enables the mesh renderer, fires `On Trail Start` and begins fading in over the Profile's `Fade In`.
3. **Capture.** In `LateUpdate` (execution order 1600, after the IK Manager and the Super Item Manager have finished moving the bones) one row is captured per frame and timestamped. If the blade moved less than `Min Distance` since the last row, the newest row is refreshed in place instead of adding a duplicate. Rows older than `Duration` are dropped.
4. **Build.** The mesh is rebuilt from the live rows. Row 0 is always the current blade position, so the ribbon stays welded to the weapon. Catmull-Rom `Subdivisions` are inserted between rows. The `Width` curve scales each row by its age. UVs run along the trail (U) and across it (V).
5. **Stop.** `Stop()` stops capturing, fires `On Trail End` and fades out over the Profile's `Fade Out`. When the fade reaches zero the renderer is disabled and the component goes dormant. A dormant trail costs a single `if` per frame.

The buffers are allocated once for the Profile's `Max Segments`. Unused rows collapse onto the tail as degenerate triangles, so the vertex count never changes and nothing allocates after the first build.

{% hint style="info" %}
The generated mesh is **not** a child of the weapon. Every trail mesh is parented under a shared GameObject called **"Weapon Trails"** at the scene root, pinned to position zero, rotation identity and scale one, because the vertices are written in world space. The object is never saved in the scene or the build. It is hidden in the Hierarchy unless `Debug` is on.
{% endhint %}

## Parameters

\[Insert Image - Weapon Trail component inspector]

#### Index

Identifies this trail so a **Weapon Trail Behaviour** or code can target it when the character carries several weapons (dual wield, off-hand shield). Minimum is 1. A Weapon Trail Behaviour with `Trail Index` **0** plays every trail it finds, so a single weapon can keep the default. The shipped `Sword` prefab uses `100`.

#### Profiles

List of **Weapon Trail Profile** assets this trail can use. Slot 0 is the default. Which slot is live is chosen when the trail plays: by the Behaviour's `Profile Index`, by the Reaction, or by `PlayProfile(int)`. Use several slots when the same weapon needs a different look for a normal attack, a charged attack, an elemental buff and so on.

{% hint style="info" %}
Older versions had a single `Profile` field. It is migrated automatically into `Profiles[0]` the first time the component loads.
{% endhint %}

#### Tip

Transform at the tip of the blade. This is the outer edge of the ribbon.

#### Base

Transform at the base of the blade. This is the inner edge of the ribbon. The distance between `Tip` and `Base` is the full width of the ribbon (before the `Width` curve is applied).

When either field is empty the inspector shows a **Create Tip and Base Transforms** button. It adds two children named `Trail_Tip` and `Trail_Base`, placed along the longest axis of the weapon's renderer bounds (Tip at the far end, Base a little past the center in the other direction). Move them afterwards to match the blade.

#### Debug

The bug icon at the end of the `Tip`/`Base` row. Enables the Scene View handles, the numeric position fields, the gizmos, the runtime readout and makes the generated mesh object visible in the Hierarchy. See the Debug section below.

#### Debug Color

Only visible with `Debug` on. Color used for the handles and gizmos.

#### Events

Foldout with the two UnityEvents. See the Events section.

#### Preview

Button at the bottom of the inspector. Available outside Play Mode when the trail has a Profile, a Tip and a Base. While previewing, move or rotate the weapon in the Scene View to see the ribbon. It does not scrub animations or drive the Animator. Press **Stop Preview** (or deselect the object) to end it; the generated mesh is destroyed when the preview stops.

## Weapon Trail Profile

\[Insert Image - Weapon Trail Profile asset inspector]

The Profile is a ScriptableObject. Every tuning value lives here so one asset can drive many weapons. Edits made while the trail is playing are picked up live.

### Material

| Field                      | Description                                                                                                                                                                                                                                                   |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Material`                 | Material used by the trail mesh. Leave empty to use a built-in unlit fallback. The shader must be transparent, two sided and multiply by vertex color.                                                                                                        |
| `Color Property`           | Shader property that receives the `Tint`. Leave it **empty** to leave the material's own color alone. Default `_BaseColor`.                                                                                                                                   |
| `Tint`                     | Color pushed into `Color Property` through a `MaterialPropertyBlock`. No material instance is created.                                                                                                                                                        |
| `Emission Property`        | Shader property that receives the `Emission` color. Empty leaves the material alone. Default `_EmissionColor`. On URP the material's **Emission** toggle must be ON.                                                                                          |
| `Emission`                 | HDR emission color. Its RGB is multiplied by the global fade, so the glow fades out with the trail. Black means no emission.                                                                                                                                  |
| `Alpha Clip Property`      | Shader property that receives the alpha clip threshold. Empty leaves the material alone. Default is empty. The material's **Alpha Clipping** must be ON for this to do anything.                                                                              |
| `Alpha Clip`               | Threshold (0–1). Pixels below this alpha are discarded.                                                                                                                                                                                                       |
| `Fade Affects Color Alpha` | Multiply the global fade (Fade In / Fade Out) into the **alpha** of `Color Property`. Default ON. Turn it OFF when the shader uses that alpha for alpha clipping or as a master opacity that has to stay at 1, otherwise fading makes the whole trail vanish. |
| `Fade Drives Alpha Clip`   | Drive the clip threshold with the global fade so the trail **dissolves** instead of dimming. At fade 1 the threshold is `Alpha Clip`; as the trail fades out it rises to 1 and burns away. Default OFF.                                                       |
| `Cast Shadows`             | Cast shadows from the trail mesh. Almost always off.                                                                                                                                                                                                          |
| `Blending`                 | Override the material's blend mode. `None`, `Additive`, `Alpha Blend`, `Premultiplied Alpha`, `Multiply` or `Custom`. See the note below.                                                                                                                     |
| `Src Blend` / `Dst Blend`  | Only shown when `Blending` is `Custom`. The raw source and destination blend factors.                                                                                                                                                                         |

{% hint style="info" %}
**Shader property names by pipeline.** `Color Property`: URP `_BaseColor`, Sprites-Default and Built-in `_Color`, legacy particle shaders `_TintColor`. `Emission Property`: URP `_EmissionColor`. `Alpha Clip Property`: Shader Graph `_AlphaClipThreshold`, URP Lit/Unlit `_Cutoff`. Writing to a property the shader does not declare does nothing, so a wrong name fails silently. The shipped profiles use a custom trail material with `_Alpha_Clip`.
{% endhint %}

{% hint style="info" %}
**Blending creates a material copy.** Blend state is not a shader uniform, so a `MaterialPropertyBlock` cannot change it. When `Blending` is anything other than `None`, the Profile creates **one shared runtime copy** of its material and writes `_SrcBlend`/`_DstBlend` (and the `Alpha` pair) onto it. Every trail using the same Profile shares that single copy, so batching is kept. `None` uses the material asset exactly as authored and costs nothing.
{% endhint %}

The four presets map to these factors:

| Blending            | Src      | Dst              |
| ------------------- | -------- | ---------------- |
| Additive            | SrcAlpha | One              |
| Alpha Blend         | SrcAlpha | OneMinusSrcAlpha |
| Premultiplied Alpha | One      | OneMinusSrcAlpha |
| Multiply            | DstColor | Zero             |

### Trail Geometry

| Field          | Description                                                                                                                                                                                                                                                                     |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Flip U`       | Flip the texture along the trail. OFF: U = 0 at the blade, U = 1 at the tail. Turn it on when the texture was authored the other way round.                                                                                                                                     |
| `Stretch UV`   | Stretch the texture across the whole ribbon so both ends always reach the edges of the texture. Default ON. OFF maps U by age instead, so a short trail (start of a swing, or while it ages out) only shows part of the texture and can end on a hard cut.                      |
| `Duration`     | Seconds a row stays alive. This is the visual length of the ribbon. One row is captured per frame, so a longer Duration also means **more rows** across the swing, which makes the arc rounder. Minimum 0.01. Default 0.25.                                                     |
| `Max Segments` | Row budget (2–256). Must be at least `Duration x framerate` to reach the full length (0.25 s at 144 FPS = 36 rows). Caps memory and cost. Default 64.                                                                                                                           |
| `Subdivisions` | Catmull-Rom rows inserted between captured rows at render time (0–8). This is what keeps fast swings smooth. 0 disables smoothing (cheapest). Default 3.                                                                                                                        |
| `Min Distance` | Distance the blade must travel before a **new** row is captured. Below this the newest row is refreshed in place, so frames where the Animator did not advance cannot create duplicate rows (which would waste the budget and leave hard corners in the spline). Default 0.002. |
| `Width`        | Width multiplier over the age of a row (0 = at the blade, 1 = oldest). This shapes the actual mesh. Default is a straight line from 1 to 0.                                                                                                                                     |
| `Fade In`      | Seconds to fade the trail in when `Play()` is called. Default 0.03.                                                                                                                                                                                                             |
| `Fade Out`     | Seconds to fade the trail out when `Stop()` is called. The trail goes dormant afterwards. Default 0.1.                                                                                                                                                                          |

There is deliberately no alpha curve along the trail. The fade from head to tail comes from the material's texture (U runs head to tail). Use the `Width` curve for the geometry and the texture for the color gradient.

Derived values: `Capacity` = `Max Segments` (minimum 2). `Render Rows` = `(Capacity - 1) x (Subdivisions + 1) + 1`. The mesh always has `Render Rows x 2` vertices.

## Weapon Trail Behaviour

\[Insert Image - Weapon Trail Behaviour on an attack state]

A `StateMachineBehaviour` that plays the trail during a slice of an attack animation. It works the same way as the Attack Trigger Behaviour: add it to the attack state (**Add Behaviour → Malbers → Weapon Trail**), drag the range handles, done. The shipped `AC Human v5`, `AC Human v6`, `AC Human v6 Rider` and `Wolf Lite Animal v2` controllers already use it.

| Field              | Description                                                                                                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Trail Activation` | Normalized range (0–1) of the animation while the trail plays. `Play` is sent when the state passes the min value, `Stop` when it passes the max value. Default 0.2 – 0.6. |
| `Trail Index`      | 0: play **all** the Weapon Trails registered on this Animator. Any other value: play only the trails whose `Index` matches.                                                |
| `Profile Index`    | Which entry of the trail's `Profiles` list to use. `-1` keeps whatever profile is already selected. Default 0.                                                             |
| `Debug`            | Bug icon next to the range. Logs which Weapon Trails received the Play/Stop.                                                                                               |

Behaviour notes:

* The trails are looked up through the registry on the Animator, not with a `GetComponentsInChildren` sweep, and the lookup is only refreshed when a trail registers or unregisters (weapon equipped, holstered or swapped).
* When the state transitions to itself (a chained attack) the Stop is skipped so the trail keeps running through the combo.
* If the state exits before the max value (an interrupted attack) the Behaviour sends Stop anyway, so a trail can never be left running forever.

The component also implements `IAnimatorListener`, so a **Messages Behaviour** on the state can call `Play`, `Stop` or `Clear` by name at a normalized time.

## Weapon Trail Reaction

\[Insert Image - Weapon Trail Reaction in a reaction list]

Found under **Tools → Weapon Trail** in any reaction list (Modes, States, Stats, Item Actions, MEvent listeners). The Reaction resolves the `MWeaponTrail` on the target, its parents or its children.

| Action              | Extra fields            | Description                                                                                                                                                                                |
| ------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Play`              | `Fade`, `Profile Index` | Starts the trail. `Profile Index` -1 keeps the current slot; any other value selects that slot first. `Fade` overrides the Profile's Fade In (negative uses the Profile value).            |
| `Stop`              | `Fade`                  | Stops the trail. `Fade` overrides the Profile's Fade Out (negative uses the Profile value).                                                                                                |
| `Clear`             |                         | Collapses every recorded row onto the current blade position.                                                                                                                              |
| `Set Profile`       | `Profile`               | Forces a specific Profile asset onto the trail, bypassing the `Profiles` list until a slot is selected again. Does nothing when `Profile` is empty.                                        |
| `Set Color`         | `Color`                 | Runtime tint **multiplier** on top of the Profile's `Tint`. White leaves it untouched.                                                                                                     |
| `Set Profile Index` | `Profile Index`         | Selects an entry of the `Profiles` list without playing. Negative does nothing.                                                                                                            |
| `Set Emission`      | `Emission`              | Runtime emission **multiplier** (0–1) on top of the Profile's `Emission`. White leaves it untouched. To flare brighter than the profile, switch to a profile whose `Emission` is stronger. |

`Set Color` and `Set Emission` go through the `MaterialPropertyBlock`, so they never create a material instance.

## Events

Both events are on the component, under the **Events** foldout.

| Event            | When                                                                                                                         |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `On Trail Start` | `Play()` was called while the trail was not already playing. Fires after the history is cleared and the renderer is enabled. |
| `On Trail End`   | `Stop()` was called while the trail was playing. Fires at the start of the fade out, not at its end.                         |

A common setup is to call `Play` from `MAttackTrigger.OnAttackBegin` and `Stop` from `MAttackTrigger.OnAttackEnd` when you do not want to touch the Animator Controller.

## API

`MWeaponTrail` lives in `MalbersAnimations.Utilities`.

```csharp
// Playback
public void Play();                                   // fade in over Profile.FadeIn
public void Play(float fadeIn);                       // fade in over fadeIn seconds
public void Stop();                                   // fade out over Profile.FadeOut
public void Stop(float fadeOut);                      // fade out over fadeOut seconds
public void Clear();                                  // collapse every row onto the current blade position

// Profiles
public void PlayProfile(int index);                   // select a Profiles slot and Play(); negative keeps the current slot
public void PlayProfile(int index, float fadeIn);
public void SetProfileIndex(int index);               // select a Profiles slot; negative keeps the current one
public void SetProfile(WeaponTrailProfile profile);   // force a preset, ignoring the Profiles list
public WeaponTrailProfile Profile { get; }            // the preset currently in use
public int ProfileIndex { get; }

// Runtime look (MaterialPropertyBlock, no material instance)
public void SetColor(Color color);                    // tint multiplier
public void SetEmission(Color multiplier);            // emission multiplier, white = untouched

// State
public bool IsPlaying { get; }                        // true while recording, stays true through the fade out
public bool IsDormant { get; }                        // true when the component does no work this frame
public int Capacity { get; }                          // rows allocated
public int RowCount { get; }                          // rows holding real data
public int VertexCount { get; }
public float CapturesPerSecond { get; }
public float FramesPerSecond { get; }
public GameObject TrailObject { get; }                // the generated mesh object, null until initialized
public Animator Anim { get; }                         // Animator this trail registered on

// Animator messages (IAnimatorListener)
public bool OnAnimatorBehaviourMessage(string message, object value);

// Registry (static)
public static List<MWeaponTrail> GetAll(Animator animator);   // null when none
public static MWeaponTrail Get(Animator animator, int index); // first trail with that Index, or null
public static int Version { get; }                            // bumps every time the registry changes
public static Transform GetContainer();                       // the shared "Weapon Trails" parent
public const string ContainerName = "Weapon Trails";
```

`WeaponTrailProfile`:

```csharp
public Material GetMaterial();                        // the asset, or the shared blend-override copy
public void ApplyBlendTo(Material m);                 // writes the chosen blend factors; no-op when Blending is None
public void GetBlendFactors(out BlendMode src, out BlendMode dst);
public int Capacity { get; }                          // max(2, MaxSegments)
public int RenderRows { get; }                        // (Capacity - 1) * (Subdivisions + 1) + 1
```

Example:

```csharp
var trail = MWeaponTrail.Get(animator, 1);
trail?.PlayProfile(1);          // charged attack look
// ... later
trail?.Stop(0.3f);
```

## Debug

Turn on the bug icon on the component.

\[Insert Image - Weapon Trail debug readout and Scene View handles]

* **Scene View handles.** A line is drawn between `Base` and `Tip` and each point gets a position handle, so the blade edges can be dragged in place with Undo support.
* **Tip Position / Base Position.** Numeric local position fields for the two points appear in the inspector. Local space, so the values can be copied between weapons.
* **Gizmos.** While the trail is playing, one line per captured row is drawn (with the inspector expanded). The density of those lines is the roundness of the arc.
* **Readout** at the bottom of the inspector:
  * `Budget` — rows max and vertex count for the current Profile.
  * `Rows at 60 / 144 fps` — how many rows the current `Duration` produces at those framerates, capped by `Max Segments`. If the 144 value hits the cap, raise `Max Segments`.
  * `Live rows` (Play Mode) — rows currently holding data.
  * `Captures / Frames per sec` (Play Mode) — real captures against frames ticked. When captures are well below frames, the Animator is advancing slower than the render loop and it, not the trail, is the resolution limit. Raise `Duration` for more rows, or check the Animator's Update Mode / Culling Mode. A warning appears automatically.
  * `State` — Dormant, Playing or Fading out.
  * `Mesh Object` — the name of the generated object under "Weapon Trails", with a **Select** button to inspect it.
* **Hierarchy.** With `Debug` on the generated `<weapon name> Trail` object is visible under "Weapon Trails" and fully inspectable. With `Debug` off it is hidden.
* The **Weapon Trail Behaviour** has its own debug toggle that logs which trails received Play and Stop, and warns when the Animator has no Weapon Trails registered.

Common problems:

* **No trail at all.** Check that the weapon is under the Animator (the registry is per Animator), that `Tip`, `Base` and a Profile are assigned, and that the Behaviour's `Trail Index` matches the component's `Index` (or is 0).
* **Trail vanishes instead of fading.** The shader uses the color alpha as opacity or clip source. Turn `Fade Affects Color Alpha` OFF, or use `Fade Drives Alpha Clip`.
* **Tint or Emission does nothing.** The property name does not match the shader. Check the names in the hint above, and make sure Emission / Alpha Clipping are enabled on the material.
* **Faceted trail.** Look at `Captures / Frames per sec`. Raise `Duration`, `Subdivisions` or `Max Segments`, and keep `Min Distance` small.
