📗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.

All code is under the namespace AxonGenesis. Some object and class names have been prefixed with Timeflow (ex TimeflowObject, TimeflowAudio, etc) to clearly distinguish them from Unity or system types without having to qualify them additionally.

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.

Partial Classes

Many class implementations in Timeflow have been split across multiple files using the partial keyword to better manage complexity and separate features by editor and function.

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

This may be used as a base class for any components which don't require Timeflow integration, such as a manager or controller script. This is only required if you wish to take advantage of the tools in the AxonGenesis space for your own custom scripts.

TimeflowBehavior

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

Please follow the inheritance model by overriding virtual methods defined in the base class as needed. This abstracts the built-in Unity calls and provides additional logic required by Timeflow. For example: instead of defining Awake(), override OnAwake() to implement your behavior. A similar pattern follows for most methods.

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.

Each component receives 1 update per frame, however the base classes offer several methods which a script may override one or all of. Which methods your script uses depends on the update logic needed.

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.

UpdateTime is executed in hierarchical order, from the top most parent (Timeflow) down through all TimeflowObjects and then to each TimeflowBehavior. It's important to implement UpdateTime for anything that is order-dependent.

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.

Whenever implementing a TimeflowBehavior that uses channels, it is preferable to do all updating during UpdateTimeChannel, rather than UpdateTime or OnUpdate. If the behavior has multiple channels which share common calculations, UpdateTime may be implemented to perform them.

UpdateTime is called on each behavior before each of it's channels are updated through UpdateTimeChannel.

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 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.

Property handlers always represent a type of Component applied to a game object. However, the data you are controlling may belong to any other type referenced by the component. This may require creating a simple MonoBehaviour script to contain any additional references that your handler may need access to (such as an asset file, or scriptable object).

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.

Each property handler is registered automatically by class name using System.Reflection. Class names must follow the format PropertiesOfType where Type is the exact name of the component class it represents. Each component type then is represented by the handler.

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

Channel names are displayed in the Timeflow view and may be changed. Since names can easily be changed, it is often better to use the channel's UniqueID to consistently identify a channel.

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.

Channel IDs may be obtained by script, or if the Inspector window is changed to debug mode then the Unique ID field can be located within the channels list. The Unique ID is generated once when the channel is first created and does not change.

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