[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `SendInitializedEvent()` 함수의 실행 공간이 multicast로 설정되어야 하는 이유와 `ItemData` 키에 저장되는 문자열 포맷 예시를 표로 정리하세요. [answer] | `SendInitializedEvent()` 함수의 실행 공간이 `multicast`로 설정되어야 하는 이유는, 인벤토리 초기화 데이터(`OnInventoryInitialized` 이벤트)가 서버와 클라이언트 양쪽에 동시에 전달되어야 하기 때문입니다. 참고 문서의 단계 8에서 `SendInitializedEvent()`는 `OnInventoryInitialized` 이벤트를 매개변수로 받아 `self.Entity:SendEvent(event)`를 호출하도록 정의되어 있습니다. `multicast` 실행 공간은 이 이벤트가 서버 측 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 6. **OnInventoryInitialized** 스크립트를 연 뒤, 아래와 같이 프로퍼티를 추가합니다. 프로퍼티 타입으로 SyncTable\<v>를 선택하고, v를 **number**로 설정하면 됩니다. ```lua Property: SyncTable<number> AllItemCounts ``` 7. 다시 **LiteInventory** 스크립트로 돌아갑니다. 이제 인벤토리 UI에 로드된 데이터를 전달해야 합니다. 위에서 작성한 `OnBeginPlay()` 함수에 아래 내용을 추가합니다
[task] | two_turn [pass] | ⏳ 미판정 [title] | 캐릭터가 발사체로 몬스터 공격하기 [question] | 발사체를 날릴 때 캐릭터의 좌우 방향에 따라 발사 위치가 어떻게 달라지는지 알려줘. [answer] | 발사체를 날릴 때 캐릭터의 좌우 방향에 따라 발사 위치가 달라지는 원리는 `PlayerAttack` 스크립트의 `AttackNormal()` 함수에서 처리됩니다. 기본적으로 캐릭터의 현재 위치(`self.Entity.TransformComponent.Position`)와 설정된 오프셋(`self.ProjectileOffset`)을 더한 위치에 발사체가 생성됩니다. 이때 캐릭터가 바라보는 방향에 따라 오프셋의 X값(좌우 방향)이 반전됩니다. 구체적인 처리 과정은 다음과 같습니다. 1. **바라보는 방향 확인**: `player [followup] | lookLeft 변수가 true일 때 ProjectileOffset의 x값이 어떻게 변경되며, 이 로직이 없으면 어떤 문제가 발생하는가? [followup_answer] | `lookLeft` 변수가 `true`일 때 `ProjectileOffset`의 x값은 **음수(-)로 반전**됩니다. 구체적으로 `AttackNormal()` 함수에서 `self.ProjectileOffset`을 클론한 후, `lookLeft`가 `true`인 경우 `projectileOffset.x = -projectileOffset.x`라는 로직이 실행됩니다. 예를 들어 `ProjectileOffset`의 X값이 `0.4`로 설정되어 있다면, 캐릭터가 왼쪽을 보고 있을 때(`lookLeft == true`) 발사 위치 오프셋 [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 > 본 가이드는 아래의 순서로 이어지는 과정입니다. > 1편. [원거리 발사체 만들기](/docs?postId=935) > **▶ 2편.** [**캐릭터가 발사체로 몬스터 공격하기**](/docs?postId=937) 원거리 발사체를 날려서 몬스터를 처치하는 월드를 만들어보는 마지막 과정입니다. 이제 캐릭터가 발사체로 몬스터를 공격하도록 만들어봅시다. # 공격 키 누르면 발사체 날리기 1. **PlayerAttack** 스크립트를 열고 아래와 같이 프로퍼티를 추가합니다. ```lua Prop
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | OnInventoryModified 이벤트 타입에 ItemIndex와 ItemCount 프로퍼티를 추가해야 하는 구체적인 이유는 무엇인가요? [answer] | `OnInventoryModified` 이벤트 타입에 `ItemIndex`와 `ItemCount` 프로퍼티를 추가하는 구체적인 이유는 서버에서 발생한 인벤토리 변경 내용을 클라이언트 UI에 정확하게 전달하기 위해서입니다. 서버에서 `ModifyItem` 함수를 통해 특정 아이템의 수량이 변경되면, 이 변경 사항이 어떤 아이템인지(인덱스) 그리고 변경된 후의 최종 수량이 얼마인지(개수)를 클라이언트가 알아야만 UI를 올바르게 업데이트할 수 있습니다. 구체적으로 두 프로퍼티의 역할은 다음과 같습니다. 1. **`ItemIndex [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | ```lua [server only] void ModifyItem(number itemIndex, number deltaAmount) { -- 아이템 인덱스가 정상인지 확인 if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end -- 변경 이전 아이템 개수 확인 local
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | Sync 속성으로 선언된 boolean 타입의 IsInitialized 프로퍼티를 정의하고, 서버에서 데이터 로드 및 초기화 완료 후 클라이언트로 동기화되도록 설정하는 LiteInventory 스크립트의 Property 블록을 작성해줘. [answer] | ```lua Property: [Sync] boolean IsInitialized = false ``` [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `ModifyItem` 함수의 인덱스 범위 및 음수 방지 로직을 3문장 이내로 설명하세요. [answer] | `ModifyItem` 함수는 `itemIndex`가 1 미만 또는 인벤토리 배열 길이(`#self._T.inventoryData`)를 초과하는 경우 에러를 기록하고 즉시 반환하여 범위 밖 접근을 차단합니다. 현재 아이템 수량에 `deltaAmount`를 더한 결과가 0보다 작으면 음수를 허용하지 않고 수량을 0으로 강제 변환합니다. 이 과정을 거쳐 최종적으로 계산된 수량이 `inventoryData` 배열의 해당 인덱스에 저장됩니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | ```lua Property: [Sync] boolean IsInitialized = false Function: [server only] void OnBeginPlay() { -- inventoryData에 UserItemDataSet를 불러옴 self._T.inventoryData = {} for i = 1, _DataService:GetRowCount("UserItemDataSet") do self._T.inventoryData[i] = 0 end local userDataSto
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | OnBeginPlay 함수에서 self.IsInitialized = true를 설정하는 시점이 SendInitializedEvent 호출 전이라면 어떤 문제가 발생할 수 있나요? [answer] | 참고 문서에 제시된 코드 흐름을 보면, `OnBeginPlay()` 함수 내에서 `self.IsInitialized = true`를 설정하는 시점은 `self:SendInitializedEvent(initEvent)` 호출 **이후**입니다. ```lua -- 기존 내용 self._T.userDataStorage = userDataStorage -- 아래 내용을 이어서 작성합니다. self:SendInitializedEvent(initEvent) self.IsInitialized = t [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 6. **OnInventoryInitialized** 스크립트를 연 뒤, 아래와 같이 프로퍼티를 추가합니다. 프로퍼티 타입으로 SyncTable\<v>를 선택하고, v를 **number**로 설정하면 됩니다. ```lua Property: SyncTable<number> AllItemCounts ``` 7. 다시 **LiteInventory** 스크립트로 돌아갑니다. 이제 인벤토리 UI에 로드된 데이터를 전달해야 합니다. 위에서 작성한 `OnBeginPlay()` 함수에 아래 내용을 추가합니다
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `HandleUserLeaveEvent`에서 `event.UserId`와 `self.Entity.PlayerComponent.UserId`를 비교하여 일치하지 않으면 `return`하는 로직이 필요한 이유는 무엇인가요? [answer] | `HandleUserLeaveEvent`에서 `event.UserId`와 `self.Entity.PlayerComponent.UserId`를 비교하여 일치하지 않으면 `return`하는 이유는, 해당 이벤트가 **특정 유저가 퇴장할 때 모든 관련 스크립트 인스턴스에 발생**하기 때문입니다. 구체적인 이유는 다음과 같습니다: 1. **이벤트 대상의 한정**: `HandleUserLeaveEvent`는 유저가 게임을 나가는 순간 트리거됩니다. 만약 해당 엔티티(캐릭터)에 부착된 스크립트가 여러 유저의 인벤토리를 관리하거나, 또는 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 본 예제에서는 저장 주기를 300초로 설정하였습니다. 저장 주기를 더 짧게 설정하면 인벤토리 데이터 정보 유실의 위험은 줄어들겠지만 불필요하게 자주 `_DataStorageService`를 호출하면 부하가 발생할 수 있고, 추후 DB 저장에 제약이 생길 수 있기 때문에 적당한 주기로 설정해야 합니다. ```lua -- 인벤토리 정보를 저장하도록 설정 local saveInventory = function() local dataConcatString = table.concat(self.
[task] | two_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | ItemData 키에 "3, 5" 형태의 쉼표 구분 문자열로 저장하는 포맷을 생성하기 위해 _TimerService:SetTimerOnce()를 사용할 때, 특정 아이템 개수가 0인 경우나 숫자 형식 변환 예외 처리는 어떻게 해야 하나요? [answer] | 제시된 참고 문서에는 `_TimerService:SetTimerOnce()`를 사용하여 인벤토리 정보를 주기적으로 저장하는 단계(10번 항목)와 저장할 데이터 포맷("아이템 인덱스 별 소지 개수를 쉼표로 구분한 문자열", 예: "3, 5")에 대한 설명만 포함되어 있습니다. 그러나 **특정 아이템 개수가 0인 경우의 처리 로직**이나 **숫자 형식 변환 예외 처리**에 대한 구체적인 코드, API, 또는 설명은 문서에 명시되어 있지 않습니다. 따라서 이 부분에 대한 정확한 구현 방법은 해당 문서에서 확인이 불가능합니다. [followup] | OnBeginPlay() 함수 내에서 self._T.inventoryData 배열을 반복하며 initEvent.AllItemCounts[i]에 값을 대입할 때, 배열 인덱스(i)와 실제 아이템 인덱스 간 불일치가 발생할 경우의 디버깅 방법은 무엇인가요? [followup_answer] | 제시된 참고 문서에는 `OnBeginPlay()` 함수 내에서 `self._T.inventoryData` 배열을 반복하며 `initEvent.AllItemCounts[i]`에 값을 대입하는 코드(7번 항목)가 포함되어 있습니다. 그러나 **배열 인덱스(i)와 실제 아이템 인덱스 간 불일치가 발생할 경우의 디버깅 방법**에 대한 설명, 로그 출력 예시, 또는 관련 API는 문서에 명시되어 있지 않습니다. 따라서 이 부분에 대한 구체적인 디버깅 방법은 해당 문서에서 확인이 불가능합니다. [scores] | [judge_reason] | [source_doc] | 6. **OnInventoryInitialized** 스크립트를 연 뒤, 아래와 같이 프로퍼티를 추가합니다. 프로퍼티 타입으로 SyncTable\<v>를 선택하고, v를 **number**로 설정하면 됩니다. ```lua Property: SyncTable<number> AllItemCounts ``` 7. 다시 **LiteInventory** 스크립트로 돌아갑니다. 이제 인벤토리 UI에 로드된 데이터를 전달해야 합니다. 위에서 작성한 `OnBeginPlay()` 함수에 아래 내용을 추가합니다
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | DataService의 GetRowCount 메서드로 UserItemDataSet의 행 수를 조회하여 1부터 해당 행 수까지 반복하며 inventoryData 테이블의 각 인덱스를 0으로 초기화하는 로직을 OnBeginPlay 함수 상단에 구현해줘. [answer] | ```lua Property: [Sync] boolean IsInitialized = false Method: [server only] void OnBeginPlay() { self._T.inventoryData = {} for i = 1, _DataService:GetRowCount("UserItemDataSet") do self._T.inventoryData[i] = 0 end } ``` [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | RemoveItem 함수에서 itemIndex 범위 검증 실패 시 발생하는 동작을 3문장 이내로 설명하세요. [answer] | 범위 검증 실패 시 `log_error("itemIndex error")` 로그를 출력합니다. 이후 즉시 함수를 종료하여 추가적인 처리를 수행하지 않습니다. 결국 인벤토리 데이터 수정이나 이벤트 전송이 일어나지 않습니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | [server only] void RemoveItem(number itemIndex) { if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end self._T.inventoryData[itemIndex] = 0 local modifiedEvent = OnInventoryModified() modifiedEvent.ItemIndex = ite
[task] | code [pass] | ⏳ 미판정 [title] | 캐릭터가 발사체로 몬스터 공격하기 [question] | StaticMonster, MoveMonster, ChaseMonster 엔티티에 TriggerComponent를 추가하고 CollisionGroup을 Monster로 설정하여 발사체와 몬스터의 충돌 감지가 가능하도록 하는 환경 설정 스크립트나 에디터 조작 절차를 작성해줘 [answer] | 제공된 문서에 따르면, `StaticMonster`, `MoveMonster`, `ChaseMonster` 엔티티에 `TriggerComponent`를 추가하고 `CollisionGroup`을 `Monster`로 설정하는 작업은 **mLua 스크립트로 자동화하는 것이 아니라 에디터에서 수동으로 조작**하는 절차입니다. 문서에는 해당 설정을 위한 스크립트 코드가 제공되지 않으며, 에디터에서의 조작 절차만 명시되어 있습니다. 에디터에서의 조작 절차는 다음과 같습니다. 1. **Workspace**에서 **BaseEnvironment [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 > 본 가이드는 아래의 순서로 이어지는 과정입니다. > 1편. [원거리 발사체 만들기](/docs?postId=935) > **▶ 2편.** [**캐릭터가 발사체로 몬스터 공격하기**](/docs?postId=937) 원거리 발사체를 날려서 몬스터를 처치하는 월드를 만들어보는 마지막 과정입니다. 이제 캐릭터가 발사체로 몬스터를 공격하도록 만들어봅시다. # 공격 키 누르면 발사체 날리기 1. **PlayerAttack** 스크립트를 열고 아래와 같이 프로퍼티를 추가합니다. ```lua Prop
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | ModifyItem 함수에서 아이템 개수가 0보다 작아지는 경우를 어떻게 처리하는지 궁금합니다. [answer] | `ModifyItem` 함수는 아이템 개수가 0보다 작아지는 경우를 방지하기 위해 계산된 결과가 음수인지 확인하는 로직을 포함합니다. 구체적인 처리 과정은 다음과 같습니다. 1. **변경 전 개수 확인**: 현재 인벤토리에 저장된 해당 인덱스의 아이템 개수(`prevAmount`)를 가져옵니다. 2. **변경 후 값 계산**: 기존 개수에 전달된 증감 값(`deltaAmount`)을 더한 `amountResult`를 계산합니다. 3. **음수 체크 및 보정**: 계산된 `amountResult`가 0보다 작은지(`amoun [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | ```lua [server only] void ModifyItem(number itemIndex, number deltaAmount) { -- 아이템 인덱스가 정상인지 확인 if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end -- 변경 이전 아이템 개수 확인 local
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | OnInventoryInitialized 이벤트 핸들러의 매개변수 arg1에 반드시 event를 입력해야 하는 이유는 무엇인가요? [answer] | `OnInventoryInitialized` 이벤트 핸들러의 매개변수 `arg1`에 `event`를 입력해야 하는 이유는, 이벤트 객체 자체를 함수 본문 내에서 직접 참조하고 조작하기 위해서입니다. 메이플스토리 월드의 mLua 스크립트에서 이벤트 핸들러는 이벤트가 발생했을 때 해당 이벤트의 정보를 담고 있는 객체를 전달받습니다. `OnInventoryInitialized` 이벤트는 인벤토리 초기화 시 필요한 데이터(예: `AllItemCounts`)를 포함하고 있으며, 이 데이터를 접근하기 위해서는 이벤트 객체 자체에 대한 참조가 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 6. **OnInventoryInitialized** 스크립트를 연 뒤, 아래와 같이 프로퍼티를 추가합니다. 프로퍼티 타입으로 SyncTable\<v>를 선택하고, v를 **number**로 설정하면 됩니다. ```lua Property: SyncTable<number> AllItemCounts ``` 7. 다시 **LiteInventory** 스크립트로 돌아갑니다. 이제 인벤토리 UI에 로드된 데이터를 전달해야 합니다. 위에서 작성한 `OnBeginPlay()` 함수에 아래 내용을 추가합니다
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | RemoveItem 함수 내에서 인벤토리 수정 후 클라이언트로 전송되는 이벤트의 구조와 필드 값을 표로 정리해서 제시하세요. [answer] | `RemoveItem` 함수 내에서 인벤토리 데이터가 수정된 후 생성되고 전송되는 이벤트(`OnInventoryModified()`)의 구조는 다음과 같습니다. | 필드명 | 값 | 설명 | | :--- | :--- | :--- | | `ItemIndex` | `itemIndex` | 삭제된 아이템의 인덱스 번호 | | `ItemCount` | `0` | 삭제된 아이템의 수량 (항상 0으로 설정됨) | 해당 이벤트 객체는 `OnInventoryModified()` 함수를 호출하여 생성되며, 위 두 필드에 값을 대입한 후 `sel [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | [server only] void RemoveItem(number itemIndex) { if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end self._T.inventoryData[itemIndex] = 0 local modifiedEvent = OnInventoryModified() modifiedEvent.ItemIndex = ite
[task] | two_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `DataStorageService`를 활용해 `UserItemDataSet`의 행 수만큼 초기화하고 저장된 데이터를 로드하는 `OnBeginPlay()` 로직이 어떻게 동작하는지 설명해 주세요. [answer] | `DataStorageService`를 활용한 `OnBeginPlay()` 로직은 서버에서 유저가 월드에 진입할 때 인벤토리 데이터를 초기화하고, 이전에 저장했던 데이터를 불러와 적용하는 역할을 합니다. 이 과정은 다음과 같은 단계로 진행됩니다. ### 1. 로컬 인벤토리 데이터 구조 초기화 먼저 `self._T.inventoryData`라는 테이블을 생성하여 인벤토리 데이터를 저장할 공간을 마련합니다. 이때 `UserItemDataSet` 데이터셋의 총 행 수(`_DataService:GetRowCount("UserItemDat [followup] | `userDataStorage:GetAndWait("ItemData")` 호출 시 `errorCode`가 0이 아닌 경우와 `itemData`가 `nil`인 경우 각각 어떤 처리가 이루어지며, `inventoryData` 테이블의 상태는 어떻게 유지되는지 코드 흐름을 구체적으로 분석해 주세요. [followup_answer] | `userDataStorage:GetAndWait("ItemData")` 호출 이후의 코드 흐름은 `errorCode`와 `itemData`의 값에 따라 두 가지 경로로 나뉩니다. 두 경우 모두 `self._T.inventoryData` 테이블의 상태가 어떻게 유지되는지 분석하면 다음과 같습니다. ### 1. `errorCode`가 0이 아닌 경우 (데이터 로드 실패) **처리 내용:** * `if errorCode ~= 0 then` 조건이 참이 됩니다. * `log(errorCode)`를 호출하여 오류 코드를 로그에 [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | DefaultPlayer에 LiteInventory 컴포넌트를 추가할 때, 다른 엔티티(예: NPC나 오브젝트)에도 동일한 구조의 인벤토리 스크립트를 적용하고 싶다면 PlayerComponent.UserId 대신 어떤 식별자를 사용해야 하는지 알려주세요. [answer] | 제공된 참고 문서에는 `DefaultPlayer`에 `LiteInventory` 컴포넌트를 추가하는 과정과 `PlayerComponent.UserId`를 사용하여 사용자 데이터를 로드하는 예제만 포함되어 있습니다. NPC나 오브젝트와 같은 다른 엔티티에 동일한 구조의 인벤토리 스크립트를 적용할 때 `PlayerComponent.UserId` 대신 사용해야 하는 구체적인 식별자(예: 엔티티 ID, GUID 등)나 관련 API에 대한 정보는 문서에 명시되어 있지 않습니다. 따라서 해당 질문에 대한 정확한 답변을 드릴 수 없습니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `UserService`의 `UserLeaveEvent`를 수신하여 해당 유저가 나감에 따라 `_DataStorageService`로 인벤토리 데이터를 최종 저장하는 스크립트를 작성해줘 [answer] | `UserService`에서 발생하는 `UserLeaveEvent`를 수신하여, 해당 유저가 월드에서 나가면 `_DataStorageService`를 통해 인벤토리 데이터를 최종 저장하는 스크립트입니다. 참고 문서에 따르면, 플레이를 종료할 때 데이터를 저장하기 위해 `SetAndWait` 함수를 사용해야 하며, 저장 대상 데이터는 `self._T.inventoryData` 테이블을 쉼표(`,`)로 연결한 문자열 형태입니다. 또한, 이벤트의 `UserId`와 현재 엔티티의 `PlayerComponent.UserId`가 일치하는지 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 본 예제에서는 저장 주기를 300초로 설정하였습니다. 저장 주기를 더 짧게 설정하면 인벤토리 데이터 정보 유실의 위험은 줄어들겠지만 불필요하게 자주 `_DataStorageService`를 호출하면 부하가 발생할 수 있고, 추후 DB 저장에 제약이 생길 수 있기 때문에 적당한 주기로 설정해야 합니다. ```lua -- 인벤토리 정보를 저장하도록 설정 local saveInventory = function() local dataConcatString = table.concat(self.
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | DataStorageService의 GetAndWait 호출 시 errorCode가 0이 아닌 경우 log 함수로 에러 코드를 출력하고 return으로 함수를 종료하는 예외 처리 코드를 OnBeginPlay 함수 내에 추가해줘. [answer] | `OnBeginPlay` 함수 내에서 `DataStorageService:GetAndWait` 호출 후 `errorCode`가 0이 아닌 경우, `log`로 에러 코드를 출력하고 함수를 종료하는 예외 처리를 추가한 코드는 다음과 같습니다. ```lua Property: [Sync] boolean IsInitialized = false Method: [server only] void OnBeginPlay() { self._T.inventoryData = {} for i = 1, _DataService:GetRowCo [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | OnInventoryInitialized 이벤트 타입을 생성한 후, 이 이벤트를 통해 클라이언트 UI에 인벤토리 정보를 전달하는 구체적인 코드 작성 절차나 메서드 명명이 문서에 명시되어 있지 않아 어떻게 구현해야 하는지 궁금합니다. [answer] | 제공된 참고 문서에는 `OnInventoryInitialized` 이벤트 타입을 생성하는 단계까지만 설명되어 있습니다. 문서에는 **"로드된 데이터가 있다면 UI에도 해당 정보를 전달해야 합니다. 이를 전달하기 위해 이벤트 타입을 추가합니다."** 라고 언급할 뿐, 실제로 이 이벤트를 통해 클라이언트 UI에 인벤토리 정보를 전달하는 **구체적인 코드 작성 절차, 메서드 명명 규칙, 또는 이벤트 발화(Trigger/Invoke) 및 수신(Handler) 코드 예제는 명시되어 있지 않습니다.** 따라서 제공된 문서만으로는 해당 부분 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | 문서에서 ItemService와 DataStorageService를 비교하며 '가벼운 인벤토리'라는 표현을 썼는데, DataStorageService를 사용하면 ItemService 대비 어떤 기능적 제약이나 차이가 있는지 구체적으로 알고 싶습니다. [answer] | 제공된 문서에는 ItemService와 DataStorageService의 구체적인 기능적 차이점이나 DataStorageService의 기능적 제약 사항에 대한 기술이 포함되어 있지 않습니다. 문서에서는 단순히 두 서비스의 사용 맥락만 언급하고 있을 뿐입니다. 1. **ItemService**: 월드에서 사용하는 아이템 생성, 저장, 관리를 할 때 이용할 수 있는 서비스로 제시되었습니다. 2. **DataStorageService**: 월드 내 특정 캐릭터(A)가 가진 아이템 정보를 해당 캐릭터 본인의 클라이언트와 서버만 알고 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | code [pass] | ⏳ 미판정 [title] | 캐릭터가 발사체로 몬스터 공격하기 [question] | PlayerAttack 스크립트의 AttackNormal() 메서드에서 PlayerControllerComponent의 LookDirectionX를 확인하여 발사체 오프셋 방향을 조절하고, _SpawnService:SpawnByModelId로 발사체 엔티티를 생성한 뒤 Projectile 컴포넌트의 ToLeft, Firer를 설정하여 Fire()를 호출하는 로직을 작성해줘 [answer] | 요청하신 `AttackNormal()` 메서드 로직은 `PlayerControllerComponent`의 `LookDirectionX` 값에 따라 발사체 오프셋의 X 방향을 반전시키고, `_SpawnService:SpawnByModelId`를 통해 발사체 엔티티를 생성한 뒤 `Projectile` 컴포넌트의 `ToLeft` 및 `Firer` 프로퍼티를 설정하여 `Fire()`를 호출하는 방식으로 구현합니다. 아래는 해당 로직이 포함된 `PlayerAttack` 스크립트의 관련 부분입니다. ```lua Property: [Sync [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 > 본 가이드는 아래의 순서로 이어지는 과정입니다. > 1편. [원거리 발사체 만들기](/docs?postId=935) > **▶ 2편.** [**캐릭터가 발사체로 몬스터 공격하기**](/docs?postId=937) 원거리 발사체를 날려서 몬스터를 처치하는 월드를 만들어보는 마지막 과정입니다. 이제 캐릭터가 발사체로 몬스터를 공격하도록 만들어봅시다. # 공격 키 누르면 발사체 날리기 1. **PlayerAttack** 스크립트를 열고 아래와 같이 프로퍼티를 추가합니다. ```lua Prop
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | LiteInventory 컴포넌트에서 RemoveItem 함수의 실행 공간을 server only로 설정해야 하는 이유는 무엇인가요? [answer] | 인벤토리 데이터는 서버에서 관리되는 동기화 대상이므로, 아이템을 삭제하는 로직은 반드시 서버에서만 실행되어야 합니다. 클라이언트에서 직접 인벤토리 데이터를 수정하게 되면 서버와 클라이언트 간 데이터 불일치(싱크 문제)가 발생하거나, 악용을 통한 아이템 조작이 가능해질 수 있습니다. 따라서 `RemoveItem()` 함수는 실제 데이터 변경(`self._T.inventoryData[itemIndex] = 0`)과 클라이언트로의 변경 알림(`SendModifiedEvent`)을 처리하기 위해 **server only** 실행 공간으로 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 인벤토리 비우기 테스트 편의를 위해 캐릭터가 게임에 새로 접속할 때마다 인벤토리를 비우도록 설정해 봅시다. 1. 아이템 삭제를 처리하기 위해 **LiteInventory**에 `RemoveItem()` 함수를 추가합니다. 실행 공간은 **server only**로 설정합니다. 매개 변수로 **number** 타입의 **itemIndex**를 추가합니다. 그리고 아래와 같이 작성합니다. ```lua [server only] void RemoveItem(number itemIndex) {
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | HandleUserLeaveEvent에서 인벤토리 데이터가 문자열로 변환되어 저장되는 과정을 코드만 출력하고 설명은 주석으로 작성하세요. [answer] | ```lua Event Handler: [server only] [service: UserService] HandleUserLeaveEvent (UserLeaveEvent event) { -- Parameters local UserId = event.UserId --------------------------------------------------------- -- 현재 스크립트가 부착된 엔티티의 UserId를 가져옵니다. local thisUserId = self.Entity.PlayerC [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | [server only] void RemoveItem(number itemIndex) { if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end self._T.inventoryData[itemIndex] = 0 local modifiedEvent = OnInventoryModified() modifiedEvent.ItemIndex = ite
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | 서버에서 아이템 수량을 수정한 뒤 클라이언트 UI에 바로 반영하려면 어떤 이벤트 구조로 데이터를 보내야 하나요? [answer] | 서버에서 아이템 수량을 수정한 뒤 클라이언트 UI에 즉시 반영하려면, 커스텀 이벤트 타입을 생성하여 서버에서 클라이언트로 데이터를 전송하는 구조를 사용해야 합니다. 구체적인 절차와 코드 구성은 다음과 같습니다. ### 1. 커스텀 이벤트 타입 생성 우선 `OnInventoryModified`라는 이름의 이벤트 타입을 생성하고, 전달할 데이터를 담을 프로퍼티를 정의합니다. ```lua Property: number ItemIndex = 0 number ItemCount = 0 ``` ### 2. 서버-클라이언트 데이터 전송 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | ```lua [server only] void ModifyItem(number itemIndex, number deltaAmount) { -- 아이템 인덱스가 정상인지 확인 if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end -- 변경 이전 아이템 개수 확인 local
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | DataStorageService의 GetUserDataStorage와 GetAndWait 메서드를 사용하여 서버에서 ItemData 키 값을 로드하고 nil이 아닌 경우 UtilLogic의 Split과 tonumber을 활용해 inventoryData 테이블에 숫자 배열로 변환하여 저장하는 OnBeginPlay 함수를 작성해줘. [answer] | 요청하신 `DataStorageService`와 `UtilLogic`을 활용한 `OnBeginPlay` 함수는 다음과 같이 작성할 수 있습니다. ```lua Property: [Sync] boolean IsInitialized = false Method: [server only] void OnBeginPlay() { -- inventoryData 테이블 초기화 self._T.inventoryData = {} -- UserItemDataSet 행 수만큼 0으로 초기화 for i = 1, _Da [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 인벤토리의 동작을 제어할 인벤토리 스크립트를 작성합니다. 1. 새 스크립트 컴포넌트를 생성하고 이름을 **LiteInventory**로 설정합니다. 2. **Workspace - DefaultPlayer**의 콘텍스트 메뉴에서 **Add Component**를 클릭한 후 **LiteInventory** 컴포넌트를 추가합니다. 3. **LiteInventory** 스크립트를 열고 스크립트 에디터에서 프로퍼티를 추가합니다. ```lua Property: [Sync] boolean IsInitialize
[task] | code [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | RemoveItem 메서드와 HandleUserLeaveEvent 이벤트 핸들러를 구현하여 인벤토리에서 특정 인덱스의 아이템을 제거하고, 사용자가 게임을 나갈 때 인벤토리 데이터를 문자열로 변환하여 UserDataStorage에 저장하는 스크립트를 작성해줘 [answer] | 요청하신 `RemoveItem` 메서드와 `HandleUserLeaveEvent` 이벤트 핸들러를 포함한 mLua 스크립트는 다음과 같습니다. ```lua Property: [None] number inventoryData = {} [None] UserDataStorage userDataStorage = nil Method: [server only] void RemoveItem(number itemIndex) { if itemIndex < 1 or itemIndex > #self._T.inventoryData then [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | [server only] void RemoveItem(number itemIndex) { if itemIndex < 1 or itemIndex > #self._T.inventoryData then log_error("itemIndex error") return end self._T.inventoryData[itemIndex] = 0 local modifiedEvent = OnInventoryModified() modifiedEvent.ItemIndex = ite
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 몬스터 드롭 아이템과 인벤토리 UI 만들기 [question] | `_ItemService`를 사용할 때 데이터셋 이름이 `UserItemDataSet`이어야 하는 이유를 3문장 이내로 설명하세요. [answer] | `_ItemService` API는 내부적으로 특정 이름의 데이터셋을 참조하도록 구현되어 있기 때문에 데이터셋 이름을 반드시 `UserItemDataSet`으로 설정해야 합니다. 이름이 일치하지 않으면 서비스가 아이템 데이터를 올바르게 찾지 못해 정상적으로 동작하지 않습니다. 따라서 `_ItemService`를 사용하는 프로젝트에서는 데이터셋 이름을 `UserItemDataSet`으로 생성하는 것이 필수적입니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 > 본 가이드는 아래의 순서로 이어지는 과정입니다. > 1편. [몬스터를 스폰해보자](/docs?postId=204) > **▶ 2편.** [**몬스터 드롭 아이템과 인벤토리 UI 만들기**](/docs?postId=943) > 3편. [인벤토리 스크립트 작성하기](/docs?postId=942) > 4편. [인벤토리 UI 스크립트 작성하기](/docs?postId=946) > 5편. [아이템 스크립트 작성하기](/docs?postId=947) 지난 시간에 이어 이번 과정에서는 몬스터를 처치했을 때 드롭될 아
[task] | single_turn [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | ClearInventoryOnBeginPlay 스크립트에서 while not inven.IsInitialized do wait(0.5) end 구문을 사용해야 하는 이유는 무엇인가요? [answer] | `OnBeginPlay()` 함수는 스크립트 컴포넌트가 시작될 때 호출되지만, 이 시점에는 `LiteInventory` 컴포넌트의 데이터가 완전히 초기화되지 않았을 수 있습니다. `IsInitialized` 프로퍼티는 인벤토리가 준비되었는지 여부를 나타내며, 이 값이 `false`인 상태에서 `RemoveItem()`을 호출하면 인벤토리 데이터(`inventoryData`)가 존재하지 않거나 불완전하여 오류가 발생할 수 있습니다. 따라서 `while not inven.IsInitialized do wait(0.5) end` 구문은 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 인벤토리 비우기 테스트 편의를 위해 캐릭터가 게임에 새로 접속할 때마다 인벤토리를 비우도록 설정해 봅시다. 1. 아이템 삭제를 처리하기 위해 **LiteInventory**에 `RemoveItem()` 함수를 추가합니다. 실행 공간은 **server only**로 설정합니다. 매개 변수로 **number** 타입의 **itemIndex**를 추가합니다. 그리고 아래와 같이 작성합니다. ```lua [server only] void RemoveItem(number itemIndex) {
[task] | code [pass] | ⏳ 미판정 [title] | 범용 리소스 모음 [question] | Preset List의 '위 화살표' 스프라이트 RUID(621d0abe16e84a79a4e2cd3df1fad1a0)를 사용하여, _EntityService:GetEntityByPath로 특정 엔티티를 찾은 후 HandleTouchEvent 이벤트 핸들러에서 해당 엔티티의 TransformComponent.Position을 클릭 시마다 10만큼 X축으로 이동시키는 스크립트를 작성해줘 [answer] | ```lua Property: [None] Entity TargetEntity = nil Method: [server only] void OnBeginPlay() { self.TargetEntity = _EntityService:GetEntityByPath("/maps/map01/targetEntity") if self.TargetEntity == nil then log("Target entity not found") end } Event Handler: [self] HandleTouchEve [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 메이플스토리 월드에서는 원작의 풍부한 리소스를 제공하고 있습니다. 크리에이터는 **Preset List**와 **리소스 스토리지**, **MSW 리소스 조회 페이지**에서 다양한 리소스를 사용할 수 있습니다. 원작 리소스의 양이 워낙 방대하기 때문에 원하는 리소스를 찾기 어려울 때가 종종 있습니다. 크리에이터들이 자주 찾을 만한 리소스를 소개합니다. > **더 알아보기** > 메이플스토리 월드 팀은 보다 편리한 리소스 이용 환경을 만들기 위해 꾸준히 노력하고 있습니다. > 여기서 소개되는 리소스는 앞으로 손쉽게 검색할 수 있도록
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 인벤토리 스크립트 작성하기 [question] | `OnBeginPlay` 내에서 인벤토리 데이터를 로드하는 순서를 단계별 번호 목록으로 정리하세요. [answer] | `OnBeginPlay` 함수 내에서 인벤토리 데이터를 로드하는 순서는 다음과 같습니다. 1. `self._T.inventoryData` 테이블을 빈 테이블로 초기화합니다. 2. `_DataService:GetRowCount("UserItemDataSet")`을 호출하여 데이터셋의 행 수를 확인하고, 해당 횟수만큼 `self._T.inventoryData`의 각 인덱스에 `0`을 대입합니다. 3. `_DataStorageService:GetUserDataStorage(self.Entity.PlayerComponent.UserId) [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | ```lua Property: [Sync] boolean IsInitialized = false Function: [server only] void OnBeginPlay() { -- inventoryData에 UserItemDataSet를 불러옴 self._T.inventoryData = {} for i = 1, _DataService:GetRowCount("UserItemDataSet") do self._T.inventoryData[i] = 0 end local userDataSto