[기획/구현] 아이템 추가 - 0. 아이템 구현

가이등·2022년 9월 26일
0

John Lemon 프로젝트

목록 보기
7/8

⚡ 아이디어: 아이템

게임에는 보통 수집할 수 있는 아이템이 존재한다.
아이템이 있으면 맵을 더 자세히 탐험하게 되고, 플레이 타임을 늘리면서, 다른 도전 욕구를 자극할 수 있다.

다음은 기본 에셋으로 주어진 맵이다. 맵을 자세히 보면,

기본 플레이로 설정된 경로 (보라색 선) 를 보면 사용하지 않는 공간 (초록색 동그라미) 이 보인다.
해당 공간에 아이템을 배치한다면 더 재미있는 플레이를 만들 수 있을 것이다.

구상한 아이템의 종류는 다음과 같다.

  1. 점수 시스템을 위한 코인
  2. 히든 엔딩을 보기 위한 열쇠
  3. 적(Ghost)와 친구가 되기 위한 컵케이크

이 중 이 글에서 소개할 내용은 전반적인 아이템 구현에 관한 설명이다.

2. 열쇠3. 컵케이크 는 글이 길어질 것 같아 따로 소개하고자 한다.


구체적인 구현 방식은 다음과 같다.

코인

  • 코인 10개를 생성하여 맵 전역에 배치한다.

열쇠, 히든 엔딩

  • Rust Key 3개를 배치한다.
  • Rust Key 3개로 열 수 있는 문(Secret Door)을 만든다.

  • Rust Key로 열 수 있는 방 안에 Hidden Key를 배치한다.
  • Hidden Key로 열 수 있는 문(Hidden Door)을 만든다.
  • Hidden Key로 문을 열고 탈출하면 Hidden Ending을 볼 수 있다.

컵케이크, 유령과 친구되기

  • 컵케이크를 배치한다.
  • 컵케이크를 Crying Ghost에게 가져다 주면 유령과 친구가 될 수 있다.
  • 친구가 되면 모든 유령은 더 이상 플레이어를 공격하지 않는다.

⚡ 아이템 구현 순서

  1. 아이템 획득 관련 수치를 관할하는 Static Class(GameManager)를 만든다.

  2. 플레이어와 충돌 시 관련 수치를 +1 혹은 true로 설정한 후, 아이템을 destroy한다.

  3. 아이템 관련 UI 를 구현한다.


⚡ GameManager 스크립트 작성

아이템 획득, 더 나아가서 게임 점수까지 관할하는 스크립트를 작성하려고 한다.

게임 내에서 하나만 존재하고, 많은 스크립트들이 GameManager Class를 참조할 것이기 때문에
GameManager Class를 Static으로 만들기로 하였다.

public static class GameManager {
    public static string playerName = "John Lemon";
    public static int coin = 0;
    public static float time = 0;
    
    public static int rustKey = 0;
    public static bool isSecretDoorOpened = false;
    public static bool gotHiddenKey = false;
    public static bool isHiddenDoorOpened = false;

    public static bool gotCupCake = false;
    public static bool isFriendWithGhost = false;

    public static bool isGameEnd = false;
    
    public static void GotCoin() {
        coin++;
    }

    public static void GotRustKey() {
        rustKey++;
    }

    public static void GotHiddenKey() {
        gotHiddenKey = true;
    }
    
    public static void GotCupcake() {
        gotCupCake = true;
    }
}

다른 스크립트에서는 GameManager.rustKey 같이 직접적으로 변수에 접근할 수 있다.

변수에 직접적으로 접근하는 것보단 접근할 함수를 따로 정의하는 것이 더 낫지만 시간 관계 상 그렇게 하진 못했다.


⚡ 아이템 에셋 준비

아이템에 사용할 모델링은 유니티 에셋에서 받았다.

Coin 에셋.

Rust Key와 Hidden Key 에셋.

Cupcake 에셋.

에셋을 받고 유니티에서 임포트해준다.


⚡ 아이템 스크립트 작성

플레이어와 충돌할 경우 동작을 설정해주어야 한다.

각 아이템마다 Tag를 생성하여 태그를 수정한다.


아이템 오브젝트에 적용된 콜라이더 컴포넌트에서 IsTrigger을 체크하여 트리거로 만든다.

public class Item : MonoBehaviour {
    private void OnTriggerEnter(Collider other) {
        if (other.CompareTag("Player")) {
            if (this.CompareTag("Coin"))
                GameManager.GotCoin();
            else if (this.CompareTag("RustKey")) 
                GameManager.GotRustKey();
            else if (this.CompareTag("HiddenKey"))
                GameManager.GotHiddenKey();
            else if (this.CompareTag("Cupcake"))
                GameManager.GotCupCake();
            Destroy(gameObject);
        }
    }
}

충돌한 오브젝트가 플레이어일 경우, GameManager의 관련 수치를 +1 또는 true로 설정하고 자신(아이템)을 destroy한다.


⚡ 발생한 문제: 아이템 회전

문제

아이템이 가만히 떠있는 것은 어색하니, 아래처럼 회전하는 동작을 주려고 하였다.
아이템 오브젝트의 rotation.x, rotation.z 수치는 유지한 채, rotation.y 값만 바뀌기를 원했다.

Transform 클래스의 Rotate 함수를 알게 되어 아이템 스크립트에 다음과 같은 코드를 추가하였는데,

	...
    private float rotationSpeed = 100f;

    private void Update() {
        transform.Rotate(Vector3.up * Time.deltaTime * rotationSpeed);
    }
    ...

다음과 같이 rotation의 모든 값(x,y,z)이 바뀌어 회전하게 되었다.


해결

public void Rotate (Vector3 eulerAngles, Space relativeTo = Space.Self);
//eulerAngles 를 적용하여 rotate한다.
//relativeTo가 없거나 Space.Self일 경우 transform의 local axes에 회전을 적용하고, Space.World일 경우 world axes 에 적용한다. 

Rotate() 함수의 인수가 1개일 경우, Space.Self로 적용되어 transform의 local axes에 회전을 적용한다.
Space.Self 냐 Space.World 이냐는 직접 적용해본 후 선택하는 것이 이해가 빠르다고 한다.

따라서 코드를 다음과 같이 수정하였다.

	...
    private float rotationSpeed = 100f;

    private void Update() {
        transform.Rotate(Vector3.up * Time.deltaTime * rotationSpeed, Space.World);
    }
    ...

코드를 수정하니 의도한 결과를 얻을 수 있었다.


⚡ 아이템 UI 설계

아이템은 우측 상단에 표시되도록 설계하였다.

코인은 얻은 갯수를 옆에 숫자로 표시하도록 하였고,
기타 아이템(열쇠, 컵케이크)은 획득했을 때 표시되고, 사용하면 사라지도록 설계하였다.

모든 UI 요소는 해상도에 맞춰질 수 있도록 앵커(우상단)를 활용하였다.


⚡ 아이템 UI 스크립트

public class ItemUI : MonoBehaviour {
    public GameObject rustKey_0UI, rustKey_1UI, rustKey_2UI, hiddenKeyUI, cupCakeUI;
    public TextMeshProUGUI timeText, coinText;
    
    private void Update() {
        GameManager.CheckTime();

        timeText.text = string.Format("{0:0.0} Time", GameManager.time);
        coinText.text = GameManager.coin.ToString();

        if (GameManager.rustKey == 1)
            rustKey_0UI.SetActive(true);
        else if (GameManager.rustKey == 2)
            rustKey_1UI.SetActive(true);
        else if (GameManager.rustKey == 3)
            rustKey_2UI.SetActive(true);

        if (GameManager.gotHiddenKey)
            hiddenKeyUI.SetActive(true);
        if (GameManager.gotCupCake)
            cupCakeUI.SetActive(true);
        
        if (GameManager.isSecretDoorOpened) {
            rustKey_0UI.SetActive(false);
            rustKey_1UI.SetActive(false);
            rustKey_2UI.SetActive(false);
        }
        if (GameManager.isHiddenDoorOpened)
            hiddenKeyUI.SetActive(false);
        if (GameManager.isFriendWithGhost)
            cupCakeUI.SetActive(false);
    }
}

GameManager 클래스의 값을 참조하여 상황에 맞도록 활성화/비활성화 되게 하였다.


⚡ 결과

동전을 먹을 때

열쇠(rust key)를 먹을 떄1

열쇠(rust key)를 먹을 떄2

컵케이크를 먹을 떄

아이템 충돌 시 액션과 GameManager 클래스에서의 수치 관리, UI 까지 모두 정상적으로 돌아가는 것을 확인할 수 있다. ‡

profile
안녕하세요구르트

0개의 댓글