Результат
Вы получите рабочий 3D-контроллер от третьего лица:
- персонаж ходит по
W/S/A/D; - камера осматривается мышью;
Shiftвключает бег и анимациюrunning;Fзапускает анимациюkick;- во время
kickблокируются движение/локомоция, но обзор мышью остаётся доступным; - после завершения
kickуправление возвращается; - есть
idle,walking,running,kick; - переходы анимаций сглажены
Default Blend Time = 0.2 s; - чувствительность мыши экспортируется в Inspector и в
main.tscnвыставляется0.2; - персонаж сталкивается с полом и тестовыми боксами.
Этот туториал намеренно не добавляет clamp по вертикальной оси камеры и не добавляет освобождение мыши. Это ограничение, а не скрытый шаг.
Требования и ресурсы
Нужен Godot 4.0.2 и проект, созданный на рендерере Forward+.
Ассеты: lukky-nl/third_person_controller_assets. Скачайте репозиторий через Code → Download ZIP, распакуйте его и скопируйте каталоги models и textures в созданную внутри проекта папку assets. Не оставляйте лишний уровень third_person_controller_assets-main, иначе пути ниже не совпадут.
В папке проекта должна получиться следующая структура:
res://assets/models/mixamo_base.glb
res://assets/textures/grids/Dark/texture_05.png
res://assets/textures/grids/Orange/texture_09.png
res://assets/textures/grids/Purple/texture_09.png
res://assets/textures/grids/Green/texture_09.png
В модели mixamo_base.glb должны быть анимации с точными именами:
idle
walking
running
kick
Имена узлов, анимаций и Input Map в Godot case-sensitive. Важно писать именно:
player
visuals
mixamo_base
camera_mount
AnimationPlayer
idle
walking
running
kick
forward
backward
left
right
run
kick
Пошаговая сборка
1. Создайте пустой проект
- Откройте Project Manager Godot 4.0.2.
- Нажмите Create New Project.
- Заполните:
-
Project Name:third_person_tutorial-Project Path: выберите каталог и создайте папкуthird_person_tutorial-Renderer: Forward+ -Version Control Metadata:Git, если хотите - Нажмите Create & Edit.
Ожидаемо: открыт редактор Godot, вкладка сцены пуста, внизу слева виден Filesystem с res:// и icon.svg.

2. Импортируйте папку assets
- В панели File System выберите
res://. - Перетащите из операционной системы папку
assetsв окно File System наres://. - Дождитесь завершения импорта.
Ожидаемо: в дереве появились:
res://assets/models/mixamo_base.glb
res://assets/textures/grids/Dark/texture_05.png
res://assets/textures/grids/Orange/texture_09.png
res://assets/textures/grids/Purple/texture_09.png
res://assets/textures/grids/Green/texture_09.png
Если импорта завис, нажмите правой кнопкой по res:// и выберите Reload.
3. Создайте корень сцены world
- В пустой сцене добавьте новый узел:
- нажмите
+рядом с Scene Tree; - или правый клик по пустому дереву → Add Child Node. - В поиске введите
Node3D. - Выберите Node3D и создайте узел.
- Переименуйте узел в
world.
Ожидаемое дерево:
world (Node3D)
4. Создайте контейнер карты map
- Выберите узел
world. - Добавьте дочерний узел:
-
+→ поискNode3D. - Создайте Node3D.
- Переименуйте в
map.
Ожидаемое дерево:
world (Node3D)
└─ map (Node3D)
5. Создайте пол floor
Родитель пола — map.
- Выберите узел
map. - Нажмите
+. - В поиске введите
box. - Выберите CSGBox3D.
- Создайте узел.
- Переименуйте его в
floor. - В Inspector установите:
-
Size:1000,1,1000-Position:0,-0.5,0
Если Use Collision сейчас Off, не включайте его пока. Мы включим коллизию позже массово для floor, box, box2, box3.
Ожидаемо: в 3D-вьюпорте большая плоская плита лежит так, что её верхняя грань находится примерно на y = 0.

6. Назначьте материал полу
- В File System откройте:
res://assets/textures/grids/Dark
- Перетащите
texture_05.pngиз File System прямо на поверхностьfloorв 3D-вьюпорте. Godot автоматически создастStandardMaterial3D, поместит его вGeometry → Material Overrideи назначит картинку как albedo-текстуру. - Снова выберите
floorи в Inspector откройте созданный ресурсGeometry → Material Override. - В инспекторе материала раскройте
UV1и включитеTriplanar. - Проверьте, что:
-
Albedo → Texture = texture_05.png-UV1 → Triplanar = On- верхнее CSG-полеMaterialосталось<empty>: материал находится именно вGeometry → Material Override.
Ожидаемо: пол получил тёмную сетку, клетки выглядят примерно как 1×1 м.

7. Создайте три тестовых бокса
Все три бокса должны быть прямыми детьми map, а не детьми floor.
7.1. Бокс box
- Выберите
map. +→ поискbox→ CSGBox3D → создать.- Переименуйте в
box. - В Inspector оставьте
Size:1,1,1. - Перетащите
res://assets/textures/grids/Orange/texture_09.pngнаboxв 3D-вьюпорте. - Откройте созданный
Geometry → Material Overrideи включитеUV1 → Triplanar.
7.2. Бокс box2
- Снова выберите
map. - Добавьте новый CSGBox3D.
- Переименуйте в
box2. Size:2,2,2.- Перетащите
res://assets/textures/grids/Purple/texture_09.pngнаbox2в 3D-вьюпорте. - Откройте созданный
Geometry → Material Overrideи включитеUV1 → Triplanar.
7.3. Бокс box3
- Выберите
map. - Добавьте новый CSGBox3D.
- Переименуйте в
box3. Size:3,3,3.- Перетащите
res://assets/textures/grids/Green/texture_09.pngнаbox3в 3D-вьюпорте. - Откройте созданный
Geometry → Material Overrideи включитеUV1 → Triplanar.
Позиции и повороты боксов задайте gizmo визуально, чтобы получить похожую композицию. Точные координаты здесь не фиксируются.

8. Включите коллизию для пола и боксов
- В Scene Tree выделите сразу четыре узла:
-
floor-box-box2-box3
Используйте Ctrl/Shift для множественного выделения.
- В Inspector установите:
Use Collision = On
Ожидаемо: Godot подтверждает множественное выделение, например CSGBox3D (4 selected), и Use Collision становится On для всех выбранных CSG-узлов.
9. Добавьте окружение и солнце
Порядок важен: сначала WorldEnvironment, затем DirectionalLight3D.
- В 3D-вьюпорте откройте панель окружения: - трёхточечное меню вьюпорта → Environment - или кнопку Environment в тулбаре 3D-вьюпорта.
- Нажмите Add Environment to Scene.
- Затем нажмите Add Sun to Scene.
Ожидаемо: под world появились узлы:
WorldEnvironment
DirectionalLight3D

10. Настройте SDFGI и туман
- Выберите узел
WorldEnvironment. - В Inspector раскройте параметры окружения.
- Включите:
SDFGI → Enabled = On
- Раскройте
Fogи включите:
Fog → Enabled = On
Fog → Light Color = #ffffff
Fog → Density = 0.001
Ожидаемо: в сцене появились мягкие тени/глобальное освещение и лёгкий белый туман.

11. Создайте папки scenes и scripts
- В File System выберите
res://. - Правый клик → Create Folder.
- Наберите
scenesи подтвердите. - Повторите:
- правый клик по
res://→ Create Folder - имяscripts.
Ожидаемо:
res://assets
res://scenes
res://scripts
res://icon.svg
12. Сохраните сцену мира как main.tscn
- Выберите меню Scene → Save Scene As….
- Перейдите в
res://scenes/. - Сохраните файл как
main.tscn. - Нажмите Ctrl+S, если вкладка помечена изменённой.
Ожидаемо: вкладка называется main.tscn, в res://scenes/ появился файл main.tscn.
Сцена игрока
13. Создайте сцену player.tscn
- Выберите меню Scene → New Scene.
- В пустой сцене нажмите Other Node.
- Найдите и создайте CharacterBody3D.
- Переименуйте корневой узел в
player. - Сохраните сцену:
- Scene → Save Scene As…
- путь
res://scenes/- имяplayer.tscn.
Ожидаемо: в дереве есть корень player типа CharacterBody3D. Пока может быть предупреждение, что нет collision shape — это нормально, следующий шаг добавит форму.

14. Добавьте CollisionShape3D
Родитель формы — player.
- Выберите узел
player. - Нажмите
+. - В поиске введите
colli. - Выберите CollisionShape3D.
- Создайте узел.
- В Inspector:
-
Shape: New CylinderShape3D -Height:2-Radius:0.3-Position:0,1.1,0
Ожидаемо: вокруг origin появился проволочный цилиндр, а предупреждение о недостающей collision shape у player исчезло.
15. Создайте узел visuals
- Выберите
player. - Нажмите
+. - Добавьте Node3D.
- Переименуйте в
visuals.
Ожидаемо:
player
├─ CollisionShape3D
└─ visuals
16. Добавьте модель mixamo_base
- В File System откройте:
res://assets/models
- Найдите
mixamo_base.glb. - Перетащите
mixamo_base.glbна узелvisualsв Scene Tree. - можно перетащить и в 3D-вьюпорт, но надёжнее перетаскивать на узелvisualsв дереве.
Ожидаемо: под visuals появился узел mixamo_base.

17. Разверните модель на 180° вокруг Y
Нужно, чтобы forward персонажа совпадал с ожидаемым направлением Godot.
- Выберите
mixamo_base. - Либо в Inspector поставьте:
Rotation: 0, 180, 0
Либо поверните модель gizmo вокруг вертикальной оси на 180°.
Ожидаемо: модель развернута лицом в нужном направлении.

18. Создайте camera_mount
Родитель камеры — camera_mount, а не player и не visuals.
- Выберите
player. - Нажмите
+. - Добавьте Node3D.
- Переименуйте в
camera_mount. - В Inspector установите:
Position: 0, 1.4, 0
Ожидаемо: camera_mount находится на уровне груди персонажа.
19. Добавьте Camera3D
- Выберите
camera_mount. - Нажмите
+. - В поиске введите
cam. - Выберите Camera3D.
- Создайте узел.
- Убедитесь, что
Current = On.
Точные локальные координаты камеры здесь не фиксируются. Нужно подобрать позицию визуально под over-the-shoulder вид.
- Выберите
Camera3D. - Включите предпросмотр камеры:
- в тулбаре 3D-вьюпорта нажмите Preview
- или нажмите
Ctrl+P. - Двигайте
Camera3Dgizmo так, чтобы персонаж был виден сзади и немного сбоку.
Ожидаемо: в Preview камера показывает персонажа в стиле от третьего лица, без «слишком близкого» и «слишком далёкого» вида.

- Отключите Preview, если хотите вернуть обычный вьюпорт.
- Сохраните
player.tscn.
Привязка скрипта и первая проверка
20. Прикрепите скрипт к player
- Откройте
res://scenes/player.tscn. - Выберите корневой узел
player. - Нажмите кнопку Attach Script над Scene Tree или используйте правый клик по
player→ Attach Script…. - В диалоге:
-
Path:res://scripts/player.gd-Language: GDScript -Inherits: CharacterBody3D -Template: On - шаблон CharacterBody3D: Basic Movement. - Нажмите Create.
Ожидаемо: создан файл res://scripts/player.gd, у player появился скрипт, в редакторе кода открыт базовый movement-шаблон.

- Сохраните
player.tscn.
21. Добавьте player в main.tscn
- Откройте
res://scenes/main.tscn. - В Scene Tree выберите корневой узел
world. - В File System откройте
res://scenes/. - Перетащите
player.tscnна корневой узелworldв Scene Tree. - альтернатива: правый клик поworld→ Instantiate Child Scene… → выберитеplayer.tscn. - Если нужно, переименуйте инстанс в
player. - Сохраните
main.tscn.
Ожидаемо: player — прямой ребёнок world, на одном уровне с map, а не ребёнок map.

22. Назначьте main scene
- Выберите меню Project → Project Settings….
- Перейдите:
Application → Run → Main Scene
- Установите:
res://scenes/main.tscn
Альтернатива: при первом F5 выберите текущую сцену main.tscn.
Ожидаемо: проект понимает, какую сцену запускать кнопкой Run.
23. Первый тест: движение на стрелках
- Запустите проект:
- нажмите Run Project
- или
F5. - В игре проверьте: - движение стрелками; - столкновение с полом и боксами.
Ожидаемо: персонаж ходит, не проходит сквозь геометрию, камера следует за ним.
Остановите игру, продолжая редактировать скрипт.
Мышь и чувствительность
24. Захватите мышь
Откройте res://scripts/player.gd.
Добавьте метод _ready() перед _physics_process(delta):
func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
Сохраните файл.
Запустите игру.
Ожидаемо: курсор мыши скрыт и удерживается внутри игрового окна.

Ограничение: в этом туториале мышь не освобождается. Чтобы вернуть курсор, закройте игровое окно или остановите отладку в Godot.
25. Горизонтальный обзор
В player.gd добавьте переменную и метод _input(event).
Промежуточный фрагмент:
var sens_horizontal = 0.5
func _input(event):
if event is InputEventMouseMotion:
rotate_y(deg_to_rad(-event.relative.x * sens_horizontal))
Важно:
- знак перед
event.relative.x— минус; - вращается сам
playerпоY; deg_to_rad()обязателен.
Сохраните и запустите игру.
Ожидаемо: движение мыши влево/вправо поворачивает обзор в естественную сторону и не слишком быстро.

26. Вертикальный обзор камеры
Добавьте ссылку на camera_mount и вертикальную чувствительность.
Промежуточный фрагмент:
@onready var camera_mount = $camera_mount
var sens_vertical = 0.5
Внутри _input(event), после горизонтального поворота, добавьте:
camera_mount.rotate_x(deg_to_rad(-event.relative.y * sens_vertical))
Итог блока _input на этом этапе:
func _input(event):
if event is InputEventMouseMotion:
rotate_y(deg_to_rad(-event.relative.x * sens_horizontal))
camera_mount.rotate_x(deg_to_rad(-event.relative.y * sens_vertical))
Сохраните и запустите игру.
Ожидаемо: мышью можно осматриваться вверх/вниз; наклон делает именно camera_mount.
27. Экспортируйте чувствительность
В player.gd измените объявления:
@export var sens_horizontal = 0.5
@export var sens_vertical = 0.5
Затем:
- Откройте
res://scenes/main.tscn. - Выберите инстанс
player. - В Inspector найдите:
-
Sens Horizontal-Sens Vertical - Установите оба значения:
Sens Horizontal = 0.2
Sens Vertical = 0.2
- Сохраните
main.tscn.
Ожидаемо: чувствительность стала настраиваемой из Inspector, а в основной сцене используются значения 0.2.

Input Map
28. Создайте действия WASD
- Выберите меню Project → Project Settings….
- Откройте вкладку Input Map.
- Убедитесь, что Show Built-in Actions выключен.
- В поле Add New Action создайте действия:
-
forward-backward-left-right
Для каждого действия:
- Нажмите
+справа от имени действия. - В режиме Listening for input… нажмите нужную физическую клавишу:
-
forward→W-backward→S-left→A-right→D - В Additional Options выберите Physical Keycode (Position on US QWERTY Keyboard).
- Подтвердите.
У всех четырёх действий должен быть Deadzone = 0.5.
Ожидаемо: в Input Map есть:
forward = W (Physical)
backward = S (Physical)
left = A (Physical)
right = D (Physical)

29. Замените ввод на custom actions
В player.gd найдите строку:
var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
Замените на:
var input_dir = Input.get_vector("left", "right", "forward", "backward")
Важно: порядок аргументов должен совпадать:
left, right, forward, backward
Сохраните и запустите игру.
Ожидаемо: персонаж ходит на W/S/A/D.
Анимации
30. Сделайте mixamo_base локальным
- Откройте
res://scenes/player.tscn. - В Scene Tree разверните:
player
└─ visuals
└─ mixamo_base
- Правый клик по
mixamo_base. - Выберите Make Local.
- Сохраните
player.tscn.
Ожидаемо: содержимое импортированной сцены стало редактируемым.
31. Включите loop для нужных анимаций
- Выберите узел
AnimationPlayer. - В нижней панели Animation выберите анимацию
idle. - Включите Animation Looping.
- Выберите
walking. - Включите Animation Looping.
- Выберите
running. - Включите Animation Looping.
- Выберите
kick. - Убедитесь, что у
kickloop выключен.
Ожидаемо: idle, walking, running зациклены; kick не зациклен.

32. Установите Default Blend Time
- Оставьте выбранным узел
AnimationPlayer. - В Inspector раскройте Playback Options.
- Установите:
Default Blend Time = 0.2 s
- Сохраните
player.tscn.
Ожидаемо: переходы между анимациями стали плавными, но короткими.

Локомоция: ходьба, бег, kick
33. Добавьте ссылку на AnimationPlayer
В player.gd добавьте:
@onready var animation_player = $visuals/mixamo_base/AnimationPlayer
Путь должен быть точным:
$visuals/mixamo_base/AnimationPlayer
Если путь неправильный, Godot даст ошибку поиска узла.
34. Скорость ходьбы
В player.gd измените:
const SPEED = 5.0
на:
var SPEED = 3.0
Это нужно и для лучшей подгонки под анимацию, и для последующей переменной смены на running_speed.
35. Играть walking и idle
В _physics_process(delta) внутри блока:
if direction:
добавьте проверку и воспроизведение walking:
if animation_player.current_animation != "walking":
animation_player.play("walking")
В блоке:
else:
добавьте:
if animation_player.current_animation != "idle":
animation_player.play("idle")
Промежуточный фрагмент:
if direction:
if animation_player.current_animation != "walking":
animation_player.play("walking")
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
if animation_player.current_animation != "idle":
animation_player.play("idle")
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
Сохраните и запустите игру.
Ожидаемо: в покое играет idle, при движении — walking.

36. Ориентация модели по направлению движения
Добавьте ссылку на visuals:
@onready var visuals = $visuals
В _physics_process(delta) внутри if direction: добавьте:
visuals.look_at(position + direction)
В _input(event) внутри if event is InputEventMouseMotion: добавьте компенсирующее вращение модели:
visuals.rotate_y(deg_to_rad(event.relative.x * sens_horizontal))
Важные знаки:
player: -event.relative.x
visuals: +event.relative.x
camera_mount: -event.relative.y
Сохраните и запустите игру.
Ожидаемо:
- при движении модель поворачивается в сторону движения;
- при свободном обзоре мышью модель визуально остаётся стабильнее;
- при начале движения снова ориентируется по направлению.

37. Добавьте действие run
- Откройте Project Settings → Input Map.
- Создайте действие:
run
- Для
run: - нажмите+; - нажмите физическую клавишуShift; - выберите Physical Keycode; - подтвердите.
Ожидаемо:
run = Shift (Physical)
38. Добавьте переменные скорости
В начале player.gd добавьте:
var walking_speed = 3.0
var running_speed = 5.0
var running = false
39. Переключайте скорость и анимацию бега
В начале _physics_process(delta) добавьте:
if Input.is_action_pressed("run"):
SPEED = running_speed
running = true
else:
SPEED = walking_speed
running = false
Важно: в else обязательно должно быть:
running = false
Затем в блоке if direction: замените логику выбора анимации на:
if running:
if animation_player.current_animation != "running":
animation_player.play("running")
else:
if animation_player.current_animation != "walking":
animation_player.play("walking")
Сохраните и запустите игру.
Ожидаемо:
- без
Shift— ходьба; - с
Shift— бег; - при отпускании
Shift— обратно ходьба; - переходы сглажены blend time.

40. Добавьте действие kick
- Откройте Project Settings → Input Map.
- Создайте действие:
kick
- Для
kick: - нажмите+; - нажмите физическую клавишуF; - выберите Physical Keycode; - подтвердите.
Ожидаемо:
kick = F (Physical)

41. Добавьте блокировку во время kick
Добавьте переменную:
var is_locked = false
В начало _physics_process(delta) добавьте сброс блокировки, если AnimationPlayer больше ничего не играет:
if !animation_player.is_playing():
is_locked = false
Добавьте обработку kick:
if Input.is_action_just_pressed("kick"):
if animation_player.current_animation != "kick":
animation_player.play("kick")
is_locked = true
Важно:
kickзапускается только если текущая анимация ещё неkick;is_locked = trueустанавливается внутри условия запуска.

42. Заблокируйте локомоцию во время kick
В _physics_process(delta) оберните логику анимаций/ориентации в if !is_locked:.
В блоке движения:
if direction:
if !is_locked:
if running:
if animation_player.current_animation != "running":
animation_player.play("running")
else:
if animation_player.current_animation != "walking":
animation_player.play("walking")
visuals.look_at(position + direction)
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
В блоке покоя:
else:
if !is_locked:
if animation_player.current_animation != "idle":
animation_player.play("idle")
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
И вызов move_and_slide() тоже должен выполняться только без блокировки:
if !is_locked:
move_and_slide()

Ожидаемо:
- во время
kickперсонаж не скользит; - во время
kickлокомоционные анимации не перезаписывают удар; - обзор мышью остаётся доступным;
- после завершения
kickуправление возвращается.
Итоговый player.gd
Для гарантированного совпадения замените всё содержимое res://scripts/player.gd на этот блок.
extends CharacterBody3D
@onready var camera_mount = $camera_mount
@onready var animation_player = $visuals/mixamo_base/AnimationPlayer
@onready var visuals = $visuals
var SPEED = 3.0
const JUMP_VELOCITY = 4.5
var walking_speed = 3.0
var running_speed = 5.0
var running = false
var is_locked = false
@export var sens_horizontal = 0.5
@export var sens_vertical = 0.5
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _input(event):
if event is InputEventMouseMotion:
rotate_y(deg_to_rad(-event.relative.x * sens_horizontal))
visuals.rotate_y(deg_to_rad(event.relative.x * sens_horizontal))
camera_mount.rotate_x(deg_to_rad(-event.relative.y * sens_vertical))
func _physics_process(delta):
if !animation_player.is_playing():
is_locked = false
if Input.is_action_just_pressed("kick"):
if animation_player.current_animation != "kick":
animation_player.play("kick")
is_locked = true
if Input.is_action_pressed("run"):
SPEED = running_speed
running = true
else:
SPEED = walking_speed
running = false
# Add the gravity.
if not is_on_floor():
velocity.y -= gravity * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
var input_dir = Input.get_vector("left", "right", "forward", "backward")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
if !is_locked:
if running:
if animation_player.current_animation != "running":
animation_player.play("running")
else:
if animation_player.current_animation != "walking":
animation_player.play("walking")
visuals.look_at(position + direction)
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
if !is_locked:
if animation_player.current_animation != "idle":
animation_player.play("idle")
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
if !is_locked:
move_and_slide()
После замены кода:
- Сохраните
player.gd. - Сохраните
player.tscn. - Откройте
main.tscn. - Убедитесь, что у инстанса
player: -Sens Horizontal = 0.2-Sens Vertical = 0.2 - Сохраните
main.tscn.
Итоговые деревья сцен
main.tscn
world (Node3D)
├─ DirectionalLight3D
├─ WorldEnvironment
├─ map (Node3D)
│ ├─ floor (CSGBox3D)
│ ├─ box (CSGBox3D)
│ ├─ box2 (CSGBox3D)
│ └─ box3 (CSGBox3D)
└─ player (instance of res://scenes/player.tscn)
Проверьте:
floor,box,box2,box3— прямые детиmap;player— прямой ребёнокworld, инстансres://scenes/player.tscn;WorldEnvironmentиDirectionalLight3D— детиworld.
player.tscn
player (CharacterBody3D, script: res://scripts/player.gd)
├─ CollisionShape3D
├─ visuals (Node3D)
│ └─ mixamo_base (Node3D, local)
│ ├─ Armature
│ │ └─ Skeleton3D
│ │ ├─ Beta_joints
│ │ └─ Beta_Surface
│ └─ AnimationPlayer
└─ camera_mount (Node3D)
└─ Camera3D
Проверьте:
CollisionShape3D— ребёнокplayer;visuals— ребёнокplayer;mixamo_base— ребёнокvisuals;AnimationPlayer— доступен по пути$visuals/mixamo_base/AnimationPlayer;camera_mount— ребёнокplayer;Camera3D— ребёнокcamera_mount.
Чек-лист непрерывности
Файлы
- [ ] Проект Godot 4.0.2.
- [ ] Renderer: Forward+.
- [ ]
res://assets/models/mixamo_base.glbимпортирован. - [ ]
res://assets/textures/grids/Dark/texture_05.pngдоступен. - [ ]
res://assets/textures/grids/Orange/texture_09.pngдоступен. - [ ]
res://assets/textures/grids/Purple/texture_09.pngдоступен. - [ ]
res://assets/textures/grids/Green/texture_09.pngдоступен. - [ ] Существует
res://scenes/main.tscn. - [ ] Существует
res://scenes/player.tscn. - [ ] Существует
res://scripts/player.gd. - [ ] Main Scene установлен на
res://scenes/main.tscn.
Узлы main.tscn
- [ ] Корень
world—Node3D. - [ ]
map—Node3D, ребёнокworld. - [ ]
floor—CSGBox3D, ребёнокmap. - [ ]
box—CSGBox3D, ребёнокmap. - [ ]
box2—CSGBox3D, ребёнокmap. - [ ]
box3—CSGBox3D, ребёнокmap. - [ ]
WorldEnvironment— ребёнокworld. - [ ]
DirectionalLight3D— ребёнокworld. - [ ]
player— прямой ребёнокworld, инстансres://scenes/player.tscn.
Свойства мира
floor
- [ ]
Size = 1000, 1, 1000. - [ ]
Position = 0, -0.5, 0. - [ ]
Geometry → Material OverrideсодержитStandardMaterial3D. - [ ] В материале
Albedo Texture = res://assets/textures/grids/Dark/texture_05.png. - [ ]
UV1 → Triplanar = On. - [ ]
Use Collision = On.
box
- [ ]
Size = 1, 1, 1. - [ ]
Geometry → Material OverrideсодержитStandardMaterial3D. - [ ]
Albedo Texture = res://assets/textures/grids/Orange/texture_09.png. - [ ]
UV1 → Triplanar = On. - [ ]
Use Collision = On.
box2
- [ ]
Size = 2, 2, 2. - [ ]
Geometry → Material OverrideсодержитStandardMaterial3D. - [ ]
Albedo Texture = res://assets/textures/grids/Purple/texture_09.png. - [ ]
UV1 → Triplanar = On. - [ ]
Use Collision = On.
box3
- [ ]
Size = 3, 3, 3. - [ ]
Geometry → Material OverrideсодержитStandardMaterial3D. - [ ]
Albedo Texture = res://assets/textures/grids/Green/texture_09.png. - [ ]
UV1 → Triplanar = On. - [ ]
Use Collision = On.
WorldEnvironment
- [ ]
SDFGI → Enabled = On. - [ ]
Fog → Enabled = On. - [ ]
Fog → Light Color = #ffffff. - [ ]
Fog → Density = 0.001.
Узлы player.tscn
- [ ]
player—CharacterBody3D. - [ ] У
playerесть скриптres://scripts/player.gd. - [ ]
CollisionShape3D— ребёнокplayer. - [ ]
CollisionShape3D.Shape—CylinderShape3D. - [ ]
Height = 2. - [ ]
Radius = 0.3. - [ ]
Position = 0, 1.1, 0. - [ ]
visuals—Node3D, ребёнокplayer. - [ ]
mixamo_base— ребёнокvisuals. - [ ]
mixamo_baseразвёрнут на 180° вокругY. - [ ]
mixamo_baseсделан локальным через Make Local. - [ ]
AnimationPlayerдоступен как$visuals/mixamo_base/AnimationPlayer. - [ ]
camera_mount—Node3D, ребёнокplayer. - [ ]
camera_mount.Position = 0, 1.4, 0. - [ ]
Camera3D— ребёнокcamera_mount. - [ ]
Camera3D.Current = On.
Input Map
- [ ]
forwardсуществует. - [ ]
forwardназначен наW (Physical). - [ ]
backwardсуществует. - [ ]
backwardназначен наS (Physical). - [ ]
leftсуществует. - [ ]
leftназначен наA (Physical). - [ ]
rightсуществует. - [ ]
rightназначен наD (Physical). - [ ]
runсуществует. - [ ]
runназначен наShift (Physical). - [ ]
kickсуществует. - [ ]
kickназначен наF (Physical). - [ ] Deadzone у custom actions:
0.5. - [ ]
ui_acceptне удалён и используется для прыжка.
Анимации
- [ ]
idleсуществует. - [ ]
walkingсуществует. - [ ]
runningсуществует. - [ ]
kickсуществует. - [ ]
idleloop включён. - [ ]
walkingloop включён. - [ ]
runningloop включён. - [ ]
kickloop выключен. - [ ]
AnimationPlayer → Playback Options → Default Blend Time = 0.2 s.
Код
- [ ]
extends CharacterBody3D. - [ ] Есть
@onready var camera_mount = $camera_mount. - [ ] Есть
@onready var animation_player = $visuals/mixamo_base/AnimationPlayer. - [ ] Есть
@onready var visuals = $visuals. - [ ] Есть
var SPEED = 3.0. - [ ] Есть
const JUMP_VELOCITY = 4.5. - [ ] Есть
var walking_speed = 3.0. - [ ] Есть
var running_speed = 5.0. - [ ] Есть
var running = false. - [ ] Есть
var is_locked = false. - [ ] Есть
@export var sens_horizontal = 0.5. - [ ] Есть
@export var sens_vertical = 0.5. - [ ] Есть
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity"). - [ ] В
_ready()естьInput.mouse_mode = Input.MOUSE_MODE_CAPTURED. - [ ] В
_input(event)обрабатываетсяInputEventMouseMotion. - [ ] Горизонтальный поворот:
rotate_y(deg_to_rad(-event.relative.x * sens_horizontal)). - [ ] Компенсация модели:
visuals.rotate_y(deg_to_rad(event.relative.x * sens_horizontal)). - [ ] Вертикальный поворот камеры:
camera_mount.rotate_x(deg_to_rad(-event.relative.y * sens_vertical)). - [ ] Используется
Input.get_vector("left", "right", "forward", "backward"). - [ ] Используется
visuals.look_at(position + direction). - [ ] При
run: SPEED = running_speedrunning = true- [ ] При отсутствии
run: SPEED = walking_speedrunning = false- [ ] При отсутствии движения:
velocity.x = move_toward(velocity.x, 0, SPEED)velocity.z = move_toward(velocity.z, 0, SPEED)- [ ] При отсутствии анимации:
if !animation_player.is_playing(): is_locked = false- [ ]
kickзапускается только если: Input.is_action_just_pressed("kick")animation_player.current_animation != "kick"- [ ] При запуске
kick: animation_player.play("kick")is_locked = true- [ ] Локомоционные анимации и
visuals.look_at(...)защищеныif !is_locked:. - [ ]
move_and_slide()вызывается только внутриif !is_locked:.
Инстанс player в main.tscn
- [ ] В Inspector инстанса
playerестьSens Horizontal. - [ ] В Inspector инстанса
playerестьSens Vertical. - [ ]
Sens Horizontal = 0.2. - [ ]
Sens Vertical = 0.2. - [ ]
main.tscnсохранён после изменения этих значений.
Финальная проверка в игре
Запустите main.tscn.
Проверьте:
-
Ходьба -
W— вперёд; -S— назад; -A— влево; -D— вправо; - персонаж сталкивается с полом и боксами. -
Мышь - курсор захвачен; - горизонтальный обзор работает; - вертикальный обзор работает; - чувствительность комфортная.
-
Анимации - в покое —
idle; - при движении —walking; - сShift—running; - переходы не являются мгновенными скачками. -
Kick - нажмите
F; - запускаетсяkick; - во времяkickперсонаж не скользит; - во времяkickidle/walking/runningне перезаписывают удар; - обзор мышью остаётся доступным; - после завершенияkickуправление возвращается. -
Бег -
Shiftувеличивает скорость до5.0; - безShiftскорость возвращается к3.0; - анимация меняется сwalkingнаrunning.

Управление
| Действие | Ввод |
|---|---|
| Вперёд | W |
| Назад | S |
| Влево | A |
| Вправо | D |
| Обзор | мышь |
| Бег | Shift |
| Kick | F |
| Прыжок | ui_accept, по умолчанию Space/Enter |
Важно:
forward,backward,left,right,run,kick— custom Input Map actions;ui_acceptостаётся стандартным действием для прыжка;- мышь захватывается и не освобождается в этом туториале.
Ограничения реализации
-
Нет clamp по вертикальной оси камеры. -
camera_mount.rotate_x(...)не ограничен вручную. - Это может привести к чрезмерному наклону камеры. - В этом туториале это сознательно не добавляется. -
Нет освобождения мыши. - Мышь остаётся в режиме
MOUSE_MODE_CAPTURED. - Чтобы вернуть курсор, остановите игру или закройте игровое окно. -
Kick блокирует движение и локомоцию, но не обзор. - Во время
kickне вызываетсяmove_and_slide(). - Во времяkickне переключаютсяidle/walking/running. - Мышью можно продолжать осматривать сцену.
Что в видео задаётся визуально, а не числом
Эти параметры нужно подбирать глазами, а не вводить фиксированными числами:
-
Позиции и повороты
box,box2,box3. - размеры заданы:1×1×1,2×2×2,3×3×3; - финальныеPositionиRotationзадаются gizmo визуально; - точные координаты не являются обязательными. -
Локальная позиция
Camera3Dвнутриcamera_mount. -camera_mount.Position = 0, 1.4, 0задан; - но локальный offset камеры подбирается через Camera Preview; - цель — удобный over-the-shoulder вид. -
Общая композиция тестовой сцены. - важно, чтобы боксы были видны и препятствовали движению; - их точная раскладка может немного отличаться.
-
Визуальная подгонка скорости. -
walking_speed = 3.0иrunning_speed = 5.0заданы; - но ощущение «скольжения» всё равно проверяется в игре глазами. -
Визуальная проверка анимаций. - loop, blend time и
look_atпроверяются через результат:- персонаж не «плывёт»;
- ступни не скольжат чрезмерно;
- повороты модели выглядят естественно.
