Virtual Controls
Use virtual controls to give a mobile player on-screen movement, camera, and action controls without changing the character’s abilities. UCC supplies one layout for Unity’s Input System and another for the legacy Input Manager.
Before you begin
- Set up and test the character with its normal input first. See Input to choose the Unity Input System or legacy Input Manager route.
- Add the scene managers, camera, and Canvas before arranging a final mobile HUD. Quick Setup covers that scene workflow.
- Use the virtual-control type that matches the character’s input component. The two supplied layouts are not interchangeable.
Choose a control type
| Character input | Setup Manager Input Type | Generated root component | How controls send input |
|---|---|---|---|
| Opsive Unity Input System | InputSystem | On Screen Controls | Unity On-Screen Button and On-Screen Stick components feed the control paths used by the action asset. |
| Legacy Unity Input | InputManager | Virtual Controls Manager | Opsive Virtual Button, Virtual Joystick, and Virtual Touchpad components feed named Input Manager buttons and axes. |
Use the Unity Input System route for a new project that already uses actions and control schemes. Use the legacy route only when the character uses the Input Manager or when an existing project depends on its named mappings.
Add the supplied controls
- Open Tools > Opsive > Ultimate Character Controller > Setup Manager and select Scene.
- Under Virtual Controls Setup, choose Input Type:
- InputSystem for a character with Opsive Unity Input System.
- InputManager for a character with legacy Unity Input.
- Select Add Virtual Controls. The Setup Manager creates a Canvas if the scene does not already have one, then adds a VirtualControls UI root.
- Select the new root and set Character for a fixed owner, or leave it empty to follow the Camera Controller’s character. With Input System controls, attach before the input component’s Start or refresh its on-screen mode after attachment as described below; attaching an already-running character initially hides the root.
- Select the character’s input GameObject and set Force Input for the current test:
- On Opsive Unity Input System, choose On Screen.
- On legacy Unity Input, choose Virtual.
- Enter Play Mode and confirm that the root becomes active. Return Force Input to None before the mobile build if platform detection should decide between desktop and on-screen input.
On supported mobile builds, both input implementations select their on-screen mode automatically unless Force Input is set to Standalone. Forcing the mode is mainly useful for testing the layout with a mouse in the Editor.
Configure Unity Input System controls
The supplied InputSystem layout contains movement and camera sticks plus Attack, Next, Reload, Action, and Jump buttons. Its root On Screen Controls component owns the character reference; each child uses Unity’s standard on-screen component.
- For a button, set Control Path to a control bound to the intended action. The supplied Attack button sends
<Keyboard>/leftAlt, whichCharacterInputbinds toOnScreenFire, while the default Use ability requestsFire1. Add a<Keyboard>/leftAltbinding to the character’s existingFire1action, or deliberately change the relevant Use input name toOnScreenFire. Choose one route and test it. - For a stick, set Control Path to the vector control used by the movement or look bindings.
- Keep Use Isolated Input Actions enabled on a stick when control-scheme switching would otherwise cancel its pointer drag.
- Adjust Movement Range to the visible stick radius. Use Behaviour to decide whether the origin stays fixed or follows the first touch.
The supplied prefab uses <Gamepad>/leftStick for movement and <Gamepad>/rightStick for camera look. Both sticks use Movement Range 75, Exact Position With Static Origin, and Use Isolated Input Actions. Buttons simulate keyboard controls, including Space for Jump, R for Reload, and left Alt for Attack. Complete the Attack-to-Use mapping above; the integration does not alias OnScreenFire to Fire1.
Changing a button’s label does not change its action. Update Control Path and the input action binding together, then verify the result in Play Mode.
Configure legacy controls
All legacy Virtual Button, Virtual Joystick, and Virtual Touchpad objects must remain below the Virtual Controls Manager so they can register their input names with the correct character.
Movement joystick
The supplied Virtual Joystick uses Horizontal Input Name Horizontal, Vertical Input Name Vertical, Radius 100, and Deadzone Radius 5. Keep Joystick assigned to the movable knob. Increase the deadzone if small unintended motion occurs near the center; increase the radius when the visual control should allow a longer drag.
Camera touchpad
The supplied Virtual Touchpad uses Mouse X and Mouse Y for camera look. Delta Position Multiplier scales received drag input; leave Use Axis Input disabled for a touch-driven area. In the released implementation, its fixed update calculates a zero drag delta and clears the stored value once movement has occurred, regardless of Require Active Drag and Active Drag Damping. Those settings cannot provide the advertised configurable decay. Test the resulting look response or use a suitable joystick/Input System control; smooth touchpad decay requires a corrected implementation.
Action buttons
Set each Virtual Button > Button Name to the exact Input Manager mapping requested by the ability or item action. The supplied layout includes Fire1, Equip Next Item, Reload, Action, and Jump.
The supplied Virtual Controls Manager leaves Allow Axis Input disabled and enables Allow Button Input. That keeps physical axes from being mixed into the touch joystick while still allowing a physical button to supplement an unpressed virtual button when the pointer is not over UI. Change those settings only when the game deliberately supports hybrid touch and physical input.
Design for common mobile scenarios
- Third-person movement and camera: Keep the left movement stick, use the touchpad or right stick for look, and place action buttons where they do not cover the look area.
- Twin-stick controls: Use a second stick for the look axes. Match its legacy input names or Input System control path to the camera bindings.
- Large drag-to-look area: Stretch the touchpad or right-stick hit area behind the action buttons, but keep interactive buttons above it in the Canvas hierarchy so their pointer events are not stolen.
- Controller plus touch: Keep the virtual controls active and use the route’s physical-input options or bindings. Test simultaneous input because the legacy fallback and Input System control-scheme behavior are different.
- Multiple local players: Give each UI root an explicit Character. This chooses which UCC input instance is enabled by that root, but it does not by itself pair Unity Input System devices, divide touch regions between players, or configure per-player UI navigation. Complete those responsibilities in the selected input system and follow Split Screen.
Verify in Play Mode
- Force the matching on-screen mode and enter Play Mode. The VirtualControls root should become visible only for a character with the matching input implementation.
- Drag the movement control in all directions. The character should move smoothly, return to idle on release, and remain still inside the intended deadzone.
- Drag the look control slowly and quickly. The camera should rotate on both axes without continuing after the interaction ends unless that response is intentional.
- Press each action button once. Only the action whose mapping or binding matches that control should start.
- Press two controls together, such as movement and Jump. Both should remain responsive under multitouch.
- Open menus and press near UI boundaries. Gameplay controls should not activate through an interactive menu control.
- Change the Camera Controller’s character when Character is empty, or assign a new owner explicitly. Legacy controls query that owner’s current mode. Input System controls instead hide on attachment and wait for a future mode event; refresh Force Input after attaching an already-running character and confirm the root becomes visible again.
- Build to a target device and repeat the test. Confirm safe-area placement, UI scale, orientation changes, and real multitouch instead of relying only on mouse simulation.
Troubleshooting
| Symptom | Check | Fix |
|---|---|---|
| The controls never appear or disappear after a character switch. | Check matching input type and Force Input. Input System controls hide when attaching and do not query the owner’s current on-screen state. | Attach before input Start, or after runtime attachment change Force Input to Standalone and then On Screen to emit a fresh mode event. Restore the project’s chosen automatic policy afterward if needed. |
| The Console reports that the character has no Unity input component. | The root’s Character may use the other input route or reference the wrong character. | Assign a character with Opsive Unity Input System to On Screen Controls, or legacy Unity Input to Virtual Controls Manager. |
| A legacy control logs that it cannot find Virtual Controls Manager. | The Virtual Button, Joystick, or Touchpad is outside the manager’s hierarchy. | Move the control below the correct Virtual Controls Manager root. |
| A button highlights but no action starts. | Check its Button Name or Control Path against the requested action. The supplied Attack path maps to OnScreenFire, while default Use requests Fire1. |
Bind left Alt to the intended Use action or deliberately change that Use input to OnScreenFire; do not assume an alias exists. |
| The movement stick never returns to zero. | Its knob reference, pointer-up event, or UI raycast may be blocked. | Confirm Joystick is assigned, the control has a raycast target, and no full-screen UI element intercepts pointer release. |
| Camera look is too slow, too fast, or has unexpected decay. | Check Delta Position Multiplier, input sensitivity, and the released touchpad fixed-update limitation. | Tune received drag response, but do not rely on Require Active Drag or Active Drag Damping for decay. Use a suitable alternative control or a corrected touchpad implementation when smooth decay is required. |
| Desktop input unexpectedly changes touch input. | Legacy Allow Axis Input or Allow Button Input, or Input System control-scheme switching, may combine sources. | Disable the unwanted legacy fallback or revise the Input System bindings and device pairing. |
| Two players respond to the same touch controls. | Both roots may follow the same camera character, or the virtual device may not be paired per player. | Assign Character explicitly on each root and configure ownership in the selected input system. |
Related pages
Developer reference
Both roots expose Character for runtime assignment. The legacy manager also exposes AllowAxisInput and AllowButtonInput and reads the new owner’s current mode. Input System attachment only subscribes to future mode events and hides the root. The sample below attaches an already-initialized character, then deliberately forces On Screen through a mode transition so the root receives a fresh event. Keep this owner script outside the controls root so hiding that root does not disable the ownership workflow. For automatic platform selection, manage and refresh that policy explicitly instead of always forcing on-screen mode.
using Opsive.Shared.Input.InputManager.VirtualControls;
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using Opsive.Shared.Input;
using Opsive.Shared.Input.InputSystem;
#endif
public class VirtualControlOwner : MonoBehaviour
{
[SerializeField] private VirtualControlsManager m_LegacyControls;
#if ENABLE_INPUT_SYSTEM
[SerializeField] private OnScreenControls m_InputSystemControls;
#endif
public void SetCharacter(GameObject character)
{
if (m_LegacyControls != null) {
m_LegacyControls.Character = character;
}
#if ENABLE_INPUT_SYSTEM
if (m_InputSystemControls != null) {
m_InputSystemControls.Character = character;
if (character != null) {
var playerInput = character.GetComponent<IPlayerInput>();
if (playerInput is PlayerInputProxy proxy) {
playerInput = proxy.PlayerInput;
}
if (playerInput is UnityInputSystem input) {
// Reassigning the same ForceInput value emits no event.
input.ForceInput = UnityInputSystem.ForceInputType.Standalone;
input.ForceInput = UnityInputSystem.ForceInputType.OnScreen;
}
}
}
#endif
}
}
Changing UnityInput.ForceInput raises OnUnityInputTypeChanged with the legacy input instance and a Boolean virtual-input state. A changed UnityInputSystem.ForceInput raises OnUnityInputSystemTypeChanged with a Boolean on-screen state on the input GameObject; assigning the same value returns without an event. The roots listen to those events to activate or hide themselves. Input System controls need a new event after runtime attachment because they do not replay the current state.