Skip to main content

Lesson 6 · Handling input

The ninja has only been standing. This lesson we make them walk.

It is a phone game, so there is no keyboard. Press the lower left of the screen with a thumb and a joystick stands at that spot, and the ninja walks in the direction you push.

1. The finished screen​

Pixel 10 — press the lower left and a stick appears; walk in the direction you push

2. Why a floating stick​

Drawing a stick fixed to one spot on the screen is easier to make. But when you hold the phone in landscape, the spot a thumb reaches differs per hand.

  • For someone with large hands the stick is too far inward
  • For someone with small hands the hand cannot reach
  • Change the phone size and both are wrong

A floating stick appears where you pressed. Wherever you rest your thumb is the center. Most mobile games like Brawl Stars use this method.

It appears where you pressed — it is not a fixed stick (silent)

3. Check the official docs​

We look at Godot's official Step by step / Listening to player input and Coding the player.

Official docsWhat we take from them
Listening to player inputThe difference between _input and _gui_input
Coding the playervelocity and move_and_slide()

The official sample uses keyboard arrow keys. We are touch, so we handle InputEventScreenTouch and InputEventScreenDrag directly.

4. There is more than one finger​

This is the place people get wrong most often. There is one mouse, but there are several fingers.

In Lesson 12 a dash stick appears on the right. Dash with the right hand while walking with the left and touch events come in on two lines. Each event has an index attached.

## The finger currently holding this stick. -1 if none.
var _touch_index: int = -1

Look only at your own finger. Skip this and the moment you dash with the right hand, the left stick snatches that finger and the character goes somewhere they should not.

It is tested as touch on a computer too

We turned on Emulate Touch From Mouse in Lesson 1. Mouse drag comes in as InputEventScreenTouch/Drag.

A mouse is only one finger, though. Two-finger motion can only be verified on a phone.

5. Build it yourself​

5-1. A pad that takes the left half​

Lay one Control over the left half of the screen, and pressing anywhere inside it stands the stick.

[node name="VirtualStick" type="Control"]
anchors_preset = 9 # pin left, fill vertical
anchor_bottom = 1.0
offset_right = 404.0 # half of 808

anchors_preset = 9 is a layout pinned to the left and filling vertically. We picked expand in Lesson 1, so width differs per device. Pin it with anchors and even a wide phone keeps the left half.

5-2. Stand it where you pressed​

func _gui_input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
var touch: InputEventScreenTouch = event
if touch.pressed and _touch_index == -1:
_touch_index = touch.index
_origin = touch.position
_show_at(_origin)
accept_event()

_origin is the center. How far you pushed from here is the input.

accept_event() is the mark that this event ends here. Do not call it and things behind receive the same touch again.

5-3. When the finger leaves the pad​

This is the second trap.

_gui_input only receives what happened inside that control. Push your thumb outside the left half and from that moment events stop coming. The stick stays on and the character keeps walking.

Receive once more with _input. This side sees the whole screen.

func _input(event: InputEvent) -> void:
if _touch_index == -1:
return
if event is InputEventScreenDrag and (event as InputEventScreenDrag).index == _touch_index:
_move_to(make_input_local(event).position)
elif event is InputEventScreenTouch:
var touch: InputEventScreenTouch = event
if not touch.pressed and touch.index == _touch_index:
_release()

make_input_local() converts screen coordinates into this control's space. Skip the conversion and the stick is drawn a long way from the finger.

5-4. Dead zone​

Even just resting a thumb, the value is not 0. The hand trembles a little. Leave it and the character creeps even when you are still.

@export var dead_zone: float = 0.16

var strength: float = length / radius
_value = Vector2.ZERO if strength < dead_zone else direction * strength

Inside 16% of the radius we treat it as 0.

Make the dead zone too large and response feels dull Past 0.3 you get the feeling of "I pushed and it did not go." Pick a value between 0.1 and 0.2 by holding the phone and feeling it yourself.

5-5. Moving​

The player does not read the stick directly. The arena reads it and passes it on.

# arena.gd
func _process(_delta: float) -> void:
_player.set_move_input(_stick.get_value())
# player.gd
func _physics_process(delta: float) -> void:
var target: Vector2 = _wish * speed
var rate: float = ACCELERATION if _wish.length() > 0.0 else FRICTION
velocity = velocity.move_toward(target, rate * delta)
move_and_slide()

Why the player does not read the stick directly In Lesson 8 enemies use the same body. Enemies have no stick. When we add dash in Lesson 12 we also avoid touching the input side.

The player only needs to know "go this way." Whether that direction comes from a finger or from AI, they do not have to know.

move_toward means speed does not snap on; it slides. We set stopping (FRICTION 1200) faster than starting (ACCELERATION 900) so starting is smooth and stopping is crisp.

5-6. Cannot leave the forest​

const BOUNDS: Rect2 = Rect2(96, 150, 616, 172)

position.x = clampf(position.x, BOUNDS.position.x, BOUNDS.end.x)
position.y = clampf(position.y, BOUNDS.position.y, BOUNDS.end.y)

Putting a collider on every tree is the "proper" way. But the forest has 1,296 nodes. Putting colliders on them is a different job, unrelated to what this lesson teaches.

We clip coordinates. As long as the player is not buried in a tree, that is enough.

This is a place we will have to change later In Lesson 9 a moonlight gate opens at the far end of the forest. This rectangle will not be enough then. For now, just know "this will change later" and move on.

5-7. Attach the walk animation​

Every frame we call the two functions we made in Lesson 5.

face_toward(_wish)
set_walking(velocity.length() > WALK_THRESHOLD)

We look at velocity, not _wish. Even after you release the stick, you are still walking for as long as you are sliding.

6. Check your work so far​

pnpm game:check

Check these on the phone:

  • Press the lower left and a stick stands at that spot
  • The ninja walks in the direction you push, and faces that way
  • Lift your hand and they stop
  • Just resting a thumb does not move them
  • Push outside the left half of the screen and it keeps following, without stopping
  • Pull your hand off the screen and the character does not keep walking

7. Break it on purpose​

① Delete the whole _input() function → Push your thumb to the right of the screen and the stick freezes on and the character keeps walking. Lift your hand and they still do not stop.

② Set dead_zone to 0.0 → Just resting a thumb makes them creep.

③ Delete the _touch_index check → It does not show much yet. Once the dash stick exists in Lesson 12, touching with both hands sends the character somewhere they should not go.

④ Change set_walking(velocity.length() > ...) to _wish.length() > 0 → The moment you lift your hand the animation stops, but the character is still sliding. They move without moving their feet.

⑤ Delete the length guard on face_toward() that we put in in Lesson 5 → Every time you lift your hand the ninja looks down. That is the handling Lesson 5 said "does not show yet."

8. Completion checklist​

  • Press anywhere on the left half and a stick stands at that spot
  • They walk in the direction you push and the matching animation plays
  • Lift your hand and they stop smoothly
  • Just resting a thumb does not move them
  • If the finger leaves the pad it keeps following
  • They do not go outside the forest edge
  • pnpm game:check exits with code 0
  • On the phone, it actually controls with a thumb

9. Exercises​

  1. Change speed to 40 and 200. 40 is sluggish and 200 is slippery. Feel in your body why we picked 96.

  2. Make FRICTION equal to ACCELERATION. They slide more when stopping. That is why we keep the two different.

  3. Widen BOUNDS to the whole screen. The ninja walks into the trees. This is a preview of why that rectangle has to change in Lesson 9.