전체 페이지뷰

2017년 10월 13일 금요일

Unity Tutorial: 2D Roguelike, part 10


Adding UI and Level Transitions



제목 , 점수 등을 보여줄 UI를 추가하려고 합니다.

Hierarchy에 Create>UI>Canvas를 선택하여 캔버스를 생성합니다. 이 캔버스에 앞으로 추가될 모든 UI 요소를 올려놓게 될 것입니다.

먼저 Create>UI>Image로 이미지를 하나 추가하고 이름을 LevelImage라고 정합니다. 씬 뷰에서 잘 보이도록 크기 조절을 해놓고, Image를 선택 후 Anchor Presets에서 좌하단을 Alt+Click하여 이미지를 전체로 넓힙니다.


그리고 그 색은 검은색으로 바꿉니다.

다음으로 현재 몇 판째인지를 알려 줄 텍스트를 만들겠습니다.

Canvas를 선택한 상태에서 Create>UI>Text하고 이름은 LevelText라고 하겠습니다. 그리고 역시 Anchor Preset에서 가운데 사각형을 Alt+click하고, Color는 흰색으로 바꿉니다. Font Size는 32로 해 주는데, 이렇게 하면 현재 정해진 텍스트 크기에 비해 폰트가 너무 크기 때문에 화면에서 글씨가 사라집니다. 게다가 이 텍스트에서 보여줄 것들의 종류가 다양하고 글씨도 여러가지이므로 이것을 해결하기 위해 Horizontal Overflow와 Vertical Overflow를 모두 Overflow로 바꿉니다. 정렬은 두개 다 가운데로 하고, 폰트는 프로젝트에 포함된 폰트인 PressStart2P-Regular로 합시다. 그리고 Text는 "Day 1"으로 하겠습니다.



여기까지 했으면 이제 LevelImage가 deactivate되었을 때 LevelText도 함께 deactivate 되도록 Hierarchy 상에서 LevelText를 드래그하여 LevelImage의 자식으로 만들어줍니다.



다음은 FoodText 차례입니다. Canvas를 선택하고 다시 한번 Create>UI>Text하여 텍스트를 생성하고 이름은 FoodText로 합니다. 이 텍스트는 화면 하단 중앙에 위치시킬 예정입니다. Anchor Presets를 열고 하단 중앙 사각형을 Alt+클릭합니다.



Color는 흰색, Font Size 24, 정렬은 역시 둘 다 가운데로 합니다. Text는 "Food 100"으로 바꾸고 Font를 PressStart2P-Regular로 합니다. 글씨가 잘리지 않도록 Horizontal, Vertical Overflow를 Overflow로 선택합니다.

그런데 글씨가 너무 아래에 있는 것 같아 조금 끌어올리려고 합니다.
앵커를 조금 끌어올리기 위해 Anchor의 Y Min, Max를 모두 0.05로 바꿉니다. 그러면 글씨 위치는 변하지 않고, Rect Transform의 Pos Y만 -6으로 바뀌었습니다. 이것을 0으로 해 주면 글씨가 변경된 앵커에 맞추어 살짝 올라옵니다.



전에도 말씀 드렸지만 캔버스의 Hierarchy는 포토샵의 레이어처럼 층 구조를 이루고 있어서 가장 아래에 위치한 것이 레이어상으로 가장 위에 있는 것처럼 표시됩니다.

FoodText는 게임 중에만 나와야 하므로 드래그해서 LevelImage 위로 순서를 바꿉니다.


이제 이 UI들을 스크립트로 제어하도록 하겠습니다.

Scripts 폴더의 GameManager를 에디터로 엽니다.

전에 테스트를 위해 level을 3으로 초기화했었는데 이것을 1로 바꿉니다.
private int level = 1// 레벨(3부터 적이 나타남)
cs

UI요소들을 사용하기 위해 네임스페이스 선언도 추가합니다.
using UnityEngine.UI;
cs

몇 가지 변수들을 추가합니다.

public float levelStartDelay = 2f; // 레벨 시작 전 딜레이
...
private Text levelText; // 현재 레벨 표시용 텍스트
private GameObject levelImage; // 레벨 텍스트 백그라운드 이미지
private bool doingSetup; // 보드 세팅 중인지 판정
cs


다음으로 동영상에서는 OnLevelWasLoaded API를 사용하고 있는데, 이것은 현재 deprecated 되었고 대신 SceneManager.sceneLoaded event를 사용해야 합니다.
따라서 네임 스페이스 선언부에
using UnityEngine.SceneManagement;
cs
를 추가합니다.

이 이벤트를 사용하기 위해서는 먼저 이벤트가 로딩 되었을때 알려줄 delegate가 필요합니다.
// 씬이 로딩을 마칠 때마다 호출됨
void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
{
    // 레벨에 1을 더함
    level++;
    // 레벨 초기화
    InitGame();
}
private void OnEnable()
{
    // OnLevelFinishedLoading 메소드에게 씬변화 이벤트를 듣도록 시킴
    SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
private void OnDisable()
{
    SceneManager.sceneLoaded -= OnLevelFinishedLoading;
}
cs

다음은 InitGame()을 수정하고, 그 안에서 사용할 HideLevelImage()를 작성합니다.
void InitGame()
{
    doingSetup = true;
    levelImage = GameObject.Find("LevelImage");
    levelText = GameObject.Find("LevelText").GetComponent<Text>();
    levelText.text = "Day " + level;
    levelImage.SetActive(true);
    Invoke("HideLevelImage", levelStartDelay);
    enemies.Clear();
    boardScript.SetupSecene(level);
}
private void HideLevelImage()
{
    levelImage.SetActive(false);
    doingSetup = false;
}
cs

Update()도 수정합니다.
private void Update()
{
    // 플레이어의 차례이거나 이미 적이 움직이는 중이라면 메소드 빠져나감
    if (playersTurn || enemiesMoving || doingSetup)
        return;
    ...
}
cs

GameOver도 수정합니다.
public void GameOver()
{
    levelText.text = "After " + level + "days, you starved.";
    levelImage.SetActive(true);
    enabled = false;
}
cs

이제 스크립트를 저장하고 Player 스크립트를 에디터로 엽시다.
FoodText를 표시하도록 해야 합니다.

네임 스페이스 선언부에 UI를 추가합니다.
using UnityEngine.UI;
cs

그리고 퍼블릭 변수 하나도 추가합니다
public Text foodText;
cs

그리고 Start() 내에서 foodText를 초기화하고,
protected override void Start ()
{
    animator = GetComponent<Animator>();
    // 게임매니저에 저장된 푸드 포인트를 얻어 옴
    food = GameManager.instance.playerFoodPoints;
    foodText.text = "Food: " + food;
    base.Start();
}
cs

AttmptMove에서도 푸드 포인트를 감소시킨 뒤에 푸드텍스트를 다시 설정합니다.
protected override void AttemptMove<T>(int xDir, int yDir)
{        
    food--// 한턴 움직일 때마다 푸드 포인트 1 감소
    foodText.text = "Food: " + food;
    ...
}
cs

OnTriggerEnter2D에서 food, soda를 만났을 때에도 텍스트를 수정해야 합니다.
private void OnTriggerEnter2D(Collider2D other)
{
    // 감지한 것이 Exit이면 다음판 시작
    if (other.tag == "Exit")
    {
        Invoke("Restart", restartLevelDelay);
        enabled = false;
    }
    // Food이면 포인트 추가하고 사라지게 함
    else if (other.tag == "Food")
    {
        food += pointsPerFood;
        foodText.text = "+" + pointsPerFood + " Food:" + food;
        other.gameObject.SetActive(false);
    }
    // Soda도 포인트 추가, 사라지게 함
    else if (other.tag == "Soda")
    {
        food += pointsPerSoda;
        foodText.text = "+" + pointsPerSoda + " Food:" + food;
        other.gameObject.SetActive(false);
    }
}
cs

LoseFood에서도 푸드 포인트를 잃으면 텍스트를 변경합니다.
public void LoseFood(int loss)
{
    // 애니메이터 상태 변경
    animator.SetTrigger("playerHit");
    food -= loss; // 음식 포인트 차감
    foodText.text = "-" + loss + " Food:" + food;
    CheckIfGameOver();
}
cs

스크립트를 저장하고 유니티로 돌아갑시다.

Hierarchy의 Player를 선택하면 Player 스크립트 컴포넌트에 Food Text라는 프로퍼티가 생겨나 있습니다. 여기에 FoodText를 드래그하여 연결합니다.



이 상태에서 테스트를 하니 영상과는 달리 Player.cs에서 NullReferenceException이 발생됩니다. 곤혹스럽네요. 유니티 포럼 검색해보니 같은 증상을 호소하시는 분들이 간혹 있는데 딱 떨어지는 답을 찾을 수가 없었습니다.

한참을 찾다보니 언제 어느 과정에서 들어간건지는 모르겠지만 GameManager 컴포넌트에 Player 스크립트가 포함되어 있는 것을 발견했습니다. 거기에 들어가 있을 이유가 없는데 왜  있나 싶어서, 체크해제하고 실행해보니 에러가 사라집니다.



며칠 동안 어디가 잘못된 것인가 찾았는데 역시 문제는 저였군요. Player 스크립트 작성하는 영상의 후반부 프리팹에 스크립트 연결하는 부분에서 잘못되었던 것 같습니다(해당 글은 수정했습니다).

댓글 없음:

댓글 쓰기