Skip to content

TAS Movie Script Tutorial

Eddio0141 edited this page Mar 2, 2023 · 16 revisions

Prelude

The movie scripts accepted for UniTAS are lua 5.2 scripts, and this tutorial won't cover how lua works, but it should be simple to understand how it works with other tutorials on lua

A simple TAS

To start off, we'll make a simple TAS that just presses the space bar every frame for 3 seconds

MOVIE_CONFIG = {
    fps = 60
}

for i = 1, 30 * 3 do
    key.hold("Space")
    adv()
    key.release("Space")
    adv()
end

Let's break this down

MOVIE_CONFIG = {
    fps = 60
}

This is the configuration for the movie, and it's required to be defined before the first adv call

You can check out what's possible to configure in the TAS Movie API page


key.hold("Space")
adv()
key.release("Space")
adv()

This is the main part of the script, and in this case, key.hold and key.release are the methods that are used to hold and release a keyboard key

and adv is the method that is used to advance the frame in the movie


If your script looks like this

-- first frame
adv()
-- second frame

The first frame is ran BEFORE the adv() call, and the second frame is ran AFTER the adv() call, in total, the script will run for 2 frames

If you want to check out what other methods are available, you can check out the TAS Movie API page

A TAS with concurrent method

Now let's make a TAS that will hold space if the fps goes below 30, and release space if the fps goes above 30

MOVIE_CONFIG = {
    fps = 60
}

function concurrent_method()
    if env.fps < 30 then
        key.hold("Space")
    else
        key.release("Space")
    end
end

concurrent.register(concurrent_method)

adv(30)
env.fps = 10
adv(30)

Let's break this down

function concurrent_method()
    if env.fps < 30 then
        key.hold("Space")
    else
        key.release("Space")
    end
end

This is the concurrent method, and it will be ran concurrently with the main script coroutine

This itself wouldn't do anything, so we need to register it


concurrent.register(concurrent_method)

This will register the concurrent method, and it will be ran before the main adv() call

By default, registering a method will immidiately run it on that line, but you can change the behaviour by passing a second argument


adv(30)
env.fps = 10
adv(30)

To finish off our code, here we're frame advancing 30 frames, and setting the fps to 10, and frame advancing 30 frames again

This makes it so the concurrent method will hold space at the second adv() call

A concurrent method will run indefinitely every env(), unless manually stopped or the script is stopped


Purpose of the concurrent method is to automate inputs in the background of the TAS

As a real world example, you can have a concurrent method that will press the jump button if the player lands on the ground, making it so the player will jump automatically


You can also register a method that will run after the main adv() call

concurrent.register(concurrent_method, true)

Unlike the default behavior, registering a method with the second argument being true will not immidiately run it on that line


What if you want the concurrent method to run only once?

In that case, you can use concurrent.register_once, which has the same arguments as before

concurrent.register_once(concurrent_method)

Advanced stuff

Main scope

The main scope is a coroutine in disguise, where the adv() call is the coroutine.yield() call, and normally you can't yield if its not in a coroutine

However the main scope is secretly wrapped in a method as a coroutine like this

-- main script
key.hold("Space")
adv()
key.release("Space")
adv()

Into this

return function()
    -- main script
    key.hold("Space")
    adv()
    key.release("Space")
    adv()
end

You can change this behavior by setting MOVIE_CONFIG.is_global_script to true

MOVIE_CONFIG = {
    is_global_script = true
}

This will make the main script not wrapped in a coroutine, and you must instead return a function that will be ran as the main script like this

return function()
    -- main script
    key.hold("Space")
    adv()
    key.release("Space")
    adv()
end

Concurrent methods

Concurrent methods are also coroutines, and you can yield in them

If you have a method like so registered as a concurrent method on pre-advancing

function concurrent_method() end

You can think of the script running like this

concurrent_method()
adv()
concurrent_method()
adv()

However if you have a method like so registered as a concurrent method on post-advancing

function concurrent_method() end

concurrent.register(concurrent_method, true)

You can think of the script running like this

adv()
concurrent_method()
adv()
concurrent_method()
Clone this wiki locally