전체 페이지뷰

2017년 7월 4일 화요일

Unity Tutorial: Space Shooter, part 3

Creating shots


비행선을 움직일 수 있게 되었으니 이제 총을 발사해야겠죠. 제일 먼저 플레이어 게임 오브젝트를 체크해제하여 deactivating 시켜놓고 시작합시다. 그리고 새로운 Create>Create Empty하여 새로운 오브젝트를 생성하고 이름을 Bolt라고 합니다. 이 Bolt는 우리의 총의 부모 오브젝트가 될 것입니다. 또한 게임 로직과 총의 시각 효과를 분리할 것인데 그렇게 하면 새로운 무기와 시각효과를 추가할 때 로직은 그대로 사용하고 시각효과 레이어만 교체하여 손쉽게 재사용할 수 있습니다.

BoltReset하여 오리진 위치로 가져다 두고, 시각효과를 보여주는데 스크린으로 사용될 Quad를 추가하여 이름을 VFX라 짓습니다. VFXTransform 역시 Reset하고 드래그하여 Bolt의 자식으로 만듭니다.


씬 뷰를 보면 역시 Background 때처럼 카메라가 볼 수 없는 방향으로 Quad가 서 있는 것을 알 수 있습니다. x Rotation을 90으로 바꾸어 카메라를 향하게 합니다.



이제 발사되는 총알의 모습을 레이저 볼트처럼 보이게 할 차례입니다. 에셋에 이 텍스쳐가 준비되어 있습니다. Assets>Textures>fx_laser_orange_dff 를 찾습니다.


이 전에 백그라운드 때는 그냥 드래그해서 Quad에 갖다 놓았습니다. 그러면 유니티가 자동으로 Material을 생성해 주었는데 이번에는 이 과정을 직접 해보려고 합니다.

Assets>Materials 폴더를 선택하고 Create>Material하여 새로운 Material을 생성하고 이름을 fx_bolt_orange로 만들어 줍니다(그런데 동영상과는 달리 해당 폴더에 그 material이 이미 생성되어 있네요...만들었다고 치고 그대로 따라가겠습니다).

이 빈 material과 texture를 연결해야 합니다.

새 material을 선택하고 인스펙터 창을 보면 우측 네모칸에 텍스쳐를 선택하는 Select라는 글씨가 있습니다. 클릭하고 텍스쳐 선택 창이 뜨면 fx_laser_orange_dff를 선택합니다.



이렇게 연결해도 되고 또는 프로젝트 창에서 텍스쳐를 드래그해서 위 그림의 창으로 그냥 드래그해도 됩니다. 쉐이더가 Mobile>Particles>Additive로 설정되어 있는데 튜토리얼의 진행을 위해 일단 Standard로 바꾸겠습니다.

Material을 생성했으면 그것을 Quad에 적용합니다. 역시 그냥 드래그하여 넣으면 됩니다.

VFX Quad의 인스펙터 창을 보면 Mesh Renderer에 fx_bolt_orange가 연결되어 있습니다.

이제 게임뷰를 보면 검은 사각 위에 뭉툭한 레이저 모양이 떠 있을 볼수 있습니다.

이 사각 배경을 없에기 위해 쉐이더를 Particles>Additive로 변경합니다.
이 쉐이더에서 검은색은 0으로 설정되어 화면에 나타나지 않습니다. 원래 설정처럼 Mobile>Particles>Additive로 설정해도 문제 없습니다. 모바일로 되어 있긴 하지만 다른 플랫폼에서도 잘 작동합니다.

비주얼 설정이 끝났으니 로직으로 넘어갑니다.

Hierarchy 창에서 Bolt를 선택합니다. 물리법칙을 적용하여 움직이게 하기 위해서는 rigid body를 적용해야 합니다. Add Component>Physics>Rigidbody를 선택하여 더해줍니다.


Rigid body가 추가되면 볼트가 우주 공간에서 낙하하는 일이 없도록 Use Gravity는 체크해제합니다.

다음으로는 충돌을 계산할 콜라이더가 필요합니다. 콜라이더를 추가하기 전에 먼저 VFX를 선택하고 인스펙터 창을 봅시다. Quad 오브젝트이기 때문에 Mesh Collider가 이미 설정되어 있습니다. Mesh Renderer를 체크 해제하고, Mesh Collider의 Convex에 체크해보면 Quad 전체에 콜라이더가 설정되어 있는 것을 볼 수 있습니다. 우리는 Bolt자체에만 콜라이더가 들어가 있기를 원하기 때문에 Mesh Collider의 톱니모양을 클릭하고 Remove Component를 선택하여 제거합니다.

그리고, 다시 parent인 Bolt의 인스펙터 창에서 Add Component>Physics>Capsule Collider를 선택해 새로운 콜라이더를 추가합니다.


그리고 캡슐 콜라이더의 크기를 레이저 볼트에 맞춰줍니다.

기준 축을 Z로 바꾸었고 반경과 높이를 조절하여 크기를 맞추었습니다. 그리고, Is Trigger를 체크해서 직접 물리적으로 충돌하는 것이 아니라 맞았는가 아닌가를 판정할 수 있도록 합니다. 그리고 이것을 콘트롤하는 스크립트를 작성하기 위해 Add Component>New Script하고 이름은 Mover로 정합니다. 그러면 루트 폴더에 C# 코드가 생성되는데 Scripts 폴더로 드래그하여 옮겨 놓고 에디터로 엽니다.

이 볼트는 자신만의 속도를 가지고 있습니다. 그것을 위해 일단 Rigidbody에 대한 레퍼런스를 얻어놓고, Start() 함수 내에 속도를 정해줍니다.

public class Mover : MonoBehaviour
{
    public float speed;
    private Rigidbody rb;
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.velocity = transform.forward * speed;
    }
}
cs

코드는 이것이 전부입니다. 총을 여러 개 쏘아도 모두 이것이 복제되어 생성되는 것입니다. 같은 것을 여러 개 만들어야 하니까 프리팹으로 설정합니다. Hierarchy창의 Bolt를 드래그 하여 프로젝트 창의 Prefabs 폴더로 끌어다 놓기만 하면 됩니다.


이 프리팹을 선책하고 인스펙터 창에서 Speed 프로퍼티를 설정합시다. 배가 10이니까 20정도로 해볼까요?

그리고 마지막으로, 이 볼트는 발사되었을 때에만 생겨나는 것이고, 프리팹으로 이미 만들어두었으므로 씬에 만들어진 Bolt 인스턴스는 필요없습니다. Hierarchy창으로부터 지웁니다.

인스턴스로부터 Bolt를 지웠으므로 볼수가 없습니다. 이것을 테스트 하려면 게임뷰로 가서 실행한 후 프리팹의 Bolt를 끌어다 놓으면 발사되는 것이 보일 것입니다.


계속해서 끌어다 놓으면 여러개의 볼트가 생성되고 실행 중지하면 모두 사라집니다.




Shooting shots


이제 플레이어가 Bolt를 쏠 수 있게 만들도록 합시다. 꺼두었던 Player 오브젝트를 다시 체크하여 활성화시키고, 에디터로 PlayerController 스크립트를 다시 엽니다.

프리팹에 Bolt를 저장해 두었습니다. 우리가 해야 할 것은 버튼을 누르거나 마우스가 클릭되었을 때 Bolt 프리팹을 인스턴스화 하는 것입니다. 그럼 이 코드는 어디에다 작성해야 할까요? 단순히 총을 쏘는 일에는 물리법칙이 필요없으므로 FixedUpdate() 내부는 맞지 않습니다. Frame마다 호출되는 Update() 내부가 적절할 것 같습니다. 그 안에서 Bolt를 Instantiate 해야 합니다.

public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);

Instantiate라는 메소드를 사용하기 위해서는 인수가 세 개 필요합니다. 인스턴스화할 오브젝트, position, rotation입니다. 오브젝트는 Bolt 프리팹입니다. 프리팹이 생성될 position과 roatation은 바로 게임 오브젝트의 현재 position, rotation이므로, Player의 Transform을 참조하면 되겠습니다.

그렇다면 이 총알을 인스턴스화하기 위해서는 어떻게 하는게 좋겠습니까?

그것을 위해 먼저 Create>Create Empty 하여 빈 오브젝트를 하나 만들고 그 이름을 Shot Spawn이라고 하고 이 오브젝트의 transform을 총이 발사되는 포인트로 간주합니다. 이 포인트를 총이 붙어있는 가상의 포인트라고 생각해도 좋겠습니다.

이 포인트는 플레이어 오브젝트와 함께 이동해야 하므로 드래그하여 Player오브젝트의 자식으로 만듭니다.


씬뷰에서 보면 이제 이 Shot Spawn의 위치가 보입니다. 이 오브젝트는 Player오브젝트에 대해 상대적인 위치에 존재하게 되며, 현재는 가운데에 존재합니다. 총은 비행선의 전면부에 위치해야 하므로 z축 상에서 앞으로 당겨줍니다. 대략 1.25정도면 될 것 같습니다.



발사될 때의 위치가 적당한가 보기 위해 Prefab의 Bolt를 드래그하여 Shot Spawn의 자식 위치에 가져다 놓아봅니다.


그림상으로 나쁘지 않아 보입니다. 위치를 확인 했으면  Bolt 인스턴스는 지우고 다시 스크립트로 돌아갑니다.

이제 새로 생성한 오브젝트와 transform에 대한 레퍼런스를 생성하기 위해 변수를 몇개 더 도입합니다.

public GameObject shot;
public Transform shotSpawn;
cs

shotSpwan의 경우도 GameObject 타입으로 선언하여 사용시 shotSpawn.transform.position..처럼 사용할 수 있습니다만 Transform타입으로 선언하여 인스펙터에서 연결해주면 훨씬 코드가 간단해 질 것입니다.

이제 Instantiate 메소드를 사용해서 인스턴스화 할 수 있게 되었습니다.

발사 버튼이 눌러지면 Bolt를 인스턴스화 하면 될 것입니다. 버튼이 눌려지는 메소드를 먼저 알아야 하겠는데, 그것은 Input.GetButton입니다. API를 찾아보면 샘플 코드가 나오는데 그것을 조금만 수정하면 될 듯합니다.

총이 발사되는 빈도인 fireRate 변수와, 내부적으로 다음 쏠 수 있을 때까지의 시간을 계산할 nextFire변수를 도입하고, Update()메소드 내에서 총을 인스턴스화 해줍니다.

현재까지의 전체 코드는 아래와 같습니다.
[System.Serializable]
public class Boundary
{
    public float xMax, xMin, zMax, zMin;
}
 
public class PlayerController : MonoBehaviour
{    
    public float speed;
    public float tilt;
    public Boundary boundary;
    private Rigidbody rb;
 
    public GameObject shot;
    public Transform shotSpawn;
    public float fireRate;
 
    private float nextFire;
 
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
 
    private void Update()
    {
        if (Input.GetButton("Fire1"&& Time.time > nextFire)
        {
            nextFire = Time.time + fireRate;
            Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
        }
    }
 
    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");
 
        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * speed;
 
        rb.position = new Vector3
            (
            Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax), 
            0.0f, 
            Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
            );
 
        rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt); 
    }
}
cs


스크립트를 저장하고 다시 유니티로 돌아갑니다. Player 오브젝트를 선택하고 인스펙터창을 보면 프로퍼티가 추가되어 있습니다.

이제 Shot 프로퍼티창에는 프로젝트 창으로부터 Bolt 프리펩을, Shot Spawn 프로퍼티창에는 Hierarchy창으로부터 Shot Spawn을 드래그해서 연결해줍니다. Shot프로퍼티에는 사실 오브젝트가 아닌 Transform이 연결되어야 하는 것이지만 그건 유니티가 알아서 해줍니다. 그리고 Fire Rate는 초당 4발에 해당하는 0.25를 입력합니다.



이제 씬을 세이브하고 플레이 해봅니다.

마우스 클릭시 볼트가 발사됩니다.

댓글 없음:

댓글 쓰기