Adding LiveSplit Auto Split Support to your Unity Game

A quick guide on how to give your Unity game a high quality speedrunning experience using LiveSplit auto splitters.

Adding LiveSplit Auto Split Support to your Unity Game

In this post, I'll be outlining some of the basics of LiveSplit and auto splitters, how you can add support for them to your Unity game, and how to make your own auto splitter.

What is LiveSplit?

Livesplit Example.png LiveSplit is a popular speedrun timer, allowing speedrunnners to track their overall completion time in a game as well as completion times for individual sections of a game. If you're already familiar with LiveSplit, feel free to skip ahead to the next section.

In LiveSplit, right click -> Edit Splits will bring up a menu that lets you modify the segments or "splits" that your timer will show. By default, your LiveSplit will probably only be showing a single time value, so check the "Use Layout" box and pick "Default Layout" to get a reasonable default view that shows splits. You can also right click -> Edit Layout to customize the layout yourself, but that's out of scope for this guide. LiveSplit_EditSplits.png

Now try starting your timer. You can use right click -> Settings to see the hotkeys for controlling the timer, by default NumPad1 is the key to start and split. Controlling the timer manually while trying to speedrun is difficult and inaccurate, so LiveSplit has support for "Auto Splitters": scripts that control the timer automatically. These scripts work by reading the process memory of the game, and using that information to decide when to start, split, reset, etc.

Expose The Important Variables

In order for the auto splitter to trigger splits at the right time, you need to expose relevant variables. In your Unity game, this is as simple as adding a static class with static variables for whatever you want to expose.

In PogoChamp, the game is broken up into 101 levels. You gain "Stars" for completing levels, with each level granting 1-3 stars depending on how quickly you complete the level. You pick levels from a Level Select screen, and groups of levels are separated by barriers that require a minimum number of stars to open.

If your game already has a speedrunning community, talk to them to understand what kinds of things they use as important milestones in a run and see if you can include variables that would make triggering splits at those times simple. The PogoChamp speedrunning community was focused on speedrunning individual levels at the time I started working on autosplit support, so I decided to include data that would be useful for the standard "Any %" and "100%" categories that apply to nearly every game.

Things like the total star count, current level index, various timers used by the game, an indication of the current game state, and several other fields seemed like they could be relevant to an auto splitter or timer, so I included them. An example from my implementation is below:

public static class AutosplitData
{
    /// <summary>
    /// True if the game is in a load screen.
    /// </summary>
    public static bool isLoading = false;

    /// <summary>
    /// The current state of the game in human readable format.
    /// 
    /// Possible values are: 
    /// "Playing", "Crashed", "Replay", "Victory", "Exiting Level", "Level Select", 
    /// "Cinematic", "Main Menu", "Loading", or null.
    /// </summary>
    private static string gameState = null;

    /// <summary>
    /// True if the player is in a level, false otherwise.
    /// </summary>
    public static bool isInLevel = false;

    /// <summary>
    /// The index of the level the player is in. -1 if not in a level.
    /// </summary>
    public static int levelIndex = -1;

    /// <summary>
    /// The in-game time for the current attempt on this level.
    /// </summary>
    public static float attemptTime = 0f;

    /// <summary>
    /// The total playtime of this save file that you are playing on.  This is the same time that is shown
    /// in the save file selection menu.
    /// </summary>
    public static float totalPlaytime = 0f;

    /// <summary>
    /// The number of stars that you have unlocked.
    /// </summary>
    public static int starCount = 0;

    /// <summary>
    /// The number of rainbow gems that you have unlocked.  This will be 0 unless rainbow gems are
    /// visible (either by enabling them in the in-game speedrun settings, or by collecting all stars).
    /// </summary>
    public static int rainbowGemCount = 0;

    /// <summary>
    /// The total number of jumps that you have done on this save file.
    /// </summary>
    public static int jumpCount = 0;
}

Note: it doesn't seem to matter if a variable is public or private, the autosplitter will still be able to use it.

After you decide what values to expose, it just becomes a matter of wiring it up to your existing code. I recommend keeping these variables "write only" in your game code, and not using them to perform any game logic, but it's up to you how you handle them.

Testing Your Implementation

A quick and simple way to check that your auto split variables are implemented properly is to make a MonoBehavior script that exposes all the variables to the Unity inspector. You can just attach this script to a new object in your initial scene for testing purposes, and then delete the object later. Here's the script I used:


public class AutosplitDataChangeTester : MonoBehaviour
{
    public bool isLoading = false;

    public string gameState = null;

    public bool isInLevel = false;

    public int levelIndex = -1;

    public float attemptTime = -1;

    public float totalPlaytime = 0f;

    public int starCount = 0;

    public int rainbowGemCount = 0;

    public int jumpCount = 0;

    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }

    private void Update()
    {
        isLoading = AutosplitData.isLoading;
        gameState = AutosplitData.GameState;
        isInLevel = AutosplitData.isInLevel;
        levelIndex = AutosplitData.levelIndex;
        attemptTime = AutosplitData.attemptTime;
        totalPlaytime = AutosplitData.totalPlaytime;
        starCount = AutosplitData.starCount;
        rainbowGemCount = AutosplitData.rainbowGemCount;
        jumpCount = AutosplitData.jumpCount;
    }
}

Now you can just view that object in the inspector and make sure the values change properly when you play your game. AutosplitDataViewer.png

Writing an Auto Splitter

Required: Before you can start working on your Auto Splitter, you will need to make sure you have UnityASL.bin in your Livesplit/Components/ folder. That file is what allows LiveSplit to read data directly from Unity. You can get the UnityASL.bin from here: github.com/just-ero/asl-help/raw/main/libra..

Exposing the proper data is one thing, but without an auto splitter script, it doesn't actually do anything. If your speedrun community is big (and technical), they may be able to take the changes you made to the game and make their own auto splitter, but if the community is small you might have to make an initial version yourself.

The Auto Splitter documentation is available here: github.com/LiveSplit/LiveSplit.AutoSplitter.. . It can be a bit overwhelming for someone who isn't familiar with LiveSplit or Auto Splitters, so I'll try to give you some background to help you get started.

What is going on??

Auto Splitters are scripts that control the progress of a LiveSplit timer. The ASL script format allows you to implement several different functions which control the timer. Those functions are:

  • start : If this function returns true, the timer will start (if not already running). Docs link
  • split : If this function returns true, the timer will move on to the next split. Docs link
  • reset : If this returns true, the timer will fully reset. Runs also cannot be started while reset is true. Docs link

The other ASL functions are related to the script's lifecycle, and to specific features that LiveSplit provides, for example startup, init, update, shutdown, isLoading, gameTime, etc. For a full list, check the Actions section of the documentation.

Getting Started

Unity ASL Links:

You'll start by putting the process name of your game in the state function. (Pro Tip: Use Task Manager to grab the real name, not the name of the window).

state("") {}

Then in startup you'll need to load the UnityASL.bin, so that it can read the variables from Unity.

startup
{
    vars.Unity = Assembly.Load(File.ReadAllBytes(@"Components\UnityASL.bin")).CreateInstance("UnityASL.Unity");
}

And in init you can assign the variables you'd like to access from Unity. Here I use some of the PogoChamp values as an example. Note that Make<T> and MakeString are different functions. Each of these Make lines are assigning a name to a Unity variable so that it can be looked up in other functions using vars.Unity["<name>"]. You can find the full list of Unity ASL functions in the documentation.

init
{
    vars.Unity.TryOnLoad = (Func<dynamic, bool>)(helper =>
    {
        var myClass = helper.GetClass("Assembly-CSharp", "AutosplitData");

        vars.Unity.Make<bool>(myClass.Static, myClass["isLoading"]).Name = "isLoading";
        vars.Unity.MakeString(myClass.Static, myClass["gameState"]).Name = "gameState";

        vars.Unity.Make<int>(myClass.Static, myClass["levelIndex"]).Name = "levelIndex";

        vars.Unity.Make<float>(myClass.Static, myClass["attemptTime"]).Name = "attemptTime";
        // ....

        return true;
    });

    vars.Unity.Load(game);
}

And then in update, you just need to update the Unity variables

update
{
    if (!vars.Unity.Loaded) return false;

    vars.Unity.Update();
}

At this point, all the Unity specific stuff should be set up, and it's just a matter of implementing start, split, reset or whatever other functionality you wanted. In general you will be accessing your variables through vars.Unity["variableName"], which will wrap your value inside an object that has Current, Old and Changed fields. The PogoChamp.asl file has plenty of examples of how to use those variables to decide when to split, start or reset.

Testing

Information on how to test and debug your script is available in the Auto Splitter documentation, and the Speedrun Tool Development Discord.

Releasing

Once your auto splitter is finished, you can add it to the Auto Splitters XML file in Livesplit. Information about that is available here: github.com/LiveSplit/LiveSplit.AutoSplitter..

It's a pretty straight forward process, just edit the file and add in the info for your new splitter. The one thing to note is that you'll need to include a reference to the UnityASL.bin in the <URLs> section. Here's the XML for PogoChamp:

    <AutoSplitter>
        <Games>
            <Game>PogoChamp</Game>
        </Games>
        <URLs>
            <URL>https://raw.githubusercontent.com/JakeRabinowitz/PogoChampAutosplitter/main/PogoChamp.asl</URL>
            <URL>https://github.com/just-ero/asl-help/raw/main/libraries/UnityASL.bin</URL>
        </URLs>
        <Type>Script</Type>
        <Description>Autosplit support with several different modes, made by the developer of PogoChamp.</Description>
        <Website>https://github.com/JakeRabinowitz/PogoChampAutosplitter</Website>
    </AutoSplitter>

Once your pull request is accepted, anyone using LiveSplit will be able to use the auto splitter for your game by simply hitting the "Activate" button from the menu in LiveSplit. LiveSplit_Activate.png

Conclusion

I hope this helped you get a better understanding of what Auto Splitters are, how they work, and how you can add them to your Unity games! Let me know if this inspired you to add Auto Splitter support to your game, and if you have any questions feel free to ask them in the comments.