[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 6회차 미션 시작합니다.


6회차 플레이어 카메라, 라이팅 시스템에 대한 교육 시간입니다.


일단 저번 시간에 이어 카메라 에디터 기능을 확장하는 것에 대한 내용입니다.
카메라 기능을 Scene Editor에서 수정할 수 있도록 Editor 기능을 확장하는 것입니다.

TopDownCamera 스크립트와 별개로 동작하는 스크립트가 필요합니다.
/Assets/ 위치에서 "Create C# Script" - "TopDownCamera_SceneEditor" Script를 만들어서 더블클릭하여 Editor로 진입합니다.

 


TopDownCamera_SceneEditor.cs

Editor를 확장하는 것이므로, Editor를 상속받는 클래스로 변경해주고, Start() Update() 함수는 제거합니다.
그리고 중요한 CustomEditor 임을 지정하는 코드를 추가합니다.

[CustomEditor(typeof(TopDownCamera))] //## 지정자를 지정해야지만 Scene View에 표시됨.
public class TopDownCamera_SceneEditor : Editor
{
}

 


필요한 변수들을 추가해줍니다.

private TopDownCamera targetCamera;


타겟 카메라를 받아오기 위해 OnInspectorGUI() 함수를 구현합니다.

public override void OnInspectorGUI()
{
  targetCamera = (TopDownCamera)target;
  base.OnInspectorGUI();
}

받아온 카메라에 대한 로직을 구현하기 위해서 OnSceneGUI() 함수를 구현합니다.

 


private void OnSceneGUI()
{
  if (!targetCamera || !targetCamera.target)
  {
    return;
  }
  
  Transform cameraTarget = targetCamera.target;
  Vector3 targetPosition = cameraTarget.position;
  targetPosition.y += targetCamera.lookAtHeight; // 타겟 카메라가 바라보는 위치값
    
  Handles.color = new Color(1f, 0f, 0f, 0.15f);
  Handles.DrawSolidDisc(targetPosition, Vector3.up, targetCamera.distance);
  
  Handles.color = new Color(0f 1f, 0f, 0.75f);
  Handles.DrawWireDisc(targetPosition, Vector3.up, targetCamera.distance);
  
  // Slider 기능 추가
  Handles.color = new Color(1f, 0f, 0f, 0.5f);
  targetCamera.distance = Handles.ScaleSlider(targetCamera.distance, targetPosition, -cameraTarget.forward, Quaternion.identity, targetCamera.distance, 0.1);

  targetCamera.distance = Mathf.Clamp(targetCamera.distance, 2f, float.MaxValue); // 최소, 최대 지정
  
  Handles.color = new Color(0f, 0f, 1f, 0.5f);
  targetCamera.height = Handles.ScaleSlider(targetCamera.height, targetPosition, Vector3.up, Quaternion.identity, targetCamera.height, 0.1f);

  targetCamera.height = Mathf.Clamp(targetCamera.height, 2f, float.MaxValue);


  GUIStyle labelStyle = new GUIStyle();
  labelStyle.fontSize = 15;
  labelStyle.normal.textColor = Color.white;
  
  labelStyle.alignment = TextAnchor.UpperCenter;

  Handles.Label(targetPosition + (-cameraTarget.forward * targetCamera.distance), "Distance", labelStyle);
 
  labelStyle.alignment = TextAnchor.MiddleRight;
  Handles.Label(targetPosition + (Vector3.up * targetCamera.height), "Height", labelStyle);
  
  targetCamera.HandleCamera(); // TopDownCamera 스크립트의 HandleCamera() 함수를 public으로 수정.
}

 


이렇게 ScaleSlider()를 사용하여 Camera distance와 height를 가변하는 기능으로 확장한 것입니다.
Debug 상태에서 지저분한 라인들을 표시하지 않기 위해 TopDownCamera 스크립트에서 Debug.DrawLine() 부분들을 주석처리하여 제거합니다.



플레이를 진행해보면 Scene View에서 Camera를 조절하여 Game View에 실시간으로 적용되어 구동되는 것을 확인할 수 있습니다. 대단하네요. 그냥 단순한 Object의 배치만이 아니라 각종 기능들을 추가함으로써 좀더 개발자 및 디자이너, 기획자에게 유용한 도구로 확장할 수가 있습니다.

캐릭터에 AI를 표시한다던지 게임요소들의 디버깅을 위해 정보를 표시한다던지 하는 형태로 확장할 수 있는 것이므로 꼭 카메라만이 아니라 여러 클래스에서 유용한 정보입니다.


 

 

 

 

 


이전까지 캐릭터 구현 및 카메라 구현등을 완료하였다면, 이제 게임 환경 관련 구축하는 내용입니다.

 

+ Lighting의 기본 개념 - 3D 게임중에 중요한 부분중 하나가 Lighting에 대한 이해와 활용입니다. 
+ Lighting Mapping 알아보기 - Lighting을 Texture에 입혀서 사용하는 방법입니다.
+ Light/Reflection Probe 알아보기 - 동적인 오브젝트에 대한 Global Illumination 처리.
+ 지형 시스템 알아보기 - 유니티에 내장된 Terrain System.
+ 네비게이션 시스템 알아보기 - 유니티에 내장된 Navigation System


라이팅이란 광원에서 반사된 빛이 물체에 반사되어 이것을 카메라로 보는 것입니다. 음영인 부분은 빛이 반사되지 않는 부분을 말합니다.

이와 관련하여 3D Graphics에서 보이는 색상을 계산하는 기본 공식입니다.


3D Graphics에서는 Ambient와 Diffuse Color 2가지를 조합하여 Final Color를 나타냅니다.
공식이 조금씩 다를 수는 있지만 보통 3D를 처리하는 OpenGL과 같은 Shader 기능에서는 비슷한 공식을 사용한다고 보면 됩니다.

이렇게 직접 계산하는 방식을 Direct Illumination이라고 합니다.
하지만 실제로는 한 오브젝트에 여러 Lighting이 적용되어 보여지는 경우가 많겠지요.

 

 

 


위와 같은 상태에서 오른쪽 구의 좌측면에는(마우스 위치) 아주 옅지만 붉은색 조명이 방사된 빛이 비치는 걸 볼 수 있습니다.

이러한 조명 처리를 Indirect Illumination이라고 합니다.

그리고 빛은 반사되는 물체에 따라 여러가지 반응을 나타내게 됩니다. 아래처럼 말이죠.


+ Transmission - 투영
+ Reflection - 반사
+ Refraction - 굴절
+ Diffraction - 회절
+ Adsorption - 흡착
+ Scattering - 산란

결국 Direct와 Indirect Lighting이 합쳐져서 Global Illumination이 만들어지게 되는 것이네요.
우리가 익히 알고 있는 자연현상이지만 이렇듯 하나하나 쪼개보니 이 또한 재미지네요.. 결국 이런 것이 물리학의 시작이겠지요.. ㅎㅎ



하지만 조명값을 실시간 계산하려면 부하가 많이 걸리므로 LightMapping 기능을 많이 사용하게 됩니다.

정적인 Object에 대한 Direct Lighting과 Indrect Lighting에 대한 Global Illumination 결과값을 Texture에 저장해 놓고 Object에 입혀서 사용하는 방식인 것입니다. 이러한 기능을 유니티에서는 "Baking"이라고 합니다.

유니티에서 베이킹은 시간이 걸리는 작업이라 준비된 프로젝트로 진행합니다.



유니티에서 Baking 작업하기.

일단 Global Illumination입니다.

+ Type: Directional
+ Color:
+ Mode: Mixed (Realtime | Mixed | Baked)
  '- Realtime - 실시간으로 모든 Object에 영향을 미칩니다. (Global Illumination Baking에는 포함되지 않음)
  '- Baked - 정적인 Object에만 영향을 미칩니다.
  '- Mixed - 정적인 Object에는 Lighting 기법을 사용하고, 동적인 Object에는 실시간 계산을 합니다.

 


Lighting 속성 설정 방법

[Window] - Rendering - Lighting Settings

+ Skybox Material: Default-Skybox
+ Sun Source: Directional Light (Light)
+ Source: Skybox
+ Realtime Global Illumination [ ] - 실시간 태양의 각도 변화를 따라 변화하는 것등을 할 수 있지만 많은 Performance를 요구하기 때문에 꼭 필요한 곳에서 잘 활용해야겠습니다.

 


기본적으로 태양광으로 설정되어 있으며, 던전 같은 Scene의 작업이라면 위의 값을 모두 삭제하여 "None" 처리를 하면 됩니다.

Lightmapper는 예전에는 Enlighten을 사용하였었는데 옆에 Deprecated 표시된 것처럼 향후에 사라질 예정입니다. 현시점에서의 유니티에서는 개발 상태에서 Progressive GPU로 테스트를 하고 Build할 때 Progressive CPU로 하는 방식을 추천하고 있습니다.

[Genrate Light]을 누르면 Baking을 준비하게 됩니다. 시간이 걸린 이후에 "Baked Lightmaps"에서 생성된 Texture를 볼 수 있으며, 이를 확인하기 위해 다음을 진행합니다.

 

 


[Scene View] - [Shaded] - Baked Lightmap


와우 예쁘네요 ^^~
이렇게 처리하여 결국 정적인 Object에서도 Global Illumination의 효과를 볼 수가 있게된 것입니다.

녹색, 빨강, 파랑, 흰색 등 오브젝트가 가진 색상에 따라 각각 처리되고 있습니다.



움직이는 캐릭터에는 어떤 영향을 미치게 될까요?


실행을 해보면 캐릭터는 주변 오브젝트들의 Lighting(Global Illumination)에 영향을 받지 않고, Direct Light에 설정한 직접 광원에만 영향을 받고 있습니다.

이걸 처리하기 위해 Light Probe를 처리해주어야 하고 다음 시간에 이어지겠습니다 ^^~

 

 


패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 5회차 미션 시작합니다.

무료 모델과 애니메이션을 사용하여 상태 머신을 구성하는 방법입니다.


STEP1. Mixamo - 모델링을 무료로 사용할 수 있는 사이트. (가입필요)
STEP2. Animator 구성하기 - Idle, Walk Animation을 다운로드 받아 Animator 상태 머신을 구성하고, 이를 플레이어 캐릭터와 연결하는 작업
STEP3. Animator 확장하기 - Animation State Machine Behavior를 구현하여 Animator의 구성을 확장.

 

 

 

mixamo.com

 

Mixamo

 

www.mixamo.com


+ Characters에서 'ybot' 검색하여 "Y Bot" - 다운로드 "FBX for Unity(.fbx)"를 내려받습니다.
+ Animations에서 'idle' 검색하여 아무 것이나 3개를 다운로드. Skin은 "With Skin"으로 내려받습니다. 애니메이션만 받으려는 경우에 "Without Skin"을 사용합니다.
+ Animations에서 'walk' 검색하여 마찬가지로 원하는 애니메이션 1개를 다운로드 합니다.




Unity에 Importing 하기.


Model 파일..

다운받은 파일들을 "Assets" 하위로 "FastCampus/Arts/Character/YBot/Models"에 임포팅합니다.

[Model]의 설정을 변경할 수 있지만 일단 기본으로 시작합니다.

[Rig]의 Animation Type을 "Humanoid"로 설정합니다. 사람 모양의 뼈대를 사용한다고 설정하는 것입니다. 사람 모양이 아닌 경우 "Generic"으로 설정하면 됩니다.

Skin Weights는 Vertex들이 주변 몇 개의 뼈들에 의해 영향을 받을 것인지 설정하는 것입니다. 예전에는 모바일에서 성능 향상을 위해 2개의 뼈 영향을 하기도 했지만, 요즘은 폰 성능의 향상으로 기본 4개로 설정하는 추세입니다.

[Animation] 설정은 모두 끄고, [Materials]도 기본 설정을 그대로 두겠습니다.

"Apply" 버튼 클릭하여 위에 설정한 값들을 적용합니다.

 

 

 


Animation 파일.

[Animation]에서 "Import Animation [v]"을 체크해주고 아래의 Root 옵션들은 일단 그대로 사용합니다.

ybot@idle_00, ybot@idle_01, ybot@idle_02 모두 동일하게 설정해 줍니다.





Scene에 설정하기

ybot 모델을 Hierarchy View에 가져다 놓고, 이름을 "ybot" -> "PlayerCharacter"로 설정합니다.
이 상태에서 Animator를 신규 추가해도 되고, idle_00 애니메이션을 드래그하여 "PlayerCharacter"에 가져다 놓으면 자동으로 "Animator" 컴포넌트가 생성되며 연결됩니다.

여기서 생성된 애니메이터(PlayerCharacter1)를 더블클릭하면 "Entry"에 추가된 "idle_00" 노드를 확인할 수 있습니다. 여기서 "idle_00" 노드를 더블클릭하면 위에서 설정한 애니메이션 설정값이 연동됩니다.

 

 

 



플레이를 해보면 캐릭터가 Idle 상태로 숨쉬듯이 조금씩 움직이는 모습을 볼 수 있습니다.

우리가 원하는 것은 "Idle" 상태일 때 3가지 정도로 Random하게 동작시킬 것이므로 아래와 같이 설정해 주어야 합니다.

"PlayerCharacter" - "Animator" - "Apply Root Motion [ ]"을 UnCheck합니다.

그리고 아래와 같이 Animator Node 설정에서 RMB "Create Sub-State Machine"을 선택하여 Sub-State 상태 머신을 생성합니다.

 



"New StateMachine" -> "Idle StateMachine"으로 변경하고 "Idle StateMachine"을 더블클릭하여 내부로 진입합니다.

여기서 idle_00, idle_01, idle_02 애니메이션을 드래그하여 가져다 놓습니다. idle_00은 "Base Layer"에서 설정되어 있었기에 자동으로 연결이 된 것으로 나옵니다.

이제 "Base Layer"에 있는 "idle_00"을 삭제하면 자동으로 "Entry" -> "Idle StateMachine"으로 연결됩니다.

다시 "Idle StateMachine"으로 진입해서 "idle_00" ~ "idle_02"가 랜덤하게 설정되도록 하기 위해 좌측의 "Parameters"를 설정합니다. "+"를 클릭하여 "Int"를 추가하고 변수이름은 "RandomIdle"로 지정합니다.




"idle_00" 위에서 우측클릭하여 "Make Transition"을 선택하고 "idle_01"로 연결합니다. 마찬가지로 "idle_00" -> "idle_02"로 연결해주고, "idle_01" -> "idle_00"과 "idle_02" -> "idle_00"도 추가해줍니다.

"idle_00" -> "idle_01"로 가는 화살표를 클릭하여 "Has Exit Time [ ]"을 UnCheck하고, Conditions에서 "RandomIdle", "Equals", "1"로 설정해주면 됩니다. "idle_00" -> "idle_02"로 추가해 줍니다.

 




Behavior

"Idle_00"에서 "Add Behavior"를 클릭하고, New Script에서 "IdleRandomStateMachineBehavior"로 추가합니다.




IdleRandomStateMachineBehavior.cs

변수를 추가합니다.
public int numberOfStates = 2; // 00은 기본이기에 추가된 01, 02에 대한 2개를 지정
public float minNormTime = 0f; // 최소 실행 시간
public float maxNormTime = 5f; // 최대 실행 시간
public float randomNormalTime;

readonly int hashRandomIdle = Animator.StringToHash("RandomIdle"); // string 비교의 오버헤드가 크기에 int로 hash 처리하여 비교.

override public void onStateEnter() // 기본 상태로 진입했을 때 호출됨 (idle_00 노드 진입시)
{
  randomNormalTime = Random.Range(minNormTime, maxNormTime);
}

override public void OnStateUpdate() // 상태 진입후 업데이트 될때 발생.
{
  // "Base Layer"에 있음을 의미
  if (animator.IsInTransition(0) && animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash)
  {
    animator.SetInteger(hashRandomIdle, -1); // 아무 것도 하지 않음.
  }
  
  if (stateInfo.normalizedTime > randomNormalTime && !animator.IsInTransition(0))
  {
    animator.SetInteger(hashRandomIdle, Random.Range(0, numberOfStates));
  }
}

Unity로 돌아와서 에러 없는지 확인후 플레이를 진행하면 Idle 상태가 넘어가는 것을 확인할 수 있습니다.




이로써 Idle 상태 Transition은 모두 완성이 되었습니다.

 




Walking Animation..

"Base Layer"에서 "Walking" 애니메이션을 드래그하여 추가합니다. Idle 상태에서 캐릭터 이동시 걸어가도록 할 것이므로 "Idle StateMachine"에서 "Make Transition"하여 "Walking" 노드로 연결해줍니다.

Idle에서 Walk 상태로의 변화를 체크하기 위해 "bool Move" 변수를 Parameter에 추가합니다.
Transition 화살표를 클릭하여 Conditions가 "Move", "true"일 때로 설정하면 됩니다.

마찬가지로 "Walk"에서 "Idle"로의 State Transition도 추가합니다.

"Ide StateMachine"의 탈출(Exit)을 설정하기 위해 아래와 같이 각 idle에서 Exit로 "Move == true" 조건을 설정해 줍니다.

 


플레이를 해보면 Idle 상태로 있다가, Move를 체크해주면 걷는 동작으로 바뀌는 것을 확인할 수 있습니다.
PlayerCharacter Script를 추가합니다.
"PlayerCharacter"의 Animator를 드래그하여 Script의 "Animator"에 연결합니다.



PlayerCharacter 스크립트를 더블클릭하여 Editor로 진입하고 필요한 변수와 함수를 작업합니다.

작업완료후 실행하면 아래와 같이 클릭하여 이동할 때 Walk State로 잘 변경되어 이동하는 것을 확인할 수 있습니다. 앞으로 공격 등의 애미메이션을 구현할 때 이와 같은 상태머신으로 작업을 하게 되는 것입니다.

 

 







3인칭 카메라 구현.



3인칭 TopDown 카메라를 구현하고 설정들을 에디터에서 확장하여 처리하는 방법입니다.

"Main Camera"에서 New Script "TopDownCamera"를 추가합니다



TopDownCamera.cs

변수추가.

public float height = 5f; //카메라 높이
public float distance = 10f; // 카메라와 타겟의 거리
public float angle = 45f; // 카메라 각도
public float lookAtHeight = 2f; // 타겟 높이
public float smoothSpeed = 0.5f; // 부드럽게 이동하기 위해 속도
private Vector3 refVelocity; // 내부 Velocity 계산용 변수
public Transform target; // 카메라의 타겟을 위한 변수

Start(), Update()는 모두 삭제하고 다음을 추가합니다.

private void LateUpdate()
{
  HandleCamera();
}

private void HandleCamera()
{
  if (!target)
  {
    return;
  }
  
  // 카메라 world position 계산
  Vector3 worldPosition = (Vector3.forward * -distance) + (Vector3.up * height);
  Debug.DrawLine(target.position, worldPosition, Color.red);
  
  Vector3 rotatedVector = Quaternion.AngleAxis(angle, Vector3.up) * worldPosition;
  Debug.DrawLine(target.position, rotatedVector, Color.green);
  
  Vector3 finalTargetPosition = target.position;
  finalTargetPosition.y += lookAtHeight;
  
  Vector3 finalPosition = finalTargetPosition + rotatedVector;
  Debug.DrawLine(target.position, finalPosition, Color.blue);
  
  transform.position = Vector3.SmoothDamp(transform.position, finalPosition, ref refVelocity, smoothSpeed);
  
  transform.LookAt(target.position); // 카메라가 타겟 바라보기
}


카메라가 바라보는 위치와 캐릭터가 바라보는 위치를 표시하여 보기위해서 디버그용 함수를 구현합니다.

OnDrawGizmos()
{
  Gizmos.color = new Color(1f, 0f, 0f, 0.5f);
  if (target)
  {
    Vector3 lookAtPosition = target.position;
    lookAtPosition.y += lookAtHeight;
    Gizmos.DrawLine(transform.position, lookAtPosition);
    Gizmos.DrawSphere(lookAtPosition, 0.25f);
  }
  
  Gizmos.DrawSphere(transform.position, 0.25f);
}

 

 


Unity로 돌아와서.. "Main Camera"의 스크립트의 Target에 "PlayCharacter"를 드래그 하여 연결해 줍니다.



그리고 플레이하면 캐릭터를 따라다니는 카메라를 구현할 수 있습니다.
변수값들을 실시간 변경해 보면서 캐릭터와 TopDown 카메라의 관계를 확인할 수 있습니다.



휴우.. 유니티는 정말 어마무시하긴 하네요.. 예전에는 코드 레벨에서 하던 작업을 UI 상에서 구성하고 연결만 하면 사용할 수 있으니 코드는 최소한으로만 작성하고 유니티툴에서 잘 활용하면 되는 것이겠습니다. 굳이 단점이라면야 소스 코드를 잘 짜는 사람이더라도 유니티툴을 잘 다룰줄 알아야 한다는 것이겠지요.. 어디에 어떤 메뉴가 있고 어떤 시점에 연결해서 써야하는지 확인해야 하는지 등을 모두 알아야 하니까요.. 눈이 뱅글뱅글 돌아갑니다. @~@


패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 4회차 미션 시작합니다.

 


Character Controller를 이용한 캐릭터 이동/점프/대시 구현

 


CharacterController를 이용한 캐릭터 구현입니다.

캐릭터콘트롤러는 물리엔진을 사용하지 않고 GameObject에 대한 움직임을 Logic으로 쉽게 처리할 수 있도록 해줍니다. 충돌 처리를 위해서 자체 Collider도 포함하고 있습니다.

Rigidbody하고의 가장 큰 차이점은 Step Offset과 Slope Limit의 설정입니다.

Step Offset은 계단과 같이 캐릭터가 올라갈 수 있는 높이를 말하고, Slope Limit은 캐릭터가 올라갈 수 있는 경사도를 설정할 수 있는 기능입니다.
물리엔진을 사용하지 않기 때문에 중력하고 이동속도 같은 계산법을 직접적으로 구현해야 합니다.



CharacterController를 이용한 Character GameObject 구성

구성 Component들:
+ Capsule Mesh - 캐릭터 UI 표시
+ CharacterController Component - 캐릭터 이동 회전 및 충돌 처리
+ ControllerCharacter.cs - 사용자 입력 및 이동 처리

 

 

기본 구성은 Rigidbody 테스트와 거의 동일하게 설정해 줍니다.
근데 Cube를 코로 생각했는데... Eye!! 눈입니다. ㅎㅎ 머 개발자 설정하기 마음이지만요. 

"GameObject"를 "ControllerCharacter"로 변경하고 진행합니다.
"ControllerCharacter"를 선택하고 가장 중요한 "Character Controller" 컴포넌트를 추가해 줍니다.
Center Y 값을 "0" -> "1"로 설정합니다. Capsule Mesh의 Position Y값도 "1'로 설정하여 Collider를 맞추어 주도록 합니다.

+ Slop Limit: 45
+ Step Offset: 0.3
+ Skin Width: 0.08 - 캐릭터 컨트롤러와 다른 Collider가 부딪혔을 때 겹칠 수 잇는 값
+ Min Move Distance: 0.001
+ Radius: 0.5
+ Height: 2

이제 스크립트를 작성해 보도록 합니다.
"Add Component"에서 "New Script"로 하여 "ControllerCharacter"를 생성합니다.
추가되었으므로 더블클릭하여 연동된 Editor가 실행되기를 기다립니다. Rigidbody 때와 마찬가지로 이동/점프/대시를 구현할 예정입니다.
기능이 거의 동일하므로 Rigidbody 때 만든 변수들을 동일하게 사용합니다. 복사->붙여넣기..
중력과 이동속도 관련된 변수는 추가됩니다.

 

 

 

 

ControllerCharacter.cs

private Rigidbody rigidbody; 를
private CharacterController characterController; 로 변경합니다.

중력을 처리하기 위한 변수를 추가합니다.
public float gravity = -9.81f;

저항값을 처리하기 위해 변수를 추가합니다.
public Vector3 drags;

// 점프, 대시, 저항력 계산용 변수
private Vector3 calcVelocity;

rightBody = GetComponent<rigitbody>(); 를
characterController = GetComponent<CharacterController>();로 변경합니다.

Update() 함수 최상단에서 사용한 CheckGroundStatus() 함수를 그대로 사용해도 되지만 CharacterController 자체에서 제공하는 isGrounded를 활용할 수 있습니다.

CheckGroundStatus(); -> isGrounded = characterController.isGrounded; 로 변경합니다.

 


땅에 도착했을 때는 중력값을 받지 않도록 코드를 수정해 줍니다.
if (isGrounded && calcVelocity.y < 0) calcVelocity.y = 0;

이동 처리를 위한 Vector3 설정 및 적용.
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
characterController.Move(move * Time.deltaTime * speed);
if (move != Vector3.zero)
{
  transform.forward = inputDirection;
}

점프 코드를 수정해 줍니다.
if (Input.GetButtonDown("Jump") && isGrounded)
{
  calcVelocity.y += Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);
}


대시도 마찬가지로 작업해 주고, 가장 중요한 중력값 계산하는 코드를 작성합니다.

 


여기까지 작업하면 스크립트에 대한 작업은 완료되었습니다.
이 상태로 실행하면 잘 동작하는 것을 확인할 수 있는데요. 대신 점프를 하면 엄청난 점프 신공을 보여줍니다. ㅋㅋㅋ Unity의 기본 중력값이 9.81이 아니기 때문입니다.
아래와 같이 설정값을 변경해 줍니다.

 

 


CharacterController와 Rigidbody는 꼭 별개로 사용되어야 하는 건 아니고 조합해서도 사용 가능합니다. 

자 이제 실행을 해보면 Rigidbody 작업 때와 같은 결과를 확인할 수 있습니다. 이동/점프/대시 모두 잘 동작합니다.

 

 

 

 

 

 



이제 NavMesh를 사용하는 캐릭터를 구현해보도록 하겠습니다.

 


NavMesh를 사용하기 위해서는 NavMeshAgent를 사용해야 합니다.
NavMeshAgent는 Unity 내부의 길찾기 시스템을 사용할 수 있게 해주고, 장애물 설정하여 캐릭터 피하기 및 길찾기 우선수위로 비용 계산도 가능하도록 해줍니다. 그리고 이 방식은 Click & Move에도 적합한 방식입니다.

 


NavMeshAgent를 이용한 Character GameObject 구성
구성 Component들:
+ Capsule Mesh - 캐릭터 추가
+ CharacterController Component - GameObject의 이동/회전 로직 및 충돌
+ NavMeshAgent - 길찾기 시스템을 이용하기 위한 Component
+ AgentControllerCharacter.cs - 사용자 입력 및 이동 처리.

 

 

Window - AI - Navigation을 추가하고, Radius / Height / Step Height / Max Slope의 값을 Capsule 캐릭터와 동일한 값으로 설정합니다.

Navigation의 [Object]를 선택하고 Scene Filter를 "Mesh Renderers"로 선택합니다.
그리고 Navigation을 적용할 GameObject들을 선택합니다.

Generate OffMeshLink는 점프라던지 순간이동 또는 다른 행동을 설정할 수 있는 것입니다.
여기서는 사용하지 않을 것이므로 Uncheck합니다.

 


그리고 컨테이너 3개에는 걸어들어갈 수 없어야 하므로, "Not Walkable"을 설정합니다.

 

 

좀더 명확한 위치 및 확인을 위해서 많긴 하지만 이미지를 많이 삽입하였습니다.

 

 

Terrain도 마찬가지로 Generate OffMeshLink를 Uncheck하고, Navigation Area를 "Walkable"로 설정합니다.


Bake - Navigation Mesh를 좀더 상세하게 설정하고 빌드할 수 있습니다.
현재는 기본으로 두고 "Bake"를 클릭하면 Navigation Mesh가 생성된 것을 확인할 수 있습니다.

 


ControllerCharacter를 선택하고 "Nav Mesh Agent" 컴포넌트를 추가합니다.
여기까지 설정은 완료되었고 스크립트 작업을 진행합니다.

Steering 값들은 내부 에이전트 Mesh가 이동할 때 사용되는 Velocity를 계산하기 위해 사용되는 값들입니다.
Obstacle Avoidance는 물체를 피해갈 때 얼마의 값들을 기준으로 피할 것인지 설정하는 것입니다.
Path Finding은 길찾기 관련된 설정을 하는 값들입니다.


기존 Controller Character를 그대로 사용하며 확장하는 형태로 만들것이므로 "Controller Character" 스크립트를 더블클릭하여 Editor를 실행합니다.



ControllerCharacter.cs

클릭 앤 무브 방식이므로 기존 변수값들중 불필요한 변수들을 제거합니다. 그리고 NavMeshAgent 및 Camera를 추가합니다.

 


void Start()
{
  characterController = GetComponent<CharacterController>();
  agent = GetComponent<NavMeshAgent>();
  agent.updatePosition = false; // Agent의 이동 시스템을 사용하지 않겠다. CharacterController의 이동 시스템으로 움직일 것이므로..
  agent.updateRotation = true; // Agent의 회전 시스템 사용.
  
  camera = Camera.main;
}

클릭 앤 무브 방식으로 처리할 것이라서 Update() 내용을 모두 제거하고 새로 작성합니다.

void Update()
{
  if (Input.GetMouseButtonDown(0))
  {
    Ray ray = camera.ScreenPointToRay(Input.mousePosition); // Make ray from screen to world

    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100, groundLayerMask) // Peeking에 대한 체크 거리가 너무 멀면 Performance에 영향이 크기 때문에 100 정도로 설정.
    {
      Debug.Log("We hit " + hit.collider.name + " " + hit.point);
  
      agent.SetDestination(hit.point); // Agent가 알아서 이동 시작.
    }

    if (agent.remainingDistance > agent.stoppingDistance) // 가야할 거리가 남았다.
    {
      characterController.Move(agnet.velocity * Time.deltaTime);
    }
    else
    {
      characterController.Move(Vector3.zero);
    }
  }
}


Agent의 이동방향으로 캐릭터의 이동방향을 맞추어 주기 위해 LateUpdate() 함수를 구현합니다.
private void LateUpdate()
{
  transform.position = agent.nextPosition;
}

 

여기까지가 작업 완료입니다. NavAgent를 사용하기 때문에 정말 쉽게 코드가 작성되는 신비함을 느껴볼 수 있습니다.



저번에 샘플 예제가 없다고 하였는데 제가 잘못 알고 있었네요. ^^;;;
강의 자료에 해당 샘플 예제가 모두 포함되어 있었고 실행도 잘 되는 것을 확인하였습니다. ㅎㅎ
이제 앞으로의 교육 진행을 조금은 마음 편히 따라 갈 수 있을 것 같습니다.

지금까지의 내용을 실제 유니티로 실행하고 테스트 해 본 화면입니다.

 


잘 되네요~

여기까지 쉴틈 없이 따라 하고 있는데요.. 솔직히 강의 들으면 들을수록 너무 좋네요..
완전 따라가기 어렵지 않을까.. 챕터 몇개 대략 봤을 때도 불안감이 앞서있었는데요..
차근히 하나하나 듣고 따라해보니 정말 됩니다.. 기가막히게 좋네요. 
패스트캠퍼스 정말 대단합니다. 이런 좋은 강의를 착한 가격에 들을 수 있다니.. 감사할 따름이네요.

 

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts