7강입력 받기
키를 하드코딩하지 않고 액션으로 묶는다.
- 입력 맵(Input Map) 으로 키를 액션 이름에 묶는다.
- 폴링(
Input.is_action_pressed)과 이벤트(_input)의 차이를 안다. - 대각선 이동이 빨라지는 문제를 이해하고 고친다.
키를 코드에 박으면 안 되는 이유#
가장 순진한 입력 처리는 이렇습니다.
if Input.is_key_pressed(KEY_D):
position.x += 300 * delta동작은 합니다. 그런데 이 코드는 다음을 전부 못 합니다.
- 화살표 키로도 움직이게 하기
- 게임패드 지원
- 모바일 터치 지원
- 사용자가 키 설정 바꾸기
- 왼손잡이용 대체 키
그리고 이런 코드가 프로젝트 전체에 흩어져 있으면, 나중에 게임패드를 붙이려면 모든 파일을 다 뒤져야 합니다.
"D키"라고 쓰지 말고 "move_right" 라는 이름으로 씁니다.
그 이름에 어떤 키·패드 버튼·터치가 연결될지는 프로젝트 설정에서 따로 정합니다.
"move_right"
← D키, →키, 패드 스틱 오른쪽, D-Pad 오른쪽코드는 이름만 알면 되고, 장치가 늘어나도 코드는 한 줄도 안 바뀝니다.
입력 맵 설정하기#
프로젝트 → 프로젝트 설정 → 입력 맵(Input Map) 탭을 엽니다.
- 위쪽 칸에
move_left를 입력하고 추가 - 목록에 생긴
move_left오른쪽+를 눌러 키를 등록- 키보드
A - 키보드
← - 게임패드 왼쪽 스틱 왼쪽 (Joypad Axis)
- 키보드
- 같은 방식으로
move_right,move_up,move_down,jump,attack을 만듭니다
Godot에는 ui_left, ui_accept, ui_cancel 같은 액션이 기본 등록돼 있습니다.
UI 조작 전용이라 생각하고, 게임플레이에는 직접 만든 액션을 쓰는 게 좋습니다.
나중에 "메뉴에서 스페이스를 눌렀는데 캐릭터가 점프한다" 같은 사고를 막습니다.
기본 액션을 보려면 입력 맵 탭 아래의 "내장 액션 표시" 를 켜세요.
읽는 방법 두 가지#
방법 1 — 폴링 (매 프레임 물어보기)#
"지금 이 키가 눌려 있나?"를 매 프레임 확인합니다. 이동처럼 계속 유지되는 입력에 씁니다.
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("move_right"):
position.x += 300.0 * delta| 함수 | 의미 |
|---|---|
is_action_pressed("x") |
지금 눌려 있는 중인가 (계속 true) |
is_action_just_pressed("x") |
이번 프레임에 막 눌렸는가 (1프레임만 true) |
is_action_just_released("x") |
이번 프레임에 막 떼었는가 |
get_action_strength("x") |
얼마나 눌렸나 (0.0~1.0, 아날로그 스틱용) |
방법 2 — 이벤트 (알림 받기)#
"키가 눌리는 사건이 발생했다"는 알림을 받습니다. 한 번만 반응하는 입력에 좋습니다.
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("attack"):
_shoot()
elif event.is_action_pressed("ui_cancel"):
_toggle_pause()- 이동, 조준, 계속 누르는 것 → 폴링 (
_physics_process안에서) - 점프, 공격, 메뉴 열기, 대화 넘기기 → 이벤트 (
_unhandled_input)
이유는 놓침 때문입니다. 프레임이 튀는 순간 아주 짧게 눌렀다 뗀 키는 폴링으로는 아예 못 볼 수 있습니다. 이벤트는 큐에 쌓이므로 놓치지 않습니다.
_input 과 _unhandled_input 의 차이
입력 이벤트는 아래 순서로 흘러갑니다. 중간에 누가 "내가 처리했다"고 표시하면 거기서 멈춥니다.
_input() ← 무조건 가장 먼저. 모든 노드
↓
UI(Control) 노드들 ← 버튼 클릭, 텍스트 입력 등
↓
_shortcut_input()
↓
_unhandled_input() ← UI가 안 먹은 입력만 여기 온다게임플레이 입력은 _unhandled_input에 넣으세요.
그러면 채팅창에 글자를 치는 동안 캐릭터가 점프하는 사고가 안 생깁니다.
UI가 이미 그 입력을 먹었기 때문입니다.
_input에 넣으면 UI보다 먼저 가로채므로, 정말 전역적인 것(스크린샷 키 등)에만 씁니다.
직접 처리를 끝냈음을 알리려면 get_viewport().set_input_as_handled() 를 부릅니다.
4방향 입력을 한 줄로#
상하좌우를 각각 if로 쓰면 길어집니다. Godot에 전용 함수가 있습니다.
var dir := Input.get_vector(
"move_left", "move_right", "move_up", "move_down")이 한 줄이 반환하는 것:
- 아무것도 안 누르면
(0, 0) - 오른쪽만 누르면
(1, 0) - 오른쪽+아래를 누르면
(0.707, 0.707)← 이미 정규화되어 있습니다 - 아날로그 스틱이면 기울인 만큼
(0.4, 0.0)같은 중간값
직접 만들면 이런 코드가 나옵니다.
var dir := Vector2.ZERO
if Input.is_action_pressed("move_right"): dir.x += 1
if Input.is_action_pressed("move_down"): dir.y += 1
# 오른쪽+아래 = (1, 1) → 길이가 1.414!(1, 1) 벡터의 길이는 √2 ≈ 1.414입니다.
그래서 대각선으로 가면 40% 빨라집니다. 플레이어는 곧바로 이걸 발견하고
모든 이동을 지그재그로 하기 시작합니다.
해결 — dir = dir.normalized() 로 길이를 1로 맞추거나, 애초에 Input.get_vector() 를 쓰세요.
실습 — 사방으로 움직이는 캐릭터#
2강에서 만든 player.tscn의 스크립트를 아래로 교체합니다.
extends Node2D
@export var speed := 300.0
func _physics_process(delta: float) -> void:
var dir := Input.get_vector(
"move_left", "move_right", "move_up", "move_down")
position += dir * speed * delta세 줄입니다. 이제 WASD·화살표·게임패드 전부로 움직입니다.
키보드로 사방 이동이 되고, 대각선이 더 빠르지 않으면 성공입니다. 게임패드가 있다면 꽂고 확인해 보세요. 코드를 한 줄도 안 고쳐도 됩니다.
실습 — 마우스 방향 보기#
조준을 붙여봅니다.
func _physics_process(delta: float) -> void:
var dir := Input.get_vector(
"move_left", "move_right", "move_up", "move_down")
position += dir * speed * delta
# 마우스가 있는 쪽을 바라본다.
look_at(get_global_mouse_position())look_at() 은 노드의 X축(오른쪽)이 대상을 향하도록 회전시킵니다.
그림이 위쪽을 보고 그려져 있다면 90도 어긋나 보입니다. 그때는 이렇게 보정합니다.
rotation = (get_global_mouse_position()
- global_position).angle() + PI / 2터치 기기(폴드)에서의 입력
마우스가 없는 기기에서는 위 코드가 동작하지 않습니다. 두 가지 대응이 있습니다.
1) 마우스 에뮬레이션 켜기
프로젝트 설정 → Input Devices → Pointing → Emulate Mouse From Touch
터치를 마우스처럼 처리해 줍니다. 간단한 게임엔 이걸로 충분합니다.
2) 가상 조이스틱 만들기
TouchScreenButton 노드나 Control + 드래그로 직접 만듭니다.
26강 웹 배포 후 폰에서 플레이하려면 이게 필요합니다.
# 터치 위치를 조이스틱 방향으로 바꾸는 최소 예
func _input(event: InputEvent) -> void:
if event is InputEventScreenDrag:
var v := event.position - _touch_origin
_stick = v.limit_length(64.0) / 64.0- 입력 맵에
move_right의 대체 키로 화살표→를 추가하고 확인하세요. speed를 인스펙터에서 100 / 300 / 900 으로 바꿔가며 조작감을 비교하세요.attack액션을 만들고_unhandled_input에서print("탕!")를 찍어보세요. 연타했을 때 누른 횟수만큼 정확히 찍히는지 확인하세요.- 같은 것을
is_action_pressed폴링으로 바꿔보세요. 한 번 눌렀는데 수십 번 찍힙니다. 이게 폴링과 이벤트의 차이입니다.
- 개발자 — 입력 맵 이름은 동사로 통일하세요(
jump,attack,interact). 나중에 리매핑 UI를 만들 때 이름을 그대로 화면에 보여주게 됩니다. - 기획자 — 조작 스펙을 쓸 때 "어떤 키"가 아니라 "어떤 액션" 으로 적으세요. "스페이스로 점프" 대신 "점프 — 기본 키 스페이스, 패드 A". 키는 바뀌지만 액션은 안 바뀝니다.
- 디자이너 — 조작 안내 UI는 연결된 장치에 따라 아이콘이 바뀌어야 합니다. 키보드/플스/엑박 3종 아이콘 세트를 초기에 준비해두면 나중에 급하지 않습니다.
"액션이 없다는 오류가 난다"
→ The InputMap action "xxx" doesn't exist. 프로젝트 설정에서 오타를 확인하세요.
액션 이름은 대소문자를 구분합니다.
"키를 눌러도 반응이 없다"
→ ① 게임 창에 포커스가 없음, ② 액션에 키가 등록 안 됨,
③ UI 노드가 입력을 먹고 있음(_unhandled_input까지 안 옴).
"게임패드 스틱을 살짝 건드려도 캐릭터가 움직인다" → 스틱 데드존 문제입니다. 입력 맵의 각 조이패드 축 항목에서 Deadzone 값을 0.2~0.3 정도로 올리세요.
- 입력 맵에 액션을 만들고 여러 장치를 묶었다
- 폴링과 이벤트를 언제 쓰는지 구분한다
-
Input.get_vector()로 대각선 문제를 피했다 -
_unhandled_input을 쓰는 이유를 안다
다음 강의 — 방금 코드를 _physics_process에 넣었습니다. 왜 _process가 아니었을까요?
두 개의 시계를 제대로 봅니다.