전체 페이지뷰

2017년 7월 10일 월요일

Unity Tutorial: Space Shooter, part 4


Boundary



이제 총이 발사되는 것까지 구현되었습니다. 그런데 또 아직 문제가 있습니다. 총을 발사하면 할 수록 Hierarchy 내에 인스턴스가 쌓여갑니다.


게임 영역을 지정해 주지 않았기 때문에 생긴 일입니다. 따라서 게임의 경계를 지정해주고, 이를 벗어난 인스턴스는 제거될 방법을 찾아야 합니다.

이것을 해결하기 위해 우리는 게임의 영역을 박스의 형태로 지정해주려고 합니다.

Create>3D object>Cube를 선택하여 큐브를 게임의 경계로 사용하려고 합니다. 사실 메쉬와 메쉬렌더러는 필요치 않고 콜라이더만 필요할 뿐이지만 시각적으로 확인 가능하도록 일단 그대로 두고 이름만 Boundary로 바꿔줍니다. 역시 origin 포인트로 위치하도록 Reset하는 것을 잊지 마시기 바랍니다.


이 오브젝트는 실제 다른 오브젝트와 상호작용 하는 것이 아닙니다. 따라서 Box ColliderIs Trigger에 체크해 줍니다. Mesh Renderer는 체크해제해서 Player 오브젝트가 보이도록 해서 게임뷰를 봅시다.

메쉬 렌더러를 꺼주면 게임뷰의 카메라로는 보이지 않고 콜라이더의 역할을 할 뿐입니다. 다시 메쉬렌더러를 체크하여 보이도록 하고, Z Position을 5로 바꿔주면 아래의 그림과 같은 상태가 됩니다.


이제 Scale을 변경해서 씬 전체를 커버할 수 있도록 해 봅시다. Backgorund의 X Scale이 15이므로 Boundary의 크기도 거기에 맞춰서 15로 합니다. Z Scale은 늘려보면 대략 20 정도가 좋을듯 합니다. 사실 20이 정확한 수치인 게, 20은 orthogrphic 카메라 크기의 두배이기 때문입니다. 화면의 상하 스케일은 언제나 orthographic 카메라의 두배입니다.

다시 메쉬렌더러를 끄고 씬뷰를 봅시다.


녹색 경계가 잘 보이고 있습니다. 이제 이 경계가 역할을 하도록 하기 위해서는 스크립트를 작성해야 합니다. Boundary 오브젝트를 선택한 상태에서 Add Component>New Script하여 DestroyByBoundary라는 이름을 스크립트를 추가하겠습니다. 루트 레벨에 생긴 스크립트를 Scripts 폴더로 옮겨주고 에디터로 엽니다.

우리는 콜라이더에 trigger가 발생했을 때 어떻게 할 것인지를 코딩하려고 합니다. trigger를 API에서 검색해서 살펴보니 Collider.OnTriggerExit()가 필요할 듯 싶습니다.
다른 콜라이더가 트리거에 닿는 것을 중지했을 때 호출됩니다. 샘플 코드가 우리가 원하는 그대로입니다.

public class DestroyByBoundary : MonoBehaviour 
{
    void OnTriggerExit(Collider other)
    {       
        Destroy(other.gameObject);
    }
}
cs


트리거 포인트를 지나가게 되면 파괴하는 단순한 코드입니다. 저장 후 유니티로 돌아갑니다. 그리고, Mesh Renderer와 Mesh Filter의 톱니 모양을 누르고 Remove Component하여 제거합니다.

씬을 저장하고 테스트해 봅시다. 총을 쏴도 경계를 지나가면 인스턴스가 제거되는 것이 Hierarchy창에서 보입니다.


Creating hazards



우리의 비행선이 무기를 장착하고 발진 준비를 마쳤습니다. 이제 이 비행선이 도전할 장애물들이 필요하겠죠. Bolt 프리팹을 만든 것과 유사한 방법으로 우리를 위협할 소행성을 만들 예정입니다.

먼저 새로운 게임 오브젝트를 만들고 이름을 Asteroid로 바꿉니다. 역시 Reset해서 오리진 위치로 옮기는데 플레이어 오브젝트와 겹치므로 Z 축으로 8만큼 옮겨서 상단으로 오게 해 놓습니다.

그리고 이 오브젝트에 artwork를 추가합니다. 에셋의 Models 폴더에 보면 세 가지의 다른 소행성 모델이 있습니다. 그 중 첫번째 모델을 드래그하여 Asteroid의 자식으로 만듭니다.


그런 후 Reset하여 parent의 오리진으로 위치시킵니다.

이제 이 소행성에 컴포넌트와 스크립트 등을 추가해야 하겠습니다.
개별 아트워크가 아닌 부모 Asteroid를 선택하고 Add Component>Physics>Rigidbody를 추가합니다. 그리고 우주 공간이므로 낙하하지 않도록 Use Gravity는 체크 해제합니다.

또 한번 더 Add Component>Physics>Capsule Collider도 추가합니다. 이상적인 방법은 아니지만 캡슐 콜라이더의 반경과 높이를 조절해서 적당히 크기를 맞춥니다. 보다 정교한 조절을 위해서 캡슐 콜라이더의 Edit Collider를 누르면 핸들이 나타납니다. 그 핸들을 클릭한 상태에서 Alt 키를 누르면 타원 하나씩, Shift를 누르면 전체 타원의 크기를 조절할 수 있게 됩니다.



적절히 조절되었으면 씬을 저장하고 게임뷰로 가서 플레이 해봅니다.

음...그냥 소행성이 덜렁 떠 있네요. 소행성에 생명을 주어야겠습니다. Asteroid에 Add Component>New Script하고, 이름을 RandomRotator라고 짓습니다. 에셋 루트 폴더에 생성된 스크립트를 Scripts폴더로 옮긴 후 에디터로 엽니다.

샘플 코드를 지우고 나서 가장 먼저할 것은 float타입의 tumble이라는 변수를 선언하는 것입니다. 이는 최고 회전 속도를 지정해줄 변수입니다. 그리고 rigidbody에 대한 레퍼런스 변수인 rb도 선언합니다.

그리고, Start()메소드 내부에서 rb를 받아오고, rb의 임의의 각속도를 결정해야 합니다. 각속도에는 angularVelocity라는 프로퍼티가 사용됩니다. 이 프로퍼티를 Random 클래스를 이용해 지정하려고 합니다. Random 클래스 중 InsideUnitSphereVector3 값을 반환하므로 구형에 가까운 소행성에서는 적당한 듯 보입니다.

public class RandomRotator : MonoBehaviour
{
    public float tumble;
    private Rigidbody rb;
 
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.angularVelocity = Random.insideUnitSphere * tumble;
    }
}
cs

코드를 저장하고 유니티로 돌아갑니다. 생성된 Tumble 프로퍼티를 5로 지정하고 플레이 해보면, 소행성이 제자리에서 임의의 방향으로 회전하다가 점점 느려지는 것을 볼 수 있습니다. 느려지는 이유는 저항(공기의 마찰력 같은)을 정의하는 두 개의 파라미터가 Rigidbody 내에 설정되어 있기 때문인데 그것은 DragAngular Drag입니다. 그러나 이곳은 우주 공간이므로 저항이 없습니다. Angular Drag를 0으로 바꾸어줍니다.



이제 소행성의 대략의 모습이 만들어졌습니다. 여기에 우리 비행선에서 Bolt를 쏘면 어떤 일이 일어날까요? 아무 일도 없이 그냥 지나갑니다. 그것은 이것들이 트리거 콜라이더이기 때문입니다. 트리거로 지정되면 물리적 충돌을 일으키지 않습니다. 이제 트리거 이벤트에 대한 코드를 작성할 때입니다.

Asteroid를 선택하고 DestroyByContact라는 또 하나의 스크립트를 추가하고 에디터로 엽니다. 또 다시 트리거와 관련된 메소드를 사용해야 하므로 API를 검색해봅니다. Bolt가 소행성에 접촉하면 부서져야 하므로, 콜라이더가 다른 트리거 이벤트에 침입했을 때 호출되는 OnTriggerEnter라는 메소드가 적당해 보입니다.

public class DestroyByContact : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}
cs

다른 콜라이더가 침입하면 그 콜라이더와 자신을 모두 파괴하는 코드입니다. 저장하고 유니티로 가서 게임뷰에서 실행을 누르면...소행성이 사라졌습니다. 어떻게 된 것일까요?

간단히 디버깅을 해보겠습니다.
public class DestroyByContact : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        Debug.Log(other.name);
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}
cs

Debug.Log(other.name); 이라는 코드를 넣어 저장 후 실행해 봅시다. 이제 콘솔창을 보면,

침범한 콜라이더는 Boundary였고, 결과적으로 Hierarchy창에서 Boundary가 파괴되어 사라진 것을 볼 수 있습니다. 그렇다면 왜 이런 일이 벌어진 것일까요?

씬뷰를 봅시다.



우리의 오브젝트들은 Boundary의 한 가운데에 있습니다. 따라서 시작하자마자 바로 침범된 것으로 판정되어 사라진 것입니다. Boundary와의 침범은 무시하도록 코드에 반영해주어야 하겠습니다.

오브젝트들을 판별하는 간단한 방법은 tag를 사용하는 것입니다.

Boundary의 인스펙터 창 좌상단에 Tag란이 있습니다. 이것을 클릭하여 Add Tag를 선택하고,


+버튼을 누르고 Boundary를 추가해 줍니다.

다시 Hierarchy창에서 Boundary를 선택하면 Tag란이 여전히 Untaggeed로 되어 있습니다. 여기를 누르면 우리가 추가한 Boundary가 리스트에 나타나므로 선택합시다.

스크립트로 돌아가서 디버그용 코드를 삭제하고, tag를 판정하여 Boundary이면 오브젝트를 파괴하는 일 없이 넘어가도록 합니다.

public class DestroyByContact : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Boundary")
            return;
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}
cs


스크립트를 저장하고 돌아가 플레이해보면 소행성이 나타나고 총이 발사되어 만나면 사라집니다.

댓글 없음:

댓글 쓰기