Ввод — 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-объект,
обновляющий “какие клавиши держатся”.
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. Это правильный путь для геймплейного ввода, потому что клик по кнопке меню не запустит стрельбу.
Иерархия проброса:
_inputна всех узлах.- GUI обрабатывает событие, если оно касается Control-узла.
_shortcut_input— для горячих клавиш меню._unhandled_key_input— только клавиатура, после GUI._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 — дельта по сравнению с прошлым кадром, что нам и нужно.
Без него мышка уйдёт за пределы окна на повороте. 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).
В следующей главе — физика.