ContextActionService improvements

Hello, fellow kids. I’ve been working on some fun features that should make ContextActionService a lot more attractive.

I’m sure we’ve all encountered the downsides of ContextActionService. You can’t use it with mouse clicks because it ALWAYS eats input, and you have almost no control over what takes precedence. And it’s totally unclear as to what’s even bound!

ContextActionResult

Now you can decide whether or not a bound action should sink or pass the input event, meaning other things (including other bound actions) can process it.

To use this, simply return Enum.ContextActionResult.Pass in your callback function, and every action below it in the stack will get a chance to process it. It will also continue on to SurfaceGuis and the like, and it won’t make the second argument to UIS.InputBegan/Changed/Ended true. This requires no change to your existing code, because it will treat anything other than Pass as Enum.ContextActionResult.Sink, including nil. It will also sink if you yield in your callback (i.e., you use wait()).

Here’s an example of this in action:

ContextActionService:BindAction("Bottom", function(actionName, inputState, inputObj)
    if inputState == Enum.UserInputState.Begin then 
        print("Action named", actionName, "was fired!")
    end
end, false, Enum.KeyCode.F, Enum.KeyCode.E)

ContextActionService:BindAction("Top", function(actionName, inputState, inputObj)
    if inputState == Enum.UserInputState.Begin then 
        print("Action named", actionName, "was fired!")
    end
    if inputObj.KeyCode == Enum.KeyCode.E then
        return Enum.ContextActionResult.Pass
    end
end, false, Enum.KeyCode.F, Enum.KeyCode.E)

Pressing F will give you the following output:

Action named Top was fired!

Pressing E will give you the following output:

Action named Top was fired!
Action named Bottom was fired!

ContextActionPriority

You can also decide what priority to bind your action to; do you want it to be the lowest priority so it always remains when more contextual stuff comes and goes? Or do you want to make sure it always gets first dibs?

Now, all you have to do is use ContextActionService:BindActionAtPriority(actionName, callbackFunc, createTouchButton, priorityLevel, ...). That new argument, priorityLevel, is an integer. It’s a little like BindToRenderStep in that there’s an Enum.ContextActionPriority that provides sensible defaults spaced out by 1000.

All actions made with ContextActionService:BindAction will default to a priority of Enum.ContextActionPriority.Default.Value, or 2000. In the future, we plan on updating the default control scripts to use Enum.ContextActionPriority.Low.Value in order to avoid accidentally overriding developer bindings to WASD and so on. You shouldn’t need to make any changes to your existing code, but let me know if something doesn’t work correctly.

Here’s a usage example:


ContextActionService:BindActionAtPriority("FirstOneBound", function(actionName, inputState, inputObj)
    if inputState == Enum.UserInputState.Begin then 
        print("Action named", actionName, "was fired!")
    end
end, false, Enum.ContextActionPriority.High.Value, Enum.KeyCode.F)

ContextActionService:BindActionAtPriority("SecondOneBound", function(actionName, inputState, inputObj)
    if inputState == Enum.UserInputState.Begin then 
        print("Action named", actionName, "was fired!")
    end
end, false, Enum.ContextActionPriority.Default.Value, Enum.KeyCode.F)

If you press F, you’ll see the following output:

Action named FirstOneBound was fired!

This is because FirstOneBound was bound at a higher priority level than SecondOneBound. Without this update, you would expect SecondOneBound to take precedence because it was bound later.

If you bind two actions at the same priority level, you’ll get the same old stack-based behavior, meaning none of your code needs to be updated to work after this update.

Action Bindings tab

To top it all off, you will now notice a new tab in the developer console when you hit F9. It’s called “Action Bindings” and it just provides a list of all the input types and keycodes currently bound to ContextActionService. If you click on one of the rows, it’ll expand to show you all of the actions that are binding that input type and what priority level they’re at. They’re sorted by priority, and actions that are bound by Roblox’s CoreScripts will appear in italics with a red background.

Hope it helps!

61 Likes

Cool beans.

8 Likes

Sweet.

1 Like

Will make a feature request post for this too, however:

It would be awesome if we could bind to mouse buttons using this service too for the sake of having everything in one place.

On top of that it would be awesome if an option to have the binds re-bindable via a bool / some other way when running BindAction and then have a keybind GUI added to the menu…

2 Likes

You can bind to mouse buttons, just use Enum.UserInputType.MouseButton1/2/3.

I’ve been thinking of a way to make it possible to rebind in-game. This would likely be some sort of object you pass to the end of BindAction/BindActionAtPriority that has a default binding and a unique name.

4 Likes

Oh lord, was this recent? I was sure I tried to do that when I made my own rebind system…! <3

1 Like

I don’t think so! It’s just more feasible to use now, since you can decide to pass it if it isn’t relevant instead of always eating it.

2 Likes

Quite handy.

2 Likes

This is awesome.

3 Likes

Thaaankkk youuuuu F000000000D

2 Likes

Yessss! This update is perfect! Thank you so much.

3 Likes

Is it possible to bind over a core action momentarily? I want to use the L1 and R1 buttons to navigate through tabs in a gui while it’s displayed, but my bound actions never fire while there is something in the player’s backpack.

local ContextActionService = game:GetService('ContextActionService')
ContextActionService:BindActionAtPriority('HighPriorityR1', function(name, state, input)
	print('Fired')
end, false, Enum.ContextActionPriority.High.Value, Enum.KeyCode.ButtonR1)

This works on an empty baseplate if there are no tools in the backpack, but as soon as a tool is added my callback stops firing. Is that intended functionality? If so, is there some other way I can temporarily bind over core actions (specifically using R1 and L1 to switch through tools in the backpack)?

3 Likes

Yeah, that’s an unfortunate side-effect of using Core actions for that. I don’t think there is any way other than just disabling the Backpack.

I think we could consider making these actions non-core. They don’t really need to be.

5 Likes

Speaking of Backpacks, is there a chance of being able to modify it’s UI?

Yeah, the plan is to move backpack into PlayerScripts. No ETA yet.

6 Likes

Ah, alright. :stuck_out_tongue:

Hoping it’s out later this year or early next year.