게임 엔진/Unity

Unity 12일차 - 아이템 받기 게임-1(광원, 태그, 지금까지 것들을 응용)

FakeZero 2022. 2. 9. 04:08

  이번 강좌와 다음 강좌까지가 기타무라 마나미 저자의 [유니티 교과서]로 하는 마지막 강좌가 됩니다. 마지막까지 힘내봅시다.

 

  이번 강좌와 다음 강좌에는 지금까지 배웠던 것을 총동원하여 게임을 만들껍니다. 지금까지 배웠던 것을 다시 복습하는 시간이 되었으면 좋겠습니다.


1. 계획하기

  1. 만들 게임: 사과 받기 게임

  2. 필요한 리소스: 사과, 폭탄, 바구니, 밑판, UI

  3. 게임이 구동될 플랫폼: PC

  4. 필요한 기능: 자동으로 사과나 폭탄이 나오는 기능, 바구니를 움직이는 기능, 점수를 기록하는 UI


2. 리소스 준비하기

chapter8.zip
0.08MB

  (본 파일은 <유니티 교과서>의 출판사, [길벗]에서 제공한 것임을 밝히며 2차 수정 및 배포를 금지합니다.)


3. 프로젝트 생성 및 사전 설정

  Unity Hub의 새로운 버전이 나왔습니다. 새로워진 Unity Hub에서 프로젝트를 만들어 봅시다.

  에디터 버전을 잘 확인하고 만들도록 합시다. 저는 2021버전으로 만들었습니다.

  에셋도 불러와 주도록 합시다.

  씬도 저장해주도록 합시다.


4. 광원 및 오브젝트 배치

  Stage 에셋은 0, 0, 0 위치에, Main Camera 오브젝트를 0, 3.8, -1.6에 배치하고 Main Camera의 Rotation을 60, 0, 0으로 설정합시다.

 

  이번 게임에서는 사과와 폭탄이 떨어집니다. 떨어지는 위치를 미리 알려주기 위해선 오브젝트의 그림자 필요합니다. 그림자는 광원에 의해 생기므로 광원을 배치하도록 합시다. Unity에는 4가지 종류의 광원이 있습니다.

라이트 역할
Directional Light 태양광처럼 직선으로 한곳을 향해 나란히 가면서 빛을 비추는 라이트다. 라이트에서 떨어져도 빛의 세기가 감쇠하지 않는다.
Point Light 전체 방향으로 동등하게 빛을 비추는 라이트다. 라이트에서 벗어나면 빛의 세기가 감쇠한다.
Spot Light 특정 방향에서 중앙에서 사방으로 뻗어나가는 모양으로 빛을 비추는 라이트다. 라이트에서 벗어나면 빛의 세기가 감쇠한다.
Area Light 직사각의 평면에서 전체 방위로 투사되는 라이트다. 빛과 그림자의 정보를 미리 계산하고 텍스쳐에 새겨두는 처리(전문 용어로 베이크(bake))를 할 때만 사용할 수 있다.

  옛날에는 빛과 그림자의 방향을 직접 계산해야 했지만 기술의 발전에 따라 광원에 따른 그림자 위치나 그림자가 보이는 방향을 게임 엔진이 자동으로 계산해서 표시합니다.

 

  이번 경우에는 수직으로 내려오는 광원이 필요하므로 Directional Light를 써야 합니다. Directional Light는 이미 프로젝트를 만들 때 생성되어 있었으므로 이것을 사용하도록 하겠습니다.

  Directional Light의 Rotation을 90, 0, 0으로 설정해 줬습니다. 이것만으로도 조명이 상당히 변했을 것입니다. 기회가 된다면 Directional Light의 각도를 이리저리 돌려보세요. 상당히 다른 느낌을 받을 수 있을 것입니다.

 

  빛이 너무 강력하므로 조금 줄이도록 하겠습니다.

  Light 컴포넌트의 Intensity를 0.7로 줄여줍시다. 이것으로 광원 설정은 끝났습니다.


5. 배운걸 총동원! - 1

  이때까지 배운 것을 총동원해서 게임을 완성시켜 나갑시다.

 basket에셋을 0, 0, 0에 배치해 줍시다.

  만약 그림자가 맘에 안든다면 위의 Project Settings의 Shadow Distance를 조정해 그림자가 어느 정도로 생길지 정할 수 있습니다.

 

  이제 클릭으로 바구니를 이동할 수 있는 스크립트를 짜 봅시다.

  바닥은 3x3 타일입니다. 이곳에 그냥 클릭을 하면 위에서 떨어지는 오브젝트를 잡기가 쉽지 않을 것입니다. 따라서 스크립트를 통해 편의성을 제공할 필요가 있습니다.

  위의 그림 기준 X축만 생각했을 때

  탭한 좌표가

  1.5 이상 -0.5 미만 일때는 -1.0 위치에

  -0.5 이상 0.5 미만 일때는 0.0 위치에

  0.5 이상 1.5 미만 일때는 1.0위치에

  바구니가 생기도록 제어 하겠습니다. 즉, 위치 정보를 받아서 반올림을 하겠다는 뜻입니다. Z축도 같은 알고리즘을 적용할 수 있습니다. 저번에 사용한 적이 있는 Mathf 클래스를 이용하면 손쉽게 반올림을 할 수 있을 것입니다.

 

  새로운 C# 스크립트를 BasketController 라는 이름으로 바꿔줍시다. 그리고 다음과 같이 코드를 짜 줍시다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasketController : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                float x = Mathf.RoundToInt(hit.point.x);
                float z = Mathf.RoundToInt(hit.point.z);
                transform.position = new Vector3(x, 0, z);
            }
        }
    }
}

  이 스크립트에서는 탭한 좌표를 구하고 그 스크린 좌표를 월드 좌표로 변환할 수 있도록 클릭한 좌표로 향하는 광선을 구합니다.

 

  이전 시간에 이 광선은 콜라이더와의 충돌을 감지할 수 있다고 했습니다. 12번 줄의 hit라는 이름으로 되어있는 변수가 충돌을 감지하는 변수입니다. 13번 줄의 Physics.Raycast 메서드가 광선이 콜라이더에 닿았는지 확인해줍니다.

Physics.Raycast(|광선의 시작점|, |광선 방향|, |콜리전을 확인할 수 있는 최대 거리|)

  광선의 시작점을 클릭한 스크린 좌표가 변환된 월드 좌표(ray)로 하고 광선의 방향은 광선이 바닥에 맞은 좌표의 방향(hit)으로, 콜리전을 확인할 수 있는 최대 거리는 무한대로 하기 위해 Mathf 클래스를 이용해 무한대를 대입해줍시다. 광선 방향 쪽 인자에 들어가 있는 out 키워드는 hit에 포함되어 있는 좌표를 가지고 와서 쓸 수 있도록 해줍니다.

  만든 BasketController 스크립트를 적용했어도 아직 이 스크립트는 제대로 작동하지 않습니다. 밑판에 콜라이더가 없기 때문입니다.

  밑판에 Box Collider 컴포넌트를 할당하고 Size 값을 3, 0.1, 3으로 바꿔줍시다.

 

  원하는 장소로 잘 이동하는 것을 확인할 수 있습니다.

 

  이제 위에서 떨어질 오브젝트를 만들어봅시다.

  사과와 폭탄 오브젝트를 배치하고 위와 같은 위치에 적용해줍시다.

 

  이제 오브젝트를 떨어뜨리는 스크립트를 작성합시다. ItemController라는 이름으로 새로운 C# 스크립트를 만들어서 다음과 같이 프로그램합시다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemController : MonoBehaviour
{
    public float dropSpeed = -0.03f;

    void Update()
    {
        transform.Translate(0, this.dropSpeed, 0);
        if(transform.position.y < -1.0f)
        {
            Destroy(gameObject);
        }
    }
}

  스크립트를 적용한 오브젝트를 매 프레임마다 0.03씩 높이를 내리고 -1.0 미만으로 y좌표를 내려가면 해당 오브젝트를 제거하도록 합니다.

  이제 위에서 떨어질 오브젝트인 사과와 폭탄 오브젝트에 스크립트를 적용해줍시다.

 

  잘 떨어지는 것을 확인했습니다.

 

  이제 아이템이 바구니에 들어갈 수 있도록 스크립트를 짜도록 하겠습니다. 일단 바구니로 오브젝트를 받으면 콘솔 창에 잡았다는 표시가 나오도록 하겠습니다.

 

  그 전에 스크립트가 필요한 정보를 얻을 수 있도록 오브젝트들에 컴포넌트를 넣어주도록 하겠습니다.

  일단 떨어지는 오브젝트에 구형 콜라이더를 추가해서 중심은 0, 0.25, 0으로 반지름은 0.25로 만들어줍시다.

  바구니는 Rigidbody 컴포넌트를 추가하고 Is Kinematic을 활성화해서 물리 엔진의 영향을 받지 않도록 합시다.

  Box Collider 컴포넌트도 추가하고 중심은 0, 0.5, 0으로 Size는 0.5, 0.1, 0.5로 바꿔줍시다. 그리고 충돌해도 다른 영향이 있어선 안되므로 Is Trigger도 활성화 해 줍시다.

 

  이제 BasketController를 아래와 수정합시다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasketController : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        Debug.Log("Catch!");
        Destroy(other.gameObject);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                float x = Mathf.RoundToInt(hit.point.x);
                float z = Mathf.RoundToInt(hit.point.z);
                transform.position = new Vector3(x, 0, z);
            }
        }
    }
}

(7~11번 줄이 추가되었습니다.)

  Is Trigger가 활성화 되어있으므로 OnCollision이 아니라 OnTrigger을 이용합니다. 충돌한 후에는 충돌한 오브젝트를 삭제합니다.

 

  제대로 Catch가 출력되었습니다. 다만 이래서는 사과와 폭탄을 구별할 수 없습니다.


6. 태그

  유니티에서는 태그(Tag)라고 하는 오브젝트를 분별하는 시스템이 있습니다.

 

  그림자를 설정할 때 들어갔던 Project Setting에 다시 한 번 들어갑시다.

  Apple과 Bomb이라는 두 개의 태그를 만들어 둡시다.

  위의 방법으로 Inspector 창에서 apple과 bomb 오브젝트에 각각 태그를 지정해줍시다.

 

  이제 BasketController 스크립트를 열고 다음과 같이 수정합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasketController : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.tag == "Apple")
        {
            Debug.Log("Tag=Apple");
        }
        else
        {
            Debug.Log("Tag=Bomb");
        }
        Destroy(other.gameObject);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                float x = Mathf.RoundToInt(hit.point.x);
                float z = Mathf.RoundToInt(hit.point.z);
                transform.position = new Vector3(x, 0, z);
            }
        }
    }
}

(9~17번 줄이 추가되고 변경되었습니다.)

  콜라이더에 오브젝트가 감지되면 해당 오브젝트의 태그가 Apple인지 확인하고 태그에 따라서 콘솔창에 다른 문구를 출력합니다.

 

  이제 사과 오브젝트와 폭탄 오브젝트를 구분할 수 있게 되었습니다.

 

  단순히 받기만 하면 재미없으니 사운드도 집어넣도록 하겠습니다.

  Basket 오브젝트에 Audio Source 컴포넌트를 추가해두도록 합시다. 한 개의 효과음을 낼 때는 오디오 에셋을 지정해주면 되지만 이번엔 2가지 에셋을 써야하므로 비워두고 스크립트를 통해 제어하겠습니다. BasketController 스크립트를 열도록 합시다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasketController : MonoBehaviour
{
    public AudioClip appleSE;
    public AudioClip bombSE;
    AudioSource aud;

    void Start()
    {
        this.aud = GetComponent<AudioSource>();
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.tag == "Apple")
        {
            this.aud.PlayOneShot(this.appleSE);
        }
        else
        {
            this.aud.PlayOneShot(this.bombSE);
        }
        Destroy(other.gameObject);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                float x = Mathf.RoundToInt(hit.point.x);
                float z = Mathf.RoundToInt(hit.point.z);
                transform.position = new Vector3(x, 0, z);
            }
        }
    }
}

(7~14번줄이 추가되고 18~25번 줄이 수정되었습니다.)

  Audio Source 컴포넌트를 제어하기 위해 AudioSource 변수를 씁니다. PlayOneShot은 여러개의 오디오 에셋을 이용할 때 쓰는 메서드입니다.

  이제 아웃렛 접속을 이용해 AppleSE에는 get_se에셋을, BombSE에는 damage_se에셋을 할당하도록 합니다.

 

  소리가 잘 나오는 것을 확인했습니다.


  다음 강좌가 정말 마지막 강좌가 됩니다. 다음 강좌까지만 열심히 해 봅시다.

 

  긴 강좌 읽고 따라하시느라 수고하셨습니다.