📗Scripting

Kickstart guide to scripting with Timeflow

This documentation provides a brief introduction and overview intended for intermediate to advanced Unity developers with some experience writing C# scripts in Unity.

If you have not done so already, please read over How it Works to get an overview of general structure of Timeflow objects and channels.

Base Classes

Timeflow uses class inheritance to extend MonoBehaviour with virtual methods that wrap or extend standard Unity methods. It is suggested to get familiar with these classes by reviewing the code, which offers further explanation in the comments.

AxonGenesisBehavior

This is the lowest level base class which wraps MonoBehaviour. Instances of this class should not be applied to game objects directly, but only as the base class for other components. This includes debugging features, improved update methods, and common settings. For further details, please refer to AxonGenesisBehavior.cs

TimeflowBehavior

All components that integrate with Timeflow must be derived from TimeflowBehavior, or subclass, to ensure updating and timing are handled properly.

For a simple example of a custom behavior, see Distance.cs.

Update Methods

All behaviors update through the virtual methods: OnUpdate, UpdateTime, and UpdateTimeChannel. This is initiated by Unity's standard Update, however Timeflow manages updates hierarchically through all objects and behaviors in order. This is necessary to resolve dependencies and to create a layered system of channels.

If update methods are incorrectly used, behaviors may not function or may produce unexpected results. There are additional considerations such as an object's Update Settings. Please refer to the code and comments for a more a comprehensive picture of the methods and options available.

OnUpdate()

This method extends the built-in Update function and is not managed by Timeflow. The order of updating is based on Unity's Script Execution Order. Use this method to implement any update processing which is not dependent on the current time in Timeflow.

To take advantage of inheritance of the AxonGenesisBehavior base class, it is strongly advised to override OnUpdate rather than re-defining the standard Update function, which would replace the base class implementation.

UpdateTime()

This method should be implemented for general updating at the current time and is preferred over OnUpdate for Timeflow behaviors. Use this method to perform any general calculations not specific to channels. This method is called on the same frame before UpdateTimeChannel.

UpdateTimeChannel(TimeflowChannel channel)

This method should be implemented for all behaviors that use channels. Each channel is updated according to its order and Update Settings, regardless of the order of behaviors or objects.

The calculations performed during UpdateTimeChannel depend on the desired behavior, but generally the channel should calculate a new output value or update animation based on the channel's local time.

For examples, please look at implementations of UpdateTimeChannel throughout the code, including the default behavior implemented in the base class TimeflowBehavior.

If a TimeflowBehavior implements channels but does not implement UpdateTimeChannel to calculate the channel's value, then the channels may not work properly with Channel Link, which pulls data from each behavior using this method. Also, channel links may have time offsets, so the behavior should be able to accurately calculate the value for any time provided, whenever possible.

Property Mapping

Timeflow uses reflection to map animations to specific component properties or fields. All properties that are read/write enabled are available to animate in Timeflow. When adding a new channel or selecting a property, the menu will auto-populate with all fields and properties found in each component on the game object.

Nested Properties

Whenever components use structs or class composition to encapsulate additional serialized fields, these are not mappable directly in Timeflow. To solve this, classes should provide public accessors at the component level to get/set the underlying values for any properties that might be animated.

Here is a very simplified example showing how to provide an accessor to animate fields that are otherwise inaccessible due to encapsulation or permission.

using UnityEngine;

public class ItemManager : MonoBehaviour
{
    [System.Serializable]
    private class Item
    {
        // Not accessible due to encapsulation
        [SerializeField] private float Amount;
    }

    [SerializeField] private Item item = new Item();

    // Public accessor for Amount that is animatable in Timeflow
    public float ItemAmount
    {
        get => item.Amount;
        set => item.Amount = value;
    }
}

Property Handlers

Timeflow uses a custom method of mapping animation to properties, covered in more detail in Properties. By default, properties are automatically discovered using System.Reflection to find all writable fields and properties for each component and material on a game object. However, there are cases where this approach doesn't apply and extra steps are needed.

Custom Properties

There may be values you want to animate but there's no way to map a property to it. If the target field does not belong to a component or material, such as when using a ScriptableObject for example, then it is necessary to use an intermediate script that handles the reading and writing of property values, using the programming interface required for that type.

In practice, what this means is that the property handler implements all methods pertaining to reading and writing data for the specific component type it represents.

Volumes Example

Volumes are relatively new to Unity and only exist in URP and HDRP. They provide additional render settings such as post processing and more. However, due to the way Volumes store settings, their properties are not immediately accessible to Timeflow. To solve this, the PropertiesOfVolume.cs handler script was created to manage listing and reading/writing data using Unity's API for volume parameters.

All classes derived from PropertiesHandler must be in the AxonGenesis namespace.

Transform Example

Another key example is PropertiesOfTransform.cs. This blocks direct access to transform.localPosition and all the other fields on a transform component that probably shouldn't be animated. Instead, it shows a nicely formatted list of standard properties a user would expect, and ensures these values are read from and written to the transform component correctly. This handler also hides other properties of Transform that would be confusing or not suited to animate.

For more code examples, search for scripts starting with the name PropertiesOf.

Scripting Timeflow Channels

It is possible to also leverage Timeflow animation channels in your own scripts for custom interpolation and other uses. First, you need to get a reference to the containing TimeflowBehavior or TimeflowObject component which owns or manages the channel (using GetComponent), and then you need to get the channel reference using a name or unique ID, using GetChannel(name) or GetChannelByID(id).

Once you have obtained a reference to a channel, you may perform operations such as InterpolateValue() to get the channel's animated value at any arbitrary time. Additional Interpolate methods exist for each value type, depending on your use case. Refer to the source code of TimeflowChannel.cs for all available methods.

To see an example, please refer to the included example script InterpTimeflowChannel.cs

TimeflowChannel references should not be stored as serialized fields in scripts, otherwise it will result in a copy of the channel being stored instead of a references. This is because TimeflowChannel's are simple serialized objects and not MonoBehaviors. Therefore, to maintain a reference to a channel it is recommended to store the channel's UniqueID string and use the method TimeflowBehavior.GetChannelByID() during setup in your script.

Experimental Features

There are a few extra features throughout the AxonGenesis namespace that may be enabled by adding scripting define symbols in the player settings. These features are not recommended, nor fully supported or documented, so please use at your own risk. The main reason for mentioning it here is for those curious about the use of these symbols in the code.

AXON_DEVELOPMENT This includes features that either aren't relevant or incompletely implemented. Do not use this as it will result in immediate errors due to missing scripts. All of these features are either deprecated or features in development for future releases.

AXON_EXPERIMENTAL This symbol may be safely used, however please note that the few minor features it unlocks may not be useful or reliable. They are strictly experimental and may or may not be a part of future development. These features are not documented.

Last updated