본문 바로가기

Unity

[Unity] ✨3D 게임 핵심 메서드 및 응용✨

728x90
반응형
24.01.13 수정

라이팅 설정

Light Map(라이트 맵)

Level Art 프리팹을 씬에 추가하면 컴퓨터가 느려지거나 팬이 도는 현상이 발생하는데 이는 유니티 내부에서 라이트 맵과 라이팅 정보를 포함한 라이팅 에셋을 굽고(bake) 있기 때문이다.

 

이때 라이트맵이란 빛과 관련된 '상호작용'을 보여주는 미리 그려두는 텍스처이다. 이러한 라이트맵을 생성하는 것을 '라이트맵을 굽는다'라고 표현한다.

[ 라이팅 설정하기 ]

· Window > Rendering > Light Setting을 클릭, 라이팅 설정 창 열기

· 환경광을 원하는 컬러로 변경(씬의 전체 컬러와 분위기 변화)

 

라이팅 창에서는 아래와 같이 두 가지의 글로벌 일루미네이션을 설정할 수 있다.

 

1️⃣ 실시간 글로벌 일루미네이션

 

·  빛의 세시와 방향이 달라졌을 때 그 변화를 간접광에 실시간으로 반영함.

·  빛의 예상 이동 결로 등의 정보를 미리 계산해서 저장하므로 빛의 변화에 따른 결과를 적은 비용으로 추측/적용 가능.

· 미리 계산해야 하는 정보가 있으므로 라이팅 데이터 에셋을 구울 필요가 있음.

 

2️⃣ 베이크 된 글로벌 일루미네이션

 

· 고정된 빛에 의한 간접광을 라이트맵으로 구워 미리 입힘.

· 실시간 글로벌 애니메이션보다 표현의 질과 런타임 성능에서 우의를 가짐.

· 빛의 세기나 방향이 변화해도 게임에 변화되지 않아 이질감이 발생할 가능성이 큼.

[ 라이트 맵 굽기 ]

① Lighting 창 > Mixed Lighting > Baked Global Illumination 체크 해제

② Lightingmapping Settings > Indirect Resolution을 0.5로 변경(텍스처 해상도)

③ Generate Lighting 클릭

 

위의 과정을 했다면 Scene 폴더에서 Main에 저장된 라이트 맵을 확인할 수 있다.

플레이어 캐릭터 구성

플레이어 캐릭터 추가

임의의 3D 모델 에셋을 하이어라키 창으로 드래그&드롭하여 게임 오브젝트를 생성한다.

플레이어 캐릭터 오브젝트를 생성했다면 게임과 상호작용이 가능하도록 아래의 컴포넌트를 추가한다.

 

리지드 바디 추가  ① Angular Drag을 20으로 설정 (Angular Drag: 각항력, 회전에 대한 마찰력)
② Freeze Rotation X / Z
캡슐 콜라이더 추가  Center와 Radius를 생성한 캐릭터에 맞게 재설정
오디오 소스 추가 오디오 소스 컴포넌트 추가 후 Play On Awake 끄기

애니메이터 설정

[ Apply Root Motion ]

Apply Root Motion(루트 적용 모션)은 게임 오브젝트의 위치와 회전을 애니메이션이 적용하도록 한다.

걷는 애니메이션을 실행하면 앞으로 걸어가도록 애니메이션이 오브젝트의 위치와 회전을 변경한다.

[ 애니메이터 레이어 ]

유한 상태 머신에서는 하나의 상태만 현재 상태가 될 수 있다. 하지만 여러 개의 유한 상태 머신을 병렬로 실행하는 방식으로 여러 상태가 동시에 현재 상태로 중첩되게 할 수 있다.

 

같은 원리로, 레이어를 여러 개 사용함으로써 여러 애니메이션 상태가 게임 오브젝트에 하나에 중첩되게 할 수 있다.

 

Base Movement 걷고 뛰는 기본 움직임에 관한 애니메이션을 재생
Upper Body 캐릭터 상체에만 적용할 동작 애니메이션을 재생

[ 블렌드 트리 ]

일반적으로 애니메이터의 상태에는 애니메이션 클립이 할당되지만 평범한 애니메이션 클립이 아닌 특수한 종류의 모션을 상태에 할당하는 것이 가능한데, 그중 하나가 애니메이션 클립을 혼합하는 블렌드 트리 모션이다.

 

블랜드 트리를 가진 상태는 아래와 같이 만들 수 있다.


애니메이터 창 > 우클릭 > Create State > From New Blend Tree

 

블렌드 트리에 접근하는 방법은 아래와 같다. (예시)

· Animator > Layers > Movement State 더블 클릭 

 

순서 애니메이션 클립 임계값 재생 속도
1 Humanoid Run(뒤로 뛰기) -1 -1
2 Humanoid Walk(뒤로 걷기) -0.5 -1
3 Humanoid Idle(대기) 0 1
4 Humanoid Walk(걷기) 0.5 1
5 Humanoid Run(뛰기) 1 1

위 사진에서 1번과 2번 클립의 애니메이션 재생속도가 -1이라는 점에 주목하자.

애니메이션의 재생 속도가 음수라는 것은 애니메이션 클립을 거꾸로 재생하는 것으로, 각각 걷기와 뛰기 애니메이션의 역재생을 이용한다.

 

위 표에서 임계값이란 자신의 애니메이션 클립이 100% 섞이는 지점을 말한다.

임계값이 파라미터의 값과 가까울수록 해당 애니메이션 클립이 많이 섞이며, 임계값과 동일한 파라미터를 가지는 경우 100% 해당 애니메이션을 수행한다.

 

파라미터의 값이 1 -> 0 -> -1로 변화하는 경우 5번 클립에서 1번 클립으로 자연스럽게 섞어 사용한다.

이때 1D 타입은 하나의 파라미터만 사용하며, 2D 타입의 경우 두 개의 파라미터를 사용할 수 있다.

애니메이터 레이어 나누기

애니메이터 컨트롤러에 레이어를 두 개 이상 만들면 각 레이어에서 재생하는 애니메이션은 위에서 아래 순으로 덮어쓰기가 적용된다.

이때 두 개의 애니메이션의 레이어 중에 겹치지 않는 부위는 애니메이션이 합쳐짐유의한다.

 

이렇게 레이어를 나눈 이유는 대표적으로 효율적인 분담을 위함이다.

레이어를 나누는 경우 '뛰면서 총을 쏘는' 애니메이션을 만들 필요 없이 '뛰는' 애니메이션과 '총을 쏘는' 애니메이션을 합쳐 사용할 수 있다.

이렇게 애니메이터의 레이어 별로 부위를 다르게 하려면 아바타 마스크를 설정해야 한다.

[ 휴머노이드 릭 ]

사람 형태의 3D 모델은 대부분 휴머노이드 타입으로 리깅 되어있다.

이때 리깅이란 3D 모델의 골격과 움직임을 정의하는 조인트 계층 구조로, 같은 휴머노이드 타입으로 리깅 된 모델은 체형이 달라도 애니메이션 클립이 호환된다!

 

또한 신체 골격 정보가 3D 모델에 포함돼 있으므로 애니메이션 클립을 특정 부위에만 적용할 수 있다.

[ 아바타 마스크 ]

휴머노이드 타입의 3D 모델에서 특정 신체 부위에만 애니메이션을 적용하려면 위에서 언급한 아바타 마스크를 사용한다.

아바타 마스크는 아래와 같이 만들 수 있다.


프로젝트 창 > Create > Avartar Mask

 

마스크 정보를 확인하면 애니메이션을 적용할 부위(녹색)와 적용하지 않을 부위(적색)를 확인할 수 있다.

이러한 정보 변경은 클릭하여 전환이 가능하다.

 

이때 레이어의 IK(Inverse Kinenatics)는 역운동학을 사용한다는 뜻으로 자세한 내용은 링크를 참고한다.

IK

24.01.14 추가

 

대부분의 애니메이션은 스켈레톤의 조인트 각도를 미리 정해진 값으로 회전하여 만든다. 자식 조인트의 포지션은 부모의 회전에 따라 변하므로 조인트 체인의 끝 점은 체인에 포함된 각 조인트의 각도와 상대 위치에 따라 결정될 수 있다. 이런 스켈레톤 포즈 메서드를 순운동학(FK)이라고 한다.

 

FK 부모 조인트에서 자식 조인트 순서로 움직임을 조정하며 움직이고자 하는 물체의 최종 위치는 상위에서 하위 조인트의 누적 움직임으로 결정된다.
IK 하위 조인트의 최종 위치를 먼저 결정할 수 있으며, 거기에 맞춰 원하는 (상위)가동 오브젝트의 위치를 결정

FK(Forwrad Kinematics)

캐릭터의 애니메이션은 기본적으로 진진 운동학 즉 , FK로 작동한다.

위의 언급에서 나왔듯 FK는 부모 조인트에서 자식 조인트 순서로 움직임을 조정하며 큰 단위의 관절에서 세부적인 관절 순서로 움직임을 조절한다. 따라서 움직이고자 하는 물체의 최종 위치는 상위에서 하위 조인트의 누적 움직임으로 결정된다.

 

따라서 FK는 최종 위치 계산 결과를 따르므로 원하는 오브젝트의 위치를 '먼저' 정하고 '거기에 맞춰' 애니메이션을 변형 할 수 없다.

물건에 위치에 맞춰 원하는 오브젝트의 위치를 변형 할 수 없으므로 이를 해결하기 위해서는 위치를 순간이동할 수 밖에 없다.

IK(Inverse Kinematics)

FK와 반대로 조인트 포즈 작업을 반대 시각에서 바라보는 것이 유용한 경우도 있는데, 공간에서 선택된 포지션에 따라서는 역으로 작업하여 적합한 조인트 방향을 찾아 해당 포지션에 조인트 끝점이 오도록 하는 방법이 유용할 수 있다.

 

이 방법은 사용자가 선택한 포인트의 오브젝트를 캐릭터가 건드리게 하거나, 울퉁불퉁한 표면 위에 캐릭터의 두 발이 자연스럽게 밀착해있도록 하려는 경우에 유용하다. 이 접근법을 역운동학(IK)이라고 한다.

 

IK는 하위 조인트의 최종 위치를 먼저 결정할 수 있으며, 거기에 맞춰 원하는 (상위)가동 오브젝트의 위치를 결정할 수 있다. 따라서 물건이 어디에 있든 자연스럽게 집을 수 있는 모션이 가능해진다.

 

애니메이터 컴포넌트가 IK 정보를 갱신할 때마다 OnAnimatorIK 메시지가 발생하며 스크립트에서 IK 정보가 갱신될 때마다 자동 실행되는 OnAnimatorIK() 구현하면 IK를 어떻게 사용할 지 코드를 작성할 수 있다.

IK 응용 메서드

24.01.14 추가

GetIKHintPosition() 메서드 : AvatarIKHint 타입으로 현재 위치를 가져옴

캐릭터의 오른쪽 팔꿈치 위치를 찾아 gunPivot의 위치로 사용한다.

 

gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

애니메이터 컴포넌트의 GetIKHintPosition() 메서드는 AvatarIKHint 타입으로 부위를 입력받아 해당 부위의 현재 위치를 가져온다.

여기서 사용한 AvatarIKHint타입은 IK 대상 중 다음 부위를 표현하는 타입이다.

 

· AvatarIKHint.LeftElbow : 왼쪽 팔꿈치

· AvatarIKHint.RightElbow: 오른쪽 팔꿈치

· AvatarIKHint.LeftKnee: 왼쪽 무릎

· AvatarIKHint.RightKnee: 오른쪽 무릎

SetIKPositionWeight() / SetIKRotationWeight() 메서드: IK 대상의 회전 가중치 변경

캐릭터 왼손의 위치와 회전을 leftHandMount의 위치와 회전으로 변경한다. 그러기 위해 먼저 왼손 IK에 대한 위치와 회정 가중치를 1.0으로 변경한다. (100%)

playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

 

IK 대상의 가중치를 설정할 때의 메서드는 아래와 같다.

위치 SetIKPositionWeight()
회전 SetIKRotationWeight()

 

위 두 메서드는 가중치를 변경할 IK 대상과 적용할 가중치를 입력받는다. 이때 사용한 AvatarIKGoal 타입은 IK 대상 중 아래의 부위를 표현하는 타입이다.

 

· AvatarIKGoal.LeftHand : 왼손

· AvatarIKGoal.RightHand: 오른손

· AvatarIKGoal.LeftFoot: 왼발

· AvatarIKGoal.RightFoot: 오른발

 

IK 가중치의 범위는 0에서 1까지이며 IK 가중치는 해당 부위의 원래 위치와 IK에 의한 목표 위치 사이에서 실제로 적용할 중간값을 결정한다. 즉, IK의 가중치가 0.5(50%)인 경우 원래 위치와 IK 목표 위치가 절반씩 섞여 적용된다.

SetIKPosition() / SetIKRotation() 메서드: IK 대상이 사용할 목표의 위치/ 회전 설정

왼손 IK의 목표와 목표 회전을 leftHandMount의 위치와 회전으로 지정한다. 애니메이터의 아래 메서드는 IK 대상이 사용할 목표 위치와 목표 회전을 설정한다.

 

playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand,leftHandMount.position);
playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand,leftHandMount.rotation);

 

적용할 IK 대상은 AvatarIKGoal.LeftHand, 목표 위치는 총의 왼손 손잡이의 위치인 leftHandMount.position, 목표 회전은  왼손 손잡이인 AvatarIKGoal.leftHandMount, 목표 회전은 leftHandMount.rotation으로 설정한다.

 

오른손의 IK 설정은 아래와 같다.

playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);

playerAnimator.SetIKPosition(AvatarIKGoal.RightHand,rightHandMount.position);
playerAnimator.SetIKRotation(AvatarIKGoal.RightHand,rightHandMount.rotation);

시네머신 추적 카메라 설정

플레이어를 추적하여 따라다니는 카메라를 만들어보자. 플레이어 추적 카메라는 시네머신을 이용한다.

먼저 시네머신은 크게 브레인 카메라와 가상 카메라로 나뉜다.

 

브레인 카메라 게임 월드를 촬영하는 실질적인 카메라이며 씬에 단 한개만 존재함.
가상 카메라 브레인 카메라를 대신하는 역할을 하며, 브레인 카메라는 이러한 여러 가상 카메라 중 하나를 골라 활성화된 카메라로서 사용함.

이때 브레인 카메라한 번에 하나의 가상 카메라만 사용 가능하다.

추적 카메라 만들기

[ 가상 카메라 만들기 ]

추적 카메라를 만들기 위해서는 씬에 존재하는 카메라를 브레인 카메라로 설정한 후 가상 카메라를 생성한다.

 

① 히어라키 창 > Main Camera

② Cinemachine Brain 컴포넌트 추가 ( Add Component > Cinemachine > Cinemachine Brain )

③ 가상 카메라 추가 ( Cinemachine > Create Virtual Camera 클릭 )

④ 생성된 카메라의 이름 변경: Follow Cam

[ 추적 대상 설정 ]

  하이어라키 창 > Follow Cam

② Create Virtual Camera 컴포넌트의 Follow 필드와 Look At 필드에 플레이어 캐릭터 오브젝트를 드래그 & 드롭

 

Follow 해당 필드에 할당된 게임 오브젝트를 따라 다님.
 Look At 해당 필드에 할당된 게임 오브젝트를 주시함.

시네머신 카메라라는 대상을 주시하는 동안 카메라 연출이 자연스럽게 느껴지도록 지연시간을 두고 카메라를 부드럽게 회전한다.

이때 데드존, 소프트존, 하드리밋은 주시하는 물체가 게임 화면 밖으로 벗어나지 않게 추적의 세기를 단계별로 설정한다.

[ Body와 Aim 설정 ]

가상 카메라의 시야각/ Body 파라미터 설정/ Aim 파라미터 파라미터 변경
[1] 가상 카메라 시야각과 Body 파라미터

Field Of View > 20 : 시야각 변경
② Body > Biding Mode > Wolrd Space
Follow Offset > (-8,16,-8) 변경:  몸체와 카메라 사이의 전역 공간
X/Y/Z Damping을 0.1로 변경: 부드럽게 이어주는 비율

[2] Aim 파라미터

Tracked Object Offset > (0, 0.5, 0): 추적 대상에서 조준 위치
Horizen/Vertical Damping > 0: 회전 속도에 대한 제동값
Soft Zone Width / Height > 0: 소프트존 제거
24.01.13 추가

라인 렌더러

라인 렌더러 구성요소는 3D 공간에서 두 개 이상의 점 배열을 가져와 각 점 사이에 직선을 그린다. 라인 렌더러를 사용하면 단순한 직선부터 복잡한 나선형까지 무엇이든 그릴 수 있다.

 

선은 항상 연속적이며 두 개 이상의 완전히 분리된 선을 그려야 한다면 여러 개의 선을 사용해야 하며 라인 렌더러는 너비가 다음과 같은 라인을 렌더링 하지 않는다. 아래는 라인 렌더러를 생성하는 방식이다. 오른쪽의 [링크]를 참고한다.

 

To create a Line Renderer:

  1. In the Unity menu bar, go to GameObject > Effects > Line.
  2. Select the Line Renderer GameObject.
  3. Add points to the Line Renderer’s Positions array, either by directly setting array values in the Inspector
     window or by using the Create Points Scene Editing Mode.
  4. Use the Inspector window to configure the color, width, and other display settings of the line.

라인 렌더러의 요소

Cast Shadow 오브젝트의 궤적이 그림자를 생성
Receive Shadow 오브젝트 궤적 위에 그림자가 비침
position > Size 선의 사이즈: 선을 그리는 유무 설정
Width 렌더러의 두께( 게임 오브젝트의 두께 설정)

코루틴

번쩍이는 궤적을 구현하라면 라인 렌더러를 켜서 선을 그린 다음 라인 렌더러를 다시 꺼야한다. 이때 매우 짧은 시간 동안 처리를 일시 정지한다. 따라서 라인 렌더러를 끄고 다시 켜는 처리 사이에 대기 시간이 필요한데, 이때 코루틴이 사용된다.

Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드로, 코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있다.

대부분의 경우 메서드를 호출하면 실행을 완료한 뒤 호출한 메서드에 제어와 선택적 반환 값을 반환한다. 즉 메서드 내에서 발생한 모든 행동은 단일 프레임 업데이트 내에서 발생해야 하지만 시간의 흐름에 따른 이벤트의 시퀀스나 절차상의 애니메이션을 포함하기 위해 메서드 콜을 사용하고자 하는 상황에서 코루틴을 사용할 수 있다.

코루틴의 동작 원리

한 가지 예로 오브젝트의 알파(불투명도) 값을 다음과 같이 보이지 않을 때까지 점차 줄이는 작업을 예를 들어보자.

더보기
void Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
    }
}

이 예제에서 Fade 메서드를 실행한다면 점차 불투명도가 커지는(페이딩 효과) 기대했던 효과를 낼 수 없다. 페이딩을 보이게 하려면 Unity가 렌더링하는 중간 값을 표시하기 위해 프레임의 시퀀스 중간 중간 페이드 알파를 줄여야 하지만 위 예제의 메서드는 단일 프레임 업데이트 내에서 전체를 실행한다. 따라서 중간 값은 표시되지 않으며 오브젝트는 실행 후 사라진다.

 

이 상황을 해결하려면 코드를 프레임 단위로 페이드를 실행하는 Update 함수에 추가할 수 있으나 이런 종류의 작업에는 코루틴을 사용하는 것이 더 편리하다. C#에서 다음과 같이 코루틴을 선언합니다.

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return null;
    }
}

코루틴의 문법

코루틴에서 대기 시간을 지정하는 대표적인 방법은 다음과 같다.

[ 초 단위로 쉬기 ]

yield return new WaitForSeconds(.1f);

[ 한 프레임 만 쉬기 ]

yield return null;

 

코루틴은 Ienumerator 반환 타입과 바디 어딘가에 포함된 yield 반환문으로 선언하는 메서드이다. yield return null 라인은 실행이 일시 정지되고 다음 프레임에서 다시 시작되는 지점이며 코루틴 실행을 설정하려면 다음과 같이 StartCoroutine 함수를 사용해야 한다.

[ 코루틴 실행과 종료 ]

코루틴 실행을 설정하려면 다음과 같이 StartCoroutine 함수를 사용한다.

 

StartCoroutine(SomeCoroutine()); 코루틴 메서드를 실행한 반환값
StartCoroutine("SomeCoroutine"); 코루틴 메서드의 이름

 

코루틴 메서드를 싱행하면서 그 반환값을 SomeCoroutine()에 입력락는 방식은 다음과 같이 실행할 코루틴 메서드에 입력값을 직접 전달 할 수 있다.

StartCoroutine(SomeCoroutine(100));

 

StopCoroutine과 StopAllCoroutines을 사용하여 코루틴을 정지할 수 있다.

 

StopCoroutine("SomeCoroutine"); 인자로 받은 실행 중인 코루틴 메서드를 도중에 종료할 수 있다.

 

코루틴에 연결된 게임오브젝트를 비활성화하기 위해 SetActive false로 설정하면 코루틴이 정지된다. 

참고: 활성화에서 false로 설정하여 MonoBehaviour를 비활성화한 경우에는 코루틴이 정지되지 않는다.

728x90
반응형
댓글