Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Custom Timeline for Unity

A lightweight, data-driven timeline system for Unity, built upon ScriptableObject assets. It provides an intuitive editor and an extensible runtime component for creating and managing reusable event sequences, ideal for character skills, VFX, audio cues, and simple cutscenes.

The core design decouples timeline data from scene objects, allowing for a robust, modular, and scalable workflow.

3


Features

1. Visual Editor

  • Integrated Editor Window: A standalone, dockable editor window for all timeline-related tasks.
  • Resizable Panels: The UI is composed of three resizable panels: the Track Group List, the Timeline View, and the Inspector, allowing for flexible layout customization.
  • Direct Clip Manipulation: Clips on the timeline can be moved via drag-and-drop and resized from either edge. A snapping utility ensures precise placement.
  • Track and Group Management: Organize tracks into logical groups. Tracks can be added, removed, and reordered within their group.

4

2. Data-Driven Workflow

  • ScriptableObject Based: All timeline data is stored as .asset files. This decouples the sequence logic from prefabs or scenes, making the data highly reusable and source-control friendly.
  • Save/Load Functionality: The editor provides simple controls to save the current state to a CustomTimelineAsset or load an existing one.
image

3. Extensible "Notify" System

  • Custom Event Injection: Attach custom events, known as "Notifies," to any clip. These Notifies execute logic at the clips Start, Update (while active), and End events.
  • Automatic Inspector Generation: Classes derived from NotifyBase appear in the inspector automatically. Supported public fields include string, float, int, bool, Vector2, Vector3, Color, enums, and Unity asset references. Unsupported field types are identified in the inspector.
image

4. Runtime Playback

  • CustomTimelinePlayer Component: A lightweight runtime component that advances CustomTimelineAsset data and emits clip callbacks.
  • Playback API: Select a group with player.Play("GroupName") or player.Play(GroupID). The owning component must call player.OnUpdate(deltaTime) and connect the clip callbacks to NotificationDispatcher. TestUnit provides a complete integration example.

Design and Efficiency

Reliability and Performance

  • Reliable Event Processing: The editor and runtime share CustomTimelinePlayback. Clip boundaries are sorted once per playback, processed in chronological order, and split exactly at loop boundaries. Active clips receive updates at intervals of at most 0.01 seconds. Empty intervals skip directly to the next event.
  • Optimized Runtime Lookups: Upon initialization, the CustomTimelinePlayer caches track groups in a Dictionary, providing an O(1) time complexity for lookups when Play() is called.
  • Safe Lifecycle: Stop, disabling the player, changing its data, and switching groups finish active clips. Event callbacks may stop or switch playback. Negative or non-finite time/speed inputs are rejected; speed 0 pauses advancement.
  • Bounded Catch-up: Each update processes at most 1,000 time steps. Excess elapsed time is retained for subsequent updates, preventing a long frame or very short loop from blocking the caller indefinitely.
  • Authoring Safety: Editing uses a separate working copy, supports Undo/Redo, warns about unsaved changes, and validates data before saving. Save As creates an independent asset, and saving outside the project's Assets folder is rejected.

Core Design: Extensibility

The systems primary strength is its extensibility. The Notify architecture allows for the addition of new functionality without modifying the core timeline source code.

Process for adding a new Notify type:

  1. Define a Notify Data Class: Create a new class that inherits from NotifyBase. Define public fields for any data required by the Notify.
// Defines the data for a sound-playing notify.
[System.Serializable]
[UnityEngine.Scripting.Preserve]
public class PlaySoundNotify : NotifyBase
{
    public AudioClip soundClip;
    public float volume = 1.0f;
}
  1. Implement a Notification Handler: Create a class that implements the INotificationHandler interface. This class defines the logic that will execute when the associated Notify is triggered.
// Defines the runtime logic for the PlaySoundNotify.
[UnityEngine.Scripting.Preserve]
public class PlaySoundNotifyHandler : INotificationHandler
{
    public Type NotifyType => typeof(PlaySoundNotify);

    public void OnClipStart(GameObject owner, NotifyBase notify)
    {
        var myNotify = notify as PlaySoundNotify;
        // Logic to play myNotify.soundClip on an AudioSource attached to the owner.
    }

    public void OnClipUpdate(GameObject owner, NotifyBase notify, float progress) { }
    public void OnClipEnd(GameObject owner, NotifyBase notify) { }
}

NotificationDispatcher discovers concrete handlers with public parameterless constructors. Keep the Preserve attributes on reflection-discovered types for managed stripping/IL2CPP builds, or reference and register a handler explicitly through NotificationDispatcher.Register. Register custom instances before dispatching events; do not register a second handler for an already registered Notify type. Handlers are shared across owners, so keep them stateless or explicitly scope state by owner. PlaySoundNotify then appears in the editor's Add Notify dropdown.


Getting Started

  1. Open this repository as a Unity project, or copy Assets/UnityCustomTime and its .meta file into your project's Assets folder. Keep all included .meta files. Runtime and Editor contain the core tool; Samples is optional. Editor code must remain under an Editor folder. Existing serialized type names and script GUIDs are preserved.
  2. Open the editor via the Custom > CustomTimelineWindow menu item.
  3. Create a Track Group and a Track.
  4. Right-click on a track to add a Clip.
  5. Select the clip and use the Inspector panel to add and configure Notifies.
  6. Use the Save button to create a CustomTimelineAsset.
  7. Add CustomTimelinePlayer and TestUnit to a GameObject and assign the saved asset in the player's Timeline Data field. Samples/Scenes/SampleScene.unity already contains this setup. If you omit Samples, use your own owner component as described below.
  8. Use TestUnit's Play button in the Inspector to try the sample. From gameplay code, call player.Play("YourGroupName") or unit.Play(GroupID). TestUnit advances the player and dispatches Notify callbacks; a custom owner can implement the same pattern.

Folder layout

Assets/UnityCustomTime/
  Runtime/          Timeline data, playback and Notify handlers
  Editor/           Timeline window and asset editing
  Samples/
    Runtime/        TestUnit integration example
    Editor/         TestUnit inspector
    Scenes/         SampleScene
    Prefabs/        TestUnit and SpawnSample
    Data/           Example timeline asset
  Data/             Default folder for new timelines (created on first save)
  README.md
  LICENSE.txt

New timelines default to Assets/UnityCustomTime/Data. The save dialog also accepts other folders inside Assets. Sample prefabs live outside Resources, so installing the sample does not automatically include those prefabs in every player build.

Runtime and Editor require no Input System or 2D Sprite package. The optional sample's square sprite uses Unity's 2D Sprite package; install that package or assign your own sprite. The repository's InputSystem_Actions.inputactions file and ProjectSettings are Unity project scaffolding and are not part of the distributable folder. Include LICENSE.txt when redistributing the tool.

Runtime integration

CustomTimelinePlayer intentionally uses an external clock. Call OnUpdate exactly once per frame or simulation tick. It does not start automatically when the scene loads. TestUnit subscribes in OnEnable, stops and unsubscribes in OnDisable, and calls OnUpdate(Time.deltaTime) during play.

Use SetData(asset) when replacing data, including when refreshing an asset modified by your own code. Group IDs and names must be unique. Playback samples the group's tracks and timing when Play is called; stop and restart after changing authoring data. The editor stops its preview when data is edited or the selected group changes. Dragging the time slider moves the playhead and stops playback; it does not reconstruct gameplay state at that time.

The built-in SpawnPrefabNotify creates a persistent object at a world-space position during Play Mode. The owner is responsible for destroying or pooling spawned objects. Its edit-mode branch does not spawn objects; LogNotify can be used to check event timing outside Play Mode. Reverse playback is not supported.

Validation

The project records Unity 6000.2.7f2. Release hardening is validated in an isolated copy using the installed Unity 6000.2.10f1 editor; project settings and package versions are not upgraded by this change. The custom timeline runtime uses Unity's core APIs and has no dependency on the Unity Timeline package. Other platforms and IL2CPP require their own build validation.


License

This project is licensed under the MIT License.

About

a data-driven visual timeline tool for unity, featuring an extensible notify event system and scriptableObject-based assets.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages