메이플스토리월드 :: 인벤토리 구현 06

chooha·2026년 7월 28일

메이플스토리월드

목록 보기
6/15

📝 개발일지 - 디버그 패널(아이템 획득 UI) 구현 트러블슈팅

👨‍💻 오늘의 개발 작업

아이템 획득 방식으로 확정한 "디버그 버튼 패널"을 구현함. 스크롤에 나열된 아이템 아이콘을 클릭해 선택하고, 수량을 입력한 뒤 추가 버튼을 누르면 서버에 RPC 요청을 보내 인벤토리에 반영하는 흐름. 기존에 만들어둔 UIItemSlot(아이콘 렌더링 컴포넌트)을 그대로 재사용하는 구조로 설계했는데, 이 과정에서 엔진 API를 다루는 방식과 리소스 자체의 한계에서 비롯된 문제 세 가지를 순서대로 만남.


💡 오늘의 기록

1. ConnectEvent는 Component가 아니라 Entity의 메서드였다

현상: 추가 버튼의 클릭 이벤트를 연결하려는데 실행하자마자 에러가 뜸.

AttemptToCall : 'ConnectEvent'을 호출할 수 없습니다. 'ConnectEvent'은 nil입니다.

처음엔 버튼을 아래처럼 받고 있었음:

property ButtonComponent addButtonComp = nil
...
self.addButtonComp:ConnectEvent(ButtonClickEvent, function() self:OnClickAddButton() end)

조사 과정: self.addButtonComp == nil 방어 코드는 이미 통과한 상태라, 인스펙터 연결 자체는 문제가 없었음. 즉 "연결이 안 된 것"이 아니라 "연결된 컴포넌트에 그 메서드가 없는 것"으로 원인을 좁힘. MSW에서는 하나의 오브젝트가 엔티티(Entity) 하나에 여러 컴포넌트가 얹히는 구조인데, 이벤트 구독을 위한 ConnectEvent는 각 컴포넌트가 아니라 엔티티 자신에게 있는 메서드라는 걸 확인함. 클릭 이벤트를 발생시키는 주체는 ButtonComponent이지만, 그 이벤트를 "구독"하는 진입점은 엔티티 쪽으로 통일되어 있는 구조였던 것.

해결: 프로퍼티 타입 자체를 Entity로 바꾸고, 인스펙터에서도 컴포넌트가 아니라 엔티티 자체를 다시 연결함.

property Entity addButtonEntity = nil
...
self.addButtonEntity:ConnectEvent(ButtonClickEvent, function() self:OnClickAddButton() end)

같은 실수가 아이템 버튼을 동적 생성하는 루프(CreateItemButtons) 안에도 있었어서 함께 수정함.

교훈: 다른 엔진(예: Unity, Unreal)에서 "버튼 컴포넌트에 리스너를 붙인다"는 습관이 있으면, MSW에서도 무의식중에 컴포넌트 타입으로 프로퍼티를 받기 쉬움. 하지만 이 엔진은 이벤트 구독의 주체가 컴포넌트가 아니라 엔티티라는 점이 명확히 달랐음.


2. RPC 요청 메서드의 ExecSpace를 Server로 지정해야 하는 이유

현상: 서버 검증 로직 자체는 이미 완성해뒀는데, 클라이언트에서 버튼을 눌러 호출하자 다시 nil 에러가 뜸.

AttemptToCall : 'RequestAddItemFromButton'을 호출할 수 없습니다. 'RequestAddItemFromButton'은 nil입니다.

당시 이 메서드는 ServerOnly로 선언되어 있었음:

@ExecSpace("ServerOnly")
method void RequestAddItemFromButton(integer itemId, integer amount)
	self:AddItem(itemId, amount)
end

조사 과정: ExecSpace 옵션(사용하지 않음/Client/ClientOnly/Server/ServerOnly/Multicast)의 의미를 다시 짚어봄. ServerOnly는 그 메서드 자체가 서버 공간에만 존재한다는 뜻이라, 클라이언트 스크립트 입장에서는 애초에 그런 이름의 메서드가 없는 것과 같음(그래서 "실행 실패"가 아니라 "이름 자체가 nil"로 나타남). 반면 Server는 클라이언트에서 호출하되 실제 실행은 서버에서 되도록 RPC로 전달해주는 옵션이라는 걸 확인함. 지금 필요한 건 "클라이언트 버튼 클릭 → 서버 실행"이었으므로 Server가 맞는 선택이었음.

해결:

@ExecSpace("Server")
method void RequestAddItemFromButton(integer itemId, integer amount)
	-- 검증 없이 AddItem으로 위임 (검증은 AddItem(ServerOnly)이 전담)
	self:AddItem(itemId, amount)
end

교훈: "서버에서만 실행되어야 한다"는 요구사항과 "클라이언트에서 호출 가능해야 한다"는 요구사항은 서로 다른 축이라, ServerOnlyServer를 목적에 맞게 구분해서 써야 함. 클라이언트가 진입점으로 삼는 Request* 계열 메서드는 전부 Server, 실제 검증/처리를 전담하는 내부 메서드는 ServerOnly로 나누는 패턴을 이후 다른 기능(정렬/합치기/삭제)에도 동일하게 적용함.


3. 장비 아이템 아이콘만 안 뜨는 문제

현상: 소비 아이템(포션 등)은 아이콘이 정상적으로 표시되는데, 장비 아이템만 아이콘이 안 뜸. 에러나 경고 로그도 전혀 없이 그냥 빈 칸으로 보임.

조사 과정: 에러가 없다는 게 오히려 단서였음. 먼저 다음 가설들을 하나씩 배제함.

  • "itemCnt가 0이라 SetEmpty 처리된 게 아닐까?" → 디버그 버튼은 itemCnt를 무조건 1로 고정해서 넘기는 구조라 배제.
  • "장비는 스택이 1개뿐이라 어딘가에서 걸러지는 게 아닐까?" → 코드 전체를 봐도 스택 관련 필터링 로직 자체가 없어서 배제.
  • "RUID 형식이 다른 게 아닐까?" → 소비 아이템과 장비 아이템의 RUID를 나란히 비교했으나(둘 다 32자리 동일 형식) 형식 차이는 없었음.
  • 직접 테스트: 빈 엔티티에 SpriteGUIRendererComponent를 붙이고 장비 RUID를 인스펙터에서 직접 넣어봐도 안 뜸 → 코드 문제가 아니라 리소스 자체의 문제로 좁힘.

여기서 Claude를 통해 문제 원인을 찾아보다가 유사한 사례를 다룬 글을 발견함. 다만 그 글을 지금 다시 확인할 수 있는 링크는 아니라서, 확정된 근거로 삼기보다는 추정으로만 남김: 장비 아이템만 아이콘이 안 뜨고 에러 로그도 없다는 것, 그리고 리소스 종류(착용용/아이콘용)의 차이가 원인일 가능성이 있다는 것까지가 이번에 확인한 전부임.

해결: 실제 착용용 리소스 대신, MSW의 "기타 아이템" 카테고리 리소스 중 옷/무기처럼 생긴 이미지를 찾아 아이콘으로 대체 적용함.


📚 성과와 개선 효과

  • 엔진 API의 책임 소재 구분: 이벤트 구독(ConnectEvent)은 엔티티, 이벤트를 발생시키는 로직은 컴포넌트라는 역할 분리를 명확히 이해함
  • ExecSpace 선택 기준 확립: "서버 전용 실행"과 "클라이언트에서 호출 가능한 RPC"는 다른 옵션이라는 걸 실제 에러로 체감하고, 이후 Request*/내부 처리 메서드 쌍 패턴을 전 기능에 일관되게 적용

0개의 댓글