해당 내용 출처 :) https://docs.godotengine.org/en/stable/getting_started/first_2d_game/index.html
이전 내용:) https://twd0622.tistory.com/81
저번 포스팅에 플레이어씬을 만들어 애니메이션과 충돌영역을 설정해 줬다.
이어 이번 포스팅에서는 플레이어를 코딩해 움직이게 하고 충돌을 감지하도록 해보겠다.
3장 플레이어 코딩하기
3-1 플레이어씬 스크립트 생성
먼저 플레이어에 기능을 추가하기 위해선 스크립트를 생성해야한다.
먼저 씬 메뉴에 있는 해당 버튼을 눌러주면
이렇게 노드 스크립트 붙이기 창이 나온다. 다른거 변경할 것없이 바로 만들기를 눌러주면 된다.
그러면 이렇게 스크립트가 생성되게 된다.
기본적으로 _ready() 함수와 _process 함수가 생성되어있다.
_ready() 함수는 노드가 씬 트리에 들어가기 전에 실행되며, 화면 크기와 같은 기본 세팅을 하기 좋다.
_process() 함수는 매 프레임 마다 호출 되므로, 자주 변경될 게임 요소를 업데이트하는데 사용된다.
이제 스크립트 위쪽에 전역 변수를 선언 해준다.
extends Area2D
@export var speed = 400 # 플레이어의 속도 (pixels/sec)
var screen_size # 게임 화면의 사이즈
@export 키워드를 사용하면 인스펙터 메뉴에서 세팅할 수 있게 된다.
인스펙터 메뉴에서 해당 값을 수정하면 코드의 값도 수정 되니 주의 해야한다!
이번엔 _ready() 함수에서 screen_size에 화면 크기를 담아준다.
func _ready() -> void:
screen_size = get_viewport_rect().size # 화면 크기 담아주기
기존에 있던 pass는 삭제해주면 된다.
extends Area2D
@export var speed = 400 # 플레이어의 속도 (초당 픽셀 움직)
var screen_size # 게임 화면의 사이즈를 담아줄 변수
func _ready() -> void:
screen_size = get_viewport_rect().size # 화면 크기 담아주기
func _process(delta: float) -> void:
pass
지금까지 내용의 player.gd 이다.
3-2 키 세팅
이번엔 키보드 방향키를 눌렀을때 플레이어가 움직일 수 있도록 코딩해보겠다.
우선 키설정을 먼저 해주어야한다.
상단 메뉴에서 프로젝트를 누르고 Project Settings...를 눌러준다.
프로젝트 설정 창에서 '입력맵'으로 들어간다.
'새로운 액션 추가'에 'move_right'로 수정해주고 추가 버튼을 눌러준다.
그럼 사진처럼 하단에 액션이 추가되는데 + 버튼을 눌러주면 "move_right"에 대한 이벤트를 설정할 수 있게된다.
키보드키 'Right'를 찾아서 확인을 눌러준다.
그럼 move_right 액션에 Right(물리) 이벤트가 추가 된것을 볼수있다.
이렇게 같은 방식으로 'move_left', 'move_up', 'move_down'을 추가해주면 된다.
모두 추가한 모습이다. 이제 닫기 눌러주면된다.
이제 _process() 함수에 움직임에 대한 코드를 추가할것이다.
키보드의 반향키를 누르게 되면 _process() 함수에서
1. input을 확인
2. 지정된 방향으로 이동
3. 애니매이션 변경
순으로 반응하게 만들어 줄것이다.
1. input 확인
_process() 함수안에 해당 코드를 작성해 주자.
func _process(delta: float) -> void:
# 기본 이동 값 (0, 0)
var velocity = Vector2.ZERO # 플레이어의 움직임에 대한 Vector
# 각 버튼 별 이동 값 부여
if Input.is_action_pressed("move_right"):
velocity.x += 1 # (1, 0)
if Input.is_action_pressed("move_left"):
velocity.x -= 1 # (-1, 0)
if Input.is_action_pressed("move_down"):
velocity.y += 1 # (0, 1)
if Input.is_action_pressed("move_up"):
velocity.y -= 1 # (0, -1)
# 이동한 만큼 속도 & 이미지 설정
if velocity.length() > 0:
velocity = velocity.normalized() * speed # 속도 정규화
$AnimatedSprite2D.play()
else: # 이동이 없으면 애니 중단
$AnimatedSprite2D.stop()
- 고도엔진에서는 2D 상의 좌표에서 0점 기준 Y 위쪽이 음의 영역, 아래쪽이 양의 영역이다. 그래서 "move_up"을 하면 y좌표가 -1이 되고, "move_down"을 하면 y좌표가 +1이 된다.
- velocity = velocity.normalized() * speed를 한 이유는 속도 정규화를 위해서 인데, 상하좌우 버튼 하나만 눌러서 이동하는 것과 보다 대각선으로 움직이기 위해 상하 버튼 중 하나와 좌우 버튼 중 하나를 동시에 누르는것이 훨신 빠르게 움직이게 된다. 그래서 이를 맞춰주기 위해서 속도 정규화 처리를 해주는 것이다.
- 코드 $ 키워드는 get_node()를 줄여준 것이다. $AnimatedSprite2D.play() 와 get_node("AnimatedSprite2D").play()는 동일한 동작을 하는 코드이다.
2. 지정된 방향으로 이동
이제 방향과 이동 값을 지정해주었으니 변경된 만큼 플레이어의 위치 값을 변경해주어야한다. 그리고 플레이어가 화면 밖으로 나가지 않도록 clamp처리 해준다.
아래 코드를 _process() 함수에 추가해준다.
# 이동한 만큼 위치 변화
position += velocity * delta
# 화면 밖으로 나가지 않도록 처리 (min, max)
position = position.clamp(Vector2.ZERO, screen_size)
- 코드를 새로 추가해줄때 들여쓰기를 잘 확인해야한다. _process()안에 넣으면서 앞서 했던 코드의 마지막 else에 포함되지 않게 조심해야 한다.
- _process() 함수의 델타(delta) 매개변수는 프레임 길이, 즉 이전 프레임을 완료하는 데 걸린 시간을 나타냅니다. 이 값을 사용하면 프레임 속도가 변경되더라도 움직임이 일관되게 유지됩니다.
지금 player.gd 코드다.
extends Area2D
@export var speed = 400 # 플레이어의 속도 (초당 픽셀 움직)
var screen_size # 게임 화면의 사이즈를 담아줄 변수
func _ready() -> void:
screen_size = get_viewport_rect().size # 화면 크기 담아주기
func _process(delta: float) -> void:
# 기본 이동 값 (0, 0)
var velocity = Vector2.ZERO # 플레이어의 움직임에 대한 Vector
# 각 버튼 별 이동 값 부여
if Input.is_action_pressed("move_right"):
velocity.x += 1 # (1, 0)
if Input.is_action_pressed("move_left"):
velocity.x -= 1 # (-1, 0)
if Input.is_action_pressed("move_down"):
velocity.y += 1 # (0, 1)
if Input.is_action_pressed("move_up"):
velocity.y -= 1 # (0, -1)
# 이동한 만큼 속도 & 이미지 설정
if velocity.length() > 0:
velocity = velocity.normalized() * speed # 속도 정규화
$AnimatedSprite2D.play()
else: # 이동이 없으면 애니 중단
$AnimatedSprite2D.stop()
# 이동한 만큼 위치 변화
position += velocity * delta
# 화면 밖으로 나가지 않도록 처리
position = position.clamp(Vector2.ZERO, screen_size)
여기까지 작성했다면 스크립트를 실행시켜보자.
스크립트를 실행시킬땐 F5를 누르거나 상단에 프로젝트 실행버튼을 눌러주면 된다.
실행시켜주면 사진 처럼 메인씬을 설정하라고 나오는데 '현재 선택'을 고르면 된다. '선택'을 눌렀다면 Player.tcsn을 선택해주면 된다.
실행시키면 키보드를 누르면 플레이어 캐릭터가 움직이는 것과 화면 밖으로 나가지지 않는지 확인하면 된다.
애니메이션이 있긴 하지만 어느 방향이든 똑같이 움직이고 있다. 이제 애니메이션을 변경해줄 차례다.
3. 애니메이션 변경
이제 플레이어가 움직이는 방향에 따라 AnimatedSprite2D가 재생하는 애니메이션을 변경해주어야 한다.
좌우로 움직일 땐 "walk" 애니메이션을 위아래로 움직일땐 "up" 애니메이션을 틀어준다.
하지만 "walk" 애니메이션은 오른쪽으로 움직이는 애니메이션이기 때문에 왼쪽 버튼을 누를땐 " Flip_h" 속성을 사용해 수평으로 뒤집어야한다. "up"도 마찬가지로 위쪽으로 움직이는 애니메이션이기 때문에 "Flip_v"속성을 이용해 수직으로 뒤집어 주어야한다.
아래 코드를 _process() 함수 끝에 추가해 보자.
# 방향에 따라 애니메이션 변경
if velocity.x != 0: # 좌우로 움직일 때
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0: # 상하로 움직일 때
$AnimatedSprite2D.animation = "up"
$AnimatedSprite2D.flip_v = velocity.y > 0
- 좌우로 움직일 떄, flip_v를 "false"로 설정해둔 이유는 대각선 아래로 움직이는 경우에는 애니메이션을 뒤집지 않기 위해서 이다.
- velocity.x < 0 , velocity.y > 0 는 x가 왼쪽인 경우는 0보다 작고, y가 아래인 경우는 0 보다 크기 때문에 해당 코드를 사용하면 아래 코드처럼 if문을 두번 쓰지 않고 처리할 수 있다.
if velocity.x > 0:
$AnimatedSprite2D.flip_h = false
else:
$AnimatedSprite2D.flip_h = true
여기 까지의 코드다.
extends Area2D
@export var speed = 400 # 플레이어의 속도 (초당 픽셀 움직)
var screen_size # 게임 화면의 사이즈를 담아줄 변수
func _ready() -> void:
screen_size = get_viewport_rect().size # 화면 크기 담아주기
func _process(delta: float) -> void:
# 기본 이동 값 (0, 0)
var velocity = Vector2.ZERO # 플레이어의 움직임에 대한 Vector
# 각 버튼 별 이동 값 부여
if Input.is_action_pressed("move_right"):
velocity.x += 1 # (1, 0)
if Input.is_action_pressed("move_left"):
velocity.x -= 1 # (-1, 0)
if Input.is_action_pressed("move_down"):
velocity.y += 1 # (0, 1)
if Input.is_action_pressed("move_up"):
velocity.y -= 1 # (0, -1)
# 이동한 만큼 속도 & 이미지 설정
if velocity.length() > 0:
velocity = velocity.normalized() * speed # 속도 정규화
$AnimatedSprite2D.play()
else: # 이동이 없으면 애니 중단
$AnimatedSprite2D.stop()
# 이동한 만큼 위치 변화
position += velocity * delta
# 화면 밖으로 나가지 않도록 처리
position = position.clamp(Vector2.ZERO, screen_size)
# 방향에 따라 애니메이션 변경
if velocity.x != 0: # 좌우로 움직일 때
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0: # 상하로 움직일 때
$AnimatedSprite2D.animation = "up"
$AnimatedSprite2D.flip_v = velocity.y > 0
이제 다시 스크립트를 실행해 방향에 맞게 애니메이션이 변경되는지 확인해보자.
문제 없이 잘 진행된다면 _ready() 함수 아래에 hide()를 추가해준다. 그러면 게임이 시작될 때 플레이어를 숨겨준다.
hide()
3-3 충돌 설정
아직 적을 만들지 않았지만, 플레이어가 적과 충돌했을 때를 위해서 signal 키워드를 사용해 미리 충돌 설정을 해줄것이다.
player.gd 최상단 extends Area2D 아래에 밑의 코드를 추가해준다.
signal hit
그러면 플레이어 노드를 누르고, 노드 메뉴를 보면 hit()가 추가 된것을 볼 수 있다. hit()가 거기에 추가됬다는 것만 기억해두고, 지금 주목할 부분은 더 밑에 있는 body_entered(body:Node2D)이다.
적들의 노드는 RigidBody2D 노드로 만들것 인데 body_entered(body:Node2D)는 2D 노드가 플레이어에 닿았을 경우 반응합니다.
해당 시그널을 더블클릭 하거나 오른쪽 마우스를 누르고 '연결...'을 누르면 해당 시그널을 메서드에 연결할 수 있다.
다른 작업없이 바로 연결을 눌러주면 된다.
제대로 연결 되었다면 _on_body_entered() 코드 오른쪽에 초록색 아이콘이 보일 것이다.
이제 새로 생성된 메서드에 코드를 추가해주자
func _on_body_entered(body: Node2D) -> void:
hide() # 적과 충돌이 일어나면 플레이어를 숨김
hit.emit() # hit 신호 호출
$CollisionShape2D.set_deferred("disabled", true) # 충돌이 중복되지 않도록 충돌 불가능 설정
- $CollisionShape2D.disabled = true 방식으로 코드를 작성하면 충돌 함수와 disabled를 변경해주는 코드가 겹쳐 에러가 발생할 수 있다. $CollisionShape2D.set_deferred("disabled", true) 로 작성하게 되면 안전한 상태가 될때 까지 기다렸다가 disabled를 true로 변경해준다.
3-4 시작 함수
이제 마지막으로 게임이 시작될때 실행될 함수를 만들어 줘야한다. 아래 함수를 생성해주자.
func start(pos):
position = pos # 플레이어 위치 지정
show() # 플레이어 보여주기
$CollisionShape2D.disabled = false # 충돌 방지 해제
여기까지가 플레이어에 대한 작업이 완료 되었다.
지금까지 player.gd
extends Area2D
signal hit
@export var speed = 400 # 플레이어의 속도 (초당 픽셀 움직)
var screen_size # 게임 화면의 사이즈를 담아줄 변수
func _ready() -> void:
screen_size = get_viewport_rect().size # 화면 크기 담아주기
hide()
func _process(delta: float) -> void:
# 기본 이동 값 (0, 0)
var velocity = Vector2.ZERO # 플레이어의 움직임에 대한 Vector
# 각 버튼 별 이동 값 부여
if Input.is_action_pressed("move_right"):
velocity.x += 1 # (1, 0)
if Input.is_action_pressed("move_left"):
velocity.x -= 1 # (-1, 0)
if Input.is_action_pressed("move_down"):
velocity.y += 1 # (0, 1)
if Input.is_action_pressed("move_up"):
velocity.y -= 1 # (0, -1)
# 이동한 만큼 속도 & 이미지 설정
if velocity.length() > 0:
velocity = velocity.normalized() * speed # 속도 정규화
$AnimatedSprite2D.play()
else: # 이동이 없으면 애니 중단
$AnimatedSprite2D.stop()
# 이동한 만큼 위치 변화
position += velocity * delta
# 화면 밖으로 나가지 않도록 처리
position = position.clamp(Vector2.ZERO, screen_size)
# 방향에 따라 애니메이션 변경
if velocity.x != 0: # 좌우로 움직일 때
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0: # 상하로 움직일 때
$AnimatedSprite2D.animation = "up"
$AnimatedSprite2D.flip_v = velocity.y > 0
func _on_body_entered(body: Node2D) -> void:
hide() # 적과 충돌이 일어나면 플레이어를 숨김
hit.emit() # hit 신호 호출
$CollisionShape2D.set_deferred("disabled", true) # 충돌이 중복되지 않도록 충돌 불가능 설정
func start(pos):
position = pos # 플레이어 위치 지정
show() # 플레이어 보여주기
$CollisionShape2D.disabled = false # 충돌 방지 해제
이제 플레이어에 대한 작업이 완료되었다. 다음 포스팅에서는 적이될 몹을 만들어 보겠다.
다음 포스팅 :) https://twd0622.tistory.com/89
'Godot Engine > 게임 제작' 카테고리의 다른 글
[GodotEngine] Tutorial 2D Game - 4 (0) | 2024.12.19 |
---|---|
[GodotEngine] Tutorial 2D Game - 2 (0) | 2024.12.11 |
[GodotEngine] Tutorial 2D Game - 1 (0) | 2024.12.10 |