Topdown Survival Prototype (2)

용준·2026년 2월 20일

Project

목록 보기
8/8
  • Project Explain | A top-down combat prototype inspired by Eternal Return, focused on implementing core gameplay systems
  • Engine | Unity 6000.3.7f1
  • Development Period | X weeks
  • GitHub | Links

8. 아이템

ScriptableObject 기반 데이터 관리
아이템의 이름, 아이콘, 설명, 최대 스택 수와 같은 정보는 게임 전체에서 공유되는 정적 데이터이므로 ScriptableObject를 사용하여 관리했다.

게임 플레이 중 실제로 필요한 정보는 현재 플레이어가 아이템을 몇 개 보유하고 있는가와 같은 런타임 상태 데이터이다. 이러한 값은 플레이 과정에서 계속 변하므로 ScriptableObject에 직접 저장하지 않도록 설계했다.


ItemStack 구조 설계
런타임 상태를 관리하기 위해 ItemStack 클래스를 별도로 정의했다.

ItemStack은 특정 아이템의 보유 상태를 표현하는 역할을 하며, 아이템 정의 자체가 아니라 아이템 소지 상태를 관리하는 구조체 개념으로 사용했다.

이를 통해 다음과 같은 기능을 구현할 수 있다.

  • 동일 아이템이 여러 스택으로 분리되는 구조 지원
  • 현재 보유한 아이템 개수 관리
  • 최대 스택 제한을 고려한 자동 분리 처리

예를 들어 최대 스택이 3인 아이템을 4개 획득하면 3개 스택과 1개 스택으로 분리되어 저장되도록 구현했다.

Inspector 직렬화 처리도 같이 해주었는데

Unity는 기본적으로 MonoBehaviour, ScriptableObject, 그리고 일부 기본 자료형만 자동 직렬화를 지원한다.

따라서 ItemStack 클래스에 직렬화 특성을 추가하여 런타임 데이터 구조를 Inspector에서도 확인할 수 있도록 했다.


9. Chest 시스템

슬롯 기반 구조로 구현했다.

초기 생성 시 ScriptableObject 설정값을 기준으로 슬롯을 생성하며, 랜덤 아이템 테이블을 통해 아이템을 배치했다.

하지만 내가 구현한 초기 UI와 비교했을 때 상자 UI 구조가 다소 차이가 있었고

상자에서 아이템을 클릭했을 때와 인벤토리에서 아이템을 클릭했을 때 이후 처리 로직이 서로 달라지는 문제가 발생해서

결국 단일 슬롯 구조로는 기능 확장이 어렵다고 판단하여 UI 구조를 분리하는 방향으로 수정 작업을 진행했다.

원본 UI의 레이아웃과 상호작용 방식을 분석하며 작업 방향을 잡았다.

아이템 획득 시 슬롯을 제거하지 않고 빈 슬롯을 리스트 뒤로 이동시키는 방식으로 구현했다.

이를 통해 UI 슬롯 개수를 일정하게 유지하면서 자연스러운 정렬이 가능하도록 했다.

또한 슬롯 상태를 Filled / Empty Sprite로 구분하여 아이템 존재 여부를 시각적으로 표현했다.


10. Inventory

인벤토리 구조 설계
Inventory 역시 Chest와 동일하게 슬롯 기반 구조로 설계했다.

최대 소지 개수 제한도 적용하였다.

아이템 획득 시 기존 스택을 우선 채우고, 부족한 수량은 새로운 스택으로 분리하여 저장하도록 구현했다.


인벤토리 UI 갱신 구조

아이템 획득, 사용, 또는 제거 이벤트가 발생하면 UI Manager를 통해 화면을 갱신하도록 했다.

인벤토리가 닫혀있는 상태에서는 UI 갱신을 수행하지 않도록 조건을 추가하여 불필요한 객체 생성과 렌더링 비용을 줄였다.


인벤토리 최대 소지 제한

인벤토리 최대 슬롯 수를 10개로 제한했다.

슬롯이 가득 찬 상태에서는 아이템 획득 자체를 차단하고 경고 UI를 출력하도록 했다.

경고 메시지는 Coroutine 기반으로 일정 시간 후 자동으로 사라지도록 구현했다.

또한 경고 메시지가 중복 호출될 경우 타이머가 초기화되지 않도록 상태 기반 관리 구조를 적용했다.


11. Tooltip UI

Tooltip UI는 싱글톤 패턴으로 구현하여 게임 내 Tooltip이 하나만 존재하도록 설계했다.

ScriptableObject로 관리하는 아이템 고유의 등급, 타입에 따른 차별 적용
Tooltip 패널은 Update에서 마우스 위치를 기준으로 이동하도록 처리했고,

Tooltip은 Pointer Enter 이벤트에서 활성화되며 Pointer Exit 이벤트에서 비활성화된다.

Inventory와 Chest에서 공통 Tooltip을 사용하되, Tooltip Owner Enum을 통해 UI 창 간 충돌을 방지했다.


Tooltip Owner 상태 관리

public enum TooltipOwner
{
    None,
    Inventory,
    Chest
}

Tooltip Owner를 기준으로 다음 동작을 구분했다.

  • Inventory Tooltip → 인벤토리 UI 종료 시 Tooltip 함께 종료
  • Chest Tooltip → 상자 UI 종료 시 Tooltip 유지 여부 분리

이를 통해 서로 다른 UI 영역 간 상태 충돌을 방지했다.


12. PlayerInput 리팩토링

플레이어 이동이 발생하지 않도록 EventSystem 기반 입력 검사를 추가했다.

    bool IsPointerOverBlockUI()
    {
        if (EventSystem.current == null) return false;

        return EventSystem.current.IsPointerOverGameObject();
    }

입력 발생 판단을 위해 PlayerInput 내부에 EventSystem.current.IsPointerOverGameObject()를 활용하는 유틸리티 함수를 추가했다.

 void Update()
 {
     // UI에는 이동 안되게
     if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject()) return;

     // UI가 아닌 곳을 왼클릭할때 상자 닫히게
     if (Input.GetMouseButtonDown(0))
     {
         if (!EventSystem.current.IsPointerOverGameObject())
         {
             ChestUIManager.Instance.CloseChestUI();
         }
     }
     ...

이를 통해 다음 문제를 해결했다.

  • UI 위에서 이동 명령이 실행되는 문제 방지
  • UI 외부 좌클릭 시 Chest UI 자동 종료

회고

  • 리팩토링 과정에서 발생한 오류의 상당수는 컴포넌트 등록이 잘못된 경우에서 비롯되었다. 특히 UI 작업 중 이러한 문제가 자주 발생해 디버깅 시간이 예상보다 길어졌다.
  • 기능이 추가되면서 이전에 작성했던 코드 구조를 다시 이해하는 데 시간이 필요했던 경우도 있었다. 앞으로는 코드에 주석과 Summary를 적극적으로 활용하여 가독성을 유지하는 습관을 들일 것이다.
  • 초기 아키텍처 설계의 중요성을 체감했다. 초기에 구조를 명확히 정의하지 못한 상태에서 기능을 추가하면서 수정 작업이 반복되었고, 결과적으로 개발 시간이 지연되는 문제가 있었다. 이후 프로젝트에서는 기능 구현 전에 전체 구조를 먼저 고민하는 방식으로 작업할 필요가 있다고 느꼈다.
  • Unity에서 List 내부 데이터를 Inspector에서 확인하기 위해서는 클래스 직렬화가 필요하다.

0개의 댓글