본 게시글은 'HeartBeast'의 Make an Action RPG 파트의 내용을 다룹니다.
본 강의는 연계 강의이기 때문에 꼭 이전 편들을 보면서 진행하셔야 내용을 이해하시는데 어려움이 없습니다.

덟 번째 파트인 애니메이션 트리입니다.

저번 시간에는 애니메이션 플레이어를 생성하여 애니메이션을 제작하고 재생하는 법에 대해 배웠습니다.

이번 시간에는 애니메이션 트리(AnimationTree)를 사용하여 좀 더 디테일한 애니메이션 적용에 대해서 배워볼 것입니다.

 

우선은 노드를 새로 생성하는 것부터 시작합시다. + 아이콘을 누릅니다.

 

'animation'이라고 검색한 뒤 AnimationTree를 선택합니다.

 

AnimationTree가 생성되었는지 확인하고 그 상태에서 우측 상단에서 Assign... 를 클릭합니다.

 

열리는 창에서 AnimationPlayer를 선택하고 OK버튼을 누릅니다.

 

˅버튼을 클릭합니다.

 

New AnimationNodeStateMachine을 선택합니다.

 

Active 항목을 체크합니다.

 

실제로 적용해서 사용할 건 아니지만, 기능에 대해 이해하기 위해

애니메이션 트리 내에 애니메이션을 생성해 익숙해지는 시간을 갖도록 하겠습니다.

 

하단에 있는 이 창에서 아무 곳이나 우클릭합니다.

 

팝업창에서 Add Animation - IdleRight를 선택합니다.

 

버튼을 눌러 생성된 애니메이션을 재생합니다.

 

한번 재생을 하면 해당 애니메이션을 자동으로 반복 재생합니다.

 

하나 더 생성하도록 합시다. 이번에는 'RunRight'를 생성하겠습니다.

 

혹시 위치가 이상하다면 드래그&드롭으로 맞춰주시면 됩니다.

 

생성한 RunRight를 재생하면 이 역시 반복됩니다.

 

자 다음은 노드 연결해주는 해당 아이콘을 누르신 다음

 

IdleRight 쪽에서 드래그&드롭으로 하시면 됩니다.

 

반대쪽인 RunRight도 마찬가지로 연결합니다.

 

그다음 선택툴을 다시 누릅니다.

 

아까 연결한 노드 사이에 있는 화살표를 클릭합니다. 우선 위에서부터 하도록 하겠습니다.

 

Auto Advance를 체크합니다.

 

그러면 이렇게 자동으로 RunRight로 넘어가서 재생이 되는 모습을 보실 수 있습니다.

만약에 넘어가지 않았다면 IdleRight를 한번 재생해보세요.

 

이번에는 밑에 거 화살표도 선택해서 같은 방법으로 Auto Advance체크해봅시다.

 

체크하시면 버벅거리거나 멈추실 텐데 저는 버벅거리는듯한 모습이 보였습니다.

이건 당연하게도 둘 다 즉시 재생되고 있기 때문에 IdleRightRunRight를 빠르게 왔다 갔다 하여 그렇게 보이는 것입니다.

 

위쪽 화살표를 선택, 그리고 Auto Advance체크 해제하도록 하겠습니다.

 

그다음 아래 화살표를 다시 클릭해서 Switch Mode부분의 Immediate를 클릭해 AtEnd로 바꿔 줍니다.

 

이후에 RunRight를 재생해봅시다. 애니메이션이 한번 재생되고 넘어가는 것을 볼 수 있습니다.

정확히는 애니메이션이 끝나는 시점에 자동으로 IdleRight 애니메이션으로 넘어갑니다.

 

이제 만드신 것들을 다 지우도록 하겠습니다.

선택한 다음 DELETE키를 눌러 지우도록 합니다.

 

뻘짓이라고 생각하실 수도 있는데 전혀 그렇지 않습니다. 이런 방법도 알아둬야 필요할 때 활용할 수 있는 것입니다.

강의에서는 그냥 바로 제작하기 위한 내용만 준비할 수 있지만 이런 식으로 글을 쓰는 이유는

저를 포함해서 활용 스킬을 기르는데 초점을 두고 있습니다.

그러기 위해서는 다양한 기능에 대해 이해할 필요성이 있습니다.

 

바로 다음으로 넘어가도록 하겠습니다. 우클릭해서 Add BlendSpace2D를 선택합니다.

 

생성된 BlendSpace2D의 이름 부분을 더블클릭해 이름을 'Idle'로 바꿉니다.

 

연필✏️ 아이콘을 클릭하여 편집 모드로 들어갑니다.

 

다음으로 애니메이션 트리 창의 좌측 상단에 표시된 아이콘이 선택되어 있는 상태에서

대충 정중앙에다가 클릭을 합니다. 완전 정중앙이 아니어도 됩니다. 나중에 속성 창에서 원점으로 수정할 것입니다.

 

이 상태에서 애니메이션 생성⬥✏️아이콘을 선택합니다.

 

제일 왼쪽 부분을 클릭합니다.

 

Add Animation - IdleLeft를 선택합니다.

 

애니메이션이 생성된 것을 확인할 수 있습니다.

 

다음으로 제일 위쪽 부분을 클릭합니다.

 

Add Animation - IdleDown를 선택합니다.

 

다음으로 제일 오른쪽 부분을 클릭합니다.

 

Add Animation - IdleRight를 선택합니다.

 

마지막으로 제일 아래쪽 부분을 클릭합니다.

 

Add Animation - IdleUp를 선택합니다.

 

추가하면서 왼쪽, 오른쪽은 그렇다 쳐도 위, 아래는 게임 스크린 좌표와 차이가 있다는 것을 느끼실 텐데요.

그 이유는 BlendSpace2D에서는 위가 양수이기 때문입니다. 이점만 주의하시면 됩니다.

 

Blend(혼합)점 선 모양으로 바꿔 주도록 합니다.

 

root를 클릭해 root로 돌아옵니다.

 

Idle의 재생 버튼을 클릭합니다.

 

아까 정중앙을 찍으려고 선택했던 아이콘을 다시 선택합니다.

 

마우스를 꾹 누른 상태에서 이리저리 옮겨봅시다.

 

테스트로 이리저리 옮겨보면서 애니메이션이 각기 재생되는 것을 확인했지만,

여러분이 이리저리 옮겼던 커서 위치는 Inpurt.Vector값이 들어올 때 재생될 애니메이션이라고 보시면 됩니다.

예를 들어 (0, 1)이 들어오면 위쪽을 바라보는 애니메이션이 (1, 0)이 들어오면 오른쪽을 바라보는 애니메이션이

재생되겠죠? 따라서 초깃값은 원점으로 수정하도록 하겠습니다.

 

두 접힌 부분을 펼치고 Blend Position의 두 개의 값을 '0'으로 바꿉니다.

 

다시 root로 돌아갑니다.

 

BlendSpace2D를 하나 더 생성합니다. 이름은 'Run'으로 정하겠습니다.

 

편집하기 전에 재생 버튼을 미리 눌러주세요.

 

연필✏️ 아이콘을 눌러 편집으로 들어갑니다.

 

아까처럼 애니메이션을 생성하시면 됩니다. 단 Run애니메이션이니까 앞에 Run이 붙은걸 생성하셔야겠죠?

굳이 말하자면 왼쪽은 RunLeft, 오른쪽은 RunRight, 위쪽은 RunDown, 아래쪽은 RunUp입니다.

 

 

Blend 바꿔 주시는 거 잊지 마시고요.

 

이제 아까처럼 좌측 상단에 있는 아이콘을 선택하고 이리저리 마우스를 움직여봅시다.

 

이리저리 옮겼으니 이제 Blend Position을 원점으로 돌려야겠죠? 근데 이런, 속성 창에 설정하는 곳이 보이지 않습니다.

이럴 때는 다시 AnimationTree를 선택해주시면 됩니다.

 

 그러면 보이게 됩니다. Run에서 Blend Position의 두 값을 '0'으로 수정하도록 합시다.

 

마우스 헛질해서 Blend Position이 바뀌는 걸 방지하기 위해 바로 옆에 있는 툴을 선택해두는 것이 좋습니다.

 

그 외에도 이미 생성된 애니메이션을 클릭하면 다른 애니메이션으로도 바꿀 수 있습니다.

 

root로 돌아갑시다. 아까 애니메이션을 했던 것처럼 각 BlendSpace2D끼리 연결을 할 것입니다.

 

먼저 Idle에서 Run쪽으로 연결합시다.

 

반대쪽도 마찬가지로 연결해주시고요.

 

Idle을 선택, 그다음 표시한 아이콘을 클릭해주세요.

 

그러면 스타트 상태가 되면서 자동재생(AutoPlay)이 됩니다. 코드를 작성하기 전에 미리 켜 두고 진행하겠습니다.

 

스크립트(Script) 페이지로 넘어갑니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extends KinematicBody2D
 
const ACCELERATION = 500
const MAX_SPEED = 80
const FRICTION = 500
 
var velocity = Vector2.ZERO
 
onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
 
func _physics_process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()
    
    if input_vector != Vector2.ZERO:
        animationTree.set("")
        velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
    else:
        animationPlayer.play("IdleRight")
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
        
    velocity = move_and_slide(velocity)

animationTree 변수를 추가시킨 뒤input_vector != Vector2.ZERO:에 작성되어 있던 내용을 지우고

anmationTree.set 함수를 작성했습니다. 이제 " " 안에 내용을 넣어야 하는데요.

 

AnimationTree 노드를 선택한 상태에서 Idle - Blend Position에 마우스를 대면 다음과 같이 경로가 보입니다.

이것을 아까 " " 안에 대소문자 정확하게 넣어주도록 합시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extends KinematicBody2D
 
const ACCELERATION = 500
const MAX_SPEED = 80
const FRICTION = 500
 
var velocity = Vector2.ZERO
 
onready var animationPlayer = $AnimationPlayer
onready var animaitonTree = $AnimationTree
 
func _physics_process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()
    
    if input_vector != Vector2.ZERO:
        animationTree.set("parameters/Idle/blend_position", input_vector)
        velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
    else:
        animationPlayer.play("IdleRight")
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
        
    velocity = move_and_slide(velocity)

그리고 인자를 추가로 하나 넣어줘야 하는데요. ',' 찍고 추가로 input_vector를 넣어주도록 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extends KinematicBody2D
 
const ACCELERATION = 500
const MAX_SPEED = 80
const FRICTION = 500
 
var velocity = Vector2.ZERO
 
onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
 
func _physics_process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()
    
    if input_vector != Vector2.ZERO:
        animationTree.set("parameters/Idle/blend_position", input_vector)
        animationTree.set("parameters/Run/blend_position", input_vector)
        velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
    else:
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
        
    velocity = move_and_slide(velocity)

그다음 위에서 고대로 똑같이 Idle에서 Run으로 바꿔주고 추가로 작성합니다.

else: 구문 아래에 있는 animationPlayer.play("IdleRight")는 지우도록 합니다.

 

이제 게임을 실행해보고 확인해봅시다. 잘 작동합니다. 이제 Run도 연동해볼까요?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
extends KinematicBody2D
 
const ACCELERATION = 500
const MAX_SPEED = 80
const FRICTION = 500
 
var velocity = Vector2.ZERO
 
onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
onready var animationState = animationTree.get("")
 
func _physics_process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()
    
    if input_vector != Vector2.ZERO:
        animationTree.set("parameters/Idle/blend_position", input_vector)
        animationTree.set("parameters/Run/blend_position", input_vector)
        velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
  else:
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
        
    velocity = move_and_slide(velocity)

그전에 변수를 하나 더 추가해야 합니다. 값을 대입하는 부분에 animationTree.get(" ") 까지 쓰시고

" "안에 내용을 넣어야 하는데요.

 

마우스를 Playback에 올려두고 나오는 경로를 적어주시면 됩니다. 이것 또한 대소문자 정확하게 넣어주셔야 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
extends KinematicBody2D
 
const ACCELERATION = 500
const MAX_SPEED = 80
const FRICTION = 500
 
var velocity = Vector2.ZERO
 
onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
onready var animationState = animationTree.get("parameters/playback")
 
func _physics_process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()
    
    if input_vector != Vector2.ZERO:
        animationTree.set("parameters/Idle/blend_position", input_vector)
        animationTree.set("parameters/Run/blend_position", input_vector)
        animationState.travel("Run")
        velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
    else:
        animationState.travel("Idle")
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
        
    velocity = move_and_slide(velocity)

 

방금 작성한 변수를 활용해서 추가로 코드를 다음과 같이 작성합니다.

 

게임을 플레이해서 다시 한번 확인해봅니다. 깔끔합니다만… 한 가지 더 고려해볼 상황이 남아있습니다.

 

대각선 이동에서 일관성이 없다는 것이죠.

 

실제로 4방향 (↑↓←→)은 문제없지만 나머지 4방향(↖↗↙↘ )이

측면 이동으로 통일한다고 치면 측면 이동만 쓰는 것이 아니라 위나 아래 모션과 같은 다른 모션을 쓰게 되는 경우가 생깁니다.

이 부분은 코드를 쓰지 않고 요령(Trick)을 사용해서 의외로 간단하게 수정할 수 있습니다.

 

아까 다시 애니메이션 트리 창으로 돌아옵시다.

 

Idle의 편집 모드로 들어갑니다.

 

왼쪽과 오른쪽 모션에 우선권을 주기 위해서 y좌표의 다음 두 값을 '1.1' , '-1.1'로 바꿉니다.

 

선택툴을 선택하고 지점을 선택한 다음 드래그&드롭으로 표시한 해당 지점(1.1)까지 옮기시면 됩니다.

 

이런 식으로 옮기셨으면

 

반대쪽도 마찬가지로 -1.1에 맞춰줍시다.

 

이번엔 Run를 편집해서 아까 Idle과 똑같게 y좌표의 두 값을 수정합니다.

 

Idle과 똑같이 애니메이션 지점을 선택해서 끝에다가 맞춰줍니다.

 

한번 게임을 재생해서 확인해볼까요? 나쁘지 않네요.

 

왼쪽과 오른쪽에 우선권을 부여하기 위해 y값을 수정했기 때문에 측면 모션이 나오는 거지만

반대로 위쪽과 아래쪽을 우선권을 주기 위해 x값을 수정하면

대각선 이동에 측면 이동이 아니라 위와 아래 이동을 사용하게 될 것입니다.

 

다음은 지형을 손보기 위해 타일 맵(TileMap)과 그 안에 있는 기능인

오토 타일(Autotile)에 대해서 배워보는 시간을 갖도록 하겠습니다.

그럼 다음 파트에서 뵙겠습니다.

 

+) 05/06

불 필요한 내용 일부 삭제 및 수정

일부 텍스트에 강조표시 사용

+) 05/10 일부 용어 레퍼런스 링크 적용

728x90
728x90