Programming 

TPS 캐릭터 조작 기능 구현하기

 

작성 기준 버전 :: 2019.2

 

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

 

지난 포스트 중 하나에서는 마우스를 이용해서 클릭한 위치로 캐릭터를 이동시키는 RPG나 AOS 게임에서 주로 사용되는 캐릭터 조작 방법을 구현했었다.

 

이번에는 키보드로 이동하고 마우스로 원하는 대상을 바라보게 하는 TPS 게임에서 사용되는 캐릭터 조작 방법을 구현해보도록 하자.

 

리소스 임포트와 기본 애니메이션 세팅

 

본격적인 구현에 들어가기 전에 캐릭터로 사용할 모델링과 애니메이션을 영상 하단의 링크에서 다운로드 받아서 임포트한다.

 

practice-animation.zip
다운로드

 

FBX 파일을 임포트하고 나면 먼저 캐릭터의 이동 애니메이션을 보여주기 위한 애니메이션 세팅을 해보자.

 

 

먼저 임포트된 FBX 파일 중에 Run, Stand의 애니메이션 임포트 세팅 중에서 Loop Time을 체크하고 적용한다.

 

 

그리고 애니메이터 컨트롤러를 생성한 뒤 Bool 타입으로 isMove 파라미터를 추가한다. 

 

 

그 다음에는 Stand 애니메이션을 기본 스테이트로 추가하고 Run 애니메이션을 추가한 뒤 isMove의 상태에 따라서 Stand와 Run을 왔다갔다 하도록 세팅해준다.

 

캐릭터 배치와 카메라 세팅

 

 

이 작업이 끝나면 씬에 캐릭터가 돌아다닐 바닥이 될 Plane을 하나 배치하고 BoxMan@Stand를 배치하고 BoxMan에 붙어있는 Animator 컴포넌트의 비어있는 Controller 프로퍼티에 방금 만든 애니메이터 컨트롤러를 할당해준다.

 

 

애니메이션에 대한 세팅이 끝나면 카메라를 이동시켜서 TPS 게임에서 자주 보이는 카메라 구도를 만들어보자. TPS 시점의 게임에서는 이런 구도의 카메라 시점이 주로 사용된다.

 

일인칭으로 보여지는 FPS 게임은 이것과는 약간 다르게 캐릭터의 몸이 거의 보이지 않고 손 정도만 보이게 시점을 세팅하게 되지만 이후의 기능 구현 자체는 거의 비슷하게 진행하면 된다.

 

본격적인 조작 기능 구현에 들어가기에 앞서 카메라와 캐릭터 구조를 조금 더 살펴보자.

 

지금 시점 자체는 나쁘지 않지만 조작 기능이 들어가기에는 아쉬움이 많다.

 

 

특히 마우스를 상하좌우로 움직이면 카메라 역시 마우스의 움직임에 맞게 회전해야 하는데, 카메라를 잡고 위/아래/좌/우로 회전시켜보면 전혀 적절하지 못하다는 것을 깨달을 수 있을 것이다.

 

해결책 1 : 카메라를 캐릭터의 자식 게임 오브젝트로 만들기

 

 

첫 번째로 생각해볼 만한 방법은 카메라를 캐릭터의 자식 게임 오브젝트로 만드는 것이다.

 

 

카메라를 BoxMan의 자식 게임 오브젝트로 만들면 좌우로 움직이는건 정상적으로 동작한다.

 

 

하지만 위/아래로 움직여보면 캐릭터가 같이 기울어져서 적절하지 못한 것을 알 수 있다. 적절한 카메라 회전을 위해서는 캐릭터를 회전시키지 않으면서도 캐릭터를 중심으로 카메라를 회전시키는 방법을 찾아야 할 것이다.

 

해결책 2 : 카메라 암(Camera Arm) 만들기

 

 

다른 해결책으로는 카메라 암(Camera Arm) 역할을 할 빈 게임 오브젝트를 하나 만들고 BoxMan의 머리 정도의 위치로 이동시켜 주고 카메라를 이 빈 게임 오브젝트의 자식 게임 오브젝트로 만들어준다. 

 

 

이렇게 세팅해주면 상하좌우 모든 방향의 회전에도 문제없이 카메라가 캐릭터를 중심으로 움직이는 것을 확인할 수 있다.

 

 

이대로 Camera Arm을 BoxMan 밑에 배치해도 되지만 그렇게 하면 BoxMan의 회전에 Camera Arm이 묶이게 되기 때문에 Character라는 이름의 빈 게임 오브젝트를 만들어서 BoxMan과 Camera Arm을 그 아래에 배치하자. 이렇게 해주면 BoxMan의 회전과 Camera Arm의 회전이 분리되서 관리하기가 쉬울 것이다.

 

카메라 회전 기능 구현하기

 

이제 본격적으로 캐릭터 조작 기능을 만들어보자.

 

TPSCharacterController라는 이름으로 C# 스크립트를 생성하고 다음 코드를 따라 작성하자.

 

public class TPSCharacterController : MonoBehaviour
{
    [SerializeField]
    private Transform characterBody;
    [SerializeField]
    private Transform cameraArm;

    Animator animator;

    void Start()
    {
        animator = characterBody.GetComponent<Animator>();
    }
}

 

Transform 타입으로 BoxMan 모델을 관리할 characterBody 변수와 카메라의 회전을 관리할 cameraArm 변수를 선언하고 애니메이션을 관리할 Animator 변수를 선언하고 Start 함수에서 characterBody.GetComponent 함수로 BoxMan에 붙어있는 Animator 컴포넌트를 가져와서 저장해둔자.

 

private void LookAround()
{
    Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
    Vector3 camAngle = cameraArm.rotation.eulerAngles;

    cameraArm.rotation = Quaternion.Euler(camAngle.x - mouseDelta.y, camAngle.y + mouseDelta.x, camAngle.z);
}

 

먼저 마우스의 움직임에 따라서 카메라를 회전시키는 기능을 구현하기 위해서 LookAround 함수를 만든다. 

 

LookAround 함수에서는 먼저 마우스가 이전 위치에 비해서 얼마나 움직였는지 알아내야 한다. 마우스가 움직인 수치는 Input.GetAxis 함수에 "Mouse X"와 "Mouse Y"를 매개변수로 넣어서 가져올 수 있다. 

 

"Mouse X"로는 마우스를 좌우로 움직인 수치를 가져올 수 있고 "Mouse Y"로는 마우스를 위아래로 움직인 수치를 가져올 수 있다.

 

이 두 값을 Vector2로 묶어서 mouseDelta 변수에 저장한다. 참고로 프로그래밍에서 이전 값과 현재 값의 차이를 주로 delta라는 용어로 표현한다. 

 

그 다음에는 cameraArm의 rotation 값을 오일러 각으로 변환해둔다. 그리고 camAngle과 mouseDelta를 합쳐서 새로운 회전값을 만들어낸 다음 cameraArm.rotation에 넣어줘야 한다.

 

우선 마우스 좌우 움직임으로 카메라 좌우 움직임을 제어해야 하기 때문에 mouseDelta의 x 값을 camAngle의 y에 더해준다. 그리고 마우스 수직 움직임은 카메라 상하 움직임을 제어해야 하기 때문에 mouseDelta의 y 값을 camAngle의 x에 추가해준다.

 

이 마우스 수직 움직임의 경우에는 한국에서는 마우스를 위로 움직이면 위를 쳐다보고 아래로 움직이면 아래를 쳐다보게 하는 방식을 사용하는 유저들이 더 많지만 북미에서는 이 움직임을 반대로 사용하는 경우가 꽤 있다고 한다. 그렇게 때문에 별도의 옵션을 둬서 두 가지 조작 방식 중에 사용자가 원하는 방식을 선택할 수 있도록 하는게 좋다. 

 

우선 한국에서 주로 쓰는 방식으로 구현하자. 마우스 방향과 바라보는 방향을 일치시키려면 mouseDelta.y를 camAngle.x에서 빼주면 된다.

 

void Update()
{
    LookAround();
}

 

그리고 LookAround 함수를 Update 함수에 호출하게 만들어준 뒤 코드를 저장하고 에디터로 돌아가보자.

 

 

그 다음 Character 게임 오브젝트에 생성한 컨트롤러 컴포넌트를 붙이고 프로퍼티들을 할당해준다.

 

 

게임을 플레이시키고 마우스를 움직여보면 우선 카메라는 제대로 움직이는 것 같지만 카메라 상하 회전에 제한이 없어서 그런지 마우스를 움직이다보면 화면이 뒤집어지는 경우가 발생한다.

 

카메라 각도 제한하기

 

이런 문제를 막기 위해서는 카메라의 x 회전을 제한해야 한다.

 

위쪽 회전은 약 70도를 제한하고 아래쪽 회전은 약 25도를 제한하도록 하자. 다만 x 회전 값을 출력해보면 알겠지만 수평을 기준으로 위쪽으로 회전하면 0도에서 90도까지 각도가 나오고 아래쪽으로 회전하면 360도부터 270도까지 각도가 나온다.

 

private void LookAround()
{
    Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
    Vector3 camAngle = cameraArm.rotation.eulerAngles;
    
    //추가
    float x = camAngle.x - mouseDelta.y;
    if (x < 180f)
    {
        x = Mathf.Clamp(x, -1f, 70f);
    }
    else
    {
        x = Mathf.Clamp(x, 335f, 361f);
    }

    cameraArm.rotation = Quaternion.Euler(x, camAngle.y + mouseDelta.x, camAngle.z);
}

 

우선 camAngle.x - mouseDelta.y로 만들어낸 값을 바로 회전값에 넣지 말고 따로 x에 저장한다. 그리고 회전값에 넣어주기 전에 x값의 상태에 따라서 처리하는 과정을 구현해야 한다.

 

먼저 x가 180도보다 작은 경우는 위쪽으로 회전하는 경우이므로 x의 값을 -1도에서 70도까지 제한시켜둔다. 여기서 0도가 아닌 -1도로 최저점을 잡는 이유는 0도로 잡으면 카메라가 수평면 아래로 내려가지 않는 문제가 발생하기 때문이다.

 

그리고 x각이 180도보다 큰 경우는 아래쪽으로 회전하는 경우로 25도를 제한하기 위해서 335도에서 361도 사이로 값을 제한시킨다.

 

이렇게 구해진 x값을 camAngle의 x값 자리에 넣어준다.

 

 

카메라 회전을 제한시킨 기능을 에디터에서 테스트해보면 위아래 회전에 제한이 걸려서 카메라가 뒤집어지는 문제가 발생하지 않는 것을 확인할 수 있다.

 

이동기능 추가하기

 

이제 여기에 이동 기능을 추가할 차례이다.

 

이동방향 구하기

 

private void Move()
{
    Debug.DrawRay(cameraArm.position, cameraArm.forward, Color.red);
}

 

움직이는 기능을 추가하기 위해서 Move 함수를 만든다. 

 

캐릭터를 이동시키기 위해서는 먼저 캐릭터가 이동할 방향을 찾아야 한다. 이동할 방향으로 가장 적합한 대상은 카메라가 바라보고 있는 방향일 것이다.

 

cameraArm.forward를 사용하면 될 것 같은데 우선 이 값이 적당한 방향인지 확인하기 위해서 Debug.DrawRay로 카메라 암 위치에서 cameraArm.forward 방향으로 에디터에서 선으로 그려보자.

 

void Update()
{
    LookAround();
    Move();
}

 

Update 함수에서 Move 함수를 호출하게 만든다.

 

 

게임을 플레이시키고 마우스를 움직여보면 씬 뷰에서 Camera Arm 위치에 빨간색 선이 카메라 암이 쳐다보는 방향을 표시하는 것을 볼 수 있다.

 

캐릭터의 이동 방향으로 쓰기에 적당해 보이지만 위쪽이나 아래쪽을 쳐다볼 때는 선이 기울어지는 것을 볼 수 있다. 정상적인 이동 방향을 보장하기 위해서는 이 선을 수평으로 변환해야 한다.

 

private void Move()
{
    Debug.DrawRay(cameraArm.position, 
        new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized, Color.red);
}

 

새 벡터를 만들면서 cameraArm.forward의 y값을 넣지 않는 것으로 높이를 없애고 normalized 시켜서 길이를 1로 맞춰준다.

 

 

다시 에디터에서 게임을 플레이시키고 마우스를 움직여보면 위나 아래를 쳐다봐도 빨간 선의 방향이 수평을 유지하는 것을 볼 수 있다.

 

이동기능 구현하기

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = lookForward;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

그럼 다시 스크립트 에디터로 돌아가서 이동 기능을 구현하는 것을 진행해보자.

 

우선 Input.GetAxis 함수로 "Horizontal"과 "Vertical"로 이동 입력 값을 가져와서 moveInput 벡터를 만든다. 그리고 moveInput의 길이로 입력이 발생하고 있는지 판정하여 isMove 변수에 넣어준다.

 

moveInput의 길이가 0이면 이동 입력이 없는 것이고 0이 아니면 이동 입력이 발생하고 있는 것이다.

 

이 isMove를 animator의 "isMove"에 세팅해주면 이동 입력이 발생할 때는 걷는 애니메이션을 재생하고, 이동 입력이 없을 때는 대기 애니메이션을 재생할 것이다.

 

그리고 아까 전에 구해낸 방향을 바라보는 방향으로 저장한다. 거기에 추가로 cameraArm.right 역시 정면과 마찬가지로 평면화시켜서 저장해둔다.

 

이렇게 구해낸 lookForward와 lookRight에 moveInput을 곱해서 더하면 바라보고 있는 방향을 기준으로 이동 방향을 구할 수 있다. 그리고 구해낸 이동 방향과 Time.deltaTime, 이동속도를 모두 곱해서 transform.position에 더해주면 캐릭터를 이동시킬 수 있게 된다.

 

이동시 바라보는 방향 1 : 카메라가 바라보는 방향

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = lookForward;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

마지막으로 캐릭터가 바라보는 방향을 정하는 방법을 여러 가지로 구현할 수 있다.

 

첫 번째 방법은 캐릭터를 이동시킬 때 시선을 정면에 고정시키는 방법이다.

 

characterBody의 forward를 lookForward로 정해주면 된다.

 

 

캐릭터를 움직여보면 캐릭터를 앞뒤좌우 어느 방향으로 움직이든 카메라가 바라보는 방향을 바라본다.

 

이 기능을 위해서는 캐릭터가 좌우로 움직일 때는 옆걸음을 치는 애니메이션을 넣고, 뒤로 움직일 때는 뒷걸음을 치는 애니메이션을 넣어줘야 할 것이다.

 

이런 식으로 움직이는 방법은 캐릭터가 언제나 정면을 바라보기 때문에 좀 더 직관적인 컨트롤을 할 수 있다는 장점이 있다.

 

이동시 바라보는 방향 2 : 이동방향

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = moveDir;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

characterBody.forward를 moveDir로 지정해주면 된다.

 

 

캐릭터를 움직여보면 캐릭터는 카메라가 바라보는 방향을 기준으로 앞뒤좌우로 움직이지만 정면을 바라보지 않고 이동해야하는 방향을 바라보는 것을 알 수 있다.

 

이렇게 구현된 컨트롤은 정면만 바라보는 컨트롤에 비해서 복잡하기는 하지만 좀 더 섬세한 방향 컨트롤이 가능해진다.

 

이 두 가지 방식 중에서 게임에서 필요한 방식을 선택해서 구현하면 된다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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을 끄는 방법으로 실제 버튼 이미지보다 작은 영역만 클릭 가능하게 만드는 것도 가능하다.

 

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

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming 

클릭한 위치로 캐릭터를 이동시키는 기능 구현하기

 

작성 기준 버전 :: 2019.2

 

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

 

게임의 조작 방식에는 굉장히 다양한 방법이 있다. 그 중에서 쿼터 뷰 형식의 RPG 게임이나 AOS 장르에서는 맵의 지형을 클릭하고 그 클릭한 위치로 캐릭터를 이동시키는 조작 방식을 사용한다.

 

쿼터 뷰 RPG나 AOS 장르처럼 클릭한 위치로 캐릭터를 이동시키는 기능을 만드는 방법을 알아보자.

 

클릭한 위치로 캐릭터 이동시키기

 

public class ClickMovement : MonoBehaviour
{
    private Camera camera;

    private bool isMove;
    private Vector3 destination;

    private void Awake()
    {
        camera = Camera.main;
    }
}

 

클릭한 위치로 캐릭터를 이동시키는 기능을 구현하기 위해서 먼저 위와 같은 변수들을 선언해준다.

 

Camera는 스크린 좌표계로 표시되는 마우스의 위치를 월드 좌표계로 표시하기 위해서 선언했다. 그리고 isMove는 캐릭터가 움직이고 있는 중인지 판정하기 위해서, destination은 캐릭터가 목표 위치를 기억하기 위해서 선언했다.

 

그리고 Awake 함수에서 씬에 존재하는 메인 카메라를 가져와서 camera 매개변수에 저장해준다.

 

private void SetDestination(Vector3 dest)
{
    destination = dest;
    isMove = true;
}

 

SetDestination 함수를 만들어서 그 안에 매개변수로 받아온 dest를 destination에 넣어주고 isMove를 true로 만들어 주는 코드를 작성한다. SetDestination 함수는 클릭으로 찾아낸 마우스의 위치를 저장해두고 이동을 시작하게 하는 역할을 한다.

 

private void Move()
{
    if (isMove)
    {
        if (Vector3.Distance(destination, transform.position) <= 0.1f)
        {
            isMove = false;
            return;
        }
        var dir = destination - transform.position;
        transform.position += dir.normalized * Time.deltaTime * 5f;
    }
}

 

Move 함수를 만든다. Move 함수에서는 isMove가 true일 때, 즉 SetDestination에서 목적지를 지정한 뒤에 목적지와 캐릭터의 거리가 0.1 유니티 미터보다 작으면 이동을 중지하고, 아직 도착하지 못한 상태라면 캐릭터의 위치에서 목적지로 향하는 방향을 구해서 캐릭터를 이동시키는 작업을 처리한다.

 

void Update()
{
    if (Input.GetMouseButton(1))
    {
        RaycastHit hit;
        if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit))
        {
            SetDestination(hit.point);
        }
    }

    Move();
}

 

SetDestination 함수와 Move 함수를 모두 작성하면 Update 함수로 가서 마우스 오른쪽 버튼을 클릭했을 때 마우스 커서의 위치를 찾아내는 코드를 작성한다.

 

public class ClickMovement : MonoBehaviour
{
    private Camera camera;

    private bool isMove;
    private Vector3 destination;

    private void Awake()
    {
        camera = Camera.main;
    }

    void Update()
    {
        if (Input.GetMouseButton(1))
        {
            RaycastHit hit;
            if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit))
            {
                SetDestination(hit.point);
            }
        }

        Move();
    }

    private void SetDestination(Vector3 dest)
    {
        destination = dest;
        isMove = true;
    }

    private void Move()
    {
        if (isMove)
        {
            if (Vector3.Distance(destination, transform.position) <= 0.1f)
            {
                isMove = false;
                return;
            }

            var dir = destination - transform.position;
            transform.position += dir.normalized * Time.deltaTime * 5f;
        }
    }
}

 

코드의 전문은 위와 같다.

 

 

이 컴포넌트를 캐릭터에 부착하고 콜라이더가 있는 맵에 우클릭하면 클릭한 위치로 캐릭터가 이동하는 것을 볼 수 있다.

 

기능 개선 1 : 카메라가 캐릭터 따라가게 하기

 

하지만 몇 가지 개선해야할 점이 보인다. 우선 카메라가 캐릭터를 따라가지 않아서 위쪽을 클릭하면 캐릭터가 화면 밖을 벗어나버리는 것을 볼 수 있다.

 

 

카메라가 캐릭터를 따라가게 하기 위해서 카메라를 캐릭터의 자식 오브젝트로 만들어보자.

 

 

다시 게임을 플레이시키고 우클릭으로 캐릭터를 이동시켜보면 카메라가 캐릭터를 따라서 움직이는 것을 확인할 수 있다.

 

기능 개선 2 : 캐릭터가 이동할 방향을 바라보게 하기

 

두 번째로 개선할 점은 캐릭터가 이동할 방향을 바라보게 하는 것이다. 지금은 캐릭터를 이동시켜도 게임을 시작할 때 바라보고 있던 방향만을 바라보면서 이상하게 이동하는 것을 볼 수 있다.

 

private void Move()
{
    if (isMove)
    {
        if (Vector3.Distance(destination, transform.position) <= 0.1f)
        {
            isMove = false;
            return;
        }
        var dir = destination - transform.position;
        transform.foward = dir;	// 추가
        transform.position += dir.normalized * Time.deltaTime * 5f;
    }
}

 

Move 함수를 보면 캐릭터가 이동할 방향을 구해둔 부분이 있다. 이 방향을 캐릭터의 transform.forward에 넣어주면 이동하는 방향을 바라보게 할 수 있다.

 

 

그리고 다시 게임을 플레이시키고 이동할 지점을 우클릭 해보면 우클릭한 지점을 캐릭터가 바라보면서 이동하기는 한다. 하지만 카메라가 캐릭터의 자식 오브젝트로 되어있어서 캐릭터의 방향이 바뀔 때 카메라도 함께 돌아가서 굉장히 어지럽다.

 

 

약간 구조를 바꿔야 될 것 같다. 우선 Character Root라는 이름으로 빈 게임 오브젝트를 만든다. 그리고 BoxMan을 그 아래로 옮기고 BoxMan 밑에 있는 카메라 역시 Charactor Root 아래로 옮겨준다.

 

이렇게 구조를 만들면 캐릭터를 이동시킬 때 카메라가 캐릭터가 따라서 움직이는 것을 유지하면서 BoxMan을 정상적으로 회전시킬 수 있다.

 

구조는 완성되었으니 본격적으로 기능을 수정해보자.

 

 

우선 BoxMan에 붙어있는 ClickMovement를 Character Root로 옮겨서 이동하는 주체를 Character Root로 바꿔준다.

 

[SerializeField]
private Transform character;	// 추가

private void Move()
{
    if (isMove)
    {
        if (Vector3.Distance(destination, transform.position) <= 0.1f)
        {
            isMove = false;
            return;
        }
        var dir = destination - transform.position;
        character.transform.foward = dir;	// 변경
        transform.position += dir.normalized * Time.deltaTime * 5f;
    }
}

 

캐릭터 몸체 만을 회전시키기 위해서 Transform 변수를 선언하고 회전 주체를 transform에서 character.transform으로 바꿔준다.

 

 

코드를 모두 작성하면 유니티 에디터로 돌아와서 비어있는 Character 프로퍼티에 회전해야 하는 캐릭터 게임 오브젝트를 넣어준다.

 

 

그 다음 플레이시켜보면 카메라는 회전하지 않고 BoxMan이 우클릭한 방향을 바라보며 이동하는 것을 볼 수 있다.

 

이제 기능이 완성된 것처럼 보인다.

 

 

하지만 여기에 장애물이 추가되면 어떨까?

 

 

Character Root에는 장애물과 충돌할 수 있게 Capsule Collider와 Rigidbody를 추가한다. 그리고 장애물과 충돌했을 때 캐릭터가 넘어지지 않도록 Constraints의 Freeze Rotation을 전부 체크해준다.

 

 

다시 게임을 플레이시키고 캐릭터를 벽 너머로 이동시키기 위해서 벽 너머를 우클릭하면 돌아서 이동하지 못하고 벽에 계속 부딪히기만 하는 것을 볼 수 있다. 게다가 벽 위를 클릭하면 벽 위에 올라서기 위해서 점프까지 하는 것을 볼 수 있다.

 

기능 개선 3 : 길찾기 기능 추가하기

 

꽤 재밌어보이는 기능이 완성됐지만 우리의 목적은 캐릭터가 장애물을 회피해서 이동하는 것이기 때문에 다시 개선하는 작업에 들어가보자.

 

일단 Character Root에 붙어있는 Capsule Collider의 Is Trigger를 체크해준다.

 

일반적인 RPG 게임이나 AOS 게임에서는 장애물 건너편으로 이동하기 위해서 조작을 하면 캐릭터가 자동으로 이동할 수 있는 길을 찾아서 이동하는 것을 볼 수 있다.

 

캐릭터가 장애물을 돌아서 이동하기 위해서는 길찾기 기능이 필요하다. 길찾기 알고리즘을 직접 구현할 수도 있지만, 유니티 엔진에서 제공하는 내비게이션 기능을 사용하면 훨씬 간단하게 구현할 수 있다.

 

 

우선 상단 메뉴 바에서 [Window > AI > Navigation]을 선택해서 내비게이션 뷰를 연다. 그리고 장애물과 바닥을 선택하고 내비게이션 뷰에서 Navigation Static을 체크하고 내비게이션 메시를 구워준다.

 

 

그러면 캐릭터가 이동할 수 있는 영역이 파란색으로 표시된다.

 

 

그 다음에는 캐릭터를 이 내비게이션 메시 위에서 길을 찾아 움직일 수 있는 에이전트로 만들어줘야 한다. Character Root에 NavMeshAgent 컴포넌트를 붙여준다. 

 

using UnityEngine.AI;  // 추가

public class ClickMovement : MonoBehaviour
{
    private Camera camera;
    private NavMeshAgent agent;  // 추가

    private bool isMove;
    private Vector3 destination;

    private void Awake()
    {
        camera = Camera.main;
        agent = GetComponent<NavMeshAgent>();  // 추가
        agent.updateRotation = false;  // 추가
    }

    void Update()
    {
        if (Input.GetMouseButton(1))
        {
            RaycastHit hit;
            if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit))
            {
                SetDestination(hit.point);
            }
        }

        // Move();
        LookMoveDirection();  // 변경
    }

    private void SetDestination(Vector3 dest)
    {
        agent.SetDestination(dest);  //추가
        destination = dest;
        isMove = true;
    }

    // private void Move()
    private void LookMoveDirection()  // 변경
    {
        if (isMove)
        {
            // if (Vector3.Distance(destination, transform.position) <= 0.1f)
            if (agent.velocity.magnitude == 0.0f)  // 변경
            {
                isMove = false;
                return;
            }

            // var dir = destination - transform.position;
            var dir = new Vector3(agent.steeringTarget.x, transform.position.y, agent.steeringTarget.z) - transform.position;  // 변경
            character.transform.foward = dir;
        }
    }
}

 

그리고 스크립트 에디터로 돌아가서 AI 네임스페이스를 추가해주고 NavMeshAgent 변수를 만든다. 그 다음 Awake 함수에서 게임 오브젝트에 붙어있는 NavMeshAgent 컴포넌트를 가져와서 넣어준다.

 

컴포넌트를 가져온 다음에는 agent가 Character Root를 회전시키지 않도록 updateRotation을 false로 바꿔줍니다. 그리고 SetDestination 함수에서는 매개변수로 받아온 목표 위치를 agent.SetDestination 함수에 넣어준다.

 

이렇게 하면 NavMeshAgent가 자동으로 길을 찾아서 캐릭터를 이동하도록 만들어준다.

 

그러면 Move 함수의 원래 목적은 사라지는데, 대신 Move 함수는 BoxMan이 이동 방향을 쳐다보게 하는 기능을 해야한다.

 

변경되는 기능에 따라 함수의 이름은 LookMoveDirection으로 바꿔주면 좋다. 원래 코드는 BoxMan이 destination을 쳐다보게 되어있는데 이렇게 하면 캐릭터가 길을 돌아서 이동하는 와중에도 목적지만 쳐다봐서 부자연스럽게 움직이게 된다.

 

캐릭터는 목적지 대신에 자신이 이동하는 방향을 쳐다봐야 한다. direction을 구할 때 destination 대신 agent.steeringTarget을 사용해야 한다. 다만 아까 전에 캐릭터와 높이가 다른 목적지를 클릭 했을 때 캐릭터가 기울어졌던 것을 기억할 것이다. 캐릭터가 높이가 다른 곳을 쳐다보면서 기울어지는 것을 막기위해서 캐릭터가 agent의 steeringTarget 방향을 바라보되 높이는 캐릭터 자신의 높이를 바라보게 해야 한다. 그리고 도착 판정을 거리 대신에 agent의 속도로 변경하여 목적지에 도착한 경우면 함수를 종료하도록 만들어 준다.

 

이렇게 하면 캐릭터가 목적지에 도착한 다음 엉뚱한 방향을 바라보지 않게 될 것이다.

 

 

게임을 플레이시키고 우클릭으로 캐릭터를 이동시켜보면 잘 동작하는 것을 볼 수 있다. 벽 건너편을 클릭해도 제대로 돌아서 이동하고 벽 위를 클릭해도 캐릭터가 기울어지며 올라가려는 시도를 하지 않는 것을 알 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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개로 바뀌는 것을 볼 수 있다.

 

 

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

 

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

 

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

 

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

 

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

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

내비게이션 시스템 (2) 

NavMeshAgent와 NavMeshObstacle

 

작성 기준 버전 :: 2019.2

 

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

 

이전 포스트에서는 유니티의 내비게이션 시스템 중에서 길찾기 영역을 설정하는 NavMesh에 대해서 알아보았다.

 

이번 포스트에서는 이 NavMesh 위에서 길을 찾아서 움직일 NavMeshAgent와 NavMeshAgent의 길을 방해하는 NavMeshObstacle에 대해서 알아보도록 하자.

 

NavMeshAgent

 

Agent는 내비게이션 메시 위에서 길을 찾아서 움직일 오브젝트를 의미한다.

 

 

우선 캡슐 게임 오브젝트를 하나 만들어서 내비게이션 메시 위에 배치해보자. 그리고 캡슐의 자식 게임 오브젝트로 큐브 하나를 배치하고 캡슐의 정면이 어디를 가리키고 있는지 알기 쉽게 만든다. 거기에 더해 Rigidbody 컴포넌트를 붙이고 Constraints를 전부 체크해준다.

 

그 다음에는 NavMeshAgent 컴포넌트를 부착한다.

 

게임 오브젝트 준비가 끝나면 이 NavMeshAgent를 움직이기 위해서 Moveable라는 이름으로 C# 스크립트를 생성한다.

 

using UnityEngine;
using UnityEngine.AI;   // 스크립트에서 내비게이션 시스템 기능을 사용하려면 AI 네임스페이스를 using 선언해야함

public class Moveable : MonoBehaviour
{
    // 길을 찾아서 이동할 에이전트
    NavMeshAgent agent;

    // 에이전트의 목적지
    [SerializeField]
    Transform target;

    private void Awake()
    {
        // 게임이 시작되면 게임 오브젝트에 부착된 NavMeshAgent 컴포넌트를 가져와서 저장
        agent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        // 스페이스 키를 누르면 Target의 위치까지 이동하는 경로를 계산해서 이동
        if(Input.GetKeyDown(KeyCode.Space))
        {
            // 에이전트에게 목적지를 알려주는 함수
            agent.SetDestination(target.position);
        }
    }
}

 

유니티의 내비게이션 메시와 관련된 기능을 사용하기 위해서는 우선 AI 네임스페이스를 using 선언해줘야 한다.

 

먼저 NavMeshAgent 변수를 선언하고 Awake 함수에서 게임 오브젝트에 부착되어 있는 NavMeshAgent 컴포넌트를 가져와서 저장해준다. 그리고 agent가 목표로 잡고 찾아갈 target 변수를 Transform 타입으로 만든다.

 

그 다음에는 Update 함수에서 스페이스 키 입력을 감지하는 코드를 작성한다. 그 안에서 agent에게 목적지를 설정해주는 코드를 작성해야 한다. agent에게 가야할 목적지를 알려주는 함수는 agent.SetDestination이다.

 

매개변수로 target.position을 넣어주면 스페이스를 누를 때마다 agent가 타깃의 위치를 찾아서 경로를 계산하고 이동할 것이다.

 

 

그리고 하이어라키 뷰에서 빈 게임 오브젝트를 하나 생성하고 Target으로 이름 지은 뒤 좀 더 보기 쉽게 아이콘을 설정해준다. 다음에는 캡슐 게임 오브젝트에 Moveable 컴포넌트를 부착하고 Target 게임 오브젝트를 Target 프로퍼티에 할당해준다.

 

 

  

게임을 플레이 시켜보자. 게임 플레이가 시작되고 Target 게임 오브젝트를 적당한 위치로 가져다 놓은 다음 스페이스 키를 누르면 캡슐이 Target을 향해서 장애물과 부딪히지 않고 이동하는 것을 볼 수 있다.

 

NavMeshAgent의 프로퍼티

 

Base Offset

 

 

우선 첫 번째 프로퍼티인 Base Offset은 길을 찾을 때 사용되는 충돌 실린더의 위치이다. 이 값을 조절해보면 캡슐 게임 오브젝트가 바닥에 파묻히거나 공중에 뜨는 것을 볼 수 있다.

 

캐릭터와 에이전트의 위치를 맞추기 위해서 사용되는 프로퍼티이다.

 

Steering

 

Speed

 

Speed는 단어 그대로 에이전트가 움직이는 속도이다. 

 

Angular Speed

 

Angular Speed는 에이전트가 회전하는 속도이다.

 

Angular Speed 프로퍼티의 값은 degree/sec로 회전 속도를 결정한다.

 

Acceleration

 

Acceleration은 가속도이다. 

 

기본 값이 8에서는 에이전트가 최대 속도까지 도달하는데 어느 정도 시간이 걸리지만 20 정도로 수정해주면 빠르게 최대 속도에 도달하는 것을 볼 수 있다.

 

Stopping Distance

 

Stopping Distance는 목표와 얼마만큼의 거리를 두고 멈출 지를 결정한다.

 

이 기능은 원거리 유닛이 공격할 대상을 향해서 이동하다가 공격 가능한 사정거리에 도달하면 이동을 멈추고 공격하게 하려고 할 때 사용할 수 있다.

 

Auto Breaking

 

Auto Breaking은 에이전트가 목적지에 도착하기 직전에 감속을 시작할 것인가를 결정하는 프로퍼티이다.

 

Auto Breaking이 켜져있을 때는 도착하기 직전에 감속을 시작해서 목적지에 제대로 멈추지만 Auto Breaking을 끄고 이동을 시키면 목적지에 도착하고나서 감속을 시작하기 때문에 속도를 주체하지 못하고 목적지를 넘어간 뒤 목적지에 도착하기 위해서 계속 왔다갔다하는 모습을 볼 수 있다.

 

Obstacle Avoidance 

 

Obstacle Avoidance 파트는 다른 에이전트와 나중에 설명할 NevMeshObstacle을 어떻게 회피할 것인지를 결정하는 내용의 프로퍼티들을 가지고 있다.

 

먼저 기본 상황에서 이동하는 에이전트는 길을 막고 있는 에이전트를 향해서 이동하다가 돌아갈 길이 있으면 돌아가고 길이 없다면 살짝 밀어내고 길을 찾아간다.

 

Radius

 

Radius를 변경하면 초록색 원기둥의 두께가 두꺼워지는 것을 볼 수 있다.

 

하지만 지형인 벽과는 관계없이 다른 에이전트나 NavMeshObstacle과만 충돌하는 영역의 두께만 조절된다.

 

Height

 

Height는 에이전트끼리의 높이 충돌을 조절하는 프로퍼티이다. 

 

Quality

 

Quality는 장애물 회피 품질을 뜻한다. 

 

HighQuality로 설정하면 다른 에이전트를 회피하기 위해서 최대한 정밀한 움직임을 보이게 되고 Low Quality로 설정하면 피하는 움직임이 간소화된다. 그리고 양 쪽 다 None으로 설정하면 서로 완전히 무시하고 지나간다.

 

Priority

 

Priority는 에이전트 간의 우선 순위이다. 우선 순위는 0부터 99까지 있는데 0이 가장 높고 99가 가장 낮다.

 

우선 순위가 높은 에이전트는 길을 찾을 때 우선 순위가 낮은 에이전트를 고려하지 않고 그냥 밀고 지나가버린다. 그리고 우선 순위가 같으면 회피하려는 노력은 하지만 여의치 않을 때는 그냥 밀고 지나가게 된다. 마지막으로 낮은 우선 순위의 에이전트는 높은 우선 순위의 에이전트를 밀어내지 못한다.

 

Path Finding

 

Auto Repath

 

Auto Repath는 내비게이션 메시에 변동이 생겼을 때 자동으로 길을 다시 찾을 것인가를 설정하는 프로퍼티이다. 목적지로 가는 최단 경로의 중간이 막히면 자동으로 경로를 다시 계산해서 이동한다.

 

하지만 아주 먼거리를 이동할 때 아직 시야에 보이지 않는 중간이 막혀도 경로를 바로 재계산하기 때문에 경로가 막힌 구역까지 도착한 다음 경로를 새로 계산하기를 원한다면 체크를 해제하고 경로가 막힌 구역까지 도착한 다음 경로를 새로 계산하는 기능을 직접 구현해야 한다.

 

Area Mask

 

Area Mask에서는 이 에이전트가 지나갈 수 있는 영역과 지나가지 못하는 영역을 설정할 수 있다. 

 

특정 영역을 꺼주면 이 캐릭터는 아무리 가까운 거리라도 그 영역을 건너지 못하게 된다.

 

 

NavMeshObstacle

 

 

컴포넌트의 이름에서도 알 수 있지만 이 컴포넌트는 장애물 역할을 한다. 기본 상태에서는 작은 장애물에만 자연스러운 움직임을 보이고 큰 장애물은 부자연스럽게 회피하는 동작을 보이게 된다.

 

작은 장애물만 회피가 가능하면 그냥 Navigation Static으로 설정한 벽을 배치하는게 더 나을 것이라고 생각할 수 있다. 하지만 Navigation Static이 적용된 지형과 NavMeshObstacle은 큰 차이점이 있는데 Navigation Static으로 설정된 벽은 움직일 수 없다는 것이다. 그와 반대로 NavMeshObstacle을 사용하는 장애물은 게임이 플레이되는 도중에 언제든지 움직일 수 있다.

 

그리고 실시간으로 에이전트를 밀어내는 동작도 가능하며 장애물에 밀려난 목적지가 있는 에이전트는 다시 원래 자리로 돌아오려는 움직임으로 보이게 된다.

 

Shape

 

먼저 Shape 프로퍼티는 장애물의 형태를 결정하는 옵션으로 Box, Capsule, 두 가지 형태를 지원한다.

 

Carve를 제외한 프로퍼티는 이 Box나 Capsule의 크기를 설정하는데 쓰인다.

 

Carve

 

 

Carve는 "파내다"라는 뜻으로 내비게이션 메시 영역을 새로 굽지 않아도 실시간으로 "파내서" 에이전트가 지나갈 수 없는 영역으로 만드는 것이다.

 

Carve를 켜서 실시간으로 내비게이션 메시를 파내게 하면 큰 장애물도 자연스럽게 회피하게 할 수 있다.

 

Move Threshold

 

Carve의 하위 프로퍼티인 Move Threshold는 최소 이동 거리를 뜻한다. Move Threshold보다 조금 움직인 것은 오브젝트가 움직이지 않은 것으로 간주하여 Carve를 새로 계산하지 않는다.

 

큐브 게임 오브젝트를 미세하게 움직일 때는 내비게이션 메시의 파인 부분이 그대로 유지되지만 Move Threshold 보다 크게 움직이면 파인 부분이 사라진다. 그리고 움직임을 정지하면 내비게이션 메시에 파인 부분이 새로 생겨난다.

 

Time To Stationary

 

Time To Stationary는 게임 오브젝트가 얼마나 정지해있으면 완전히 멈춘 것으로 판정하고 Carve를 새로 계산해서 내비게이션 메시를 파낼지를 결정하는 값이다.

 

기본 값은 0.5초 후에 새로 계산하게 되어있다. 이것을 2로 변경하고 내비게이션 탭을 활성화해서 영역이 보이게 만든 다음 장애물을 움직여보자.

 

장애물이 이동을 멈추고나면 아까보다 훨씬 시간이 많이 지난 다음에 내비게이션 메시가 파지는 것을 확인할 수 있다.

 

Carve Only Stationary

 

Carve Only Stationary는 정지된 상태에서만 내비게이션 메시를 파내도록 할 것인지를 결정하는 프로퍼티이다. 

 

기본 값은 true로 체크되어 있다. 그래서 장애물이 움직이면 파내진 구멍이 사라졌다가 정지하면 내비게이션 메시가 다시 파내진다. 하지만 Carve Only Stationary를 끄면 장애물이 움직일 때 내비게이션 메시의 파인 구멍이 실시간으로 장애물을 따라 움직인다.

 

자연스러운 움직임을 위해서는 Carve Only Stationary를 끄는게 좋지만, 물체가 움직이는 상황에서 실시간으로 내비게이션 메시에 구멍을 파내는 계산을 계속하는 것은 게임의 성능에 좋지않은 영향을 끼칠 수 있기 때문에 반드시 필요한 경우에만 이 옵션을 끄고 그 외에는 이 옵션을 사용하는게 좋다.

 

이 NavMeshObstacle은 여러가지 용도로 사용할 수 있습니다.

 

유니티 공식 문서에서 언급되듯이 천천히 움직이는 탱크 같은 곳에 사용해도 되고 특정한 트리거를 발동하면 떨어져서 길을 막는 돌무더기 같은 것에도 사용해도 됩니다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

내비게이션 시스템 (1) 

NavMesh

 

작성 기준 버전 :: 2019.2

 

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

 

이번 포스트에서는 유니티의 내비게이션 시스템에 대해서 알아보도록 하자.

 

 

내비게이션이라는 단어는 실생활에서도 많이 들어보았을 것이다. 어떠한 목적지까지 가는 경로를 알려주는 프로그램을 우리는 내비게이션이라고 부른다.

 

유니티에서도 어떤 한 지점에서 다른 지점으로 이동하는 길을 알려주는 역할을 하는 것이 바로 내비게이션 시스템이다. 그런데 원하는 지점까지 캐릭터를 조작해서 이동하면 될텐데 내비게이션 시스템이 왜 필요할까?

 

물론 플레이어는 캐릭터를 잘 조작해서 원하는 지점까지 이동할 수 있을 것이다. 하지만 플레이어가 조작하는 캐릭터가 아니라 AI가 조작하는 몬스터 같은 NPC는 어떨까?

 

일직선의 아무런 장애물이 없는 길이라면 AI가 조작하는 몬스터라도 무리없이 목적지까지 도착할 수 있겠지만, 벽이나 기둥, 상자 같은 장애물이 가득하고 구불구불한 길이라면 몬스터는 장애물에 막혀서 제대로 목적지에 도착하지 못할 것이다.

 

내비게이션 시스템은 바로 이렇게 장애물이 가득한 환경에서 AI가 최단 경로를 찾아서 목적지에 도달하는 것을 도와주기 위해서 필요한 것이다.

 

그럼 이제 본격적으로 유니티의 내비게이션 시스템에 대해서 이야기해보자.

 

유니티의 내비게이션 시스템은 기본적으로 NavMesh, NavMeshAgent, NavMeshObstacle. 이 세 가지 요소로 이루어집니다.

 

NavMesh

 

이번 포스트에서는 먼저 NavMesh에 대해서 살펴보자.

 

 

하이어라키 뷰에 우클릭하고 [3D Object > Plane]을 선택해서 평면을 하나 만들고 크기를 x는 3, z는 10으로 설정한다. 그러면 적당히 캐릭터가 움직일 만한 바닥이 만들어진다.

 

 

이 다음에는 상단 메뉴바에서 [Window > AI > Navigation]을 선택하면 내비게이션 뷰가 열린다.

 

 

내비게이션 뷰에는 Agent, Area, Bake, Object, 이렇게 네 개의 탭이 있다.

 

Object 탭

 

이 중에서 일단 Object 탭을 먼저 살펴보도록 하자.

 

Object 탭에서는 제일 상단에 Scene Filter를 볼 수 있는데 필터의 종류로는 All, Mesh Renderer, Terrains가 있다.

 

우선 All 필터는 씬에 있는 모든 오브젝트를 보여주는 필터다. 

 

그리고 Mesh Renderer 필터는 Plane처럼 Mesh Renderer 컴포넌트를 가지고 있는 게임 오브젝트만을 보여주도록 하는 필터다.

 

마지막 Terrains 필터는 유니티 엔진에서 거대한 평면에 직접 높낮이를 조절하면서 지형을 만들 수 있는 터레인 오브젝트만을 보여주도록 하는 것이다.

 

터레인이 아니거나 메시 렌더러(Mesh Renderer) 컴포넌트를 가지지 않은 게임 오브젝트를 선택하면 메시 렌더러를 가진 게임 오브젝트나 터레인을 선택해 달라는 문구가 표시된다.

 

 

배치한 Plane을 선택하면 Navigation Static을 체크할 수 있는 체크박스와 함께 다른 옵션들이 나온다. 한마디로 터레인이나 메시 렌더러 컴포넌트를 가진 게임 오브젝트만 캐릭터가 돌아다닐 수 있는 내비게이션 영역으로 설정할 수 있다는 뜻이다.

 

여기서 Navigation Static을 체크해보면 아래 쪽의 옵션들도 활성화되는 것을 볼 수 있다. Navigation Static을 체크하면 해당 메시를 캐릭터가 길을 찾을 수 있는 표면으로 설정된다.

 

우선은 아래 쪽의 두 항목은 나중에 알아보도록 하고 넘겨두도록 하자.

 

Bake 탭

 

 

Bake 탭에는 원기둥 그림과 함께 다양한 옵션들이 보이지만 그보다 먼저 제일 아래에 이 탭의 주인공처럼 보이는 Bake 버튼을 눌러보자.

 

 

Bake 버튼을 누르면 씬 뷰에서 Plane 위에 파란색 영역이 생겨난 것을 볼 수 있다. 이게 바로 캐릭터가 길을 찾아서 이동할 수 있는 영역을 의미하는 NavMesh이다.

 

참고로 Bake는 빵이나 과자같은 것을 굽는다는 뜻을 가지고 있다. 그래서 이렇게 NavMesh를 만드는 작업 역시 "NavMesh를 굽는다"라고 표현한다.

 

 

캐릭터가 돌아다닐 NavMesh를 만들기는 했지만 아무 장애물도 없어서 너무 밋밋해보이기 때문에 몇 가지 장애물을 설치해보자. 그리고 새로 만든 장애물 역시 Object 탭에서 Navigation Static으로 설정해준 뒤, Bake 탭에서 NavMesh를 구워보자.

 

 

그러면 완전히 평면이었던 파란색 영역이 배치한 장애물을 따라서 여러 면으로 쪼개지는 것을 볼 수 있다.

 

Mesh라는 단어는 3D에서는 삼각형이나 다각형 여러 개로 면을 구성하는 것을 의미한다. NavMesh는 다각형 여러 개로 캐릭터가 이동할 수 있는 표면을 구성하는 것이다. 이 면들을 이용해서 캐릭터가 찾아갈 길을 찾아내는 것이다.

 

그럼 이제부터 Bake 탭의 주요 옵션들을 하나씩 알아보도록 하자.

 

Agent Radius

 

Agent는 내비게이션 메시 위에서 움직이는 대상을 의미한다. 즉 Agent Radius는 NavMesh 위에서 움직일 대상의 반지름이다.

 

 

이 값을 1로 변경하고 NavMesh를 새로 구워보면 내비게이션 메시의 비어있는 경계 부분이 넓어지는 것을 볼 수 있다. 이것은 NavMesh 위를 돌아다닐 Agent의 Radius가 증가해서 뚱뚱해졌기 때문에 벽에 바짝 붙어서 움직일 수 없기 때문에 이렇게 된 것이다.

 

Agent Radius를 원상복구하고 NavMesh를 다시 구워보면 날씬해진 Agent가 돌아다닐 수 있는 범위가 증가한다.

 

Agent Height

 

Agent Height = 2
Agent Height = 1
Agent Height = 4

 

 Agent Height를 Agent의 키를 의미한다.

 

오르막길 아래의 작은 길을 보면서 Height를 1로 변경하면 Agent의 작아진 높이 때문에 통과할 수 있는 범위가 늘어나는 것을 볼 수 있다. 반대로 Height를 4로 변경하면 오르막길 아래의 샛길로 이동하지 못하게 된다.

 

Max Slope

 

Max Slope 값은 NavMesh에 이동할 수 있는 길로 포함될 수 있는 경사로의 최대 각도를 의미한다.

 

여기 맵에 배치된 경사로의 각도는 25도이다. 그리고 Bake 옵션의 최대 경사로 각도의 기본값은 45도이다.

 

 

그렇기 때문에 NavMesh를 구울 때 이 경사로가 이동할 수 있는 지역으로 포함된 것이다.

 

 

만약 MaxSlope 값을 25도보다 작은 20도로 변경하고 다시 구우면 이 경사로가 이동할 수 없는 지역이 되는 것을 볼 수 있다.

 

Step Height

 

그 다음 Step Height는 Agent가 가벼운 계단 정도로 여기고 올라갈 수 있는 단의 높이를 의미한다. 기본 값은 0.4로 설정되어 있다.

 

 

맵에 낮은 단으로 설치된 오브젝트의 높이는 0.25로 Step Height가 이보다 크기 때문에 이 단 역시 NavMesh로 연결되어 있다.

 

 

Step Height를 0.1로 변경하고 내비게이션 메시를 구워보면 연결된 NavMesh가 끊어져서 이동할 수 없는 지역이 되는 것을 볼 수 있다.

 

이렇게 Agent Radius, Agent Height, Max Slope, Step Height를 이용해서 내비게이션 메시의 영역을 설정할 수 있다.

 

Area 탭

 

 

Area 탭에서는 사용자가 필요한 내비게이션 메시 구역을 설정하고 그 구역을 지나가는 비용을 설정할 수 있다. 기본 Area로는 Walkable, Not Walkable, Jump. 이렇게 세 가지가 있다.

 

 

User3 위치에 Water를 추가하고 비용을 3으로 설정해보자.

 

 

그리고 맵에 추가적인 구조물을 몇 개 더 설치해보자. 이렇게 다른 쪽으로 넘어가는 두 갈래 길을 만들었다.

 

한 쪽은 바닥이 내려간 길이고 다른 한 쪽은 바닥이 올라간 다리 같은 길이다.

 

먼저 제일 낮은 바닥 부분을 제외한 구역을 선택해서 Navigation Static을 체크하고 Area는 Walkable으로 둔다. 그 다음에는 바닥을 선택해서 Navigation Static을 체크하고 Area를 Water로 설정한 다음 NavMesh를 구워보자.

 

 

그러면 다른 구역은 전부 같은 파란색으로 영역이 지정되지만 가장 낮은 바닥만 다른 색으로 지정된다.

 

반대편으로 넘어가는 블록 한 칸의 이동 비용을 1이라고 가정했을 때, 여기서 건너편으로 건너가기로 결정했을 때 이 다리를 건너는 비용은 3이고, Water로 지정된 바닥을 건너는 비용은 5이기 때문에 위로 건너는 방법이나 아래로 건너는 방법이나 이동 거리 자체는 같지만, Agent는 가급적이면 이 다리 쪽 경로를 선택하려고 할 것이다.

 

이렇게 Area마다 다른 비용을 책정해서 내비게이션 메시를 잘 구성하면 길은 쉽지만 몬스터가 자주 돌아다니는 구역과 몬스터는 자주 오지 않지만 함정이 많은 구역처럼 좀 더 다양한 레벨 디자인을 손쉽게 구성할 수 있을 것이다.

 

Agents 탭

 

 

Agents 탭에는 Bake 탭에서 본 것과 같은 그림과 옵션들이 있다. 다만 Humanoid라는 기본 타입과 함께 새로운 타입을 추가할 수 있다.

 

덩치가 큰 에이전트와 덩치가 작은 에이전트를 만들어서 덩치가 작은 에이전트는 지나갈 수 있는 곳을 덩치가 큰 에이전트는 돌아서 가야되게 만들 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming 

코루틴(Coroutine) 다루기 2(코루틴 중단하기 + 코루틴 매개변수 + yield break)

 

작성 기준 버전 ::2019.2

 

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

 

이번 포스트에서는 지난 코루틴 포스트에서 다루지 못했던 코루틴 중단하기와 코루틴 함수에 매개변수 전달하기 그리고 yield break에 대해서 다뤄보도록 하자.

 

코루딘 중단하기

 

public class CoroutineTest : MonoBehaviour
{
    IEnumerator enumerator;

    void Start()
    {
        // 코루틴 함수를 직접 호출해서 중단시키려면 IEnumerator를 저장해서 사용
        enumerator = TestCoroutine();
        StartCoroutine(enumerator);
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            // 코루틴 함수를 직접 호출해서 중단시키려면 IEnumerator를 저장해서 사용
            StopCoroutine(enumerator);
        }
    }

    IEnumerator TestCoroutine()
    {
        int i = 0;
        while(true)
        {
            yield return null;
            Debug.Log("Coroutine " + i);
            i++;
        }
    }
}

 

코루틴을 정지시키는 기본적인 방법의 위의 코드와 같다.

 

위 코드는 게임이 시작되면 Start 함수에 StartCoroutine으로 TestCoroutine 함수를 실행시켜 준다. 그러면 코루틴 함수 내용에 따라서 "Coroutine"이라는 로그와 함께 반복된 횟수를 출력한다. 그리고 플레이 도중에 스페이스 키를 누르면 코루틴이 멈추면서 로그 출력이 중단된다.

 

StartCoroutine(TestCoroutine());

 

보통 때는 바로 위 코드처럼 코루틴 함수를 호출했을 텐데 IEnumerator에 저장해서 실행한 이유는 TestCoroutine 함수에서 받아온 IEnumerator를 StopCoroutine에 넣어주기 위해서 이다.

 

StartCoroutine과 StopCoroutine은 이 IEnumerator를 통해서 어떤 코루틴을 실행하고 중단시킬지 확인할 수 있다.

 

코루틴 함수 이름을 문자열로 이용하기

 

StartCoroutine("TestCoroutine");

StopCoroutine("TestCoroutine");

 

이전 코루틴 포스트에서는 알려주지 않은 방법이 있는데, 코루틴을 실행시킬 때 그 코루틴 함수의 이름만으로도 실행시킬 수 있다는 것이다. 그리고 코루틴의 이름으로도 코루틴을 정지시킬 수 있.

 

어떤 면에서 보면 코루틴의 이름으로 코루틴을 시작하고 멈추는 방법이 더 낫다고 여길 수도 있다. 하지만 한 오브젝트에서 같은 이름의 코루틴이 2개 이상 실행되고 있는 상태에서 코루틴의 함수 이름 문자열로 StopCoroutine을 호출하면 이름으로 실행된 모든 코루틴이 동시에 멈춰버린다.

 

StartCoroutine(TestCoroutine());   // 코루틴 함수를 호출해서 실행한 코루틴은

StopCoroutine("TestCoroutine");    // 코루틴 이름 문자열로 중단시킬 수 없음

 

그와 더불어 위 코드처럼 코루틴 함수를 호출해서 실행한 코루틴은 코루틴 이름 문자열로 중단시킬 수 없다.

 

문자열로 코루틴을 실행하는 방법은 편리하지만 사용을 권장하지는 않는다. 그 이유는 문자열이기 때문에 오타가 발생해도 에러 표시가 되지 않아서 문제가 발생할 소지가 높기 때문이다. 그리고 이렇게 상수 문자열은 게임을 출시하는 과정에서 앱 보안을 위한 암호화 과정을 거칠 때, 코루틴 이름과 다른 문자열로 변경되어서 코루틴 함수 호출에 실패하게 되는 경우가 발생하기도 한다.

 

모든 코루틴 동시에 중단시키기

 

StopAllCoroutines();

 

코루틴을 중단시키는 마지막 방법으로는 StopAllCoroutines가 있다. StopAllCoroutines 함수를 사용하면 이 컴포넌트가 실행하고있는 모든 코루틴을 중단시킨다.

 

코루틴 함수에 매개변수 전달하기

 

IEnumerator TestCoroutine(int count)
{
    int i = 0;
    while(i < count)
    {
        yield return null;
        Debug.Log("Coroutine " + i);
        i++;
    }
}

 

그럼 이번에는 코루틴 함수에 매개변수를 전달하는 방법을 알아보자.

 

TestCoroutine 코루틴 함수를 위와 같이 매개변수를 받을 수 있게 약간 변경한다. int 타입의 count 매개변수를 받아서 count 횟수만큼 반복하게 되었다.

 

StartCoroutine(TestCoroutine(10));

 

코루틴 함수 호출 방식에서는 TestCoroutine의 매개변수에 바로 값을 넣어주면된다.

 

StartCoroutine("TestCoroutine", 10);

 

코루틴 이름 문자열로 코루틴을 실행시킬 때는 문자열의 이름 뒤에 콤마를 찍고 매개변수를 넣어주면 된다.

 

Coroutine StartCoroutine(string methodName, object value);

 

코루틴 이름 문자열로 코루틴을 실행시키면서 매개변수를 받는 StartCoroutine의 오버로드 형식은 string으로 코루틴 함수의 이름을 받고 object 타입으로 매개변수를 받는다.

 

참고로 object 타입은 C# 프로그래밍에서 모든 변수 타입의 최상위 타입이다. 그래서 이렇게 int 타입의 숫자를 넣어주면 object 타입으로 바꿔서 받는다. 그리고 TestCoroutine 함수를 호출하면서 내부적으로 int 타입으로 바꿔서 전달해준다다.

 

StartCoroutine(TestCoroutine(10));

StartCoroutine("TestCoroutine", 10);

 

그렇기 때문에 이 두 가지 호출 방식은 똑같이 동작한다.

 

코루틴 함수 이름 문자열 실행 방식의 매개변수 전달 방식의 약점

 

박싱/언박싱의 오버헤드

 

// 박싱(Boxing)
int i = 10;
object obj = i;

// 언박싱(Unboxing)
int j = (int)obj;

 

하지만 이렇게 object로 매개변수를 전달하는 방법에는 약점이 몇 가지 있다. 먼저 프로그래밍에서 다른 타입의 변수를 object 타입으로 만드는 과정을 박싱(Boxing)이라고 부르고 object 타입의 변수를 원래 타입의 변수로 되돌리는 과정을 언박싱(Unboxing)이라고 부른다.

 

이 박싱/언박싱 과정은 미세하지만 분명히 성능적인 오버헤드를 일으킨다. 게임 최적화를 위해서는 남발하지 않는게 좋다.

 

전달 가능한 매개변수의 갯수

 

IEnumerator TestCoroutine(int count, float time)
{
    yield return new WaitForSeconds(time);

    int i = 0;
    while(i < count)
    {
        yield return null;
        Debug.Log("Coroutine " + i);
        i++;
    }
}

 

그리고 두 번째 약점을 설명하기 위해서 TestCoroutine의 기능을 조금 변경하기로 했다고 가정해보자.

 

매개변수에 float 타입으로 time 변수를 추가로 받아서 그 시간만큼 기다렸다가 count 횟수만큼 반복하도록 변경했다. 이렇게 되면 매개변수가 2개로 바뀌게 된다. 

 

StartCoroutine(TestCoroutine(10, 3f));

 

그러면 코루틴 함수 자체로 호출하는 방식에서는 새로운 매개변수를 넣어달라고 에러가 표시되기 때문에 추가된 기능에 맞게 매개변수를 넣어주기만 하면 된다.

 

하지만 코루틴 함수의 이름으로 호출하는 방식에서는 매개변수를 전달할 object가 하나 뿐이라 두 번째 매개변수를 전달할 방법이 없다. 그러니까 이름으로만 호출할 때는 매개변수를 하나 밖에 쓸 수 없는 것이다.

 

물론 클래스나 구조체로 묶어서 보내는 방법도 있겠지만 굳이 그렇게 번거로운 방법쓰는 것 보다는 코루틴 함수에 바로 매개변수 여러 개를 사용하는 것이 편할 것이다.

 

yield break

 

IEnumerator ReturnCoroutine()
{
    Debug.Log("Return 1");
    yield return null;  // 코드의 제어권을 잠시 양보했다가 돌려받아서 아래 코드를 계속 진행
    Debug.Log("Return 2");
}

IEnumerator BreakCoroutine()
{
    Debug.Log("Break 1");
    yield break;    // 코루틴 함수를 이 시점에 종료
    Debug.Log("Break 2");
}

 

마지막으로는 yield break 문에 대해서 알아보도록 하자.

 

yield return을 사용할 ReturnCoroutine과 yield break를 사용할 BreakCoroutine을 만든다.

 

이 두 코루틴을 실행하면 ReturnCoroutine은 Return 1과 Return 2가 모두 출력되지만 BreakCoroutine에서는 Break 1만 출력되고 Break 2는 출력되지 않는 것을 볼 수 있다.

 

yield break가 호출된 순간에 코루틴 함수가 완전히 멈춰버린 것이다.

 

yield return은 코드의 제어권을 유니티 엔진에 잠시 넘겼다가 특정 시점이 되면 다시 받아서 코드를 진행하지만 yield break는 그 시점에 코루틴을 완전히 멈춰버린다.

 

코루틴 내부에서 특정 조건을 만족하면 yield break로 코루틴을 멈추는 방식으로 사용할 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

UI 비법서 (6) 

Image 컴포넌트

 

작성 기준 버전 :: 2019.2

 

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

 

이번 포스트에서는 유니티 UI의 기본이 되는 Image 컴포넌트에 대해서 알아보자.

 

2D Sprite 패키지

 

유니티에서 UI와 관련된 기능을 제대로 사용하기 위해서는 2D Sprite 패키지를 설치해야 한다.

 

 

2D Sprite 패키지를 임포트하기 위해서 상단 메뉴바에서 [Window > Package Manager]를 선택하고 패키지 매니저를 연다.

 

 

잠시 기다리면 설치 가능한 패키지 전체가 나올 텐데, 만약 곧바로 2D Sprite 패키지가 보이지 않으면 왼쪽 상단의 Packages 탭 아래의 [+] 버튼 옆에 있는 버튼을 클릭해서 All Packages를 선택하면 설치가능한 모든 패키지가 나온다.

 

 

그 중에서 2D Sprite를 선택하고 오른쪽 아래의 Install 버튼을 클릭한다.

 

Sprite

 

유니티 엔진에서 사용되는 이미지 리소스를 텍스쳐(Texture)라고 부른다. 이 텍스쳐 중에서도 Image 컴포넌트나 2D 게임의 오브젝트로 그려지는 스프라이트 렌더러에서 사용되는 리소스들을 스프라이트(Sprite)라고 한다. 

 

 

보통 유니티 프로젝트에 임포트되는 텍스쳐들은 자동으로 Texture Type이 Default로 정해진다. Default는 주로 3D 모델 오브젝트의 텍스쳐로 사용되는 타입이다.

 

 

UI에 사용하기 위해서는 이 Texture Type을 Sprite로 변경해주어야 한다. 임포트한 텍스쳐들을 선택하고 Texture Type을 Sprite로 변경해주자.

 

이미지 게임 오브젝트 생성하기

 

 

이미지 게임 오브젝트를 생성하기 위해서는 하이어라키 뷰에 우클릭하고 [UI > Image] 항목을 선택하면 된다.

 

이미지 게임 오브젝트를 만들 때 씬 안에 캔버스 게임 오브젝트가 없으면 자동으로 캔버스 게임 오브젝트를 생성하고 그 아래에 이미지 게임 오브젝트가 만들어진다. 이미 캔버스 게임 오브젝트가 있다면 그 캔버스 오브젝트 아래에 이미지 게임 오브젝트가 생긴다.

 

Image 컴포넌트

 

 

유니티 엔진에서 화면에 그림을 표현하기 위해 사용되는 것이 바로 이 Image 컴포넌트다. 기본 Image 컴포넌트는 화면에 그림을 보여주기만 하고 클릭한다던가 하는 상호작용이 불가능하다.

 

UI 작업을 좀 더 편하게 하기 위해서는 단축키 [2]를 눌러서 2D 작업모드로 변경하면 된다.

 

Image 컴포넌트의 프로퍼티들

 

이미지 게임 오브젝트를 선택하고 게임 오브젝트에 부착되어 있는 Image 컴포넌트를 보면 Source Image, Color, Material, Raycast Target과 같은 프로퍼티를 볼 수 있다.

 

Source Image

 

 

우선 Source Image는 이 Image 컴포넌트가 화면에 보여줄 그림을 설정할 수 있는 프로퍼티이다. 이 프로퍼티가 비어있으면 위의 이미지처럼 그냥 하얀 이미지로 화면에 표시된다.

 

 

Source Image에 다른 스프라이트를 넣어주면 하얀 이미지가 넣어준 스프라이트로 바뀌게 된다.

 

using UnityEngine.UI;

public class ImageChanger : MonoBehaviour
{
    private Image image;

    [SerializeField]
    private Sprite sprite;

    void Start()
    {
        image = GetComponent<Image>();

        // image.sprite로 Image 컴포넌트의 Source Image에 접근
        image.sprite = sprite;
    }
}

 

스크립트에서는 image.sprite를 통해서 Source Image에 접근해서 화면에 그리는 이미지를 바꿀 수 있다.

 

Color

 

 

Color 프로퍼티는 이미지의 색상을 바꿀 때 사용된다.

 

 

하얗게 표시된 색상 부분을 클릭해보면 색을 바꿀 수 있는 Color 대화상자가 표시된다. R값은 빨간 색 계열, G값은 초록 색 계열, B값은 파란 색 계열을 의미한다.

 

 

그리고 A는 알파(Alpha) 값으로 투명도를 의미한다. 수정하면 이미지를 투명하게 만들 수 있다.

 

Material

 

Material 프로퍼티는 이미지에 머티리얼을 넣어서 흐리게 보이게 만든다거나 왜곡되어 보이게 하는 것처럼 특별한 효과를 넣고자 할 때 사용된다.

 

Raycast Target

 

Raycast Target 프로퍼티는 이 이미지를 Raycast의 타깃으로 삼을 것인가를 결정하는 것이다.

 

이 프로퍼티의 기능을 이해하려면 Raycast가 무엇인지 이해해야하는데 간단하게 설명하면 클릭한 위치에 일종의 광선을 쏴서 그 광선에 맞은 오브젝트를 검출해내는 것이다.

 

한마디로 Raycast의 타깃으로 삼는다는 것은 이 오브젝트를 클릭했을 때 Raycast에 검출이 되게 한다는 의미이다.

 

이것을 확인해보려면 Image 컴포넌트가 붙어 있는 게임 오브젝트에 Button 컴포넌트를 붙여보면 된다.

 

 

Button 컴포넌트를 붙인 다음 게임을 플레이시키고 이미지를 클릭해보면 이미지가 깜빡거리면서 상호작용이 일어나고 있는 것을 알 수 있다. 하지만 Raycast Target을 끄고 버튼을 클릭해보면 더 이상 클릭이 되지 않는다.

 

이렇게 Raycast Target 옵션은 이미지가 클릭을 받아들이게 만들지 말지를 설정하는데 사용된다.

 

Image Type

 

 

Source Image를 넣으면 비어있을 때는 없던 프로퍼티들이 새로 생긴 것을 볼 수 있다.

 

Image Type은 이미지가 그려지는 방식을 결정하는 프로퍼티이다.

 

Simple

 

Simple은 보이는 그대로 이미지를 바로 보여주는 타입이다.

 

Use Sprite Mesh

 

Use Sprite Mesh는 이미지를 그리는 영역을 지정할 때, 그냥 사각형 영역으로 그릴지 아니면 이미지의 알파 영역을 무시하고 최대한 그림의 형태에 맞는 영역으로 그릴지를 결정한다.

 

 

 

화면에 그려지고 있는 초록색 이미지를 보면 그림에 투명한 부분이 많은 것을 알 수 있다.

 

 

씬 뷰의 왼쪽 상단에 Shaded라고 표시된 부분을 클릭해서 Overdraw를 선택한다.

 

 

그러면 씬 뷰가 까맣게 변하고 UI가 있는 부분은 갈색으로, UI가 겹쳐진 부분은 옅은 갈색으로 표시되는 것을 볼 수 있다.

 

이렇게 UI가 겹쳐진 것을 Overdraw라고 부르며, 이렇게 겹쳐진 부분의 픽셀을 화면이 갱신될 때마다 겹쳐진 횟수만큼 다시 그리기 때문에 반드시 필요한 경우가 아니라면 이미지가 겹쳐서 그려지는 것을 피해야 한다. 그런 의미에서 이런 쓸데없는 알파값 부분을 제외하고 그림의 형태 대로만 다시 그리도록 하는게 좋을 것이다.

 

 

Use Sprite Mesh를 체크하면 사각형으로 잡혀있던 이미지의 영역이 최대한 이미지의 영역에 가깝게 바뀐것을 볼 수 있다.

 

Preserve Aspect

 

 

Preserve Aspect는 Source Image의 원본 비율을 지켜서 그릴 것인지를 정하는 프로퍼티이다. 보통은 Image 컴포넌트가 부착된 UI 게임 오브젝트의 너비와 높이에 따라서 그림의 비율이 변형되어서 화면에 그려지지만 Preserve Aspect를 체크하면 비율을 지킨 상태로 화면에 그려지게 할 수 있다.

 

Sliced

 

Sliced 타입은 9슬라이싱이라는 기능을 사용하기 위한 것으로 작은 크기의 이미지를 모서리 부분의 해상도를 유지하고 가운데 부분을 늘어뜨려서 채워주는 방식으로 UI의 크기를 자유자재로 사용할 수 있게 도와주는 타입이다.

 

 

우선 이미지를 9슬라이싱 하기 위해서는 프로젝트 뷰에서 9슬라이싱을 적용할 스프라이트를 선택하고 인스펙터 뷰에서 Sprite Editor 버튼을 클릭하면 된다.

 

 

 

그리고 스프라이트 에디터에서 모서리 부분과 중간에 반복될 부분을 적당히 나눠주고 Apply 시키면 9슬라이싱이 적용된다.

 

 

이렇게 슬라이싱된 이미지를 넣은 Image 컴포넌트의 Image Type을 Sliced로 적용하면 이미지가 크기에 따라 늘어지지 않는 것을 볼 수 있다.

 

Fill Center

 

 

Fill Center 프로퍼티는 Sliced로 늘어난 이미지의 가운데를 채울 것인가를 결정한다.

 

Pixel Per Unit

 

 

Pixel Per Unit 프로퍼티는 이미지의 1픽셀을 유니티에서 어떤 크기로 화면에 그릴 것인가를 결정한다.

 

Tiled

 

 

Tiled 타입은 이미지를 반복으로 그려주는 타입이다.

 

Filled

 

 

Filled는 차오르는 게이지 같은 연출을 표현할 때 주로 사용된다.

 

하위 프로퍼티로는 Fill Method, Fill Origin, Fill Amount가 있다.

 

Fill Method는 Radial 360, Radial 180, Radial 90, Vertical, Horizontal이 있다.

 

Fill Origin은 채우기를 시작하는 지점을 정하는 프로퍼티인데, Fill Method마다 조금씩 다른 값을 가지고 있다.

 

그리고 Clockwise는 Radial 타입 메소드에서 사용되는 프로퍼티로 이미지를 시계방향으로 차오르게 할 지, 반시계 방향으로 차오르게 할 지를 설정하는 데 쓰인다.

 

Radial 360

 

 

Radial 360은 중심을 기준으로 360도 회전하며 이미지가 차오르게 만드는 방식이다. 

 

Fill Origin은 Top, Bottom, Left, Right로 바꿀 수 있다. 

 

이 형태는 원형 스킬 UI에서 쿨타임이 줄어드는 연출을 하고자 할때 주로 사용된다.

 

Radial 180

 

 

Radial 180은 한 쪽 변을 기준으로 180도 회전하며 이미지가 차오르게 만드는 방식이다. 

 

Fill Origin은 Top, Bottom, Left, Right로 바꿀 수 있다. 

 

Radial 90

 

 

Radial 90은 한 쪽 꼭지점을 기준으로 90도 회전하며 이미지가 차오르게 만드는 방식이다.

 

Fill Origin은 Bottom Left, Bottom Right, Top Left, Top Right로 바꿀 수 있다. 

 

Vertical

 

 

Vertical은 이미지를 위/아래로 차오르게 만드는 방식이다.

 

Fill Origin은 Top, Bottom으로 바꿀 수 있다. 

 

사각형 스킬 UI에서 스킬 쿨다운 연출이나 과열된 장비의 냉각을 연출하는데 쓰면 좋다.

 

Horizontal

 

 

Horizontal은 이미지를 좌/우로 차오르게 만드는 방식이다.

 

Fill Origin은 Left, Right로 바꿀 수 있다. 

 

로딩 바나 경험치 바처럼 차오르는 연출을 보여주는데 자주 사용한다.

 

스크립트에서 Fill Amount 값 조절하기

 

using UnityEngine;
using UnityEngine.UI;

public class ImageFiller : MonoBehaviour
{
    private Image image;

    void Start()
    {
        image = GetComponent<Image>();
    }


    float timer = 0f;
    void Update()
    {
        timer += Time.deltaTime;
        image.fillAmount = Mathf.Sin(timer) * 0.5f + 0.5f;
    }
}

 

스크립트에서는 image.fillAmount를 통해서 Image 컴포넌트의 Fill Amount 값을 조절할 수 있다.

 

Set Native Size

 

 

Set Native Size 버튼은 Image 컴포넌트가 부착된 UI 게임 오브젝트의 Width와 Height를 Source Image로 넣은 스프라이트의 해상도와 같게 만들어주는 버튼이다.

 

예를 들어 Source Image로 512x512 해상도의 스프라이트를 넣고 Set Native Size 버튼을 누르면 이미지 게임 오브젝트의 Width와 Height도 512x512로 변경된다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts