~1 мин чтения

Ввод — Input Map и action-based система

Как читать клавиатуру, мышь, геймпад и тач через единую action-систему.

В Godot ввод — единая система: вы описываете actions в Project Settings и обращаетесь к ним из любого скрипта. Нет двух конкурирующих API как в Unity (legacy Input vs Input System).

Input Map

Project → Project Settings → Input Map. Создаёте action (например, jump), привязываете к нему любое количество клавиш / кнопок геймпада / мышь / touch:

move_forward   ← W, ↑, левый-стик-Y+
move_back      ← S, ↓, левый-стик-Y−
move_left      ← A, ←, левый-стик-X−
move_right     ← D, →, левый-стик-X+
jump           ← Space, A (gamepad)
fire           ← левый клик, RT (gamepad)
crouch         ← Ctrl, B (gamepad)

Это и есть единственная конфигурация — больше ничего настраивать не нужно.

Опрос ввода через Input

В скрипте читаете action через глобальный Input (singleton):

func _physics_process(delta: float) -> void:
    # 1D ось из двух actions: возвращает float в [-1, 1]
    var horizontal := Input.get_axis("move_left", "move_right")

    # 2D-вектор из четырёх actions: возвращает Vector2
    var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")

    # Просто проверка нажатия (повторяется каждый кадр пока зажата)
    if Input.is_action_pressed("crouch"):
        crouch()

    # Только в кадр нажатия
    if Input.is_action_just_pressed("jump"):
        velocity.y = jump_speed

    # Только в кадр отпускания
    if Input.is_action_just_released("fire"):
        stop_charging()
Веб

addEventListener('keydown', ...), addEventListener('keyup', ...) + ваш state-объект, обновляющий “какие клавиши держатся”.

Unity

Input.GetAxis("Horizontal")Input.get_axis("move_left", "move_right"). Input.GetButtonDown("Jump")Input.is_action_just_pressed("jump"). Похоже, но имена конфигурируются полностью.

_input(event) и _unhandled_input(event)

Альтернативно — event-driven подход. Godot пробрасывает каждое событие через дерево узлов:

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("pause"):
        toggle_pause()
        get_viewport().set_input_as_handled()

Разница между _input и _unhandled_input:

  • _input — получает все события до того, как UI их обработает.
  • _unhandled_input — получает события, которые не съел UI. Это правильный путь для геймплейного ввода, потому что клик по кнопке меню не запустит стрельбу.

Иерархия проброса:

  1. _input на всех узлах.
  2. GUI обрабатывает событие, если оно касается Control-узла.
  3. _shortcut_input — для горячих клавиш меню.
  4. _unhandled_key_input — только клавиатура, после GUI.
  5. _unhandled_input — всё остальное, после GUI.

Поворот камеры мышью

Классический FPS-look:

extends CharacterBody3D

@export var sensitivity: float = 0.003
@export var max_pitch: float = 1.4   # ~80°
@onready var head: Node3D = $Head
@onready var camera: Camera3D = $Head/Camera3D

func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED  # скрыть курсор и захватить

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion:
        rotate_y(-event.relative.x * sensitivity)
        head.rotate_x(-event.relative.y * sensitivity)
        head.rotation.x = clamp(head.rotation.x, -max_pitch, max_pitch)

InputEventMouseMotion.relative — дельта по сравнению с прошлым кадром, что нам и нужно.

MOUSE_MODE_CAPTURED — must-have для FPS

Без него мышка уйдёт за пределы окна на повороте. Captured-режим прячет курсор и центрирует его каждый кадр. ESC — стандартная привычка освобождать курсор:

if event.is_action_pressed("ui_cancel"):
    Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

Raycast — куда смотрит игрок

Два пути в Godot:

1. Узел RayCast3D

Декларативно в сцене. Добавляете дочерний RayCast3D, в Inspector ставите target_position (в локальных координатах, например (0, 0, -100) — на 100 м вперёд). Включаете Enabled.

@onready var aim_ray: RayCast3D = $Camera3D/AimRay

func shoot() -> void:
    if aim_ray.is_colliding():
        var hit = aim_ray.get_collider()       # Node3D или Object
        var point = aim_ray.get_collision_point()
        var normal = aim_ray.get_collision_normal()
        if hit.has_method("take_damage"):
            hit.take_damage(10)

RayCast3D автоматически обновляется в _physics_process.

2. Программный raycast через PhysicsServer

Если нужен луч “по запросу” с произвольными параметрами:

func shoot_from_camera() -> void:
    var space = get_world_3d().direct_space_state
    var from = camera.global_position
    var to = from - camera.global_transform.basis.z * 100.0  # 100 м вперёд

    var query = PhysicsRayQueryParameters3D.create(from, to)
    query.collision_mask = 1   # только слой 1
    query.exclude = [self]     # не попадать в себя

    var hit := space.intersect_ray(query)
    if not hit.is_empty():
        var collider = hit.collider
        var point = hit.position
        var normal = hit.normal

intersect_ray возвращает Dictionary с полями collider, position, normal, rid, shape, или пустой dict при промахе.

Touch и multi-touch

Touch-события приходят как InputEventScreenTouch (нажатие/отпускание) и InputEventScreenDrag (движение). У каждого — index идентификатор пальца:

func _input(event: InputEvent) -> void:
    if event is InputEventScreenTouch:
        if event.pressed:
            print("Finger ", event.index, " at ", event.position)

Для типовых мобильных контролов (виртуальные джойстики, кнопки) — Control-узлы TouchScreenButton и VirtualJoystick (последний — в 4.7).

В следующей главе — физика.