[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | `IsAttackTarget()` 함수가 false를 반환할 때와 true를 반환할 때의 처리 흐름 차이를 3문장 이내로 설명하세요. [answer] | `IsAttackTarget()`이 false를 반환하면 공격 실패로 간주되어 이후 절차가 즉시 중단됩니다. 반면 true를 반환하면 공격자의 `CalcDamage()`로 계산된 대미지가 피격자의 `HitComponent`에 전달되며, 피격자의 `IsHitTarget()` 판별은 생략됩니다. 이 경우 피격자는 무조건 대미지를 적용받게 됩니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | Entity 타입의 프로퍼티를 정의하고 OnBeginPlay()에서 해당 엔티티의 Name 속성을 log()로 출력하는 스크립트를 작성해줘 [answer] | Entity 타입의 프로퍼티를 정의하고 `OnBeginPlay()`에서 해당 엔티티의 `Name` 속성을 출력하는 스크립트는 다음과 같습니다. ```lua Property: [Sync] Entity TargetEntity = EntityPath Method: [Client Only] void OnBeginPlay() { log(self.TargetEntity.Name) } ``` **설명:** 1. **Property 블록**: `[Sync]` 속성을 가진 `Entity` 타입의 프로퍼티 `TargetEntity`를 선 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 3차원 벡터와 위치를 표현합니다. **Vector2**와 마찬가지로 프로퍼티 또는 변수에 할당 시 참조됩니다. **Vector2**에서 멤버 변수 z가 추가됩니다. **TransformComponent**의 **Position**에 할당할 때를 제외하곤 주로 **Vector2**를 사용합니다. 값의 생성과 참조, 그리고 멤버 변수 x, y, z에 접근하는 방법은 아래를 참고합니다. ```lua Method: [Server Only] void OnBeginPlay() { local tmpVector3 = Vector3(10,
[task] | code [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | MonsterSpawn 컴포넌트의 OnUpdate 메서드를 server only 실행 공간으로 설정하여 5초마다 현재 몬스터 수를 확인하고 부족분을 스폰하는 로직을 구현하는 스크립트를 작성해줘 [answer] | `MonsterSpawn` 컴포넌트의 `OnUpdate` 메서드를 `[server only]` 실행 공간으로 설정하고, 5초 간격으로 현재 몬스터 수를 확인하여 최대 수량(`MaxSpawnCount`)에 도달하지 못하면 부족한 만큼만 스폰하는 로직을 구현했습니다. 참고 문서에 제시된 `GetRandomPosition` 및 `SpawnMonster` 함수는 매개변수 `spawnCount`를 받아서 동작하도록 수정되어 있으며, 이들도 함께 포함하여 완전한 스크립트 구성을 제공합니다. ```lua Property: number Tim [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 이번에는 5초마다 한 마리씩 스폰하는 것이 아니라, 5초마다 현재 몬스터 수량을 체크하여 최대 몬스터 수량보다 부족한 만큼 생성하도록 수정해 봅시다. 1. **MonsterSpawn**의 `GetRandomPosition()` 함수에 매개 변수를 하나 추가합니다. 2. 매개 변수 타입은 **number**로, 이름은 **spawnCount**로 설정하고, 리턴 타입을 **table**로 변경합니다. 함수는 다음과 같이 수정합니다. ```lua table GetRandomPosition(nu
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 같은 컴포넌트 내 프로퍼티에 접근할 때 self. 뒤에 프로퍼티명을 붙이는 문법이 궁금합니다. [answer] | 같은 컴포넌트 내에서 프로퍼티에 접근하려면 `self.` 뒤에 프로퍼티 이름을 붙이는 문법을 사용합니다. 예를 들어, `StringProperty`라는 이름의 프로퍼티가 선언되어 있다면, 함수 내에서 `self.StringProperty`라고 작성하여 해당 값에 접근하거나 값을 할당할 수 있습니다. 아래 예제는 `OnBeginPlay()` 함수 안에서 `self.StringProperty`를 통해 프로퍼티 값을 로컬 변수에 대입하고 출력하는 방법입니다. ```lua Property: [None] string StringProp [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 함수에서 프로퍼티에 접근하는 방법을 알아봅시다. 먼저 같은 컴포넌트에서는 다음과 같이 접근할 수 있습니다. ```lua Property: [None] string StringProperty = "Hello!!" Method: [Server Only] void OnBeginPlay() { local stringProperty = self.StringProperty --"self.프로퍼티명"으로 해당 프로퍼티를 가져옴 } ``` 다른 컴포넌트의 프로퍼티는 다음과 같이 접근할 수 있습니다. 아래 상황에서 접근하는 방법
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | OnBeginPlay에서 다른 엔티티의 프로퍼티를 읽은 후 log 함수로 출력했을 때 콘솔 창에 값이 정상적으로 표시되지 않는 원인은 무엇일까요? [answer] | 제공된 참고 문서에는 `OnBeginPlay`에서 다른 엔티티의 프로퍼티를 읽고 `log` 함수로 출력했을 때 콘솔 창에 값이 정상적으로 표시되지 않는 구체적인 원인에 대한 설명이 없습니다. 문서에는 `OnBeginPlay` 내에서 `_EntityService:GetEntityByPath`를 사용하여 엔티티를 가져오고, 해당 엔티티의 컴포넌트 프로퍼티에 접근하여 `log`로 출력하는 **정상적인 동작 예제**만 제시되어 있습니다. 예제에서는 `HelloMSW!!` 또는 엔티티 이름, `Position` 값이 정상적으로 출력되는 것 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 함수에서 프로퍼티에 접근하는 방법을 알아봅시다. 먼저 같은 컴포넌트에서는 다음과 같이 접근할 수 있습니다. ```lua Property: [None] string StringProperty = "Hello!!" Method: [Server Only] void OnBeginPlay() { local stringProperty = self.StringProperty --"self.프로퍼티명"으로 해당 프로퍼티를 가져옴 } ``` 다른 컴포넌트의 프로퍼티는 다음과 같이 접근할 수 있습니다. 아래 상황에서 접근하는 방법
[task] | two_turn [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | 몬스터 스폰 주기 및 최대 수량 제한을 구현할 때, `_TimerService:SetTimerOnce`를 재귀적으로 호출하는 방식과 `OnUpdate`에서 시간 차이를 누적하는 방식 중 어느 쪽이 성능 면에서 권장되나요? [answer] | 제공된 참고 문서에는 `_TimerService:SetTimerOnce`를 재귀적으로 호출하는 방식과 `OnUpdate`에서 시간 차이를 누적하는 방식 중 어느 쪽이 성능 면에서 권장되는지에 대한 정보가 포함되어 있지 않습니다. 문서에는 `OnBeginPlay`에서 엔티티를 스폰하는 예제(`_SpawnService:SpawnByEntity`, `_SpawnService:SpawnByModelId`)와, 랜덤 위치를 계산하여 몬스터를 스폰하는 `GetRandomPosition()` 및 `SpawnMonster()` 함수의 구현 방법 [followup] | 현재 스크립트 구조에 맞춰 최대 5마리의 몬스터가 동시에 존재할 수 있도록 제한하고, 스폰 주기(예: 3초)를 적용하는 `SpawnMonster` 및 타이머 관련 메서드 전체 코드 예제를 작성해 주세요. [followup_answer] | 제공된 참고 문서에는 몬스터의 최대 수량 제한(동시 존재 수)을 검사하거나 관리하는 API(예: 현재 스폰된 엔티티 목록 조회, 특정 모델 ID로 스폰된 엔티티 수 카운팅 등)에 대한 정보가 포함되어 있지 않습니다. 또한 `_TimerService`를 사용하여 주기적으로 스폰하는 구체적인 코드 예제나 메서드 시그니처가 문서에 명시되어 있지 않습니다. 문서에는 `_SpawnService:SpawnByModelId`를 사용하여 몬스터를 스폰하는 방법과 `GetRandomPosition`을 통해 랜덤 좌표를 생성하는 방법만 설명되어 있 [scores] | [judge_reason] | [source_doc] | 지정한 영역 안에서 랜덤한 위치에 몬스터를 스폰하는 기능을 추가하겠습니다. 위에 확인했던 좌표를 기준으로 스폰 영역을 정의하고, 영역 내 랜덤한 위치에 몬스터를 스폰해 봅시다. 1. **MonsterSpawn**을 스크립트 에디터에서 열고 **Vector3** 타입의 새 함수 `GetRandomPosition()`을 추가합니다. 2. `GetRandomPosition()` 함수를 다음과 같이 작성합니다. 위에서 기록해두었던 좌표를 함수에 활용합니다. ```lua Vector3 GetRandomPositio
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent의 Attack(), AttackFrom(), AttackFast() 함수의 기준점과 범위 설정 방식의 차이를 표로 정리해서 설명해 주세요. [answer] | AttackComponent의 `Attack()`, `AttackFrom()`, `AttackFast()` 함수는 공격 범위의 기준점과 설정 방식에서 다음과 같은 차이가 있습니다. | 함수명 | 기준점 | 범위 설정 방식 | 특징 | | :--- | :--- | :--- | :--- | | `Attack()` | **캐릭터 위치** | `shape` 타입으로 공격 범위 형태를 설정합니다. | 캐릭터의 현재 위치를 기준으로 공격 범위를 지정합니다. | | `AttackFrom()` | **월드 좌표** | **사각형** 공격 범위를 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent
[task] | single_turn [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | OnUpdate에서 self:GetCurMonsterCount()가 nil을 반환하는 경우를 무시하고 계속 실행하게 하려면 어떤 조건 분기를 추가해야 하나요? [answer] | `self:GetCurMonsterCount()`가 `nil`을 반환하는 경우, 현재 몬스터 수를 계산할 수 없으므로 스폰 로직을 실행하면 안 됩니다. 따라서 `nil` 체크 후 바로 `return`하여 함수를 종료하는 것이 아니라, `nil`인 경우를 건너뛰고 나머지 로직(즉, `spawnCount` 계산 및 스폰)을 실행하지 않도록 해야 합니다. 참고 문서의 `OnUpdate()` 함수에서는 이미 `nil`인 경우를 처리하기 위해 `return`을 사용하고 있습니다. 질문하신 "무시하고 계속 실행"이라는 표현은 다소 모호할 수 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 이번에는 5초마다 한 마리씩 스폰하는 것이 아니라, 5초마다 현재 몬스터 수량을 체크하여 최대 몬스터 수량보다 부족한 만큼 생성하도록 수정해 봅시다. 1. **MonsterSpawn**의 `GetRandomPosition()` 함수에 매개 변수를 하나 추가합니다. 2. 매개 변수 타입은 **number**로, 이름은 **spawnCount**로 설정하고, 리턴 타입을 **table**로 변경합니다. 함수는 다음과 같이 수정합니다. ```lua table GetRandomPosition(nu
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | `CalcDamage()`와 `OnHit()` 함수의 역할과 데이터 전달 관계를 2열 2행 표로 정리하세요. [answer] | 제공된 문서에 근거하여 `CalcDamage()`와 `OnHit()` 함수의 역할과 데이터 전달 관계를 2열 2행 표로 정리하면 다음과 같습니다. | 함수 | 역할 및 데이터 전달 관계 | | :--- | :--- | | `CalcDamage()` | 공격자의 **AttackComponent**에 위치하며, 공격 대미지 값을 계산하여 반환하는 함수입니다. | | `OnHit()` | 피격자의 **HitComponent**에 위치하며, `CalcDamage()` 함수의 반환 값을 매개 변수로 전달받아 대미지를 적용하거나 추가 계산을 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 프로퍼티의 동기화 여부 설정에서 [Sync]와 [None]의 차이가 무엇이며, 서버와 클라이언트에서 값이 어떻게 다른지 확인하는 방법은? [answer] | 메이플스토리 월드의 프로퍼티 동기화 설정인 `[Sync]`와 `[None]`의 차이와 확인 방법은 다음과 같습니다. ### 1. `[Sync]`와 `[None]`의 차이 * **`[Sync]` (동기화):** * 프로퍼티의 값이 **서버와 클라이언트 간에 동기화**됩니다. * 서버에서 프로퍼티 값을 변경하면, 클라이언트에서도 해당 값이 자동으로 업데이트되어 반영됩니다. * 모든 엔티티가 같은 값을 공유하거나, 서버 상태를 클라이언트에 전달해야 할 때 사용합니다. * **`[None]` ( [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 이번 과정에서는 메이플스토리 월드의 프로퍼티가 무엇인지 알아봅니다. 또한 프로퍼티 선언 및 초깃값 설정, 프로퍼티 동기화 방법 등 프로퍼티 관련 정보를 살펴보겠습니다. # MSW의 프로퍼티 프로퍼티는 컴포넌트의 멤버 변수로써, 엔티티 별로 다른 값을 가질 수 있습니다. 프로퍼티는 세 가지 특징이 있습니다. * 루아 변수와 달리 메이플스토리 월드의 프로퍼티는 선언할 때 타입을 설정하고, 값을 대입하거나 참조할 수 있습니다. * 컴포넌트 내 외부에서 프로퍼티에 접근할 수 있고, 각 함수에서 값을 읽거나 쓸 수
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | _entityService:GetEntityByPath 메서드를 사용해 다른 엔티티를 참조할 때 경로 문자열은 어떻게 지정해야 하나요? [answer] | `_EntityService:GetEntityByPath` 메서드에서 사용하는 경로 문자열은 **`/maps/`** 접두사로 시작하며, 그 뒤에 **맵 이름**과 **엔티티 이름**을 `/`로 구분하여 지정합니다. 문서에 제시된 예시를 보면 다음과 같은 형식을 따릅니다. * **기본 형식**: `/maps/{맵 이름}/{엔티티 이름}` * **예시 1**: `/maps/map01/A` (맵 이름이 `map01`이고, 엔티티 이름이 `A`인 경우) * **예시 2**: `/maps/map01/entityA` (맵 이름이 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 함수에서 프로퍼티에 접근하는 방법을 알아봅시다. 먼저 같은 컴포넌트에서는 다음과 같이 접근할 수 있습니다. ```lua Property: [None] string StringProperty = "Hello!!" Method: [Server Only] void OnBeginPlay() { local stringProperty = self.StringProperty --"self.프로퍼티명"으로 해당 프로퍼티를 가져옴 } ``` 다른 컴포넌트의 프로퍼티는 다음과 같이 접근할 수 있습니다. 아래 상황에서 접근하는 방법
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | HitComponent가 없는 엔티티가 AttackComponent의 공격 범위 안에 있을 때 어떤 일이 일어나나요? [answer] | HitComponent가 없는 엔티티는 AttackComponent의 공격 범위 안에 있더라도 피격 처리 대상이 되지 않습니다. AttackComponent의 함수를 활용해 공격을 했을 때, 유효한 공격 범위 안에 HitComponent를 가진 엔티티가 있어야만 그 엔티티가 피격 처리 대상이 됩니다. HitComponent는 엔티티가 피격 대상인지 판단하고, 엔티티가 받을 최종 대미지를 결정하는 역할을 수행합니다. 따라서 HitComponent가 없으면 공격 범위에 들어와도 피격 판정을 받지 않고, 대미지나 상태 변경 등의 효과가 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 다른 엔티티에 있는 컴포넌트의 프로퍼티 값을 가져와서 현재 컴포넌트의 프로퍼티에 대입하는 코드를 어떻게 작성하나요? [answer] | 다른 엔티티에 있는 컴포넌트의 프로퍼티 값을 가져와서 현재 컴포넌트의 프로퍼티에 대입하려면, `_EntityService:GetEntityByPath`로 대상 엔티티를 찾고, 해당 엔티티에서 특정 컴포넌트를 참조한 후 프로퍼티 값을 읽어서 `self.프로퍼티명`에 대입하면 됩니다. 아래는 "A" 엔티티의 "ComponentA"에 있는 `PropertyA` 값을 가져와서 현재 컴포넌트의 `PropertyB`에 대입하는 예시입니다. ```lua Property: [None] string PropertyB = "" Method: [ [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 함수에서 프로퍼티에 접근하는 방법을 알아봅시다. 먼저 같은 컴포넌트에서는 다음과 같이 접근할 수 있습니다. ```lua Property: [None] string StringProperty = "Hello!!" Method: [Server Only] void OnBeginPlay() { local stringProperty = self.StringProperty --"self.프로퍼티명"으로 해당 프로퍼티를 가져옴 } ``` 다른 컴포넌트의 프로퍼티는 다음과 같이 접근할 수 있습니다. 아래 상황에서 접근하는 방법
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | 공격 실패 시 false를 반환하는 기본 `IsAttackTarget()` 함수와 무조건 타격을 주는 true 반환 함수의 코드를 주석으로 설명 포함하여 출력하세요. [answer] | ```lua Method: [server only] void OnBeginPlay() { -- 초기화 로직 } -- 공격 실패 시 false를 반환하는 기본 IsAttackTarget() 함수 -- 충돌한 엔티티(defender)가 적합한 공격 대상인지 판단합니다. -- false: 공격 실패, 이후 절차 중단 -- true: 공격 성공, HitComponent로 판단 전달 override boolean IsAttackTarget(Entity defender) { return false } -- 무조건 타격을 주는 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | two_turn [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | 몬스터 스폰 기능에서 `GetCurMonsterCount()` 함수가 테이블의 비어 있는 공간을 어떻게 정리하는지 궁금합니다. [answer] | `GetCurMonsterCount()` 함수는 `self.MonsterArray` 테이블을 순회하면서 `isvalid()` 함수를 사용하여 각 엔티티가 실제로 존재하는지(Valid 상태인지)를 확인합니다. 몬스터가 사망하거나 파괴되면 해당 엔티티는 더 이상 유효하지 않게 되며, `isvalid()`는 `false`를 반환합니다. 이 함수는 다음과 같은 로직으로 테이블의 비어 있는 공간(더 이상 존재하지 않는 엔티티 참조)을 정리합니다. 1. **순회 및 검증**: `while` 루프를 통해 테이블의 첫 번째 인덱스부터 끝까지 [followup] | `table.remove(self.MonsterArray, i)` 실행 시 배열 인덱스가 어떻게 재배치되며, `isvalid` 체크 없이 단순히 `#self.MonsterArray`만 사용했을 때의 차이점을 코드 예시로 설명해 주세요. [followup_answer] | `table.remove(self.MonsterArray, i)`가 실행되면, 제거된 인덱스 `i` 이후에 있던 모든 요소들이 한 칸씩 앞쪽으로 이동합니다. 예를 들어, `{A, B, C, D}`에서 인덱스 2의 `B`를 제거하면 `{A, C, D}`가 되며, `C`는 인덱스 2로, `D`는 인덱스 3으로 이동합니다. `isvalid` 체크 없이 단순히 `#self.MonsterArray`만 사용할 경우, 몬스터가 사망하여 엔티티가 파괴되었더라도 테이블에는 그 참조가 남아있기 때문에 실제 존재하는 몬스터 수보다 많은 값을 반환합니 [scores] | [judge_reason] | [source_doc] | 이번에는 5초마다 몬스터를 한 마리씩 추가로 스폰하되, 최대 10개까지만 생성하는 기능을 만들어보겠습니다. 1. **MonsterSpawn**을 열고 프로퍼티를 추가합니다. ```lua Property: [Sync] number MaxSpawnCount = 10 [Sync] number Time = 0 [None] table MonsterArray = {} ``` 2. `OnBeginPlay()` 함수를 다음과 같이 수정합니다. ```lua
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 다른 컴포넌트 프로퍼티 접근 시 필요한 엔티티 경로 문자열과 컴포넌트 접근 메서드 이름을 표로 구분하여 정리하세요. [answer] | 제공된 문서에 근거하여, 다른 컴포넌트의 프로퍼티에 접근할 때 필요한 정보(엔티티 경로 문자열과 컴포넌트 접근 방식)를 정리하면 다음과 같습니다. 참고: 문서에는 `GetComponent`와 같은 메서드 이름이 명시되어 있지 않으며, 엔티티 객체에서 컴포넌트 이름을 직접 프로퍼티처럼 접근하는 방식(`entity.ComponentName`)을 사용합니다. | 구분 | 값 / 코드 | 설명 | | :--- | :--- | :--- | | **엔티티 경로 문자열** | `"/maps/map01/A"` | `_EntityService: [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 함수에서 프로퍼티에 접근하는 방법을 알아봅시다. 먼저 같은 컴포넌트에서는 다음과 같이 접근할 수 있습니다. ```lua Property: [None] string StringProperty = "Hello!!" Method: [Server Only] void OnBeginPlay() { local stringProperty = self.StringProperty --"self.프로퍼티명"으로 해당 프로퍼티를 가져옴 } ``` 다른 컴포넌트의 프로퍼티는 다음과 같이 접근할 수 있습니다. 아래 상황에서 접근하는 방법
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackFast() 함수가 일반 Attack() 함수보다 성능 면에서 어떤 이점이 있나요? [answer] | 제공된 문서에 따르면, `AttackFast()` 함수는 **반환 값이 없는** Attack 함수입니다. 이 함수의 주요 성능 이점은 **불필요한 table 객체 생성을 줄일 수 있다는 점**입니다. 일반적인 `Attack()` 함수와 달리 `AttackFast()`는 캐릭터 위치를 기준으로 공격 범위를 설정하되, 불필요하게 생성되는 table 객체를 최소화하여 메모리 할당이나 객체 생성에 드는 부하를 낮추는 데 유리합니다. 공격 범위 형태는 `shape` 타입으로 설정하며, `Attack()`과 동일하게 사용 가능합니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent
[task] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | SyncTable< string, number > 타입의 프로퍼티를 선언하고 OnBeginPlay에서 특정 키의 값을 수정하여 서버와 클라이언트 간 동기화되는 구조를 검증하는 스크립트를 작성해줘. [answer] | `SyncTable< string, number >` 타입은 일반 `table`과 달리 서버와 클라이언트 간의 동기화(Sync)가 지원되는 타입입니다. 이 타입을 사용하면 특정 키(key)에 대한 값(value)을 수정하면 그 변경 사항이 클라이언트에까지 반영됩니다. 요청하신 구조를 검증하기 위한 스크립트는 다음과 같습니다. ```lua Property: [Sync] SyncTable< string, number > ScoreTable = {} Method: [Server Only] void OnBeginPlay() { [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | #### table 루아 테이블을 참조합니다. 동기화(Sync)는 되지 않습니다. ```lua Property: [None] table TableProperty = {1,2,3,4,5} Method: [Server Only] void OnBeginPlay() { local tmpTable = self.TableProperty self.TableProperty[1] = 10 log("tmpTable : "..tmpTable[1]) --콘솔 창에 "tmpTable : 10" 출력 log("TablePr
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | CalcDamage() 함수의 오버라이드 시그니처와 반환값 100을 포함하는 코드만 출력하고 설명은 주석으로 작성하세요. [answer] | ```lua Method: -- 공격 대미지 계산 방법을 정의합니다. -- 반환값 100을 포함하여, 충돌 발생 시 항상 100의 대미지가 적용되도록 합니다. override int CalcDamage(Entity attacker, Entity defender, string attackInfo) { return 100 } ``` [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 1. **Workspace - BaseEnvironment - NativeScripts - Component - AttackComponent**를 선택합니다. 2. 콘텍스트 메뉴에서 **Extend**를 눌러 컴포넌트를 확장합니다. 확장한 컴포넌트의 이름을 **CharacterAttack**으로 변경합니다. 3. **CharacterAttack** 컴포넌트를 더블 클릭하여 스크립트 에디터를 엽니다. 4. **Method**에서 새로운 함수를 추가하고, 이름을 **NormalAttack** 으로 변경합니다. 5. `Att
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | NormalAttack 함수에서 attackSize와 attackOffset 변수의 타입 및 초기값을 2열 표로 정리해서 작성하세요. [answer] | NormalAttack 함수에서 사용되는 변수의 타입과 초기값은 다음과 같습니다. | 변수명 | 타입 | 초기값 | | :--- | :--- | :--- | | attackSize | Vector2 | Vector2(1, 1) | | attackOffset | Vector2 | Vector2(0, 0) | [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 1. **Workspace - BaseEnvironment - NativeScripts - Component - AttackComponent**를 선택합니다. 2. 콘텍스트 메뉴에서 **Extend**를 눌러 컴포넌트를 확장합니다. 확장한 컴포넌트의 이름을 **CharacterAttack**으로 변경합니다. 3. **CharacterAttack** 컴포넌트를 더블 클릭하여 스크립트 에디터를 엽니다. 4. **Method**에서 새로운 함수를 추가하고, 이름을 **NormalAttack** 으로 변경합니다. 5. `Att
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | 공격자가 자신의 공격 박스에 포함되어 타격을 받는 것을 원할 때 IsAttackTarget() 함수를 어떻게 수정해야 하나요? [answer] | 기본적으로 `IsAttackTarget()` 함수는 공격자가 자신의 공격 박스에 포함되지 않도록 처리되어 있습니다. 따라서 공격자도 공격 대상에 포함시켜 스스로 타격을 받게 하려면, 해당 함수의 리턴값을 `true`로 변경하면 됩니다. 수정 방법은 다음과 같습니다. ```lua override boolean IsAttackTarget(Entity defender) { return true } ``` 이렇게 `true`를 반환하면 공격 실패로 간주되지 않으며, 피격자의 `IsHitTarget()` 판단을 거치지 않고 무조 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | two_turn [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | `GetRandomPosition()` 함수에서 `_UtilLogic:RandomIntegerRange(0,100)`을 사용해 x좌표를 계산하는데, z좌표가 항상 0으로 고정되어 1차원 선상에서만 스폰되는 이유가 무엇인가요? [answer] | `GetRandomPosition()` 함수에서 z좌표가 항상 0으로 고정되는 이유는, 함수 본문에서 z값을 계산하는 로직이 없고 반환할 `Vector3` 객체를 생성할 때 z값을 명시적으로 `0`으로 지정하고 있기 때문입니다. 제공된 코드 예시를 보면 다음과 같이 작성되어 있습니다. ```lua Vector3 GetRandomPosition() { local leftX = -3.571 -- 스폰 영역 왼쪽의 x좌표 local rightX = 2.688 -- 스폰 영역 오른쪽의 x좌표 local y = 0. [followup] | 2.5D 환경에서 z축(깊이) 변동이 필요할 때 `Vector3` 생성 시 z값을 어떻게 변경해야 하며, 기존 코드에 z 좌표 범위를 추가하는 구체적인 수정 예시를 보여주세요. [followup_answer] | 2.5D 환경에서 z축(깊이) 변동이 필요할 때는 `Vector3` 생성 시 z값을 고정된 0이 아닌, 정의한 z 범위 내에서 랜덤하게 계산된 값으로 변경해야 합니다. 기존 코드에서는 z값이 `0`으로 하드코딩되어 있어 1차원 선상에서만 스폰되었습니다. z 좌표 범위를 추가하려면 x좌표 계산 방식과 동일하게 z축의 최소/최대 좌표를 정의하고, `_UtilLogic:RandomIntegerRange`를 이용해 해당 범위 내의 랜덤 값을 구한 후 `Vector3` 생성 시 z 인자로 전달하면 됩니다. 구체적인 수정 예시는 다음과 같 [scores] | [judge_reason] | [source_doc] | 지정한 영역 안에서 랜덤한 위치에 몬스터를 스폰하는 기능을 추가하겠습니다. 위에 확인했던 좌표를 기준으로 스폰 영역을 정의하고, 영역 내 랜덤한 위치에 몬스터를 스폰해 봅시다. 1. **MonsterSpawn**을 스크립트 에디터에서 열고 **Vector3** 타입의 새 함수 `GetRandomPosition()`을 추가합니다. 2. `GetRandomPosition()` 함수를 다음과 같이 작성합니다. 위에서 기록해두었던 좌표를 함수에 활용합니다. ```lua Vector3 GetRandomPositio
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | Attack()과 AttackFrom() 함수의 차이점과 각각 어떤 상황에 사용하는지 궁금합니다. [answer] | 메이플스토리 월드에서 `Attack()`과 `AttackFrom()` 함수는 공격 범위를 설정하는 기준점과 형태가 다르며, 각각 다른 상황에 맞게 사용됩니다. **1. Attack() 함수** * **차이점:** **캐릭터 위치**를 기준으로 공격 범위를 설정합니다. * **사용 상황:** 캐릭터가 있는 곳에서 특정 방향으로 공격을 펼치는 일반적인 공격에 사용합니다. 공격 범위의 형태는 `shape` 타입으로 지정할 수 있으며, 캐릭터의 현재 위치와 방향에 따라 공격 판정이 이루어집니다. **2. AttackFrom() [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | OnHit() 함수에서 대미지 값을 20으로 고정하고 스케일 증가 로직을 포함하는 mLua 코드 블록만 출력하세요. [answer] | ```lua Method: [server only] void OnHit(Entity attacker, int damage, boolean isCritical, string attackInfo, int hitCount) { __base:OnHit(attacker, 20, isCritical, attackInfo, hitCount) local scale = self.Entity.TransformComponent.Scale scale = scale * 1.3 self.Entity.TransformCompon [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | **AttackComponent**를 가진 엔티티가 **HitComponent**를 가진 엔티티를 공격하면, 별다른 추가 처리를 하지 않아도 엔티티는 피격 당하게 됩니다. 이 경우 추가 처리가 없기 때문에 기본값인 1로 대미지 스킨이 재생됩니다. 피격을 구현할 때는 공격량 그대로 대미지로 받을 수도 있지만, 공격과 다른 공식을 함께 사용해 피격량을 계산할 수도 있습니다. 예를 들어 100의 공격력으로 공격했지만 몬스터의 방어력이 높은 경우 절반의 공격만 유효하게 처리할 수 있습니다. 이번에는 아래 두 가지를 구현해 피격 당하는지를
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 스크립트에서 Vector2(10, 10) 같은 방식으로 벡터를 생성하고 프로퍼티에 할당하는 과정이 지역변수 할당과 어떻게 다른가요? [answer] | mLua에서 `Vector2(10, 10)`으로 벡터를 생성한 후 프로퍼티에 할당하는 과정은 지역변수 할당과 **참조(Reference)** 방식 때문에 결과가 다릅니다. Vector2는 테이블 형태로 제공되며, 프로퍼티나 변수에 할당 시 **참조**됩니다. 이는 할당된 변수나 프로퍼티가 원본 객체의 주소를 공유한다는 뜻입니다. ### 1. 지역변수 할당 시 `local tmpVector2 = Vector2(10, 10)`과 같이 지역변수에 할당하면, `tmpVector2`는 해당 벡터 객체를 참조합니다. 이후 `tmpVecto [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | #### table 루아 테이블을 참조합니다. 동기화(Sync)는 되지 않습니다. ```lua Property: [None] table TableProperty = {1,2,3,4,5} Method: [Server Only] void OnBeginPlay() { local tmpTable = self.TableProperty self.TableProperty[1] = 10 log("tmpTable : "..tmpTable[1]) --콘솔 창에 "tmpTable : 10" 출력 log("TablePr
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | 기본 HitComponent와 확장한 MonsterHit 컴포넌트를 함께 사용할 때 발생하는 오류와 이를 방지하기 위한 필수 조치 사항을 3문장 이내로 설명하세요. [answer] | 기본 HitComponent와 확장한 컴포넌트를 함께 사용하면 오류가 발생합니다. 따라서 확장 컴포넌트를 적용하기 전 모델에서 기본 HitComponent를 반드시 삭제해야 합니다. 이미 맵에 배치된 몬스터에는 변경 사항이 적용되지 않으므로 삭제 후 재배치해야 합니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | **AttackComponent**를 가진 엔티티가 **HitComponent**를 가진 엔티티를 공격하면, 별다른 추가 처리를 하지 않아도 엔티티는 피격 당하게 됩니다. 이 경우 추가 처리가 없기 때문에 기본값인 1로 대미지 스킨이 재생됩니다. 피격을 구현할 때는 공격량 그대로 대미지로 받을 수도 있지만, 공격과 다른 공식을 함께 사용해 피격량을 계산할 수도 있습니다. 예를 들어 100의 공격력으로 공격했지만 몬스터의 방어력이 높은 경우 절반의 공격만 유효하게 처리할 수 있습니다. 이번에는 아래 두 가지를 구현해 피격 당하는지를
[task] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | `string` 타입의 `StringProperty` 프로퍼티에 "Hello!!"라는 초깃값을 설정하고, `OnBeginPlay`에서 해당 값을 로컬 변수에 대입하여 로그로 출력하는 스크립트를 작성해줘 [answer] | ```lua Property: [None] string StringProperty = "Hello!!" Method: [server only] void OnBeginPlay() { local value = self.StringProperty log(value) } ``` [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 예제 코드에서 self._T.accTime == nil 조건이 필요한 이유는 무엇이며, 이 조건을 생략하면 어떤 문제가 발생할 수 있습니까? [answer] | `self._T.accTime == nil` 조건이 필요한 이유는 **_T 프로퍼티가 함수 내에서 처음 사용되는 시점에 비로소 생성되기 때문**입니다. 일반 프로퍼티는 컴포넌트가 시작할 때 초기값이 설정되지만, `_T` 프로퍼티는 명시적으로 선언되는 시점(즉, 코드가 처음으로 해당 변수를 참조하는 시점)에 값이 할당됩니다. 따라서 `OnUpdate`와 같이 반복적으로 호출되는 함수에서 `_T` 프로퍼티를 사용할 때는, 해당 변수가 아직 생성되지 않았을 수 있습니다. **이 조건을 생략하면 다음과 같은 문제가 발생할 수 있습니다: [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 프로퍼티는 컴포넌트 내 멤버 변수로, 함수에서 쓰고 읽어도 종료되지 않고 함수에서 설정한 값이 보존됩니다. 따라서 함수를 통해 어떤 값을 담아놓거나 함수를 반복 호출하여 값을 누적할 때 활용될 수 있습니다. 다음은 3초 간격으로 콘솔 창에 "HelloMSW"를 출력하는 예제입니다. ```lua Property: [Sync] number AccTime = 0 Method: [Server Only] void OnUpdate(delta) { self.AccTime = self.AccTime + delta --OnUpd
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | IsAttackTarget() 함수에서 false를 반환하면 게임 로직상 어떤 처리가 수행되나요? [answer] | `IsAttackTarget()` 함수에서 `false`를 반환하면 공격이 실패한 것으로 처리되며, 해당 공격에 대한 이후 절차가 중단됩니다. 구체적인 처리 흐름은 다음과 같습니다. 1. **공격 실패 판정**: 공격자의 `AttackComponent`에서 `IsAttackTarget()`이 `false`를 반환하면 그 엔티티는 공격 대상으로 인정되지 않습니다. 2. **절차 중단**: `false`가 반환되면 `HitComponent`로의 판단이 넘어가지 않으며, 피격자의 `IsHitTarget()` 함수는 호출되지 않습니 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | Property 블록에 선언하지 않고 코드 내에서만 _T 프로퍼티를 사용해도 컴파일 오류가 발생하지 않는 이유는 무엇입니까? [answer] | _T 프로퍼티는 일반 프로퍼티와 달리 Property 블록에 명시적으로 선언할 필요가 없으며, 코드 내에서 처음 사용된 시점에 자동으로 생성되기 때문입니다. 일반 프로퍼티는 Property 블록에서 타입과 초기값을 선언해야 컴파일러가 이를 인식하지만, _T 프로퍼티는 런타임에 동적으로 생성되는 구조입니다. 따라서 `self._T.이름` 형태로 값을 할당하거나 읽는 순간 해당 프로퍼티가 생성되며, 별도의 선언 단계가 필요 없습니다. 이 특성으로 인해 Property 블록에 선언을 생략해도 컴파일 오류가 발생하지 않습니다. 다만, [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 프로퍼티는 컴포넌트 내 멤버 변수로, 함수에서 쓰고 읽어도 종료되지 않고 함수에서 설정한 값이 보존됩니다. 따라서 함수를 통해 어떤 값을 담아놓거나 함수를 반복 호출하여 값을 누적할 때 활용될 수 있습니다. 다음은 3초 간격으로 콘솔 창에 "HelloMSW"를 출력하는 예제입니다. ```lua Property: [Sync] number AccTime = 0 Method: [Server Only] void OnUpdate(delta) { self.AccTime = self.AccTime + delta --OnUpd