Implement a Floating/Dynamic Joystick using Unity's OnScreenStick control

Hi,

this topic has come up several times, but it always ended up in unsatisfying solutions.

Using the OnScreenStick I want to implement a floating/dynamic Joystick:

  • Hidden by default.
  • User starts touching the screen.
    • Joystick will be visible and centered to the touch.
    • When the users moves his finger the OnScreenStick follows according to its setup.
  • User stops touching the screen.
    • Joystick will be hidden.

This is a pretty common scenario for mobile games, so it should not be that hard to implement.

This is my control scheme:

Control Scheme

My action map looks like this for this setup:

Input Actions

  • Movement Action
    • WSAD is just for testing in the editor.
    • The Gamepad Left Stick moves the actual player character. The OnScreenStick component feeds the value.
  • Touch Action
    • Recognizing the user’s touch and position

All actions are of type Value/Vector2.

OnScreenStick component:

In code I do:

// Component: InputReader
public void OnTouch(InputAction.CallbackContext context)
{
  if (context.started)
  {
    TouchStart(context.ReadValue<Vector2>());
  }

  if (context.canceled)
  {
    TouchEnd();
  }
}

// Component: JoystickPositionController
private void OnEnable()
{
  InputReader.TouchStart += TouchStart;
  InputReader.TouchEnd += TouchEnd;
}

private void OnDisable()
{
  InputReader.TouchStart -= TouchStart;
  InputReader.TouchEnd -= TouchEnd;
}

private void TouchStart(Vector2 position)
{
  if (GameSettings.JoystickPosition != JoystickPosition.Dynamic)
  {
    return;
  }
  
  Debug.Log("TouchStart: " + position);
  JoystickContainer.anchoredPosition = position;
  JoystickContainer.gameObject.SetActive(true); // 1
}

private void TouchEnd()
{
  if (GameSettings.JoystickPosition != JoystickPosition.Dynamic)
  {
    return;
  }
  
  Debug.Log("TouchEnd");
  JoystickContainer.gameObject.SetActive(false); // 1
}

My expectation is that when I touch the screen, I get one log message with “TouchStart” and when I stop touching I’ll see “TouchEnd”.

What happens instead is that I get those messages spammed, one after the other.
If I remove the code at // 1, I see the expected messages.

From what I understand so far, my Touch action gets canceled because the OnScreenStick also assigns an Action to the same controls. So after my touch has been recognized the OnScreenStick gets active and recognizes the touch as well, cancelling my touch again and thus TouchEnd is called.

Or it could be that the OnScreenStick sends the current value to the Gamepad/LeftStick, which switches the currently active device from TouchScreen to Gamepad, and thus, it cancels my Touch action again.

I wonder what the correct set up is to make this scenario work.

What I’ve tried so far:

  1. Using Pass through in my Touch action, but it still happens.
  2. Turning on Use Isolated Input Actions.
  3. Using 3 different control schemes, one for each device (Keyboard/Gamepad/TouchScreen).
  4. Using ExactPositionWithDynamicOrigin which is indeed what I would need, but since I want to have a non-moving background image, I need to make the placement myself. Only the inner white part is contains the OnScreenStick, everything else is background:

image

Any idea is welcome!

Thanks :slight_smile:

Agreed. And it only got more complicated with New Input System.

That’s why I wrote my own using the old input system and called it VAButton (Virtual Analog Button). It’s pretty svelte and simple, you give it some rectangles in code, off it goes.

Probably wouldn’t take much to port it to the new input system using the static reference stuff.

You can VAButton and supporting scripts (such as MicroTouch) from my proximity buttons package. See the Twin Stick Shooter for how to use it. Just build it straight to tablet / phone and it Just Works™.

proximity_buttons is presently hosted at these locations:

https://bitbucket.org/kurtdekker/proximity_buttons