Joystick virtual

Um analógico que se prende ao seu anel e conduz um ponto. No celular o mesmo código responde ao toque.

O editor acima está rodando este projeto. Mude uma linha e ele recarrega.

Classes do Godot usadas

PanelStyleBoxFlatInputEventMouseMotion

O código

scripts/main.gd
extends Node

const BASE_RADIUS: float = 90.0
const KNOB_RADIUS: float = 38.0
const SPEED: float = 420.0

var base: Panel
var knob: Panel
var runner: Polygon2D
var readout: Label
var origin: Vector2
var direction: Vector2 = Vector2.ZERO
var dragging: bool = false


func _ready() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var view: Vector2 = get_viewport().get_visible_rect().size
	origin = Vector2(BASE_RADIUS + 60, view.y - BASE_RADIUS - 60)

	runner = _make_circle(30.0, Color('#8eef97'))
	runner.position = view * 0.5
	add_child(runner)

	base = _make_disc(BASE_RADIUS, Color(0.55, 0.65, 0.95, 0.12), Color('#2a2f3d'))
	base.position = origin - Vector2(BASE_RADIUS, BASE_RADIUS)
	add_child(base)

	knob = _make_disc(KNOB_RADIUS, Color('#8da5f3'), Color('#8da5f3'))
	add_child(knob)
	_move_knob(origin)

	readout = Label.new()
	readout.position = Vector2(40, 40)
	readout.add_theme_color_override('font_color', Color('#999999'))
	add_child(readout)

	print('drag inside the ring, on a phone the same code answers to touch')


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		var press: InputEventMouseButton = event

		if press.pressed and press.position.distance_to(origin) <= BASE_RADIUS * 1.6:
			dragging = true
		elif not press.pressed:
			dragging = false
			direction = Vector2.ZERO
			_move_knob(origin)

	if event is InputEventMouseMotion and dragging:
		var offset: Vector2 = ((event as InputEventMouseMotion).position - origin).limit_length(BASE_RADIUS)

		direction = offset / BASE_RADIUS
		_move_knob(origin + offset)


func _process(delta: float) -> void:
	var view: Vector2 = get_viewport().get_visible_rect().size

	runner.position += direction * SPEED * delta
	runner.position = runner.position.clamp(Vector2(30, 30), view - Vector2(30, 30))
	readout.text = 'direction %.2f, %.2f' % [direction.x, direction.y]


func _move_knob(at: Vector2) -> void:
	knob.position = at - Vector2(KNOB_RADIUS, KNOB_RADIUS)


func _make_disc(radius: float, fill: Color, border: Color) -> Panel:
	var style: StyleBoxFlat = StyleBoxFlat.new()
	style.bg_color = fill
	style.border_color = border
	style.set_border_width_all(2)
	style.set_corner_radius_all(int(radius))

	var disc: Panel = Panel.new()
	disc.size = Vector2(radius * 2, radius * 2)
	disc.add_theme_stylebox_override('panel', style)
	# a Control eats the drag before _unhandled_input and the stick would never move
	disc.mouse_filter = Control.MOUSE_FILTER_IGNORE

	return disc


func _make_circle(radius: float, color: Color) -> Polygon2D:
	var points: PackedVector2Array = PackedVector2Array()

	for i in 24:
		points.append(Vector2(cos(TAU * i / 24.0), sin(TAU * i / 24.0)) * radius)

	var circle: Polygon2D = Polygon2D.new()
	circle.polygon = points
	circle.color = color

	return circle

Mais exemplos de Interface