전체 페이지뷰

2017년 7월 26일 수요일

Unity Tutorial: Survival Shooter, part 2

Camera setup





현재 메인 카메라 세팅은 perspective 3D입니다. Orthographic 카메라를 사용하여 isometric 뷰로 이 세팅을 바꾸려고 합니다. 그러기 위해서는 먼저 카메라 위치를 변경해야 합니다.

Hierarchy 창에서  Main Camera를 선택하고 Transform Position을 1, 15, -22로, Rotation은 30, 0, 0으로 변경합니다. 그리고, Perspective로 되어 있는 Projection을 Orthographic으로 바꾸고 Size는 4.5로 바꿔줍니다. 마지막으로 Background 칼라를 검은색으로 바꿉니다.

추가적으로  Clear Flags도 Solid Color로 바꿉시다.


이제 스크립트 차례입니다.
Scripts 폴더 내에 Camera 폴더를 생성한 후 그 안에 CameraFollow라는 이름으로 새 스크립트를 만듭니다.

두 개의 변수를 생성합니다. 하나는 카메라가 따라다닐 목표물에 대한 레퍼런스이고, 하나는 카메라가 너무 확확 전환되지 않고 조금 여유있게 따라다니도록 하기 위한 변수입니다.

public Transform target; 
public float smoothing = 5f; 
cs

카메라에서 목표물까지의 정해진 간격을 지정할 변수도 private으로 선언합니다.

Vector3 offset;
cs

그리고 Start() 메소드 내에서 처음 시작시 카메라와 목표물까지의 간격을 계산해서 offset에 저장하고 위치가 바뀌어도 계속 그 간격을 유지할 수 있게 합니다.

private void Start()
{
    offset = transform.position - target.position;
}
cs

그리고 변화가 있을 때에 호출되는 FixedUpdate()내에서 새 카메라의 위치를 계산해서 변경해줍니다.

private void FixedUpdate()
{
    Vector3 targetCamPos = target.position + offset;
    transform.position = Vector3.Lerp(transform.position, targetCamPos, smoothing * Time.deltaTime);
}
cs

지금까지 작성한 전체 코드는 다음과 같습니다.

using UnityEngine;
public class CameraFollow : MonoBehaviour
{
    public Transform target; // 카메라가 따라다닐 목표물
    public float smoothing = 5f;
    Vector3 offset; // 카메라에서 목표까지의 정해진 간격
    private void Start()
    {
        // offset을 계산
        offset = transform.position - target.position;
    }
    private void FixedUpdate()
    {
        // 목표물 위치 변경에 따른 새 카메라 벡터 계산
        Vector3 targetCamPos = target.position + offset;
        // 원 위치에서 새 위치로 부드럽게 이행시킴
        transform.position = Vector3.Lerp(transform.position, targetCamPos, smoothing * Time.deltaTime);
    }
}
cs

저장 후 유니티로 돌아갑니다.

전과는 달리 Add Component로 생성한 스크립트가 아니라서 직접 연결해 줘야 합니다. 프로젝트 창에서 스크립트를 드래그하여 Hierarchy창의 Main Camera에 가져다 놓습니다.




Main Camera를 선택해보면 스크립트가 추가된 것이 보입니다. 그 중 Target 프로퍼티를 설정해야 합니다. 타겟은 당연히 Player 오브젝트이므로 드래그하여 갖다 놓습니다.


플레이어의 움직임이 완성되었으므로 앞으로 재사용할 수 있게 Prefabs 창에 드래그하여 프리팹화 해 두고 씬을 저장합니다.

게임을 실행해보면 플레이어를 따라 카메라가 움직이는 것을 확인 가능합니다.



Creating Enemy #1



이제 우리의 플레이어를 따라 다니며 데미지를 줄 첫번째 적을 만들 차례입니다. Models>Characters폴더를 보면 Zombunny라는 것이 있습니다. 이것을 드래그하여 씬뷰 상 아무 곳에나 배치합니다. 나중에 이 Zombunny는 프리팹화 되어 스포닝 될 것이므로 위치는 중요치 않습니다.


나중에 Player가 총을 쏘아 적이 맞게 되면 파편이 튀는 효과가 이미 Prefabs폴더에 HitParticles라는 이름으로 만들어져 있습니다. 이것을 Zombunny에게 적용할 것입니다. HitParticles 프리팹을 드래그하여 Zombunny의 자식으로 만듭니다.


이 Zombunny는 다른 배경들과는 다르게 총을 쏠 수 있는 대상이 됩니다. 이것을 구분하기 위해서 Layer를 이용합니다. Zombunny가 잘 선택되었는지 보고 인스펙터 창의 Layer를 Shootable로 바꿉니다.


그러면 children도 전부 Shootable로 바꿀 것인가를 묻는 창이 나타나는데 Yes를 선택해서 모두 바꿉니다.

다음으로 Zombunny에게 물리법칙을 적용하기 위해 Add Component>Physics>Rigidbody를 추가합니다. 그리고 Player에게 했던 것과 마찬가지로 DragAngular Drag를 Infinity로 바꾸고 Constraint도 그림과 같이 적용합니다.



Add Component>Physics>Capsule Collider도 추가하고 Center와 Height를 조정합니다.


여기까지는 Player와 비슷합니다만 Player에게 데미지를 주려면 또 하나의 다른 콜라이더가 필요합니다. Capusule Collider는 물리적 실체를 부여하기 위해 쓰인 것이고 Sphere Collider를 하나 더 추가하여 Trigger용으로 사용합니다.

Add Component>Physics>Sphere Collider를 추가하고, Is Trigger에 체크, Y를 0.8, Radius도 0.8로 해줍니다.


크기가 캡슐 콜라이더보다 좀 큰데 그 이유는 Zombunny의 리치에 닿는 곳 까지를 범위로 하기 위해서 입니다.

마지막으로 Add Component>Audio>Audio Source도 추가하고 Audio Clip옆 동그라미를 클릭하여 Zombunny Hurt로 지정하고  Play On Awake는 체크 해제합니다.


자, 이제 이 녀석이 Player를 쫓아다니게 만들어야 합니다. 그러기 전에 우선 상단 메뉴에서 Window>Navigation을 선택하여 Navigation이라는 창이 하나 새로 인스펙터 창 옆에 생겨나도록 합니다.

다시 Zombunny 인스펙터 창으로 돌아가서 Add Component>Navigation>Nav Mesh Agent를 선택하여 추가합니다. 이것이 어떤 범위를 움직일 수 있는가를 결정하는 baking이라는 단계를 거쳐서 네비게이션용 AI로 작용합니다.

Nav Mesh Agent의 프로퍼티 중 Radius를 0.3, Speed 3, Stopping Distance 1.3, Height 1.1로 설정합니다.



크기를 결정했으면 이제 이 오브젝트가 돌아다닐 수 있는 영역을 결정해야 합니다. 그 전에 Environment를 먼저 살펴 봅시다. 이것은 이미 프리팹화 되어 있고, 기본 Collider등을 응용해서 충돌체가 이미 설정되어 있습니다.  Environment를 선택하고 인스펙터의 우상단을 보면 Static에 체크가 되어 있는 것이 보입니다. 드랍다운 해서 열어보면 전부 선택되어 있으므로 Navigation 역시 체크가 되어 있습니다. 오브젝트가 Navigable 영역으로 인식하고 움직이게 하려면 static으로 미리 올려두어 계산해야 하기 때문이라 여겨집니다.

이제 이 영역에서 이동 가능한 규칙들을 설정하여 bake합니다.

오른쪽 인스펙터 탭 옆에 새로이 생성되었던 navigation 탭을 누르면 다음과 같은 모습이 나타납니다.



상단 4개의 탭들 중 Bake를 누르고 Agent를 설정합니다(개인적으로 에이전트란 이름 재미있네요, 영화 매트릭스에서 에이전트들이 사람 속에 들어가서 조종하듯이 이 에이전트가 오브젝트를 자동으로 조종하니 말입니다. 그 에이전트를 다루고 있으니 우리는 네오?).

Agent Radius는 nav mesh의 반지름을 말하며, agent가 wall에 얼마나 다가설 수 있는가를 대략적으로 결정합니다. 우리는 지금 Zombunny만 생성했지만 몇 가지 적이 더 추가되어야 합니다. 크기가 다 다르기 때문에 조금 중간에서 타협하여 0.75로 합니다.

유사하게 Agent Height도 1.2 로 결정합니다.

Step Height는 넘어갈 수 있는 장애물의 높이를 뜻합니다. 바닥이 울퉁불퉁하기 때문에 이것을 너무 높이 잡으면 오브젝트가 점프해 올라가는 것처럼 보일 수가 있으므로 0.34(영상에는 0.1)로 합니다.

영상에 나오는 Width Inaccuracy는 유니티 5.x 버전부터는 없어졌습니다. 대신 Advanced에서 Manual Voxel Size를 체크하고, Voxel Size는 0.214(PDF 설명에는 0.025로 하라고 했는데 너무 작다는 메세지가 떴습니다. 유튜브 답글을 참조하여 최적이 0.214라고 판단했습니다)로 정합니다.

설정이 끝났으면 Bake 버튼을 누릅니다. 그러면 Bake 된 영역이 씬뷰에 파랗게 나타납니다.



이렇게 번거롭게 영역을 지정해 주는 이유는 효율성 때문입니다. 모든 메쉬와 충돌을 계산하여 움직임을 갖는다면 너무나 비효율적입니다. 따라서 이렇게 근사치로 영역을 정해주면 훨씬 적은 계산을 하면서 효율적으로 인공지능이 오브젝트를 움직일 수 있게 되는 것입니다. 물론 배치된 배경 오브젝트들의 위치를 바꾸거나 없애는 등의 변경 사항이 있을 때는 영역을 다시 지정하고 계산하여 bake해야합니다.

자, 이제 적 캐릭터가 플레이어를 쫓아오는데 필요한 동작 애니메이션을 설정해야 합니다.

프로젝트 창에서 Animation 폴더를 선택하고 Create>Animator Controller를 선택하고 이름은 EnemyAC로 합니다. 이 생성된 컨트롤러를 Zombunny의 Animator Controller에 드래그하여 연결해 줍니다.


이 EnemyAC는 아직 빈 상태이므로 애니메이션을 연결해 줘야 합니다. 더블 클릳 또는 인스펙터 창의 Open을 눌러서 Animator 편집 창을 열어 둡시다. 그리고 Models>Characters 폴더의 Zombunny를 선택하고 인스펙터 창을 열면 Move, Idle, Death의 세 가지 상태가 있다는 것을 알 수 있습니다.


이제 애니메이터 편집창에 또 Move, Idle, Death의 세 상태를 끌어다 놓습니다.




Player에서 했던 것처럼 Move를 Default로 설정하고, 파라미터를 만듭니다. 파라미터는 두 개이고, 모두 Trigger 타입입니다. 이름은 PlayerDead, Dead라고 정해줍니다.


그리고 역시 Transition을 설정합니다. Move에서 Idle로 Transition을 이어주고 화살선을 선택하여 인스펙터 창에서 Condition을 PlayerDead로 설정합니다(Player가 죽으면 적들의 움직임이 멈춰야 하기 때문입니다).


다음으로 Any State에서 Transition을 생성하여 Death로 잇고 Condition은 Dead로 합니다.


자, 이제 Enemy의 상태를 알려줄 스크립트를 작성할 차례입니다. Scripts>Enemy 폴더를 선택하면 이미 EnemyMovement라는 스크립트가 일부 작성되어 있습니다. 드래그하여 Zombunny에 연결해주고 에디터로 엽니다.

Enemy는 Player를 쫓아다녀야 하므로 먼저 player의 위치가 필요합니다. 그리고 Nav Mesh Agent에 대한 레퍼런스도 있어야 하겠습니다.

Transform player;
UnityEngine.AI.NavMeshAgent nav;
cs

이것들을 Awake()메소드 내에서 초기화 해줍니다.

void Awake ()
{
    player = GameObject.FindGameObjectWithTag ("Player").transform;
    nav = GetComponent <UnityEngine.AI.NavMeshAgent> ();
}
cs

다음으로는 Update() 메소드 속에서 AI가 움직일 방향을 정합니다. FixedUpdate가 아닌 이유는 물리적 변화가 주어지지 않아도 Enemy AI는 계속 움직이기 때문입니다.

void Update ()
{
    nav.SetDestination (player.position);
}       
cs

차차 플레이어와 적의 체력에 따라 코드가 수정되어야겠지만 현 단계에서는 이것이 전부입니다. 스크립트와 씬을 저장하고 플레이 해보면 Zombunny가 플레이어를 열심히 따라오는 것이 보입니다.

댓글 없음:

댓글 쓰기