Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Action wheel (tutorial)

From FiguraMC
Revision as of 05:42, 1 December 2024 by PenguinEncounter (talk | contribs) (end road work)

The Action Wheel is a GUI element added by Figura which allows you to add additional functionality to your avatar.

By default, the action wheel is opened by holding down the B key. This can be changed in Figura's Settings.

The Action Wheel consists of a set of Pages, of which only one can be active at a time. Each page can have an unlimited number of Actions. Only 8 Actions can be rendered at a time, however — if more than 8 Actions are active on a page, the scroll wheel can be used to navigate between the sets of Actions (8 at a time.)

For full documentation, see:

  • ActionWheelAPI for the global action_wheel,
  • Page for pages, and
  • Action for actions.

Basic Example

The first step to using the Action Wheel is to create a page to hold the Actions, via newPage.

local mainPage = action_wheel:newPage()

This creates a new page, but doesn't do anything else. If you save the script and try to open the Action Wheel (B), you will see a message stating that there is no active page. Using setPage with the newly created page will change the current active page:

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

The message about not having an active page should be gone now, but the page is still empty. Let's add some actions.

Adding Actions

Create a new action by calling newAction on a Page, like so:

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

local action = mainPage:newAction()

Because we haven't set up the visuals, the action is essentially invisible, but you should still be able to hover it with your mouse and have it be highlighted. Let's add some visuals!

ⓘ Note

Most methods applying to Actions return the the Action being worked upon. This also applies to quite a few other kinds of object in Figura. In the documentation, this is noted by the presence of the word 'self' in the Return Type column. Methods that return 'self' are handy because they allow for 'chaining', as demonstrated below.

  • title: Set the text displayed when hovering over the action
  • item: Use an item as the icon to be displayed
  • hoverColor: Set the background color of the action when it is hovered over, as red/green/blue
local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

local action = mainPage:newAction()
    :title("My Action") -- displayed when hovering the action
    :item("minecraft:stick") -- action icon
    :hoverColor(1, 0, 1) -- magenta

Making Actions work

Now that you can see the action, it's time to add some functionality to it. It is possible to define behavior for when an action is left-clicked and right-clicked separately. For this tutorial, we will define a left-click action.

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

local action = mainPage:newAction()
    :title("My Action") -- displayed when hovering the action
    :item("minecraft:stick") -- action icon
    :hoverColor(1, 0, 1) -- magenta
    -- this method sets the leftClick field of the Action
    :onLeftClick(function()
        print("Hello, world!")
    end)

If you click the Action in your Action Wheel (don't forget to save), it should write "Hello, world!" in your chat. However, there is a problem with the current code — its effects won't be seen by other players when playing multiplayer.

Making Actions work in multiplayer

For more information, see Pings (Tutorial).

Due to the nature of Figura, all code is run client-side. Because the Action Wheel is entirely added and controlled by Figura, the Minecraft Server will never synchronize it with other clients. To fix this problem, we need to utilize Pings to synchronize the actions you take with the Action Wheel.

The first step to this process is to move the code that would normally be executed when you click the Action into a ping function. Next, replace the 'anonymous function' (function()...end block) with the ping function.

All ping functions must have unique names. If you're adding multiple actions, you need to use a different name for each ping function. Otherwise, every Action will run the same function (this is probably not what you want!)

ⓘ Note

Choose a function name that describes what the function does. It will help you (and people giving support) understand your code when reading it in the future.

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

-- Create a ping function that does the same thing that the Action did before
-- The function needs to be defined above the code that defines the Action.
function pings.printHelloWorld()
    print("Hello, world!")
end

local action = mainPage:newAction()
    :title("My Action") -- displayed when hovering the action
    :item("minecraft:stick") -- action icon
    :hoverColor(1, 0, 1) -- magenta
    -- Pass the ping function directly to onLeftClick - note that there is no () after 'printHelloWorld'
    :onLeftClick(pings.printHelloWorld)

This code correctly synchronizes your actions across clients!

If you need to pass arguments to the function (i.e. sending data only the host knows), you need to do a bit more work. For this example, we are sending a random number to the function and we want to use Pings so that each client sees the same number (rather than selecting a different random number on each client.)

We need to wrap the call to the ping inside another function:

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

function pings.printANumber(a)
    print("the number is ", a)
end

local action = mainPage:newAction()
    :title("My Action") -- displayed when hovering the action
    :item("minecraft:stick") -- action icon
    :hoverColor(1, 0, 1) -- magenta
    :onLeftClick(function()
        pings.printANumber(math.random())
    end)

Watch out for this common mistake:

-- THIS CODE DOES NOT WORK, DON'T DO THIS
-- ... more action stuff ...
    :onLeftClick(pings.printANumber(math.random()))

While this may appear to make sense, it actually calls the ping function and then sets the result as the left-click action. Because ping functions don't return values (effectively, they always return nil) this code removes the left-click action.

Full example

local mainPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

function pings.actionClicked() -- don't forget to change the name if you're re-using this
    print("Hello, world!")
    -- one of the most common things to do is play an animation (like an emote wheel)
    -- you'll need to fill out the path to your Blockbench model and name of the animation to use this
    -- animations["<MODEL PATH HERE>"]["<ANIMATION NAME HERE>"]:play()
end

local action = mainPage:newAction()
    :title("Action name")
    :item("minecraft:stick")
    :hoverColor(1, 0, 1)
    :onLeftClick(pings.actionClicked)

Toggle Example

Read the Basic Example first to set up a page.

By using setOnToggle instead of onLeftClick, an Action can be turned into a toggle that can be turned on (true) and off (false).

There are also various Action methods that apply when an Action is toggled on. These are prefixed with toggle.

Toggle functions accept one argument (the toggle state, a boolean).

-- this uses the mainPage from the previous tutorial

function pings.toggleModel(state) -- rename to something relevant
    -- generally hiding your entire model is a bad idea but it demostrates the concept
    models:setVisible(state)
    --- If you want something to be enabled when the toggle is *disabled*, use 'not':
    -- models:setVisible(not state)

    --- You can play a looping animation continuously while a toggle is enabled:
    --- Same deal as last time; replace the model name and animation name with your own
    -- animations["<MODEL PATH HERE>"]["<ANIMATION NAME HERE>"]:setPlaying(state)
end

local modelToggle = mainPage:newAction() -- if erroring here: does the page exist?
    :title("Model disabled")
    :toggleTitle("Model enabled")
    :item("red_wool")
    :toggleItem("green_wool")
    :setOnToggle(pings.toggleModel)

Using multiple Pages

Using multiple pages allows you to create more complex setups, such as an action opening a 'sub-menu' of sorts with more actions.

Setting up multiple pages is pretty simple — use newPage multiple times and store each page in its own variable.

local mainPage = action_wheel:newPage()
local secondPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

Create an action to switch to a different page:

local mainPage = action_wheel:newPage()
local secondPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

-- This action switches to the second page when clicked.
local switchToSecond = mainPage:newAction()
    :title("Switch To Second Page")
    :item("arrow")
    :onLeftClick(function()
        -- You don't need to ping page changes because they only matter
        -- on the host (viewers don't care)
        action_wheel:setPage(secondPage)
    end)

Do something similar to create a second action to go back:

local mainPage = action_wheel:newPage()
local secondPage = action_wheel:newPage()
action_wheel:setPage(mainPage)

-- This action switches to the second page when clicked.
local switchToSecond = mainPage:newAction()
    :title("Switch To Second Page")
    :item("arrow")
    :onLeftClick(function()
        -- You don't need to ping page changes because they only matter
        -- on the host (viewers don't care)
        action_wheel:setPage(secondPage)
    end)

local switchToMain = secondPage:newAction()
    :title("Switch To Main Page")
    :item("item_frame")
    :onLeftClick(function()
        action_wheel:setPage(mainPage)
    end)

Now you should be able to switch between the pages! Each page has its own set of actions.