본문 바로가기

게임 제작 기록/Insomniac

[Unity] '맵 메이킹 게임' 3 - 블럭 선택

이번엔 블럭을 잡는 부분을 개발해보자.

 

생각난 방법은 있었으나 다른 방법도 참고하기 위해 구글링을 했고, Poly Bridge에 비슷한 기능이 있었기에 'how to make polybridge in unity'로 검색해 자료를 찾았다.

 

URocks!, "How to make a Bridge Building Game in Unity Part 1", YouTube, 2020.5.21, https://www.youtube.com/watch?v=ejStfmwNYtw

 

 

이후 이런 게임들이 Building game으로 불리는 걸 알았고, 유튜브에 'how to make a building game'으로 검색해 가장 비슷한 자료를 찾아냈다.

 

Blackthornprod, "How to make a basic CITY BUILDER in Unity! - 2022 TUTORIAL", YouTube, 2022. 1. 10., https://www.youtube.com/watch?v=n5EN2J2FxOQ

 

이 영상이 생각을 정리하는데 큰 도움을 줬다. (자잘한 몇 가지 꿀팁과 함께.)


파트를 나눠보자

크게는 오브젝트를 배치하는 기능이지만, 이는 잘게 쪼개면 아래처럼 나눌 수 있다.

  • 버튼을 클릭해 오브젝트를 선택한다.
  • 선택된 오브젝트가 마우스 커서를 따라다닌다.
  • (오브젝트가 선택된 상태에서) 타일을 누르면 오브젝트가 배치된다.

더 잘게 쪼개면 이미 배치된 타일엔 등록이 불가능하다던가, 오브젝트가 빨갛게 변한다거나 뭐 여러 가지가 있겠다만, 개발 순서대로 쪼개본다면

  1. UI - 오브젝트 클릭 후 커서를 따라다님, 우클릭 시 삭제
    1. 관련된 정보를 담을 컨테이너도 함께 생성
  2. 타일 - 자신의 상태를 설정, 비어있고 커서가 클릭되면, 스내핑되어 오브젝트가 배치됨.

순서로 개발하면 될 것 같다.


UI 개발

우선 새 브랜치부터 팠다.

 

 

이전에 Inventory.cs에 없는 오브젝트 버튼은 숨기는 기능을 추가해뒀으니, 이 부분은 걱정할 게 없다.

 

데이터를 먼저 연결하냐 UI를 먼저 연결하냐의 문젠데, 나는 데이터를 먼저 연결하기로 했다.

 

스테이지 별 오브젝트

각 스테이지 별 오브젝트 개수를 담아둘 StageInfo를 생성했다.

StageInfo엔 사용 가능한 오브젝트 딕셔너리, 카메라 위치, 초기 타일 배치 등이 포함된다.

 

타일 배치는 타일 생성 후 하기로 하고, 지금은 usableObjects 딕셔너리만 쓴다.

 

Dictionary는 에셋을 쓰지 않는 한, 인스펙터에서 만들 수 없다. 그래서 ScriptableObjects를 만들어, 1차원 배열 두 개로 저장했다.

 

그리고 이전에 아이템 코드를 넣으면 오브젝트를 만들어주는 ObjectCreator를 만들어뒀기에, 아이템 코드 배열 1개, 개수  배열 1개를 만든다.

 

using UnityEngine;

[CreateAssetMenu(fileName = "Stage 0", menuName = "Stage Info")]
public class UsableObjects : ScriptableObject
{
    public ObjectCode[] code;
    public int[] count;
}

 

ObjectCode는 ObjectCreator 위에 선언된 enum 클래스다.

 

 

그럼 이렇게, 편법으로 에셋 생성이 가능해진다. 직관적이진 않지만.

 

public Dictionary<ObjectCode, int> objectDict = new Dictionary<ObjectCode, int>();
public UsableObjects objectInfo;   // 실제 정보가 들어있는 에셋 (ScriptableObject)

private void Awake()
{
    ConvertObjectsArrayToDictionary();
}

private void ConvertObjectsArrayToDictionary()
{
    for (int i = 0; i < objectInfo.code.Length; ++i)
    {
        objectDict[objectInfo.code[i]] = objectInfo.count[i];
        Debug.Log(objectInfo.code[i] + ": " + objectDict[objectInfo.code[i]]);
    }
}

 

UsableObjects 스크립트로 스테이지 오브젝트 정보를 생성하고, 연결하고, Dictionary로 컨버트하는 과정을 거쳤다.

 

 

디버깅도 성공적으로 됐으니 디버깅 코드는 삭제.

 

이로써 스테이지 별 사용 가능한 오브젝트 정보는 완성되었다.

 

인벤토리와 연결

이제 오브젝트 목록과 개수가 정해졌으니, 이를 UI에 연동해 인벤토리에 띄워보자.

 

StageInfo를 StageStarter로 새로 이름짓고, 이 스크립트가 GameManager를 사용해 스테이지를 초기화하는 역할을 맡는다.

 

클리어 시 다음 StageStarter를 불러와 실행시키면, 다음 스테이지가 시작된다.

 

public void StartStage()
{
    Inventory.Instance.SetAllItemToSlot(objectDict);

    GameManager.Instance.InitializeStage();
}

 

이 함수가 그런 역할을 맡아줄 것이다.

 

Inventory.cs의 SetAllItemToSlot 함수는 모든 오브젝트를 슬롯에 할당하며, 개수가 0개거나 아이템이 없는 슬롯은 숨김 처리해준다.

 

public void SetObject(ObjectCode objectCode, int count)
{
    myObject = ObjectCreator.Instance.GetObject(objectCode);

    icon.sprite = myObject.GetComponent<SpriteRenderer>().sprite;
    countText.text = count.ToString();
}

 

Slot.cs의 SetObject 함수는 오브젝트 코드와 개수를 받아와 자신의 UI를 갱신하며, 내부 데이터 또한 변경된다.

방금 만든 UsableObjects를 연결해보면

 

 

이렇게 오브젝트가 잘 갱신된다. (스프라이트가 안 뜨는 이유는 아직 안 만들었기 때문)

 

클리어 시 다음 StageStarter를 활성화하고 실행하는 건 나중으로 미뤄두자.

이름 변경

이쯤에서 Object보다 Block이 더 직관적이고, 모호하지 않은 이름임을 깨달아, 모든 코드의 Object를 Block으로 교체해주었다.


클릭 시 오브젝트 선택하기

이번엔 버튼을 클릭했을 때, 마우스 위치에 선택된 오브젝트를 올려볼 것이다.

 

먼저, 커서를 따라다닐 이미지를 만들어보자.

 

 

Square 스프라이트로 생성한 후, Sprite를 None으로 변경해 투명하게 만들어줬다. 앞으로 오브젝트가 선택되지 않으면 null을 할당해 보이지 않게 처리할 것이다.

 

Selected Block는 빈 게임 오브젝트로, 아이콘의 피벗을 바꾸기 위해 만들었다.

쉽게 말하면, 커서 기준으로 얼마나 옮겨서 표시할 것인가?를 위한 것이다.

 

UI가 아닌 스프라이트로 생성한 건, 실제 오브젝트와 크기를 1:1로 맞추기 위함이다.

 

private void Update()
{
    if(currentBlock != null)
    {
        FollowCursor();
    }
}

...

public void SelectBlock(GameObject block)
{
    currentBlock = block;
    spriteRenderer.sprite = block.GetComponent<SpriteRenderer>().sprite;
}

private void FollowCursor()
{
    cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}

public void ClearSelector()
{
    currentBlock = null;
    spriteRenderer.sprite = null;
}

 

인벤토리 버튼이 클릭되면 SelectBlock에 자신의 블럭을 넣을 수 있게 코딩했으며, currentBlock이 null이면 FollowCursor 함수가 실행되지 않게 했다.

 

이후 배치 후 블럭을 비우기 위해 ClearSelector 함수를 추가해뒀다.

 

    public void SelectBlock()
    {
        BlockSelector.Instance.SelectBlock(myBlock);
    }

 

Slot.cs에 블럭을 선택하는 함수를 만들고, 자기 자신의 버튼 이벤트에 연결시켰다.

 

BlockSelector를 바로 연결하지 않은 건, 자신이 아닌 외부 오브젝트의 스크립트를 연결하면, 연결이 끊어지는 현상이 발생할 수 있기 때문이다. (씬을 옮겼는데 나만 DontDestroyOnLoad였다던가...)

 

이제 버튼을 클릭하면, 아래처럼 블럭이 마우스를 따라오는 걸 볼 수 있다.

 

속도가 빠르면 뒤늦게 따라온다. 일단은 킵

 

이로써 블럭을 선택하는 것까진 구현되었고, 이후 타일을 만든 후 타일 위에 배치하는 것만 구현하면 된다.


오늘은 여기까지만 하고, 내일 이어서 만들어야 겠다.

 

이 기능만 구현되면 개발 내적으론 거의 완성된 것이니, 조금만 더 힘내자.


출처