유니티에서 애니메이션 클립의 초당 프레임 수를 결정하는 샘플 레이트에 대해서 알아봅시다!

 

사용 엔진 버전 : 2018.4 - 2019.4

 

타임라인

0:00 인트로

0:11 애니메이션 샘플 레이트?

0:55 2018 이전 버전에서 애니메이션 샘플 레이트 변경하기

1:42 2019 이후 버전에서 애니메이션 샘플 레이트 변경하기

2:33 샘플 레이트 다루기

3:51 아웃트로

반응형

UI 비법서 (7) 

Button 컴포넌트

 

작성 기준 버전 :: 2019.2

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다.]

 

아마 게임을 하면서 플레이어와 제일 많은 상호작용을 하는 UI 요소는 바로 버튼일 것이다. 버튼은 마우스의 클릭에 반응해서 지정해둔 기능을 동작하게 하는 UI이다. 이렇게 설명만 들어도 간단해보이는 Button 컴포넌트가 이렇게 영상까지 만들어서 이야기할 필요가 있을까 싶을 것이다.

 

버튼 게임 오브젝트 생성하기

 

하지만 이렇게 간단해보이는 Button 컴포넌트에도 유용하게 사용할 수 있는 팁들이 꽤 있다.

 

 

하이어라키 뷰에 우클릭하고 [UI > Button]을 선택하면 버튼 게임 오브젝트를 생성할 수 있다.

 

 

생성된 버튼 게임 오브젝트에는 Image 컴포넌트와 Button 컴포넌트가 부착되어 있는 것을 볼 수 있다. 거기에 기본적으로 버튼 게임 오브젝트 아래에는 버튼에 글자도 표시되게 Text 컴포넌트가 붙어있는 게임 오브젝트도 자식으로 있는 것을 알 수 있다. 이게 버튼의 제일 기본적인 상태이다.

 

Button 컴포넌트의 프로퍼티

 

그럼 Button 컴포넌트의 프로퍼티들을 하나씩 알아보도록 하자.

 

Interactable

 

첫 번째 프로퍼티는 Interactable이다. Interactable은 버튼이 상호작용 가능한지를 정하는 프로퍼티입니다.

 

 

먼저 Interactabla이 체크된 상태에서 클릭해보면 클릭할 때마다 버튼이 깜빡깜빡하며 버튼과 마우스 사이의 상호작용이 일어나고 있는 것을 확인할 수 있다.

 

 

Interactable을 끄면 먼저 버튼의 색깔이 약간 어두워지고 반투명해지는 것을 볼 수 있다. 그 상태에서 버튼을 클릭해보면 아까와는 다르게 상호작용하면서 발생하는 깜빡임이 나타나지 않는 것을 알 수 있다.

 

이 프로퍼티는 특정한 조건에 따라서 버튼을 활성화/비활성화 하는 기능으로 응용할 수 있다.

 

Transition

 

그다음 프로퍼티는 Transition이다. Transition은 버튼이 각 상호작용 상태에서 어떤 모습으로 보일 것인지를 결정한다.

 

 

버튼의 상호작용 상태로는 먼저 아무런 상호작용도 일어나고 있지 않은 Normal, 마우스가 버튼 위에서 호버링되고 있는 상태인 Highlighted, 버튼을 클릭하고 손을 떼지 않은 상태인 Pressed, 버튼에 클릭을 하고 손을 뗀 상태인 Selected, 버튼이 비활성화된 Disabled. 

 

이렇게 총 5가지가 있다.

 

그리고 선택 가능한 Transition의 종류로는 None, Color Tint, Sprite Swap, Animation이 있다.

 

None

 

None 타입의 트랜지션은 버튼과의 상호작용이 일어나도 아무런 표현을 하지 않는 타입이다.

 

Color Tint

 

버튼을 생성하면 기본 트랜지션으로 설정되는 Color Tint는 버튼에 상호작용을 했을 떄 상태변화를 생상으로 표현하는 것이다.

 

위에서 버튼을 클릭할 때마다 버튼이 깜빡깜빡하던 것을 기억할 것이다. 여기 Pressed를 보면 색깔이 약간 어두운 색으로 되어 있다. 이 Pressed에 설정된 색상이 버튼 이미지에 합쳐져서 버튼을 클릭할 때마다 깜빡거렸던 것이다.

 

Sprite Swap

 

Sprite Swap은 버튼의 상호작용 상태에 따라서 버튼 게임 오브젝트의 Image를 완전히 교체하는 방식이다.

 

 

Transition을 Sprite Swap으로 바꾸면 각 버튼 상호작용 상태에 따라서 색깔을 바꾸던 프로퍼티들이 스프라이트를 넣도록 바뀐 것을 볼 수 있다.

 

Sprite Swap 타입에서는 Normal 상태가 없는 것을 볼 수 있는데 Sprite Swap의 기본 상태는 버튼에 붙은 기본 이미지의 Image Source여서 바꿔넣을 필요가 없기 때문이다.

 

  

버튼 이미지에 기본 이미지를 넣고 Pressed에 버튼이 눌린 이미지를 넣으면 위의 그림과 같이 버튼이 눌리는 듯한 연출이 가능하다.

 

Animation

 

Animation 타입은 버튼의 색깔만 바뀌는 Color Tint나 이미지만 바뀌는 Sprite Swap에 비해서 좀 더 복잡하고 다양한 연출이 필요할 때 사용한다.

 

 

Animation 타입에서 Transition의 하위 프로퍼티는 각 상태에 맞는 이름으로 되어있는데 버튼 애니메이션에 추가될 스테이트와 파라미터의 이름을 지정하는 부분입니다. 이 부분은 따로 손댈 필요가 없다.

 

아래에 있는 Auto Generate Animation 버튼을 누르면 버튼에 대한 애니메이터 컨트롤러를 생성하는 대화상자가 뜬다. 여기서 애니메이터를 생성하면 하면 자동으로 버튼 게임 오브젝트에 애니메이터 컴포넌트가 추가되고 애니메이터에서 필요한 스테이트와 파라미터들이 자동으로 생성된다.

 

버튼 게임 오브젝트를 선택한 상태로 [Ctrl +6] 단축키를 누르면 애니메이션 뷰가 열리는데 개발자는 여기서 각 상태의 애니메이션 키만 잡아주면 된다.

 

 

  

이미지 하나를 버튼의 자식 게임 오브젝트로 만들고 버튼 위에 마우스를 올렸을 때, 이미지를 활성화 시키면서 360도 회전시키는 애니메이션을 만들어보자.

 

 

그 다음 플레이시킨 뒤 버튼에 마우스를 올려보면 버튼 앞에서 사각형이 나타나서 회전하는 것을 볼 수 있다.

 

이런 연출은 확실히 Color Tint나 Sprite Swap으로는 할 수 없는 연출이다. 일반적으로는 Color Tint나 Sprite Swap을 사용하지만 버튼과의 상호작용 연출에서 이런 다양한 연출을 사용하려면 Animation 타입의 Transition을 사용하는 것이 좋다.

 

Navigation

 

그 다음 프로퍼티인 Navigation은 모든 UI 컴포넌트에 있는 것으로 키보드나 패드로 UI를 선택할 때 어떤 순서로 포커스가 넘어갈 것인지를 결정하는 옵션dl다.

 

On Click 이벤트

 

 

On Click()은 버튼을 눌렀을 때 실행될 기능을 넣을 수 있는 On Click 이벤트 리스트이다.

 

public class ButtonTester : MonoBehaviour
{
    public void OnClickButton()
    {
        Debug.Log("Button Click!");
    }
}

 

On Click에 실행될 함수를 이벤트로 등록해주기 위해서는 스크립트에서 public으로 된 함수를 만들면 된다.

 

 

그리고 버튼에 스크립트로 작성한 컴포넌트를 추가하고 On Click 항목의 [+] 버튼을 누른 뒤 비어있는 오브젝트 란에 호출할 함수를 가진 컴포넌트를 넣어주고 호출해야할 함수를 선택해주면 된다.

 

이렇게 함수를 선택해주면 이 버튼이 클릭되었을 때, 이 함수가 실행되게 된다.

 

스크립트에서 On Click 이벤트 등록하기

 

버튼의 응용 기술로는 이렇게 에디터에서 고정으로 이벤트를 등록하지 않고 스크립트에서 동적으로 등록해주는 방법이 있다.

 

using UnityEngine.UI;

public class ButtonTester : MonoBehaviour
{
    Button button;

    public void OnClickButton()
    {
        Debug.Log("Button Click!");
    }

    void Start()
    {
        button = GetComponent<Button>();
        button.onClick.AddListener(OnClickButton);
    }
}

 

스크립트에서 Button 컴포넌트와 같은 UI의 기능을 사용하기 위해서는 UI 네임 스페이스를 using 선언해줘야 한다.

 

그 다음에는 Button 변수를 선언하고 Start 함수에서 GetComponent로 게임 오브젝트에 부착된 Button 컴포넌트를 가져온다. 그리고 button.onClick.AddListener에 함수를 넣어주면 이 함수가 이 버튼을 클릭했을 때 호출되는 이벤트로 등록된다.

 

게임을 플레이해보면 Button 컴포넌트의 On Click에서는 아무런 이벤트가 등록되지 않은 것처럼 보이지만 게임 뷰에서 버튼을 클릭해보면 스크립트에서 버튼의 이벤트로 등록해준 ButtonTester의 OnClickButton 함수가 정상적으로 호출되어 로그를 출력하는 것을 볼 수 있다.

 

버튼 터치 영역 키우기

 

모바일 기기에서 작은 버튼은 터치가 잘 안되거나 잘못 터치할 확률이 높다. 이런 문제를 해결하기 위해서 그냥 버튼의 크기를 키워버릴 수도 있지만 그렇게 하면 디자이너가 지정해준 전체적인 UI 레이아웃이 깨지게 될 것이다. 그러니 실제로 보이는 버튼의 크기는 키우지 않고 버튼의 터치 영역만 키워야 한다.

 

 

우선 버튼의 자식 컴포넌트로 이미지를 하나 추가하고 적당히 버튼을 덮도록 세팅한 다음 투명하게 만들어주면 된다.

 

 

그러면 눈에 보이는 버튼 이미지보다 바깥을 클릭해도 버튼이 무사히 동작하는 것을 볼 수 있다.

 

이것을 응용해서 버튼보다 작은 투명 이미지를 만들고 그 투명 이미지를 제외한 나머지의 Raycast Target을 끄는 방법으로 실제 버튼 이미지보다 작은 영역만 클릭 가능하게 만드는 것도 가능하다.

 

버튼 이미지보다 작은 영역에 터치를 받는 테크닉은 잘 사용되지 않을 것 같지만 버튼 이미지 리소스 외곽에 그림자나 글로우가 들어간 경우에 글로우 영역이나 그림자 영역을 제외하고 실제 버튼 영역에만 클릭을 받기 위해서 사용되는 경우가 종종 있다.

 

반응형
  1. 테샤르 2020.05.25 13:07 신고

    잘보고갑니다!

Animation 

애니메이션 이벤트

 

작성 기준 버전 :: 5.6 - 2019.2

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

게임에서는 애니메이션이 실행되는 도중에 적절한 타이밍에 맞춰서 무언가가 실행되어야 하는 경우가 종종 발생한다. 예를 들자면 캐릭터가 이동할 때 발 동작에 맞춰서 발소리가 나거나 먼지가 일어나야 하는 경우, 캐릭터가 무기를 휘두를 때 타이밍에 맞춰서 대미지가 들어가야 하는 경우등이 있을 수 있다.

 

이때 발 움직임과 다르게 소리가 나거나 공격하는 동작과 다르게 대미지가 들어간다면 플레이어는 굉장히 거슬리는 느낌을 받게 될 것이다.

 

이런 애니메이션과 리액션의 타이밍을 맞추기 위해서 존재하는 기능이 바로 유니티의 애니메이션 이벤트이다. 이 기능은 애니메이션 실행 도중에, 원하는 시점에 원하는 함수를 호출할 수 있게 해준다.

 

애니메이션 이벤트를 사용하는 방법은 FBX 모델과 함께 임포트된 애니메이션에서 사용하는 방법과 애니메이션 클립에서 사용하는 방법, 이렇게 2가지가 있다.

 

방법 1 : FBX 파일에서 모델과 함께 임포트된 애니메이션에서 애니메이션 이벤트 사용하기

 

 

프로젝트 뷰에서 임포트한 FBX 파일을 선택하면 인스펙터 뷰에 그 FBX 파일의 정보들이 보인다. 그 중에 상단의 Animations 탭을 선택하면 그 FBX에 포함된 애니메이션의 정보를 볼 수 있다.

 

애니메이션 이벤트 기능은 Events 항목에서 볼 수 있다.

 

 

접혀있는 Events 항목을 열면 이런 타임라인과 비활성화된 필드를 볼 수 있다.

 

 

애니메이션에 이벤트를 추가하기 위해서는 먼저 이벤트를 추가할 지점을 애니메이션에서 선택해야 하는데 인스펙터 뷰의 제일 아래를 보면 이렇게 지금 선택한 FBX 파일에 포함되어 있는 애니메이션을 미리보기로 재생해볼 수 있는 부분이 있다.

 

여기서 재생 버튼 옆의 타임라인을 클릭해보면 애니메이션의 특정 지점을 선택할 수 있게 된다.

 

이 타임라인에서 애니메이션 지점을 선택하면 위쪽의 전체 애니메이션 타임라인은 물론이고 Events의 타임라인도 함께 움직이는 것을 볼 수 있다.

 

 

애니메이션 중에 팔을 적절한 지점을 선택하고 Events 타임라인 앞에 있는 버튼을 클릭하면 그러면 Events 타임라인에 작은 표식이 생기며 아래 쪽에 있는 입력 가능한 필드들이 활성화되는 것을 볼 수 있다.

 

참고로 파란색 표식을 잡고 드래그하면 언제든지 이벤트가 실행될 시점을 조절할 수 있다.

 

 

 필드 이름 내용 
Function  이벤트 시점에 호출할 함수의 이름 
Float  함수에 float형 매개변수가 있을 때 들어갈 값 
Int  함수에 int형 매개변수가 있을 때 들어갈 값 
String  함수에 string형 매개변수가 있을 때 들어갈 값 
Object  함수에 Object형 매개변수가 있을 때 들어갈 값 
 
필드에 입력되는 내용은 위와 같다.
 
참고로 애니메이션 이벤트가 함수를 호출하는 방식은 여기에 넣어준 함수 이름으로 애니메이터 컴포넌트가 붙어있는 게임 오브젝트에 부착되어 있는 모든 컴포넌트에서 같은 이름을 가진 함수를 찾아서 실행하는 것이다.

 

만약 게임 오브젝트에 붙어있는 A라는 컴포넌트와 B라는 컴포넌트가 둘 다 Attack이라는 함수를 가지고 있다면 이 두 함수가 모두 호출된다.

 

 

Attack 애니메이션이니 이벤트에서 호출할 함수의 이름은 Attack으로 설정하고 매개변수에는 스크립트에서 여기에 입력된 값들이 넘어오는 것을 체크하기 위해서 Float에는 3.14, Int에는 10, String에는 hello, Object에는 씬에서 큐브 게임 오브젝트를 하나 생성한 뒤 프리팹으로 만들어서 넣어주자.

 

그리고 Apply 버튼을 누르면 추가한 애니메이션 이벤트가 완전히 적용된다.

 

 

그리고 Attack 애니메이션을 재생할 애니메이터 컨트롤러를 만들어서 애니메이터 뷰를 열고 Attack 트리거를 만들어준 다음 Stand와 Attack 애니메이션을 스테이트로 넣어주고, Attack 트리거가 들어오면 Attack 애니메이션이 재생된 다음 Stand로 돌아가게 구성해준다.

 

그리고 BoxMan을 씬에 배치하여 애니메이터에 애니메이터 컨트롤러를 넣어준다.

 

using UnityEngine;

public class AttackComponent : MonoBehaviour
{
    private void Attack()
    {
        Debug.Log("Damage");
    }
}

 

애니메이션 이벤트가 실행할 함수를 만들어주기 위해서 AttackComponent C# 스크립트를 생성하고 코드를 작성한다.

 

 

애니메이션 이벤트는 앞에서 이야기한 것과 같이 애니메이션 이벤트가 포함된 애니메이터 컨트롤러를 가진 컴포넌트과 같은 게임 오브젝트에 부착된 컴포넌트 중에서 이벤트의 Function에 넣어준 이름과 같은 함수를 찾아서 실행해준다.

 

애니메이션 이벤트의 매개변수

 

private void Attack(float f)
{
    Debug.Log("Damage");
}

private void Attack(int i)
{
    Debug.Log("Damage");
}

private void Attack(string str)
{
    Debug.Log("Damage");
}

private void Attack(Object obj)
{
    Debug.Log("Damage");
}

 

애니메이션 이벤트를 정의할 때 넣어준 매개변수를 받아오기 위해서는 이벤트로 만든 함수의 비어있는 괄호 안에 받아올 매개변수 타입과 이름을 넣어주면 된다. 

 

애니메이션 이벤트의 매개변수를 사용할 때 주의할 점이 있는데, 애니메이션 이벤트 함수에서 받아올 수 있는 매개변수의 종류는 하나 뿐이라는 것이다.

 

private void Attack(float f, int i)
{
    Debug.Log("Damage");
}

 

그래서 이렇게 하나보다 많은 매개변수를 가져오려고 하면 애니메이션 이벤트가 실패하게되서 제대로 동작하지 않게 되므로 애니메이션 이벤트의 매개변수를 사용할 때는 하나의 매개변수 타입만을 받아와서 사용해야 한다.

 

그리고 한 컴포넌트에 같은 이름의 함수를 여러 개 만들어두면 스크립트 파일에서 제일 상단에 만들어둔 함수 하나만 이벤트로 호출된다.

 

방법 2 : 애니메이션 클립에서 애니메이션 이벤트 사용하기

 

애니메이션 클립은 유니티 엔진에서 3D 모델없이 애니메이션에 대한 정보만을 가지고 있는 것을 이야기한다. 애니메이션 클립을 만드는 방법은 유니티 엔진에서 직접 만드는 방법도 있고 FBX 파일에서 애니메이션 클립 만을 추출해서 사용할 수도 있다.

 

 

FBX 파일에서 애니메이션 클립 만을 추출하기 위해서는 FBX 파일의 접힌 부분을 확장해보면 재생 버튼 모양의 애니메이션 클립이 보이는데 이것을 선택하고 [Ctrl + D] 단축키를 누르면 애니메이션 클립이 복사되어 추출된다.

 

애니메이션의 최적화를 위해서 이렇게 모델링 정보를 제외하고 애니메이션 클립만 추출해서 사용하기도 한다.

 

애니메이션 클립을 선택하고 [Ctrl + 6] 단축키를 누르면 애니메이션 뷰가 열리고 현재 선택한 애니메이션의 키 값 등의 정보가 표시된다.

 

 

애니메이션 뷰에서도 FBX 파일의 애니메이션 이벤트에서 봤던 것과 같은 모양의 AddEvent 버튼을 볼수 있는데 이것을 누르면 새로운 이벤트를 생성할 수 있다.

 

 

이렇게 추가한 이벤트를 선택하면 인스펙터 뷰에서 FBX 파일에서 이벤트를 만들 때와 같이 호출할 함수의 이름과 매개변수를 넣을 수 있게 되어있다.

 

이 후에는 앞에서와 같이 여기서 추가한 이벤트의 이름과 같은 함수를 스크립트에서 만들어주면 애니메이션 이벤트 기능이 정상적으로 동작하는 것을 확인할 수 있다.

 

애니메이션 이벤트의 단점

 

이 애니메이션 이벤트는 굉장히 편리해보이지만 몇 가지 단점이 있어서 신중하게 사용해야 한다.

 

첫 번째 단점은 레거시(Legacy) 애니메이션을 사용할 때 프레임이 심하게 낮아지거나, 메카님 애니메이션에서 트랜지션을 설정할 때 블랜드되는 과정을 잘못 설정하면 애니메이션 이벤트가 실행되지 않는 경우가 발생할 수 있다.

 

이동할 때 먼지가 발생하는 수준의 기능이면 큰 문제가 되지 않겠지만, 앞에서 구현한 예시처럼 타이밍에 맞춰서 대미지를 넣는 기능을 애니메이션 이벤트로 만들었는데 이게 스킵되는 문제가 발생하면 게임에 심각한 영향을 미치게 된다.

 

두 번째 단점으로는 함수의 호출 구조를 파악하는게 어려워진다는 것이다.

 

 

스크립트 에디터에서 애니메이션 이벤트로 만든 함수를 보면 위쪽에 참조 0개라는 글자가 보인다. 이것은 이 함수를 전체 스크립트에서 몇 군데의 위치에서 호출하고 있는지를 알려준다.

 

 

예를 들어서 Start 함수에서 이 Attack 함수를 호출하고 있다면 참조 0개가 참조 1개로 바뀌는 것을 볼 수 있다.

 

 

이 글자를 클릭해보면 어디서 호출되고 있는지 볼 수 있고 호출되고 있는 위치로도 바로 이동할 수도 있다.

 

게임에 버그가 발생했을 때는 이런 방식으로 호출 구조를 파악해서 흐름을 확인해야 하는 경우가 많은데 애니메이션 이벤트로 호출된 함수는 이 참조에 표시되지 않기 때문에 문제가 발생했을 때 문제가 발생한 지점을 찾기가 어려워 진다.

 

세 번째 단점은 네트워크 게임을 만들 때 애니메이션 이벤트를 사용하면 굉장히 곤란한 문제가 발생할 확률이 높다는 것이다.

 

공격과 대미지 타이밍 같은 처리는 굉장히 중요한 판정으로 분류되기 때문에 네트워크 게임에서는 주로 서버가 처리해야되는 작업인데 서버는 최적화가 굉장히 중요하기 때문에서 애니메이션 재생 같은 부분은 처리하지 않도록 하는 경우가 많다.

 

그렇게 최적화를 진행한 서버에서는 애니메이션 재생이 진행되지 않으니 애니메이션 이벤트 역시 실행되지 않을 것이다. 그렇기 때문에 네트워크 게임에서는 공격 타이밍과 관련된 방법을 애니메이션 이벤트가 아닌 다른 방법으로 구현해야 한다.

 

 

반응형
  1. 테샤르 2020.05.11 13:06 신고

    잘보고갑니다~

  2. 박지성 2021.10.13 17:45

    애니메이션 트리거는 네트워크게임에 적합하지 않다고 하셨는데 그러면 다른 방법은 어떻게 있는지 알 수 있을 까요?

    • 박지성 2021.10.13 17:46

      어떻게 있느지가 아니고 무엇이 있는지 입니다. 죄송합니다

    • wergia 2021.10.18 20:15 신고

      보통은 애니메이션을 재생시켜두고 코루틴으로 따로 처리하도록 만듭니다.

Animation 

애니메이터 컨트롤러의 파라미터 조절하기

 

작성 기준 버전 :: 2019.2

 

[본 포스트는 유튜브 영상으로도 시청하실 수 있습니다]

 

이번 섹션에서는 애니메이터 컨트롤러의 파라미터를 스크립트에서 조절하는 방법을 알아보자.

 

그 이전에 유니티 애니메이션에 대한 기초적인 지식이 필요하다면 해당 링크를 통해서 확인할 수 있다.

 

애니메이션의 기초에 대해서 설명하는 튜토리얼을 통해서 애니메이터 파라미터로 애니메이터 컨트롤러의 흐름을 조절할 수 있다는 것을 이야기했었다.

 

우선 애니메이터 컨트롤러의 파라미터와 트랜지션에 대해서 간단하게 복습해보자.

 

애니메이터 컨트롤러의 파라미터와 트랜지션

 

유니티 애니메이션에 대한 기초를 다루었던 글에서 이야기 했듯이 애니메이터 컨트롤러의 트랜지션은 스테이트와 스테이트 사이를 이어주고, 어느 방향으로 애니메이션이 흘러갈지 결졍하는 것이고, 파라미터는 이 트랜지션이 실행될 조건을 결정하는 변수이다.

 

애니메이터 컨트롤러

 

 

우선 간단한 애니메이터 컨트롤러를 만들기 위해서 씬에 게임 오브젝트를 하나 생성해보자.

 

 

그리고 추가한 게임 오브젝트에 애니메이터 컴포넌트를 부착해준다.

 

 

그 다음 프로젝트 뷰에 우클릭해서 [Create > Animator Controller] 항목을 선택하여 새 애니메이터 컨트롤러를 생성해서 게임 오브젝트에 부착된 Animator 컴포넌트의 Controller 프로퍼티에 할당해주면 된다.

 

 

Controller 프로퍼티를 할당한 다음에는 게임 오브젝트를 선택한 상태에서 상단 메뉴바의 [Window > Animation > Animator] 항목을 선택해서 애니메이터 뷰를 열어보면 비어있는 게임 오브젝트의 애니메이터 컨트롤러를 볼 수 있다.

 

애니메이션 파라미터

 

 

애니메이터 뷰를 열었다면 뷰 제목 바로 아래 있는 파라미터 탭을 클릭한 다음, 플러스 모양의 버튼을 눌러서 위 이미지처럼 각 파라미터들을 하나씩 만들어 보자.

 

이 파라미터들은 앞서 언급했듯이 한 애니메이션에서 다른 애니메이션으로 전환되는 트랜지션이 실행되는 조건의 역할을 한다. 파라미터의 종류는 여기서 볼 수 있듯이 Float은 소수점을 나타내는 실수, Int는 정수, Bool은 참/거짓을 표현하는 논리 변수, Trigget는 신호가 들어오면 트랜지션을 통과시킨 다음에 자동으로 꺼지는 타입의 변수다.

 

애니메이터 트랜지션

 

 

각 파라미터 타입을 테스트하기 위해서 위 이미지와 같이 애니메이터 컨트롤러를 세팅해보자. 스테이트를 모두 만들고 트랜지션을 연결했다면, 트랜지션의 조건을 설정할 차례다.

 

Float in

 

 

먼저 Idle 스테이트에서 Float 스테이트로 들어가는 트랜지션의 조건이다. 트랜지션 화살표를 클릭하면 인스펙터 뷰에서 해당 트랜지션을 수정할 수 있는데, 컨디션(Conditions)의 플러스 버튼을 누르면 이 트랜지션이 동작할 조건을 추가할 수 있다. 우선 조건으로 사용될 변수는 New Float으로 하고 3보다 클 때(Greater), 동작하도록 설정했다.

 

Float out

 

 

그 다음은 Float에서 Idle로 빠져나오는 트랜지션의 조건이다. 컨디션을 추가한 다음, 원래 Greater라고 적혀있는 드롭다운 메뉴를 눌러보면 Less가 있는 것을 볼 수 있다. Less는 정해진 숫자보다 작을 때를 뜻한다. 즉, 3보다 작을 때 Float에서 빠져나가서 Idle로 향하게 된다.

 

참고로, 여기에 왜 같다를 의미하는 Equal이 없는지 궁금할 수도 있다. 이것은 컴퓨터의 고질적인 부동소수점 오차라는 문제 때문이다. 예를 들어 현실적으로는 0.0001을 만 번 더하면 정확히 1이 되어야 하는데, 컴퓨터는 소수점 계산에 문제가 있어서 정확히 1이 되지 않고 1.00001이나 다른 숫자가 되는 경우가 종종 발생한다. 이 때문에 소수점 계산에 정확히 같다라는 것을 사용하기 어렵기 때문에 Equal이 빠져있는 것이다.

 

Int in

 

 

Idle에서 Int로 들어가는 조건을 설정해보자. 컨디션을 추가한 다음 New Float이라는 변수 이름이 적혀있는 드롭다운 메뉴를 선택하면 애니메이터 컨트롤러에 만들어져 있는 파라미터를 찾아서 선택할 수 있다. 그리고 Int형 파라미터는 Float과 다르게 "같다(Equal)"라는 조건을 사용할 수 있다. 파라미터의 값이 1과 같을 때 Idle에서 Int로 들어가게 설정해보자.

 

Int out

 

 

New Int의 값이 1이 아닐 때, Int에서 Idle로 빠져나오게 만든다.

 

Bool in

 

 

Idle에서 Bool로 들어가는 조건은 New Bool이 true일 때로 설정한다. Bool형 파라미터는 true 혹은 false만 설정할 수 있다.

 

Bool out

 

 

New Bool이 false가 되면 Bool에서 Idle로 빠져나오게 만들어 준다.

 

Trigger in

 

 

Idle에서 Trigger로 들어가는 조건은 New Trigger로 넣어준다. 트리거 형식은 아까 이야기했듯이 트리거가 호출되는 순간에 한 번 켜지고, 트리거 조건이 있는 트랜지션을 통과하면 자동으로 꺼지기 때문에 별다른 세부 조건이 없다.

 

Trigger out

 

 

Trigger에서 Idle로 빠져나오는 조건은 따로 만들지 않는다. 참고로 트랜지션을 선택한 다음 볼 수 있는 인스펙터 뷰의 내용 중에서 Has Exit Timer이라는 옵션이 있는데 이 옵션이 켜져있으면 트랜지션의 조건 만족되더라도 지금 실행하는 애니메이션을 끝까지 재생하기 전에는 다음 스테이트로 넘어가지 않는다. 반대로 이 옵션을 꺼두면 지금 실행되는 애니메이션이 아직 재생이 끝나지 않았더라도 트랜지션의 조건이 만족되면 그 애니메이션을 끝내고 곧바로 다음 스테이트로 넘어가게 된다.

 

테스트 해보기

 

애니메이터 컨트롤러 설정이 모두 끝났다면 이제 플레이 버튼을 누르고 게임을 실행해보자.

 

 

그리고 애니메이터 뷰에서 파라미터의 값을 하나씩 변경하면서 테스트 해보자. 파라미터의 값에 따라서 애니메이션의 흐름이 통제되는 것을 확인할 수 잇다.

 

 

 

 

스크립트로 애니메이터 파라미터 변경하기

 

다른 섹션에서 트랜스폼 컴포넌트를 다루면서도 이야기 했지만, 이렇게 에디터에서 값을 바꾸는 방법은 실제 게임 내에선 사용할 수 없으며, 게임에서 이 애니메이터 파라미터의 값을 바꿔서 애니메이션의 흐름을 통제하기 위해서는 스크립트에서 이 애니메이터 컨트롤러의 파라미터를 바꿀 수 있어야 한다.

 

public class AnimatorParameterPractice : MonoBehaviour

{

    private Animator animator;

 

    private void Awake()

    {

        animator = GetComponent<Animator>();

    }

}

 

우선 AnimatorParameterPractice라는 이름의 C# 스크립트를 하나 생성하고, animator 멤버 변수를 하나 만든 다음, 게임이 시작되자마자 애니메이터 컨트롤러를 가져올 수 있게 Awake() 함수에서 GetComponent() 함수를 사용해서 게임 오브젝트에 부착된 Animator 컴포넌트를 가져오자.

 

참고로 GetComponent() 함수를 사용하면 지금 이 컴포넌트가 부착되어 있는 게임 오브젝트에 부착된 다른 컴포넌트를 가져올 수 있다. 지금은 Animator 컴포넌트를 가져오기 위해서 뾰족 괄호 안에<> Animator 컴포넌트 클래스 이름을 넣었습니다.

 

애니메이터의 각 파라미터 값 변경 함수

 

애니메이터 클래스 내부에는 애니메이터 안에 설정한 파라미터 값을 변경할 수 있는 함수를 제공한다.

 

Float형 변경하기

 

animator.SetFloat("New Float", 3.1f);

 

애니메이터의 Float형 파라미터의 값을 변경하려면 SetFloat() 함수를 사용하면 된다. 첫 번째 매개변수로 변경하고자 하는 파라미터의 이름을 넣고, 두 번째 매개변수에 값을 넣어준다.

 

Int형 파라미터 값 변경하기

 

animator.SetInteger("New Int"

1);

 

Int형 파라미터의 값은 SetInteger() 함수로 변경할 수 있다.

 

Bool형 파라미터 값 변경하기

 

animator.SetBool("New Bool"

true);

 

Bool형 파라미터 값은 SetBool() 함수로 변경할 수 있다.

 

Trigger형 파라미터 신호 주기

 

animator.SetTrigger("New Trigger");

 

Trigger형 파라미터는 SetTrigger() 함수를 사용하면 해당 파라미터의 신호가 켜진다.

 

키보드를 누르면 각 파라미터 값 바뀌게 하기

 

void Update()

{

    if (Input.GetKeyDown(KeyCode.F))

    {

        animator.SetFloat("New Float"3.1f);

    }

 

    if (Input.GetKeyUp(KeyCode.F))

    {

        animator.SetFloat("New Float"2.9f);

    }

 

    if (Input.GetKeyDown(KeyCode.I))

    {

        

animator.SetInteger("New Int"

1);

    }

 

    if (Input.GetKeyUp(KeyCode.I))

    {

        

animator.SetInteger("New Int"

, 0);

    }

 

    if (Input.GetKeyDown(KeyCode.B))

    {

        

animator.SetBool("New Bool"

true);

    }

 

    if (Input.GetKeyUp(KeyCode.B))

    {

        animator.SetBool("New Bool"

false

);

    }

 

    if (Input.GetKeyDown(KeyCode.T))

    {

        

animator.SetTrigger("New Trigger");

    }

}

 

AnimatorParameterPractice 클래스의 업데이트 함수에 위와 같은 코드를 작성해서 키보드를 눌렀다 뗄 때, 각 파라미터 값이 바뀌도록 해보자.

 

 

코드를 모두 작성했으면 코드를 저장하고 에디터로 돌아가서 애니메이터 컨트롤러가 붙어있는 게임 오브젝트에 AnimatorParameterPractice 컴포넌트를 부착한 뒤, 애니메이터 뷰를 켜고 플레이 버튼을 누른다. 

 

그리고 게임이 실행되면 지정한 키인 F, I, B T를 눌러보면 키보드를 눌렀다 뗄 때마다 파라미터의 값이 바뀌고 그에 따라 애니메이션의 흐름이 통제되는 것을 볼 수 있다.

반응형
  1. 아이윤맨 2020.11.05 13:45 신고

    링크해갑니다~

  2. 아이윤맨 2020.11.21 19:33 신고

    키를 입력받을때 말고 조이스틱을 땡겼을때 애니메이션이 되게 하려면 어떻게 해야하나요?

Tutorial (7)

 

애니메이션

 

작성 기준 버전 :: 2018.3.2f1

 

[튜토리얼의 내용을 유튜브 영상을 통해서도 확인하실 수 있습니다.]

 

게임에서 캐릭터가 어떠한 동작도 하지 않고 가만히 멈춘 채로 플레이어가 입력하는 대로 움직이가만 한다면 그 게임은 과연 어떤 느낌일까? 아마 그것은 굉장히 기괴한 모습이거나, 게임이 완성되지 못한 느낌일 것이다. 이렇듯이 애니메이션은 게임에 생동감을 불어넣는 중요한 요소이다.

 

메카님(Mecanim)

 

유니티에서 지원하는 애니메이션 시스템을 유니티 측에서는 메카님이라고 이름을 붙였다. 이 메카님 시스템은 기본적인 애니메이션 기능은 물론 애니메이션 레이어, 애니메이션 블렌드, 애니메이션 리타게팅 등의 다양한 기능을 제공한다. 애니메이션과 관련된 고급 기능들은 이후에 다른 섹션을 통해서 알아보도록 하고 이번 섹션에는 메카님 시스템의 기초적인 애니메이션 기능을 알아보도록 하자.

 

 

애니메이션 클립과 애니메이터 컨트롤러(Animation Clip & Animator Controller)

 

유니티의 애니메이션 시스템은 해당 오브젝트가 어떻게 움직여야 하는지에 대한 정보들이 포함된 애니메이션 클립과 플로우 차트와 같은 방식으로 애니메이션 클립들을 구조화하여 현재 어떤 클립이 재생되어야 하고 언제 애니메이션이 변경되어야 하는지 등을 추적하는 상태머신 형태의 애니메이터 컨트롤러로 이루어진다.

 

애니메이터 컨트롤러와 애니메이션 클립의 아이콘 형태는 다음과 같다.

 

 

애니메이션 클립을 만드는 방법은 크게 두 가지가 있다. 첫 번째는 3ds Max나 Maya 같은 외부의 프로그램으로 애니메이션을 만들어서 임포트 하는 것이고, 다른 하나는 유니티에서 직접 애니메이션 키를 잡아서 클립을 만드는 것이다.

 

3ds Max 같은 외부 프로그램에서 애니메이션을 만들어서 임포트하는 방식은 3D 모델링의 애니메이션을 만들고자 할 때 주로 사용하며 유니티 엔진에서 직접 키를 잡아서 애니메이션 클립을 만드는 것은 비교적 간단한 애니메이션이나 UI 애니메이션을 만들고자 할 때 사용하는 빈도가 높다.

 

 

외부에서 만든 애니메이션 임포트하기

 

Practice Animation.zip
다운로드

 

우선은 외부에서 만들어진 애니메이션을 임포트하는 방법을 배워보자. 연습용 애니메이션은 위의 첨부파일을 다운로드 받으면 된다. 다운 받은 압축파일의 압축을 해제하면 BoxMan@Run.FBX, BoxMan@Stand.FBX, BoxMan@Walk.FBX, BoxMan@Attack.FBX 네 개의 FBX 파일이 나올 것이다. 일반적으로 외부의 3D 모델링 프로그램을 통해 만들어진 애니메이션들은 FBX 확장자를 가진 것을 사용한다.

 

 

이렇게 받은 네 개의 파일을 유니티 엔진의 프로젝트 뷰로 드래그&드롭 한다. 그렇게 하면 네 개의 파일이 우리의 프로젝트에 포함되는 것을 확인할 수 있다.

 

 

임포트된 FBX 파일은 프로젝트 뷰에서 파란 직육면체에 종이가 붙은 아이콘으로 표현되며, 그 앞의 작은 삼각형을 클릭해서 접힌 부분을 열면 그 아래에 애니메이션 클립이 포함되어 있는 것을 확인할 수 있다.

 

 

이 FBX 파일을 선택한 상태로 인스펙터 창에서 애니메이션의 이름이나 길이, 반복 여부 등을 수정할 수 있다.

 

 

 

 

 

유니티 엔진에서 직접 애니메이션 클립 만들기

 

 

유니티 엔진에서 직접 애니메이션 클립을 만드는 방법을 배우기 위해서는 우선 씬에 애니메이션 클립을 만들 게임오브젝트 하나를 생성한다.

 

 

그리고 생성된 오브젝트를 선택한다. 

 

 

그 다음에 상단 메뉴바에서 Window>Animation>Animation을 선택하거나 Ctrl+6 단축키를 누르면 애니메이션을 수정할 수 있는 애니메이션 패널이 열린다.

 

 

아직 큐브에는 다른 애니메이션이 없기 때문에 애니메이션 클립을 생성하게 도와주는 Create 버튼만 보인다. 유니티 엔진에서 애니메이션 클립을 직접 만들기 위해서는 이 버튼을 사용하면 된다. 이 버튼을 클릭한다.

 

 

 

 

그러면 애니메이션 클립 생성 및 저장을 위한 대화상자가 뜨는데 CubeRotating.anim 이라는 이름으로 애니메이션 클립을 하나 생성하자.

 

 

애니메이션 클립이 생성되면 애니메이션 창에 타임라인이 표시된다.

 

  

이제 큐브가 회전하는 애니메이션을 추가하기 위해서 Add Property 버튼을 누르고 Transform 항목의 Rotation의 + 버튼을 눌러준다.

 

 

로테이션 프로퍼티가 추가되면 타임라인의 1초 지점을 클릭하고 Rotation.y 값을 360으로 설정한다.

 

 

그 다음 애니메이션 창의 재생 버튼을 눌러보면 화면에 배치된 큐브가 회전하는 것을 확인할 수 있다.

 

유니티 엔진에서 직접 만들어지는 애니메이션 클립은 대부분 이런 과정을 통해서 만들어지며 일반적으로 간단한 애니메이션이나 UI 애니메이션을 만들 때 사용되는 경우가 많다.

 

 

 

 

 

애니메이터 컨트롤러

 

애니메이션 클립에 대해서 설명했으니 이제 애니메이터 컨트롤러에 대해서 이야기할 차례이다. 앞서 이야기 했듯이 애니메이션 클립이 한 동작에 대한 애니메이션이라면 애니메이터 컨트롤러는 여러 애니메이션 클립을 모아서 오브젝트가 어느 시점에 어떤 애니메이션을 어떻게 재생할지 결정하는 역할을 한다.

 

  

애니메이터의 기본적인 구성요소는 스테이트(State), 트랜지션(Transition), 파라미터(Parameter) 이렇게 세 가지이다.

 

 

스테이트(State)

 

 

스테이트는 일반적으로 애니메이터에서 애니메이션 클립을 담고 있는 하나의 상태로, 지금 어느 애니메이션 클립이 재생되어야 하는가를 표현한다.

 

 

스테이트 중 하나를 클릭하여 선택하면 인스펙터 창에서 현재 선택된 스테이트의 정보를 확인하고 수정할 수 있다. 스테이트의 이름을 바꿀 수 있는 것은 물론이고 Motion 프로퍼티에는 현재 스테이트가 재생할 애니메이션 클립을 설정할 수 있고 Speed 프로퍼티를 통해서 애니메이션이 재생될 속도 역시 설정할 수 있다.

 

또한 위 예시 이미지에서 배치된 스테이트들은 애니메이터의 가장 기본적인 스테이트들로 스테이트당 하나의 애니메이션 클립을 담는다. 애니메이션 클립 하나를 담는 스테이트 이외에도 파라미터 값에 따라서 여러 애니메이션을 블랜딩해주는 블랜드 트리나, 여러 스테이트들을 담을 수 있는 서브-스테이트 머신이 있다. 추가적인 내용은 다른 파트에서 다루도록 하겠다.

 

특수한 스테이트

 

예시로 보여진 애니메이터 컨트롤러의 그래프를 보면 일반적인 노드는 회색의 사각형으로 표시되고 있는데, 그 외에 특별한 형태의 노드를 볼 수 있다. 

 

 

첫 번째는 엔트리(Entry)다. 엔트리는 애니메이션이 처음 시작될 때의 진입점을 의미한다.

 

 

이 엔트리에서 제일 처음으로 연결된 노드는 주황색으로 표시되며, 게임오브젝트가 활성화되어 애니메이션이 시작되면 이 주황색으로 표시된 애니메이션부터 재생이 시작된다.

 

 

노드를 기본 스테이트로 만들기 위해서는 기본 스테이트로 만들고자 하는 노드를 우클릭하고 [Set as Layer Default State] 항목을 선택하면 된다.

 

 

두 번째는 모든 스테이트(Any State) 노드이다. 이 노드는 애니메이터가 어떤 애니메이션을 재생하고 있는 상태이던 간에 트랜지션의 조건이 만족되면 무조건 다음 스테이트로 넘어가서 애니메이션을 재생하게 만든다.

 

예를 들어 캐릭터가 걷는 중이든, 가만히 서있는 중이든, 아니면 포션을 마시는 중이었든, 큰 데미지를 입어서 죽으면, 바로 Die 스테이트로 넘어가도록 하는 것이다.

 

 

마지막으로 엑시트(Exit) 노드이다. 엑시트 노드는 애니메이터의 흐름이 한 번 끝났음을 의미하고, 엑시트 노드를 통과하면 엔트리 노드로부터 다시 애니메이터의 흐름이 다시 시작된다.

 

 

파라미터(Parameter)

 

 

애니메이터의 파라미터는 한 애니메이션에서 다른 애니메이션으로 전환되는 조건이 되는 변수의 역할을 한다. Parameters에서 + 버튼을 누르면 추가할 파라미터의 종류를 선택할 수 있다. 파라미터의 종류로는 Float, Int, Bool, Trigger가 있으며 Float는 소수점을 나타낼 수 있는 실수, Int는 정수, Bool는 참/거짓을 표현하는 논리 변수이며, Trigger는 Set되면 True가 되고 해당 Trigger가 걸린 트랜지션을 통과하면 자동으로 False로 바뀌는 타입이다.

 

 

트랜지션(Transition)

 

 

트랜지션은 애니메이터에서 스테이트와 스테이트 사이를 이어주는 것이다. 스테이트 사이를 이어줄 때, 애니메이션이 어느 방향으로 흘러갈지 방향을 정할 수 있다. 그 외에도 트랜지션을 선택하면 인스펙터 창을 통해서 선택된 트랜지션이 실행될 조건이나, 한 스테이트에서 다른 스테이트로 넘어갈 때 애니메이션을 어떻게 블랜딩해줄 것인지 등을 설정할 수 있다.

 


 

이 외의 애니메이션과 관련된 포스트는 애니메이션 카테고리에서 확인할 수 있다.

 

 

반응형

애니메이션 기초

 

 

이 섹션에서는 언리얼 엔진에서 캐릭터에 애니메이션을 추가하는 방법을 알아볼 것이다.

 

기본 캐릭터 구현

모델링과 애니메이션을 추가하기 이전에 그것들을 사용할 캐릭터 클래스를 구현해보자.

 

우선 새로운 프로젝트를 추가한 뒤 언리얼 에디터의 편집 드롭다운 메뉴에서 프로젝트 세팅 창을 연다.

 

 

그리고 엔진 섹션의 입력에서 입력 매핑을 다음과 같이 설정한다.

 

 

그 다음은 콘텐츠 브라우저 패널에서 신규 추가 버튼을 누르고, 새 C++ 클래스를 선택하여 부모 클래스 선택 창을 연다.

 

그리고 Character 클래스를 부모 클래스로 선택하고 다음을 클릭한다.

 

 

클래스 이름은 AnimTestCharacter로 하자.

 

 

이제 캐릭터를 움직일 함수를 만들고 앞에서 설정한 입력 매핑과 바인딩할 차례다.

 

우선 캐릭터를 움직이기 위한 다음 함수들을 AnimTestCharacter.h의 클래스 안에 선언한다.

 

UFUNCTION()
void MoveForward(float AxisValue);

UFUNCTION()
void MoveRight(float AxisValue);

 

AnimTestCharacter.cpp에서 위 두 함수를 다음과 같이 구현한다.

 

void AAnimTestChararcter::MoveForward(float AxisValue)
{
    AddMovementInput(GetActorForwardVector(), AxisValue);
}

void AAnimTestChararcter::MoveRight(float AxisValue)
{
    AddMovementInput(GetActorRightVector(), AxisValue);
}

 

방금 만든 함수와 입력 매핑을 바인딩하기 위해서, InputComponent를 사용해야하기 때문에, AnimTestCharacter.cpp 상단의 전처리기에 다음 코드를 추가한다.

 

#include "Engine/Classes/Components/InputComponent.h"

 

그 다음엔 SetupInputComponent() 함수에 입력 매핑과 함수를 바인딩하는 코드를 추가한다.

 

InputComponent->BindAxis("MoveForward", this, &AAnimTestChararcter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AAnimTestChararcter::MoveRight);
InputComponent->BindAxis("Turn", this, &AAnimTestChararcter::AddControllerYawInput);
InputComponent->BindAxis("LookUp", this, &AAnimTestChararcter::AddControllerPitchInput);

 

코드를 모두 작성했다면 변경사항을 저장하고 솔루션 탐색기에서 빌드를 진행한다.

 

 

그리고 에디터로 돌아가서 콘텐츠 브라우저 창에서 Bluprints라는 이름의 새 폴더를 만든 뒤, C++ AnimTestCharacter 클래스에 우클릭하여 AnimTestCharacter 기반 블루프린트 클래스 생성을 클릭하고, BP_AnimTestCharacter 라는 이름으로 블루프린트 클래스를 생성한다.

 

 

이어지는 과정을 수행하기 전에, 캐릭터 모델링을 다운받아 프로젝트에 임포트한다.
TestChar.zip
다운로드

 

TestChar.fbx를 프로젝트에 임포트했다면, BP_AnimTestCharacter를 더블클릭해서 블루프린트 에디터를 열고 컴포넌트 패널에서 Mesh 컴포넌트를 선택한다.

 

 

그리고 디테일 패널에서 Mesh 섹션을 찾아서 없음으로 되어있는 Skeletal Mesh를 방금 임포트한 TestChar로 설정해준다.

 

 

스켈레탈 메시를 TestChar로 설정하고 나서, 뷰포트를 통해 확인하면 다음과 같이 캡슐 콜라이더 크기와도 맞지 않고 방향도 다른 것을 알 수 있다.

 

 

이것을 올바르게 맞추기 위해, 디테일 패널의 트랜스폼 섹션에서 위치 {0.0, 0.0, -90.0}, 회전 {0.0, 0.0, 90.0}, 스케일 {3.0, 3.0, 3.0}으로 설정한다. 설정이 모두 끝났다면 블루프린트를 컴파일하고 저장한 뒤, 블루프린트 에디터를 닫는다.

 

블루프린트를 레벨 에디터에 끌어놓아 배치한다. 그리고 플레이 버튼을 눌러서 PIE 모드로 들어가면 캐릭터에 메시가 추가된 것과 전에 설정한 이동과 카메라 제어가 정상적으로 되는 것을 확인할 수 있다.

 

 

 

하지만 아직 문제는 남아있다. 카메라가 캐릭터의 몸통 한가운데 박혀있기 때문에, 캐릭터의 움직임이 제대로 보이지 않고 부자연스럽다. 이를 해소하기 위해서 새로운 카메라를 배치하고 TPS처럼 구성해보자.

 

 

비주얼 스튜디오로 돌아가서 AnimTestCharacter.h에 다음 멤버 변수를 추가한다.

 

UPROPERTY(VisibleAnywhere)
class USpringArmComponent* TPSCameraSpringArmComponent;

UPROPERTY(VisibleAnywhere)
class UCameraComponent* TPSCameraComponent;

 

그리고 AnimTestCharacter.cpp에서 몇 가지 기능들을 사용하기 위해서 전처리기 리스트에 다음 코드를 추가한다.

 

#include "Engine/Classes/Camera/CameraComponent.h"
#include "Engine/Classes/Components/CapsuleComponent.h"
#include "Engine/Classes/GameFramework/SpringArmComponent.h"

 

다음엔 AAnimTestCharacter::AAnimTestCharacter() 생성자 함수에 스프링 암과 카메라 컴포넌트를 생성하고 세팅하는 코드를 추가한다.

 

TPSCameraSpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("ThirdPersonCameraSpringArm"));
TPSCameraSpringArmComponent->SetupAttachment(GetCapsuleComponent());
TPSCameraSpringArmComponent->SetRelativeLocation(FVector(0.0f, 0.0f, BaseEyeHeight));
TPSCameraSpringArmComponent->bUsePawnControlRotation = true;

TPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("ThirdPersonCamera"));
TPSCameraComponent->SetupAttachment(TPSCameraSpringArmComponent, USpringArmComponent::SocketName);

 

변경사항을 저장하고 프로젝트를 빌드한 뒤, 에디터로 돌아가서 플레이를 해보면 전처럼 카메라가 캐릭터의 몸 속에 있지 않고, 캐릭터의 등 뒤에 적당히 떨어진 거리에서 카메라가 있음을 확인할 수 있다.

 

 

이제 캐릭터를 추가했고, 그 캐릭터의 애니메이션을 제대로 볼 수 있는 뷰를 완성했으니, 본격적으로 애니메이션을 추가해보자.

 

 

 

 

캐릭터 애니메이션 작업
 
캐릭터 애니메이션 파트를 시작하기 전에 필요한 캐릭터 애니메이션을 다운받자.

 

애니메이션을 다운받고 압축을 해제하였다면, 콘텐츠 브라우저에서 Animations 폴더를 만들고 파일 창에 우클릭하여 /Game/Animations 에 임포트를 선택하여, TestChar_Idle, TestChar_Move를 선택한다.

 

 

FBX 임포트 옵션 창이 뜨면 Import Mesh 프로퍼티의 체크를 해제하고 Mesh 섹션의 Skeleton 프로퍼티에 TestChar_Skeleton을 설정해주고 모두 임포트를 선택한다.(Import Mesh 프로퍼티의 체크를 해제하는 경우는 모델링과 애니메이션이 모두 있는데, 그 중에 애니메이션만 임포트하려고 할 때이다.)

 

 

 

 

콘텐츠 브라우저 패널에서 신규 추가 버튼을 클릭하고 애니메이션/애니메이션 블루프린트를 선택한다.

 

 

부모 클래스로는 AnimInstance를 선택하고 타깃 스켈레톤은 TestChar_Skeleton을 선택한다. 애니메이션 블루 프린트의 이름은 TestChar_AnimBP로 하자.

 

 

생성한 애니메이션 블루프린트를 더블클릭해서 애니메이션 블루프린트 에디터를 연다. 그리고 내 블루프린트 패널에서 Boolean 타입의 IsRunning 변수를 추가한다.

 

 

변수를 추가한 뒤에는, 애니메이션 블루프린트의 이벤트 그래프로 이동해 다음과 같이 구성한다.

 

 

이 다음엔 내 블루프린트 패널에서 애님 그래프 더블클릭해서 애님 그래프를 열고 우클릭하여 컨텍스트 메뉴를 열어 스테이트 머신을 추가한다.

 

 

새로 추가한 스테이트 머신의 이름을 TestChar State Machine으로 변경하고 최종 애니메이션 포즈와 연결한다.

 

 

생성한 스테이트 머신을 더블클릭해서 스테이트 머신으로 진입하고 바탕에 우클릭하여 스테이트를 추가한다.

 

 

위와 같은 방법으로 아래 이미지처럼 Idle과 Run 스테이트를 만들고 생성된 Idle 스테이트에 더블클릭한다.

 

 

 

그 다음 스테이트 그래프 바탕에 우클릭해서 TestChar_Idle 애니메이션을 불러온다.

 

 

그 다음 추가한 애니메이션과 출력 애니메이션 포즈 노드와 연결시켜준다. 그리고 이와 같은 작업을 Run 스테이트에서도 똑같이 해준다.

 

 

다시 TestChar State Machine으로 돌아와서 트랜지션을 아래와 같이 구성한다.

 

 

먼저 Idle에서 Run으로 향하는 트랜지션을 더블클릭해서 이벤트 그래프를 다음과 같이 구성한다.

 

 

그리고 Run->Idle 트랜지션은 다음과 같이 구성한다. 이렇게 트랜지션까지 모두 구성했다면 애니메이션 블루프린트를 컴파일, 저장하고 닫는다.

 

 

 

다음엔 BP_AnimTestCharacter 블루프린트 클래스를 더블클릭해서 열고 디테일 패널의 애니메이션 섹션에서 Anim Class 항목을 방금 생성한 TestChar_AnimBP를 설정해준다. 그리고 블루프린트를 컴파일, 저장하고 블루프린트 에디터를 닫는다.

 

 

언리얼 에디터로 돌아가서 플레이 버튼을 누르고 테스트해보면 캐릭터의 애니메이션이 잘 적용된 것을 확인할 수 있다.

 

 

반응형
  1. 잘 보고 갑니다~~

네트워크 애니메이터(Network Animator)


네트워크 게임에서는 게임이나 유닛, 캐릭터 등의 상태를 동기화하는 것도 중요하지만 눈에 보이는 캐릭터들의 움직임, 즉 애니메이션 역시 동기화가 필요하다. 아무리 다른 동기화가 잘 되고 있다고 하더라도, 애니메이션 동기화가 진행되지 않아서 가만히 서있는 자세로 이동하거나 공격한다면 문제가 많을 것이다.


유니티 네트워크 시스템에서는 이러한 애니메이션 동기화를 위한 기본적인 기능을 제공하는데 그것이 바로 네트워크 애니메이터(Network Animator)다.


이번 섹션을 진행하기 위해서는 기본적인 유니티의 애니메이션 시스템과 애니메이터 컨트롤러에 대한 지식이 필요하다. 기반 지식이 부족하다면 유니티의 애니메이션 문서를 참조하여 공부를 해두는 것이 좋다.


그럼 이제부터 네트워크 애니메이터를 사용하는 방법을 알아보도록 하자.



네트워크 애니메이터 컴포넌트의 기본적인 요구사항



네트워크 애니메이터를 사용하기 위해서는 네트워크 애니메이터 컴포넌트가 있는 오브젝트와 같은 오브젝트에 에니메이터 컨트롤러와 네트워크 아이덴티티 컴포넌트가 있어야 한다.



네트워크 애니메이터의 작동 방식



네트워크 애니메이터의 작동 방식은 기본 애니메이터 컨트롤러의 파라메터가 변경되면 그 변경된 파라메터의 값을 네트워크 애니메이터를 통해서 클라이언트의 해당 네트워크 애니메이터로 전송하고 받은 측의 네트워크 애니메이터가 자신이 소유한 애니메이터 컨트롤러에 변경된 파라메터와 값을 알려서 애니메이션을 동작하게 한다.






네트워크 애니메이터를 사용하기 위한 준비


앞에서 네트워크 애니메이터의 기본적인 요구 사항과 작동 방식을 알아보았으니 이제 네트워크 애니메이터를 추가하고 사용하는 방법에 대해서 알아보도록 하자.



애니메이션 섹션의 단골인 박스맨이 이번 네트워크 애니메이터 섹션에서도 도움을 줄 것이다.



위의 이미지에 맞춰서 애니메이션 스테이트를 구성해보자. 박스맨 캐릭터는 대기(Idle) - 이동(Walk) - 공격(Attack), 세 가지 상태를 가지며, IsMove 파라메터의 상태에 따라서 대기와 이동 상태를 오가며, Attack 트리거를 받으면 공격 애니메이션을 재생하고 대기 상태로 돌아가는 아주 간단한 형태다.



위와 같이 게임 오브젝트와 컴포넌트를 세팅해주면 준비는 끝이다.





네트워크 애니메이터 추가하기



네트워크 애니메이터를 추가하는 방법은 간단하다. 애니메이션을 동기화하고자 하는 오브젝트에(애니메이터 컨트롤러 컴포넌트를 가지고 있어야 한다) 네트워크 애니메이터를 Add Component 해주면 된다. 그러면 네트워크 애니메이터와 함께 네트워크 통신에 필요한 Network Identity 컴포넌트가 자동으로 함께 추가된다.



네트워크 애니메이터를 추가한 후에는 네트워크 애니메이터 컴포넌트의 애니메이터 프로퍼티에 동기화되어야할 애니메이터를 추가해주면 된다.



그렇게하면 네트워크 애니메이터 컴포넌트에 동기화될 애니메이터의 파라메터들이 표시된다. 이 다음에는 네트워크 애니메이터를 컨트롤할 스크립트를 작성해야 한다.


using UnityEngine;
using UnityEngine.Networking;

public class PlayerCharacter : MonoBehaviour
{
    private NetworkAnimator netAnimator;

    void Start ()
    {
        netAnimator = GetComponent<NetworkAnimator>();
    }
   
    void Update ()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            netAnimator.animator.SetBool("IsMove", true);
        }
        else if (Input.GetKeyUp(KeyCode.W))
        {
            netAnimator.animator.SetBool("IsMove", false);
        }

        if (Input.GetMouseButtonDown(0))
        {

            netAnimator.animator.SetTrigger("Attack");
            netAnimator.SetTrigger("Attack");
        }
    }
}


위와 같이 스크립트를 작성한 이후에 빌드하고 실행해서 한 쪽에서 서버를 열고 다른 쪽에서 클라이언트로 접속한 뒤 서버 측에서 W를 누르거나 마우스를 클릭했을때 애니메이션이 동기화됨을 확인할 수 있다.



위의 예시 코드에서 몇 가지 의문점이 있을 수 있는데, SetBool을 호출할 때는 networkAnimator.animator를 통해서 호출하고 SetTrigger를 호출할 때는 networkAnimator.SetTrigger()로 바로 호출하는가와 트리거를 사용하는 부분에서 왜 networkAnimator.animator.SetTrigger()와 networkAnimator.SetTrigger()를 중복해서 사용했는지가 그것이다.


첫 번째 의문점의 경우에는 Trigger는 NetworkAnimtor 클래스에서 호출할 수 있는 SetTrigger() 함수가 있지만, 다른 애니메이터의 파라메터(Int, Float, Bool)는 NetworkAnimator 클래스에서 바로 호출해서 사용하는 메서드가 따로 없고, NetworkAnimator의 멤버 변수인 animator를 통해서 SetBool(), SetInt(), SetFloat() 함수를 호출하도록 만들어져 있기 때문이다.


두 번째 문제는 트리거를 사용하는 부분에서 왜 networkAnimator.animator.SetTrigger()와 networkAnimator.SetTrigger()를 중복해서 사용했는지 인데, 이것은 networkAnimator.animator.SetTrigger() 함수를 통해서 동작하는 애니메이션은 서버에서만 재생되고, networkAnimator.SetTrigger() 함수를 통해서 동작하는 애니메이션은 클라이언트에서만 재생되기 때문이다. 즉, 서버에서도 애니메이션을 재생하고 클라이언트에서도 애니메이션을 재생하기를 원한다면 위의 예시 코드와 같이 작성되어야 한다. 특히 서버 측에서 애니메이션 이벤트를 통해서 애니메이션이 진행되는 도중에 특정한 동작이 발생되도록 로직이 짜여있다면 서버에서도 애니메이션이 동작되어야 하기 때문에 반드시 위의 코드처럼 작성되어야만 한다. 하지만 이후에도 설명하겠지만, 애니메이션을 재생하는 처리는 상당히 무거운 작업에 속하기 때문에, 클라이언트가 서버의 역할을 함께하는 P2P 방식이 아닌 순수한 서버라면 애니메이션 재생 도중 호출되는 애니메이션 이벤트는 반드시 배제해야 한다. 그리고 서버에서는 애니메이션이 완전히 동작하지 않도록 하는 것이 좋다.





추가로 : 네트워크 애니메이터에 대한 중요한 사실


유니티에서 기본적으로 제공하는 네트워크 애니메이터가 있기 때문에 우선은 소개하고 사용법에 대해서 알려주지만, 사실 기본 네트워크 애니메이터를 사용하는 것은 추천하지 않는다. 오히려 직접 커스텀 네트워크 애니메이터를 따로 구현해서 사용하도록 권장하고 싶다. 지금은 주요 작업을 2017.3.03f 버전의 유니티로 하고 있기 때문에 이후의 버전에서는 더 좋게 바뀌었는지 모르겠지만, 이 버전과 이전 버전에서는 기본 네트워크 애니메이터에 상당한 문제가 있기 때문이다.


첫 번째 문제는, 기본 네트워크 애니메이터가 소모하는 데이터량이 너무 많다. 커스텀 애니메이터를 잘 구현한다면 기본 네트워크 애니메이터를 사용할 때보다 훨씬 데이터 소모량을 많이 줄일 수 있다.


두 번째 문제는, 꽤나 심각한 문제인데, 기본 네트워크 애니메이터가 굉장히 많은 가비지(Garbage)를 발생시켜서 GC로 인한 프레임드랍이 심각하다고 여겨질 만큼 발생한다는 것이다. 이것에 대한 이슈는 구글링해보면 해외 개발자들도 상당히 심각하게 느끼도 있다는 것을 알 수 있다. 실제 개발에서 네트워크 애니메이터로 애니메이션을 동기화 했다가 이 문제 때문에 네트워크 애니메이터를 모두 제거하고 커스텀 네트워크 애니메이터를 구현해서 사용해야 했었다.



반응형
  1. modernator 2018.09.30 01:21

    잘보고 갑니다. UNet의 NetworkAnimator는 잘모르겠지만, 예전에 Photon Unity Networking의 NetworkAnimator 코드를 분석한 적이 있었는데요, 동기화를 위해서 어마어마한 작업들을 하더군요. 간단한 데모 구현이나 가벼운 게임이 아니라면 말씀하신대로 직접 스크립트로 필요한 경우에만 동기화하는게 제일 좋은것 같습니다.

    • wergia 2018.10.01 11:08 신고

      유넷이 기본 제공하는 네트워크 애니메이터는 사실상 프로토타이핑용인 것 같습니다.

  2. mimus 2018.11.15 23:07

    안녕하세요 unet 관련 설명을 잘봤습니다 ㅎㅎ..
    다름이 아니라 이번에 혼자서 unet으로 VR 게임을 제작 하는데 문제가 생겨서 질문을 좀드립니다ㅠㅠ..
    멀티플레이 방식을 internt 방식을 사용해서 제작하니 방을 만든 사용자의 움직임은 방에 접속한 사람에게 보이는데
    방에 접속한 사람의 움직임은 방을 만든 사용자에게 보이지가 않습니다..
    동기화 방식은 networkTransform 을 사용했습니다..

    혼자서 아무리 생각을 해봐도 이유를 모르겠습니다 ㅠㅠ

    아그리고 추가 로 질문을 하면 혹시 internet 방식으로 멀티플레이를 하게되면 방을 만든 사람 만 local player로 인식 되는건가요?..
    답변 기다리겠습니다 ㅠ

    • wergia 2018.11.16 11:13 신고

      움직임이 보이지 않는다는 것은 애니메이션이 보이지 않는다는 의미인가요? 아니면 위치 동기화가 되지 않는다는 의미인가요?

      1. 만약 애니메이션이 보이지 않는다는 뜻이라면 방을 만들때 StartServer로 하셨나요?
      방을 만드는 사람이 플레이어로 참여하려면 StartServer로 방을 만들기보다 StartHost로 방을 만들어야 합니다. Server의 경우 기본적으로 서버로서의 계산만을 하기 때문에 기본 NetworkAnimator의 경우 애니메이션 재생을 배재하기때문에 애니메이션 동기화가 되지 않을 수 있습니다.

      2. 만약 위치 동기화가 되지 않는다면 authority 문제일 확률이 제일 높습니다. 일반적으로 네트워크 게임에서 동기화하는 권한은 서버 역할을 하는 호스트에게 있어야만 합니다. 그런데 만약 권한이 로컬 플레이어가 가지게 된다면 호스트 역할을 하는 플레이어의 경우 서버역을 겸하기 때문에 방에 접속한 다른 사용자에게 위치 동기화가 되지만, 클라이언트로서 접속한 플레이어의 위치 동기화가 이루어지지 않는 문제라고 생각됩니다.
      플레이어 프리팹 오브젝트의 NetworkIdentity 컴포넌트에서 Local Player Authority를 확인해보세요.

    • mius 2018.11.16 15:12

      감사합니다! 덕분에 해결 했습니다!

  3. myouzing 2019.08.05 23:09

    안녕하세요 unity를 공부하고 있는 학생입니다.
    저는 이제까지 single fps 게임을 개발하였습니다. 그 후에 multi player로 발전시키는 과정에서 문제가 생겨 여쭤보고 싶습니다. ㅠㅠ
    우선, 문제점은 제가 single에서 넣어놓은 적 애니메이션은 player와 관련이 있었습니다.
    player가 존재하면 걷고, 가까워지면 player를 공격하고 하는 등등의 애니메이션을 적용한 것입니다
    그러나, 제가 player에게 networkmanager를 적용시켰을 때 player를 인식하지 못하는 문제점이 있는 것 같습니다.
    이 문제에 대해 혹시 조언을 구할 수 있을까요..?
    답변 주시면 정말 감사하겠습니다 !

    • wergia 2019.08.19 17:53 신고

      상황을 정확히 알 수 없어서 자세한 답변은 어렵지만 대강 짐작가는 부분에 대한 답변을 드립니다.

      https://wergia.tistory.com/176

잘못된 애니메이션 리소스로 인해서 발생하는 버그


게임 제작에서 일반적으로 버그라고 하면, 프로그래머의 전유물 되는 경우가 많다. 물론 대다수의 게임 버그는 프로그래머의 실수나 설계 미스로 인해서 발생하는 것이지만, 잘못된 리소스로 인해서 발생하는 경우도 꽤나 있다. 대표적인 리소스로 인한 버그를 예로 들자면 3D 모델을 3ds 맥스나 마야같은 3D 툴에서 작업할 때, 실수로 폴리곤을 뒤집어서 내보내는 것과 같은 것이 있다. 이렇게 폴리곤이 뒤집힌 모델은 게임 엔진에 올릴 경우, 해당 부위가 뻥 뚫려서 보이는 문제가 발생한다. 이렇게 눈에 바로 보이는 버그라면 다행이지만 눈에 보이지 않는 리소스로 인한 버그라면 찾아내기가 매우 어려울 것이다.


찾아내기 어려운 리소스 버그 중에 하나가 바로 잘못된 애니메이션 리소스로 인해서 발생하게 되는 버그이다. 단지 애니메이션이 이상하게 움직이는 버그라면 상관이 없지만, 애니메이션은 정상적으로 동작하지만 어느 순간부터인가 캐릭터의 로테이션이 돌아가있다던지 정상적인 위치를 조금씩 벗어나게 되는 문제가 발생한다면 어떨까? 프로그래머나 엔진 작업자는 우선 캐릭터의 로테이션이나 포지션에 관여하는 모든 코드를 검토해야할 것이다. 이상한 현상을 보일 수 있는 코드가 있는지 모든 부분을 확인해야 한다. 만약 규모가 있는 프로젝트라면 이 작업만으로도 엄청난 시간을 소모해야 하지만, 이 과정을 거친 이후에 코드에 문제가 없음을 확인했다면 어떨까? 만약 이러한 문제에 대한 경험이 없다면 큰 혼란에 빠질 것이다.


나는 최근에 이렇게 코드에 문제가 없음에도 불구하고 캐릭터가 회전하는 문제와 원래 위치를 조금씩 벗어나서 움직이는 버그를 모두 겪어본 적이 있다. 코드를 검토했음에도 불구하고 코드에는 문제가 없음을 확인했지만 여전히 캐릭터가 이상한 회전이나 이동을 하는 문제가 발생했는데, 그때가 되어서야 나는 코드가 아닌 리소스를 의심해보기로 했다. 우선은 어떤 상황에서 이런 문제가 발생하는지 명확하게 확인해야 했다. 여러 번 테스트해본 결과 캐릭터의 로테이션이 이상한 각도로 변하는 문제는 캐릭터가 사망하고 난 이후에, 오브젝트 풀링으로 재활용해서 가져왔을 때 발생했고, 캐릭터가 원래 위치를 벗어나는 문제는 캐릭터가 공격 모션을 취한 다음에 조금씩 이동하는 것을 확인할 수 있었다. 그렇게 해서 우선 문제가 발생하는 원인이 사망 애니메이션과 공격 애니메이션이라는 것을 알 수 있었고, 그 이후에는 유니티에서 캐릭터의 본들의 트랜스폼을 분석해서 특정한 본이 애니메이션 이후에 원래 상태로 돌아가지 않고 이상한 각도를 유지하거나 이상한 위치를 유지하는 것을 확인할 수 있었다. 이러한 문제를 애니메이터에게 알려주고 수정된 애니메이션 리소스를 받아서 적용한 이 후에는 이 버그는 해결되었다.


버그는 코드에만 있지 않다. 스크립트에 캐릭터의 회전이나 이동에 이산한 동작을 하는 코드가 없음에도 불구하고 이상한 회전을 보인다거나 원래 위치를 조금씩 벗어나서 이동하는 버그를 보게 된다면 애니메이션 리소스의 버그를 의심해보라.

반응형
  1. 아이윤맨 2020.11.05 13:44 신고

    링크해갑니다~

+ Recent posts