전체 페이지뷰

2017년 6월 30일 금요일

Unity Tutorial: Space Shooter, part 2


Adding a Background



우리는 우주 게임을 만들고 있습니다. 우주 배경이라면 검은 색이 맞지만 전부 검은색이면 좀 지겨울 수 있습니다. 그래서 게임에 배경을 더하기로 합니다.

우리의 Player 오브젝트를 비활성화 하는 것으로 시작합니다.

Hierarchy 창에서 Player를 선택하고 인스펙터 창 이름 옆을 체크 해제합니다.

백그라운드 이미지를 유지하려면 Quad라는 것이 필요합니다.
Create>3D object>Quad 를 선택하여 생성하고 이름을 Background로 바꿉니다.

Quad를 생성했지만 게임뷰상에선 아무 것도 보이지 않습니다. 씬뷰를 보면 두께가 없는 오브젝트가 생성되었지만 카메라를 마주보고 있지 않아서 보이지 않는 것을 알 수 있습니다.

Reset하여 정확한 위치를 잡아주고 X Rotation을 90으로 바꿔줍니다.


이제 Background가 메인 카메라를 보고 있으며, 게임뷰에서 확인됩니다.

이것은 배경을 입히는 용도이므로 Mesh Collider가 필요없습니다. Mesh Collider 옆 톱니표시를 누르고 Remove Component를 선택하여 삭제합시다.

이제 이 Background에 텍스쳐를 추가할 겁니다.

Assets>Textures 폴더에 보면 tile_nebula_green이라는 파일이 있습니다.

이 이미지를 선택하면 인스펙터창에 이 이미지 importer가 뜹니다. 이 임포터의 설정은 그대로 둘 것입니다. 그 하방에 이 이미지의 미리보기가 있고 이 파일의 해상도, 압축 방식, 이미지 용량 등의 추가 정보를 볼 수가 있습니다.


이 이미지는 1024 * 2048의 큰 이미지입니다. 우리의 게임 설정은 600 * 900 이므로 이보다 많이 작습니다. 그리고 이미지는 정사각형이 아니라 직사각형입니다. 이 이미지를 메쉬에 적용하는 데는 여러가지 방법이 있습니다.

가장 간단한 방법은 이미지를 드래그하여 씬의 Quad에 가져가는 것입니다.

어떻게 이미지가 적용된 것인지에 대한 답은 Background의 인스펙터를 보면 알 수 있습니다.

Materials의 Element로 이미지 파일이 적용되어 Mesh Renderer가 이것을 참조하게 되는데, 이 과정에서 자동으로 Standard 쉐이더가 적용됩니다.

이미지는 적용되었지만 지금의 Background는 너무 작고, 어두워서 보기가 어렵습니다. 먼저 TransformScale 프로퍼티를 바꿔서 크기조정을 해 봅시다.

Quad는 2차원의 오브젝트로서 X, Y 크기만 조절 가능합니다. 여기에 이미지를 맞출 것인데 원본 이미지는 가로세로비가 1:2입니다. 이 비율을 유지해야 이미지가 뒤틀리는 일이 없을 겁니다.

게임뷰를 보면서 X Scale을 먼저 조절해 봅니다. 이리 저리 늘려보면 적어도 12 이상은 되어야 화면을 채울 수 있습니다. 넉넉잡고 15로 설정합니다. 그리고 이미지 비율을 지키려면 당연히 Y Scale은 30으로 해야 할 것입니다. 그러면 뒤틀림 없이 이미지가 가득 채워집니다.


다시 씬뷰로 돌아와 F를 눌러 Frame Selected를 해서 보기 좋게 만듭니다. 크기는 적당해진 것 같은데 아직 이미지가 너무 어두운 감이 있습니다. 게다가 Smoothness가 0.5로 설정되어 반사광이 비추고 있습니다(1.0이면 완전 무광택, 0이면 완전이 거울처럼 매끈한 면인 것 같습니다)

우리의 배경은 우주 공간이므로 우주선에 비추는 빛과 관계없어야 할 것입니다. Directional light를 이 배경에 비추는 것은 아무런 의미가 없습니다. 따라서 쉐이더를 Unlit>Texture로 바꿔서 아무런 특별한 조명을 사용하지 않게 바꿉니다.



이제 Player를 다시 화면에 나타나게 체크 해주면 좀 문제가 생긴 것을 알 수 있습니다.

비행선의 일부가 배경에 묻혀버렸네요. Background의 Y Position을 -10으로 바꿔줍시다.



우리의 시점이 Perspective가 아닌 Orthographic이기 때문에 배경에 변화없이 플레이어 오브젝트가 잘 보이게 됩니다.



Moving the player


환경이 모두 갖춰졌으니 이제 우리의 비행선을 콘트롤 해보도록 합시다. 여기에는 코드가 필요하므로 먼저 Assets의 루트 폴더에 Create>Folder 하여 Scripts라는 폴더를 생성합니다. 그리고, Hierarchy창에서 Player를 선택하고 인스펙터 창에서 Add Component>New Script를 선택하고 이름을 PlayerController로 정해줍니다.

그러면 루트 레벨에 PlayerController.cs라는 파일이 생성되는데 이 파일을 드래그하여 Scripts폴더로 가져다 놓습니다.


다시 그 폴더로 들어가 코드 파일을 선택하고 우측 인스펙터 창의 Open...을 누르면 에디터가 열립니다.

코드가 뜨면 내부의 샘플 코드를 제거하고 코드 작성에 들어갑니다.

우리는 우리의 비행선을 물리법칙에 의해 움직일 것이고, 지난 번의 예제에서 그런 경우 FixedUpdate 메소드 내에서 코드를 작성해야 한다고 알게 되었습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
    void FixedUpdate()
    {
        
    }
}
cs


FixedUpdate내에
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
cs
으로 입력된 가로세로 값을 얻어옵니다.

이제 이 얻어온 값을 우리의 게임 오브젝트에 적용해야 합니다. 그러기 위해서 전과 마찬가지로 Rigidbody에 대한 레퍼런스를 얻어옵니다.
private Rigidbody rb;
private void Start()
{
    rb = GetComponent<Rigidbody>();
}
cs

그리고, 다시 FixedUpdate 내부에서 이 rb 객체에 가해질 벡터를 하나 생성하고 velocity에 직접 접근해 새 값으로 설정합니다.

public class PlayerController : MonoBehaviour
{
    private Rigidbody rb;
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement;
    }
}
cs

여기까지의 코드를 저장하고 유니티로 돌아와 씬을 저장한 뒤 실행해보면 비행선이 움직이는 것을 확인할 수 있습니다. 그런데 굉장히 느립니다. 이유는 GetAxis 함수가 0~1사이의 값이기 때문입니다. 따라서 초당 1유닛의 속도를 넘을 수 없습니다.

다시 PlayerController 스크립트로 돌아갑니다.

스피드를 조절할 필요가 있으므로 speed라는 퍼블릭 변수를 생성합니다.
그리고 전의 예제에서처럼 movementspeed를 곱해서 속도를 조절합니다.

public class PlayerController : MonoBehaviour
{
    public float speed;
    private Rigidbody rb;
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * speed;
    }
}
cs

코드를 저장하고 다시 유니티로 돌아와서 Player를 선택하고 인스펙터 창을 보면 Speed라는 프로퍼티가 생성되어 있습니다.


이제 여기에 값을 입력하여 비행선의 속도를 조절해 볼 수 있습니다. 10을 넣고 실행해보면 시원스레 움직이는 것이 보입니다. 그런데 움직이다 보면 화면의 범위를 넘어갑니다. 게임 영역 내로 움직임을 제한할 필요가 있습니다.

여기서 우리는 Mathf 클래스를 필요로 합니다. API를 검색해보면 여러가지 메소드, 필드들이 들어 있습니다만, 그 중에 우리가 필요로 하는 것은 Clamp입니다.

Clampfloat 타입의 최대/최소값이 정해준 값의 범위를 넘지 못하게 정해주는 정적 메소드입니다. 우리의 비행선은 xz 평면에서만 움직이며, y는 0으로 고정되어 있습니다.

public float xMax, xMin, zMax, zMin;
cs
그 값의 범위에 들어갈 필드를 선언해 줍니다.

그리고 FixedUpdate 안에서 그 최대 최소값을 설정해주면 코드는 다음과 같이 됩니다.

public class PlayerController : MonoBehaviour
{    
    public float speed;
    public float xMax, xMin, zMax, zMin;
    private Rigidbody rb;
 
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    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, xMin, xMax), 
            0.0f, 
            Mathf.Clamp(rb.position.z, zMin, zMax));
    }
}
cs

저장 후 유니티로 돌아가보면 모두 합쳐 4개의 최대,최소 프로퍼티가 생성되어 있음을 볼 수 있습니다.


그런데, 사실 이렇게 하면 고작 네개의 프로퍼티에 인스펙터창의 지나칠 정도로 큰 부분을 차지할 뿐만 아니라, 여기서만 사용 가능한 스크립트가 되어 재사용성이 떨어집니다. 따라서 이 경계 설정 부분을 코드로 옮겨 독립된 클래스로 만들면 좋을 것입니다.

코드에서 Boundary라는 퍼블릭 클래스를 선언하여 네개의 최대최소 변수를 옮기고, PlayerController 클래스에서 Boundary형 변수를 선언합니다.

public class Boundary
{
    public float xMax, xMin, zMax, zMin;
}
 
public class PlayerController : MonoBehaviour
{    
    public float speed;
    public Boundary boundary;
    private Rigidbody rb;
 
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    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)
            );
    }
}
cs

이것만 가지고는 유니티에서 Boundary라는 클래스가 나타나게 할 수 없습니다. 인스펙터가 볼 수 있도록 serializing해줘야 합니다.

[System.Serializable]
public class Boundary
{
    public float xMax, xMin, zMax, zMin;
}
cs

Boundary 클래스 앞에 어트리뷰트를 덧붙여 인스펙터 창에 나타나도록 하면,
독립된 클래스로서 나타납니다.

게임뷰 상에서 플레이어 오브젝트를 이리 저리 움직여서 경계를 확인해보면
x는 -6에서 6, y는 -4에서 15 정도인데 상단에는 장애물 오브젝트들이 나와야하므로 가장 위까지 가게 하지는 않을겁니다. 그래서 y는 -4에서 8로 설정합니다.


플레이 시켜보면 화면을 벗어나지 않고 잘 움직이는 것을 볼 수 있습니다. 여기서 추가적으로 재미를 위해 좌우 이동 시 비행선이 기울어지도록 float타입의 tilt라는 변수를 추가합니다.  그리고 회전을 위해 Quaternion이라는 클래스의 Euler함수를 사용합니다.

지금까지의 전체 코드는 다음과 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
[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;
 
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    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

저장 후 유니티에 나타난 Tilt 프로퍼티를 4 정도로 입력해주면 비행선이 좌우로 움직일 때 기울어지는 것을 볼 수 있습니다.

댓글 없음:

댓글 쓰기