Skip to main content

VirtualJoystick

Important Attention Reminder

Coding Style wiki


VirtualJoystick is the UGUI virtual joystick component (MonoBehaviour). It implements the UGUI event interfaces (IPointerDownHandler, IPointerUpHandler, IDragHandler), converts touch / mouse dragging into a Vector2 stick vector, and outputs it through the onStickInput callback; supports Fixed / Floating stick types, Normalized / Delta vector modes, axis constraints, and dead-zone filtering (adjusted from annulusgames - EnhancedOnScreenStick).

NamespaceOxGKit.VirtualJoystick
Typepublic class VirtualJoystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
SourceVirtualJoystick.cs
using OxGKit.VirtualJoystick;

Attention The component is decorated with [AddComponentMenu("OxGKit/VirtualJoystick/VirtualJoystick")] and [RequireComponent(typeof(RectTransform), typeof(Image))]the host object itself is the touch area (the image can be made transparent to act as the touch surface).

Quick Start

using OxGKit.VirtualJoystick;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
[SerializeField]
private VirtualJoystick _joystick;

private Vector2 _move;

private void Awake()
{
// Configure the joystick (can also be set directly in the Inspector)
this._joystick.stickType = StickType.Floating;
this._joystick.stickVectorMode = StickVectorMode.Normalized;
this._joystick.axisConstraint = AxisConstraint.Both;
this._joystick.deadZone = 0.1f;

// Subscribe to the stick output callback (returns Vector2.zero on release)
this._joystick.onStickInput += this._OnStickInput;
}

private void _OnStickInput(Vector2 v2)
{
this._move = v2;
}

private void OnDestroy()
{
// Unsubscribe when the object is destroyed
this._joystick.onStickInput -= this._OnStickInput;
}
}

General Rules

Requirements

  1. The component must live under a Canvas: Awake fetches the parent Canvas (used for scale-factor and position conversion) — if none is found, it logs an error and disables itself (enabled = false).
  2. The scene needs an EventSystem, and the component's Image must be raycastable (not blocked by CanvasGroup.blocksRaycasts = false, etc.), otherwise no pointer events are received.
  3. The component is purely UGUI-event driven — it does not depend on Unity New InputSystem's OnScreen controls and works with both Old and New Input backends.

Output Flow

  1. Press (OnPointerDown): For non-Fixed types, the joystick background is repositioned to the press point; the press start position is recorded and OnDrag runs once immediately.
  2. Drag (OnDrag): The output vector is computed from "current position - press start position" (divided by handleMovementRange * Canvas.scaleFactor), then the axis constraint, dead-zone filtering, and vector mode are applied in order; finally the vector is emitted through onStickInput and the handle position is updated.
  3. Release (OnPointerUp): The handle snaps back to center and onStickInput returns Vector2.zero.

Enums

StickVectorMode

public enum StickVectorMode
{
Normalized,
Delta
}

The stick vector output mode:

ValueDescription
NormalizedOutput is limited to -1.00 ~ 1.00 (vectors longer than 1 are normalized); suitable for movement control.
DeltaMimics Mouse Delta; output can exceed 1, suitable for camera-look control (scale it by your sensitivity).

StickType

public enum StickType
{
Fixed = 0,
Floating = 1
}

The stick display type:

ValueDescription
FixedThe joystick stays at its original position.
FloatingOn every press, the joystick background is repositioned to the press point.

AxisConstraint

public enum AxisConstraint
{
Both = 0,
Horizontal = 1,
Vertical = 2
}

Constrains the stick output direction:

ValueDescription
BothOutputs both axes.
HorizontalOutputs the horizontal axis only.
VerticalOutputs the vertical axis only.

Members

Member Overview

MemberDescription
onStickInputCallback invoked when the stick output vector changes (returns Vector2.zero on release).
stickVectorModeThe stick vector output mode (StickVectorMode).
stickTypeThe stick display type (StickType).
axisConstraintConstrains the stick output direction (AxisConstraint).
handleMovementRangeThe handle movement range (pixel radius, default 100).
deadZoneDead-zone range (0 ~ 1); stick inputs below this value are treated as invalid.

onStickInput

public Action<Vector2> onStickInput

Callback invoked when the stick output vector changes: it fires on every input change while dragging, and returns Vector2.zero on release.

this._joystick.onStickInput += (v2) => this._move = v2;

Important onStickInput is a plain Action<Vector2> delegate field — assigning with = replaces all existing subscriptions; prefer += / -= for subscribing and unsubscribing (and unsubscribe when the object is destroyed).

stickVectorMode

public StickVectorMode stickVectorMode { get; set; }

The stick vector output mode, default StickVectorMode.Normalized (see StickVectorMode).

stickType

public StickType stickType { get; set; }

The stick display type, default StickType.Fixed (see StickType).

axisConstraint

public AxisConstraint axisConstraint { get; set; }

Constrains the stick output direction, default AxisConstraint.Both (see AxisConstraint).

handleMovementRange

public float handleMovementRange { get; set; }

The handle movement range (pixel radius, default 100); the output vector is computed as "drag delta / (handleMovementRange * Canvas.scaleFactor)".

Attention The movement range is measured in pixels and affected by the Canvas scaleFactor — test at your target resolutions / aspect ratios.

deadZone

public float deadZone { get; set; }

Dead-zone range (0 ~ 1, default 0); output vectors with a magnitude below this value are treated as invalid and emit Vector2.zero (prevents accidental touches).

Reminder The dead zone is a fraction of the movement range — values around 0.05 ~ 0.15 filter accidental touches without hurting responsiveness.


Inspector Fields

The following serialized fields can only be configured in the Inspector (no public properties); you can import the VirtualJoystickUI Prefab through Package Manager -> Samples (pre-wired, see the Module Intro):

Field (Inspector)Description
Background (_background)The joystick background UI (RectTransform, stick base image) — must be assigned.
Handle (_handle)The joystick handle UI (RectTransform, the draggable knob) — must be assigned.
Show Only When Pressed (_showOnlyWhenPressed)Whether to show the joystick background only while pressed (hidden at Awake, shown on press, hidden on release); pairs well with the Floating type.

UGUI Event Callbacks

Method Overview

MethodDescription
OnPointerDownInvoked on press (repositioning and initial output).
OnPointerUpInvoked on release (handle snaps back and output resets to zero).
OnDragInvoked while dragging (computes and emits the stick vector).

Attention These methods are the UGUI event interface implementations (IPointerDownHandler, IPointerUpHandler, IDragHandler) and are driven automatically by the EventSystem — you normally do not call them manually.


OnPointerDown

public void OnPointerDown(PointerEventData eventData)

Invoked on press: shows the joystick background (if hidden); for non-Fixed types the background is repositioned to the press point; records the press start position and runs OnDrag once immediately.

OnPointerUp

public void OnPointerUp(PointerEventData eventData)

Invoked on release: the handle snaps back to center; if Show Only When Pressed is enabled, the joystick background is hidden; and Vector2.zero is returned through onStickInput.

OnDrag

public void OnDrag(PointerEventData eventData)

Invoked while dragging: computes the output vector from the press start position, applies the axis constraint, dead-zone filtering, and vector mode in order, emits it through onStickInput, and updates the handle position (visually clamped to handleMovementRange).