[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | IsAttackTarget() 함수의 리턴값이 true가 되면 HitComponent의 IsHitTarget()은 왜 호출되지 않는가요? [answer] | `IsAttackTarget()` 함수의 리턴값이 `true`가 되면, 시스템이 해당 공격이 반드시 성공한다고 간주하기 때문에 피격자의 `HitComponent`에 있는 `IsHitTarget()` 함수는 호출되지 않습니다. 일반적인 흐름에서는 `IsAttackTarget()`이 `false`를 반환하면 공격 실패로 처리되어 이후 절차가 중단되고, `true`를 반환하면 `HitComponent`의 `IsHitTarget()`이 피격 여부를 판단한 뒤 대미지가 적용됩니다. 하지만 `IsAttackTarget()`에서 `true`를 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | `TransformComponent` 타입의 `Component` 프로퍼티와 `TransformComponentRef` 타입의 `ComponentRef` 프로퍼티를 선언하고, `OnBeginPlay`에서 각각의 `Position` 값을 로그로 출력하는 스크립트를 작성해줘 [answer] | 요청하신 `TransformComponent`와 `TransformComponentRef` 타입의 프로퍼티를 선언하고, `OnBeginPlay`에서 각각의 `Position` 값을 로그로 출력하는 스크립트는 다음과 같습니다. ```lua Property: [None] TransformComponent ComponentProperty = EntityPath [None] TransformComponentRef ComponentRefProperty = EntityPath Method: [client only] void OnBeginP [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | OnUpdate 함수에서 delta 값을 누적할 때 일반 프로퍼티를 사용하는 것과 _T 프로퍼티를 사용하는 것 중 어떤 방식이 권장되며, 그 이유는 무엇입니까? [answer] | 제공된 문서에 따르면, **_T 프로퍼티** 사용이 권장됩니다. **이유:** 일반 프로퍼티를 사용할 경우, 불필요한 프로퍼티 선언과 동기화([Sync])를 계속해야 하므로 번거롭습니다. 반면 _T 프로퍼티는 함수 내에서 처음 쓴 시점에 자동 생성되므로, 명시적인 선언과 동기화 설정 없이 간편하게 누적 값을 관리할 수 있습니다. 다만 _T 프로퍼티는 동기화가 불가능하여 **동일한 실행 공간(예: Server Only)에서만** 사용할 수 있다는 점에 유의해야 합니다. **_T 프로퍼티를 사용한 권장 코드 예시:** ```lu [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] | CalcDamage()로 만든 대미지를 HitComponent에 있는 엔티티가 실제로 받기 위해서는 어떤 처리 과정이 필요합니까? [answer] | 제공된 참고 문서에 따르면, `AttackComponent`를 통한 공격과 `HitComponent`를 가진 엔티티의 피격 처리 과정은 다음과 같습니다. 1. **HitComponent 보유**: 피격될 엔티티에는 반드시 `HitComponent`가 존재해야 합니다. 이 컴포넌트는 엔티티의 충돌체 모양, 크기, 충돌체 그룹을 설정하는 역할을 합니다. 2. **공격 범위 설정 및 실행**: 공격자 측에서 `AttackComponent`의 `Attack()`, `AttackFrom()`, 또는 `AttackFast()` 함수를 호 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent
[task] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | `any` 타입의 `EntityProperty`와 `ComponentProperty` 프로퍼티를 선언하고, `_EntityService:GetEntityByPath("/maps/map01/entityA")`로 엔티티를 가져와 `TransformComponent`를 대입한 뒤 엔티티 이름과 위치를 로그로 출력하는 스크립트를 작성해줘 [answer] | 요청하신 `any` 타입 프로퍼티를 선언하고, 지정된 경로로 엔티티를 가져와 `TransformComponent`를 대입한 후 이름과 위치를 로그로 출력하는 스크립트는 다음과 같습니다. ```lua Property: [None] any EntityProperty = nil any ComponentProperty = nil Method: [Server Only] void OnBeginPlay() { self.EntityProperty = _EntityService:GetEntityByPath("/maps/map01/enti [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 서버와 클라이언트 [question] | 서버와 클라이언트 간 통신 구조를 3문장 이내로 요약해서 설명해 주세요. [answer] | 클라이언트는 유저의 입력을 처리하거나 서버로부터 받은 정보를 화면에 출력하며, 다른 클라이언트와 직접 통신하지 않고 오직 서버와만 정보를 주고받습니다. 서버는 게임의 주요 로직과 시스템을 처리하여 진행 상황을 업데이트하고, 이를 모든 클라이언트에게 다시 전송함으로써 모든 유저가 동일한 상황을 유지하도록 합니다. 따라서 서버는 게임의 전반적인 흐름을 담당하고, 클라이언트는 유저와 게임 사이에서 정보를 전달하는 중계자의 역할을 수행합니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 멀티 플레이를 제공하는 게임의 경우 각 게임의 특성에 따라 사용되는 통신 모델들이 다양합니다. MapleStory World는 다양한 통신 모델 중에서도 서버 - 클라이언트 모델을 사용하고 있습니다. 이번 과정에서는 MapleStory World의 통신 모델인 서버와 클라이언트의 개념과 구조, 그리고 각 공간 간의 정보전달 방법에 대해 알아보도록 하겠습니다. # 서버와 클라이언트 클라이언트는 게임을 플레이하는 유저들의 모바일이나 PC, 또는 해당 기기에서 개별적으로 돌아가는 프로그램을 의미합니다.주로 유저의
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 서버와 클라이언트 [question] | 서버와 클라이언트 공간의 독립성을 설명할 때, 프로퍼티 할당과 함수 호출의 차이를 포함하여 3문장 이내로 요약해 주세요. [answer] | 서버와 클라이언트 공간은 서로 독립적이므로, 한쪽에서 프로퍼티 값을 할당해도 다른 쪽의 프로퍼티 값은 변경되지 않습니다. 마찬가지로 한쪽의 함수가 다른 함수를 호출할 때도 같은 공간에 있는 함수만 실행되며, 반대 공간의 함수는 호출되지 않습니다. 따라서 각 공간에서 선언된 프로퍼티와 함수는 기본적으로 해당 공간 내에서만 동작합니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 서버와 클라이언트가 어떻게 상호작용을 하는지를 알아보았으니, 이제 메이커에서 작성한 프로그램이 서버와 클라이언트에서 어떻게 돌아가는지 알아보도록 합니다. 제작자가 스크립트에서 선언한 프로퍼티와 함수는 별도의 설정을 하지 않는 한 기본적으로 서버와 클라이언트 두 개의 공간에 각각 독립적으로 생성됩니다. 이름은 같지만 서로 다른 공간에 존재하기 때문에 별도의 프로퍼티 또는 함수라 보는 것이 좋습니다. 기본적으로 함수의 호출과 프로퍼티의 값 참조 및 할당은 같은 공간 내에서만 이루어집니다. 예를 들어 A라는 프로퍼티에 값을 B라는 함
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | _T 프로퍼티는 외부 컴포넌트에서도 접근할 수 있다고 하는데, 이 경우에도 동기화되지 않는 한계는 어떻게 극복해야 합니까? [answer] | _T 프로퍼티는 기본적으로 동기화(Sync) 속성이 없기 때문에, 서버와 클라이언트 간에 값이 자동으로 동기화되지 않는 한계가 있습니다. 따라서 외부 컴포넌트에서 _T 프로퍼티를 접근하여 값을 공유하거나 변경해야 하는 경우, **동기화가 필요한 일반 프로퍼티를 사용하거나**, **서버와 클라이언트 간 통신을 위한 별도의 이벤트/메시지 전송 API를 사용하여 값을 전달**하는 방식으로 극복해야 합니다. 참고 문서에는 _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] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent를 확장하여 NormalAttack 메서드와 PlayerActionEvent 핸들러를 추가하는 절차를 단계별 번호 목록으로 요약하세요. [answer] | AttackComponent를 확장하여 `NormalAttack` 메서드와 `PlayerActionEvent` 핸들러를 추가하는 절차는 다음과 같습니다. 1. **Workspace** - **BaseEnvironment** - **NativeScripts** - **Component** - **AttackComponent**를 선택합니다. 2. 콘텍스트 메뉴에서 **Extend**를 눌러 컴포넌트를 확장합니다. 확장한 컴포넌트의 이름을 **CharacterAttack**으로 변경합니다. 3. **CharacterAttack** 컴 [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] | 다른 컴포넌트의 프로퍼티 접근 시 entityA.ComponentA 형태로 컴포넌트 이름을 직접 참조해야 하는 이유를 설명해 주세요. [answer] | 메이플스토리 월드의 mLua 스크립트에서 다른 컴포넌트의 프로퍼티에 접근할 때 `entityA.ComponentA` 형태로 컴포넌트 이름을 직접 참조해야 하는 이유는, **엔티티(Entity)와 컴포넌트(Component)의 관계 구조** 때문입니다. ### 1. 엔티티는 컨테이너, 컴포넌트는 구성 요소 * **엔티티(`Entity`)**: 게임 세계에서 존재하는 객체(예: 나무, 캐릭터, 아이템)를 의미합니다. * **컴포넌트(`Component`)**: 엔티티에 붙어서 특정 기능이나 데이터를 담당하는 모듈입니다. 하나의 [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] | MapleStory World에서 권장하는 서버-클라이언트 역할 분담 구조를 단계별 번호 목록으로 제시해 주세요. [answer] | MapleStory World에서 권장하는 서버-클라이언트 역할 분담 구조는 다음과 같습니다. 1. **서버**: 게임의 전반적인 로직과 시스템을 구현하고, 게임의 흐름을 주도합니다. 2. **클라이언트**: 유저의 입력을 받아 서버로 전달하는 역할을 수행합니다. 3. **클라이언트**: 서버에서 내려받은 게임 진행 상황을 처리하여 유저에게 가시적으로 출력합니다. 4. **클라이언트**: 다른 클라이언트와 직접 통신하지 않고, 오직 서버와만 정보를 주고받습니다. 5. **서버**: 특정 클라이언트로부터 정보를 수신하면, 해당 내용 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 멀티 플레이를 제공하는 게임의 경우 각 게임의 특성에 따라 사용되는 통신 모델들이 다양합니다. MapleStory World는 다양한 통신 모델 중에서도 서버 - 클라이언트 모델을 사용하고 있습니다. 이번 과정에서는 MapleStory World의 통신 모델인 서버와 클라이언트의 개념과 구조, 그리고 각 공간 간의 정보전달 방법에 대해 알아보도록 하겠습니다. # 서버와 클라이언트 클라이언트는 게임을 플레이하는 유저들의 모바일이나 PC, 또는 해당 기기에서 개별적으로 돌아가는 프로그램을 의미합니다.주로 유저의
[task] | single_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | 일반 프로퍼티와 _T 프로퍼티의 가장 큰 차이점은 무엇이며, 왜 _T 프로퍼티는 동기화(Sync)가 불가능합니까? [answer] | 일반 프로퍼티와 _T 프로퍼티의 가장 큰 차이점은 **생성 시점**과 **접근 범위**입니다. 일반 프로퍼티는 컴포넌트 정의 단계에서 명시적으로 선언해야 하며, `[Sync]` 속성을 부여하면 클라이언트와 서버 간에 동기화됩니다. 반면, _T 프로퍼티는 함수 내에서 **처음 사용한 시점에 동적으로 생성**되며, 선언 과정을 생략할 수 있습니다. 또한, _T 프로퍼티는 내부 컴포넌트뿐만 아니라 **외부 컴포넌트에서도 접근**할 수 있다는 점이 일반 프로퍼티와 다릅니다. _T 프로퍼티가 동기화(Sync)가 불가능한 이유는 문서에 명 [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] | 몬스터가 100번 피격될 때 특수 효과를 발동하려면 OnHit() 함수에서 어떤 이벤트를 활용해야 하나요? [answer] | 몬스터가 100번 피격될 때 특수 효과를 발동하려면 **HitEvent**를 활용해야 합니다. `OnHit()` 함수는 호출될 때 **HitEvent**를 발생시킵니다. 따라서 스크립트에서 `HandleHitEvent` 이벤트 핸들러를 구현하여 이벤트가 발생하는 횟수를 카운트하고, 특정 횟수(예: 100회)에 도달했을 때 원하는 특수 효과를 실행하도록 작성할 수 있습니다. 참고 문서의 예제와 동일한 방식으로 `HitEvent`를 수신하는 코드 구조는 다음과 같습니다. ```lua Property: [None] number Hi [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | two_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | Component와 ComponentRef의 참조 방식 차이와 맵 전환 시 동작의 차이점을 설명해 주세요. [answer] | **Component와 ComponentRef의 차이** `Component`와 `ComponentRef`는 모두 특정 컴포넌트를 가리키는 프로퍼티이지만, 내부적으로 참조하는 방식이 다릅니다. 1. **Component (직접 참조)** * 대상 컴포넌트를 **직접 참조**합니다. * **맵 전환 시 동작**: 다른 맵으로 이동했다가 다시 돌아오면 참조 연결이 끊어집니다. 이로 인해 프로퍼티를 더 이상 사용할 수 없게 됩니다. 2. **ComponentRef (간접 참조/키 값 기반)** * [followup] | ComponentRef가 매번 컴포넌트를 찾아야 해서 느리다는 점에 대해, 성능이 중요할 때 어떤 대안이나 최적화 방법이 있는지 알려주세요. [followup_answer] | 참고 문서에 제공된 정보에 따르면, `ComponentRef`가 매번 컴포넌트를 검색해야 하므로 `Component`보다 느린 이유는 설명되어 있지만, **성능 최적화를 위한 구체적인 대안이나 추가적인 최적화 방법은 문서에 언급되어 있지 않습니다.** 문서에 명시된 내용은 다음과 같습니다: * `Component`: 직접 참조하여 빠르지만, 맵 전환 시 참조가 끊어집니다. * `ComponentRef`: 키 값을 기반으로 접근하므로 맵 전환 후에도 안정적이지만, 매 접근 시 검색 때문에 느립니다. 따라서 성능이 최우선인 [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | code [pass] | ⏳ 미판정 [title] | 몬스터를 스폰해 보자 [question] | 몬스터가 죽으면 table 로 관리하는 배열에서 제거하고 _EntityService 로 위치를 가져와서 다시 스폰하는 SpawnMonster 로직을 작성해줘 [answer] | 요청하신 로직은 몬스터가 파괴되면 `MonsterArray`에서 해당 엔티티를 제거하고, 파괴된 몬스터의 위치 정보를 `_EntityService`를 통해 가져와서 `SpawnMonster` 함수를 호출하는 방식으로 구현할 수 있습니다. 단, 제공된 참고 문서에는 몬스터가 죽을 때 발생하는 특정 이벤트 핸들러(예: `HandleMonsterDestroyEvent` 등)나 `_EntityService`의 위치 조회 메서드(`GetPosition` 등)에 대한 명시된 API가 없습니다. 따라서 아래 코드는 **mLua의 일반적 컴포넌 [followup] | [followup_answer] | [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] | 서버-클라이언트 간 동기화 방향성과 그 이유를 비교한 표는 다음과 같습니다. | 구분 | 서버에서 클라이언트로 동기화 | 클라이언트에서 서버로 동기화 | | :--- | :--- | :--- | | **동기화 여부** | 기본적이고 일반적인 방향 | 특수한 몇몇 컴포넌트를 제외하고는 이루어지지 않음 | | **동작 방식** | 서버에서 프로퍼티 값을 변경하면, 연결된 모든 클라이언트의 해당 프로퍼티 값이 자동으로 동일한 값으로 맞춰짐 | 클라이언트에서 프로퍼티 값을 변경해도 서버의 프로퍼티 값에는 변화가 발생하지 않음 | | ** [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 서버와 클라이언트가 어떻게 상호작용을 하는지를 알아보았으니, 이제 메이커에서 작성한 프로그램이 서버와 클라이언트에서 어떻게 돌아가는지 알아보도록 합니다. 제작자가 스크립트에서 선언한 프로퍼티와 함수는 별도의 설정을 하지 않는 한 기본적으로 서버와 클라이언트 두 개의 공간에 각각 독립적으로 생성됩니다. 이름은 같지만 서로 다른 공간에 존재하기 때문에 별도의 프로퍼티 또는 함수라 보는 것이 좋습니다. 기본적으로 함수의 호출과 프로퍼티의 값 참조 및 할당은 같은 공간 내에서만 이루어집니다. 예를 들어 A라는 프로퍼티에 값을 B라는 함
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | CalcDamage()에서 반환된 대미지 값을 OnHit() 함수에서 수정하여 최종 대미지로 적용하려면 어떤 파라미터를 확인해야 하나요? [answer] | `CalcDamage()` 함수에서 반환된 대미지 값은 `OnHit()` 함수의 매개변수로 전달됩니다. 따라서 `OnHit()` 함수에서 최종 대미지를 수정하려면 `OnHit()` 함수가 받는 **매개변수**를 확인해야 합니다. 참고 문서의 예제 코드와 설명에 따르면, `OnHit()` 함수는 `CalcDamage()`의 값을 매개 변수로 전달받습니다. 이 전달받은 값을 그대로 사용할 수도 있고, 추가 계산(예: 방어자의 회피율 계산)을 거쳐 최종 대미지 값을 리턴할 수 있습니다. 구체적인 구현 예시는 다음과 같습니다. ``` [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 크리에이터의 공격 제작 방식에 따라 `IsAttackTarget()` 함수를 활용해 피격 대상을 선정할 수 있습니다. `IsAttackTarget()` 함수는 공격자가 공격 박스에 포함되지 않도록 처리되어 있으므로, 공격자가 타격을 받지 않게 처리할 필요가 없습니다. **Entity** 타입의 `defender` 매개변수는 충돌한 엔티티가 공격 대상인지 판단할 수 있는 엔티티 정보를 받습니다. 함수의 리턴값이 **false**면 **AttackComponent**에서 적합한 공격 대상으로 판별하고 이후 판단을 **HitCompone
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent를 Extend할 때 기존 네이티브 스크립트와 확장한 스크립트의 차이점이 무엇이며, 왜 직접 생성하는 대신 Extend를 사용해야 하나요? [answer] | 제공된 참고 문서에는 `AttackComponent`를 Extend할 때 기존 네이티브 스크립트와 확장 스크립트 사이의 구체적인 차이점(구조적, 기능적 차이)이나, 직접 생성하는 대신 Extend를 사용해야 하는 이유에 대한 설명이 포함되어 있지 않습니다. 문서에는 다음과 같은 절차만 안내되어 있습니다: 1. `Workspace - BaseEnvironment - NativeScripts - Component - AttackComponent`를 선택합니다. 2. 콘텍스트 메뉴에서 **Extend**를 눌러 컴포넌트를 확장합니다. [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] | 서버와 클라이언트 간 '실행제어'의 개념을 3문장 이내로 설명하세요. [answer] | 서버와 클라이언트는 서로의 공간에 정의된 함수(액션)를 요청하여 실행시키는 과정을 실행제어라고 합니다. 이 방식은 데이터 동기화와는 별개로, 한 쪽이 다른 쪽의 함수 수행을 지시하는 통신 메커니즘입니다. 실행제어는 각 함수의 실행 공간을 설정하는 방법에 따라 제작자가 의도한 방향으로 제어할 수 있습니다. [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 서버와 클라이언트 간 통신에는 데이터의 전달, 즉 동기화 외에도 공간에 정의된 "액션"을 요청하고 수행하는 과정도 필요합니다. 위의 레벨업 처리 과정을 예시로 들면, 클라이언트가 유저의 입력을 받으면, 서버에게 "몬스터 공격"이라는 "액션"을 요청하게 되고, 서버는 클라이언트의 요청에 따라 "몬스터 공격"이라는 "액션"을 수행하게 되는데요, 이렇듯 각 공간에 정의된 액션을 서로 요청하여 수행케 하는 것을 실행제어라고 합니다. 실행제어는 제작자의 의도에 따라 설정할 수 있습니다. 위에 말한 "액션"은 결국 스크립트에서는 "함수"라 할
[task] | two_turn [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | any 타입 프로퍼티를 Entity나 Component를 참조할 때 주로 활용한다고 했는데, 구체적으로 어떤 상황에서 any 타입을 사용하는 것이 적합한지 설명해 주세요. [answer] | `any` 타입 프로퍼티는 특정 타입이 정해지지 않은 객체를 유연하게 참조해야 할 때 사용합니다. 특히 `Entity`나 `Component`를 참조할 때 주로 활용하며, 다음과 같은 상황에서 적합합니다. 1. **동적으로 엔티티를 참조할 때**: 런타임에 엔티티 경로가 결정되거나, 다른 엔티티의 참조를 임시로 저장해야 하는 경우 `any` 타입으로 프로퍼티를 선언하여 `_EntityService:GetEntityByPath()` 등의 결과물을 저장할 수 있습니다. 2. **컴포넌트 접근을 유연하게 처리할 때**: 특정 엔티티의 [followup] | any 타입으로 저장한 Entity에서 TransformComponent에 접근할 때 타입 안전성이나 에러 처리는 어떻게 해야 하는지 코드 예시를 요청합니다. [followup_answer] | 제공된 참고 문서에는 `any` 타입으로 저장한 Entity에서 컴포넌트에 접근할 때의 타입 안전성 검사 방법이나 에러 처리에 대한 내용이 포함되어 있지 않습니다. 문서에 나와 있는 내용만으로는 `any` 타입 프로퍼티에 엔티티를 저장한 후 `TransformComponent`에 접근하는 방법(예: `self.EntityProperty.TransformComponent`)만 확인 가능하며, 이 과정에서 타입이 일치하지 않거나 엔티티가 존재하지 않을 경우 어떻게 에러를 방지하거나 처리하는지에 대한 API나 문법은 문서에 명시되어 있 [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | single_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | NormalAttack 함수에서 attackSize와 attackOffset 매개변수의 단위는 무엇이며, 값이 0,0일 때 공격 판정 위치가 어디로 지정되나요? [answer] | 제공된 참고 문서에는 `attackSize`와 `attackOffset`의 **단위**에 대한 명시적인 정의가 없으며, 값이 `(0,0)`일 때의 **공격 판정 위치**에 대한 설명도 포함되어 있지 않습니다. 문서에서는 `attackSize`와 `attackOffset`가 `Vector2` 타입으로 정의되고 `self:Attack()` 함수의 매개변수로 전달된다는 사실과, 예제 코드에서 `Vector2(1, 1)`이나 `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] | CalcDamage 함수를 override할 때 attackInfo 매개변수의 타입이 string인데, 이 값에 실제로 어떤 정보가 담기며 이를 활용하여 조건부 대미지를 계산할 수 있나요? [answer] | 제공된 참고 문서에는 `CalcDamage` 함수의 `attackInfo` 매개변수에 실제로 어떤 정보가 담기는지에 대한 설명이 없으며, 이를 활용하여 조건부 대미지를 계산하는 구체적인 예시나 방법도 나와 있지 않습니다. 문서에서 확인 가능한 내용은 `CalcDamage` 함수의 시그니처가 `override int CalcDamage(Entity attacker, Entity defender, string attackInfo)`라는 것과, 예제 코드에서 단순히 고정된 숫자(100, 50)를 반환하는 것뿐입니다. 따라서 `attac [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] | code [pass] | ⏳ 미판정 [title] | 프로퍼티 [question] | `TransformComponent` 타입의 `ComponentProperty` 프로퍼티를 사용하여 엔티티의 현재 위치를 로그로 출력하고, 다른 맵으로 이동 후 복귀 시 참조가 끊어지는 현상을 설명하기 위한 간단한 검증용 스크립트를 작성해줘 [answer] | `Component` 타입의 프로퍼티는 컴포넌트를 직접 참조하기 때문에, 다른 맵으로 이동했다가 복귀하면 참조가 끊어집니다. 이로 인해 프로퍼티에 접근할 때 오류가 발생하거나 `nil` 값이 반환될 수 있습니다. 이를 검증하기 위한 스크립트는 다음과 같습니다. ```lua Property: [None] TransformComponent ComponentProperty = EntityPath Method: [client only] void OnBeginPlay() { log("OnBeginPlay 실행: 컴포넌트 참조 상태 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 컴포넌트 가리키는 프로퍼티입니다. 프로퍼티 타입을 Component 선택 후, 버튼을 눌러 특정 컴포넌트를 타입으로 설정할 수 있습니다. Component와 ComponentRef의 기본 사용법은 동일하며, 참조 방식이 다릅니다. * Component: 컴포넌트를 직접 참조합니다. 다른 맵에 갔다 오면 참조가 끊어지고 프로퍼티를 사용할 수 없게 됩니다. * ComponentRef: 컴포넌트의 키 값을 가지고 있다가 실제 접근할 때 컴포넌트를 찾습니다. 다른 맵에 갔다 왔을 때 원래 가리키던 컴포넌트에 문제없이 접근할 수 있습니다.
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 서버와 클라이언트 [question] | 서버에서 클라이언트 함수를 호출하는 예시를 코드만 출력하고 설명은 주석으로 작성하세요. [answer] | ```lua Property: [None] number Message = 0 Method: [server only] void OnBeginPlay() { -- 서버에서 클라이언트 함수를 호출하는 예시 -- ExecuteClientMessage 함수의 실행 공간을 클라이언트로 설정해야 합니다. self:ExecuteClientMessage() } [client only] void ExecuteClientMessage() { -- 이 함수는 클라이언트에서 실행됩니다. -- 서버에서 호출되었기 때문 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | 서버와 클라이언트 간 통신에는 데이터의 전달, 즉 동기화 외에도 공간에 정의된 "액션"을 요청하고 수행하는 과정도 필요합니다. 위의 레벨업 처리 과정을 예시로 들면, 클라이언트가 유저의 입력을 받으면, 서버에게 "몬스터 공격"이라는 "액션"을 요청하게 되고, 서버는 클라이언트의 요청에 따라 "몬스터 공격"이라는 "액션"을 수행하게 되는데요, 이렇듯 각 공간에 정의된 액션을 서로 요청하여 수행케 하는 것을 실행제어라고 합니다. 실행제어는 제작자의 의도에 따라 설정할 수 있습니다. 위에 말한 "액션"은 결국 스크립트에서는 "함수"라 할
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | 피격 처리 시 대미지 변경과 스케일 증가가 적용되는 과정을 단계별 번호 목록으로 정리하세요. [answer] | 피격 처리 시 대미지 변경과 스케일 증가가 적용되는 과정은 다음과 같습니다. 1. `Workspace - BaseEnvironment - NativeScripts - HitComponent`를 선택하고 **Extend**를 통해 컴포넌트를 확장한 뒤 이름을 `MonsterHit`으로 변경합니다. 2. `Workspace - BaseEnvironment - NativeModel`에서 `MoveMonster` 모델을 선택합니다. 3. 프로퍼티 에디터에서 기존 `HitComponent`의 컨텍스트 메뉴를 통해 **Remove Co [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | **AttackComponent**를 가진 엔티티가 **HitComponent**를 가진 엔티티를 공격하면, 별다른 추가 처리를 하지 않아도 엔티티는 피격 당하게 됩니다. 이 경우 추가 처리가 없기 때문에 기본값인 1로 대미지 스킨이 재생됩니다. 피격을 구현할 때는 공격량 그대로 대미지로 받을 수도 있지만, 공격과 다른 공식을 함께 사용해 피격량을 계산할 수도 있습니다. 예를 들어 100의 공격력으로 공격했지만 몬스터의 방어력이 높은 경우 절반의 공격만 유효하게 처리할 수 있습니다. 이번에는 아래 두 가지를 구현해 피격 당하는지를
[task] | instruction_following [pass] | ⏳ 미판정 [title] | 서버와 클라이언트 [question] | 클라이언트 로직 처리 시 발생할 수 있는 해킹 취약점의 예시와 그 이유를 표로 정리해서 작성해 주세요. [answer] | 메이플스토리 월드에서 클라이언트에 주요 로직을 두고 서버는 동기화만 담당하는 구조를 채택할 경우, 유저가 자신의 클라이언트를 조작(해킹)하면 서버가 조작된 결과를 그대로 신뢰하기 때문에 발생하는 취약점이 있습니다. 이러한 구조의 구체적인 예시와 이유를 정리하면 다음과 같습니다. | 취약점 발생 시나리오 | 상세 내용 및 결과 | 취약점 발생 이유 | | :--- | :--- | :--- | | **경험치 조작을 통한 부당한 레벨업** | 유저 A가 몬스터를 처치할 때 클라이언트를 조작하여 경험치 획득량을 극단적으로 높인 상태로 서 [followup] | [followup_answer] | [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 멀티 플레이를 제공하는 게임의 경우 각 게임의 특성에 따라 사용되는 통신 모델들이 다양합니다. MapleStory World는 다양한 통신 모델 중에서도 서버 - 클라이언트 모델을 사용하고 있습니다. 이번 과정에서는 MapleStory World의 통신 모델인 서버와 클라이언트의 개념과 구조, 그리고 각 공간 간의 정보전달 방법에 대해 알아보도록 하겠습니다. # 서버와 클라이언트 클라이언트는 게임을 플레이하는 유저들의 모바일이나 PC, 또는 해당 기기에서 개별적으로 돌아가는 프로그램을 의미합니다.주로 유저의
[task] | code [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent의 CalcDamage 함수를 오버라이드하여 공격자와 방어자 인자를 무시하고 항상 100의 대미지 값을 반환하는 CharacterAttack 스크립트를 작성해줘 [answer] | AttackComponent를 상속받아 `CalcDamage()` 함수를 오버라이드하고, 매개변수로 전달되는 공격자(attacker)와 방어자(defender) 정보를 참조하지 않고 고정된 값 100을 반환하도록 구성한 스크립트입니다. ```lua Method: 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] | code [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent를 확장하여 NormalAttack 메서드에서 Vector2(2, 2)의 attackSize와 Vector2(0, 1)의 attackOffset을 사용하여 self:Attack 함수를 호출하는 CharacterAttack 스크립트를 작성해줘 [answer] | AttackComponent를 확장한 CharacterAttack 스크립트입니다. 요청하신 `attackSize`와 `attackOffset` 값을 사용하여 `NormalAttack` 메서드를 구현했습니다. ```lua Property: [None] number Speed = 10 Method: [server only] void NormalAttack() { local attackSize = Vector2(2, 2) local attackOffset = Vector2(0, 1) self:Attack [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] | code [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | PlayerActionEvent 핸들러에서 event.ActionName이 "Attack"일 때만 NormalAttack 함수를 호출하도록 분기 처리하는 CharacterAttack 스크립트를 작성해줘 [answer] | `AttackComponent`를 확장한 `CharacterAttack` 컴포넌트에서 `PlayerActionEvent`를 처리하여 `event.ActionName`이 `"Attack"`일 때 `NormalAttack()`을 호출하도록 작성한 스크립트입니다. ```lua Method: void NormalAttack() { local attackSize = Vector2(1, 1) local attackOffset = Vector2(0, 0) self:Attack(attackSize, attackOffset, nil) } [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] | two_turn [pass] | ⏳ 미판정 [title] | 공격과 피격 [question] | AttackComponent의 Attack()과 AttackFrom() 함수로 공격 범위를 설정하는 방식과 차이점에 대해 알려주세요. [answer] | AttackComponent는 게임에서 실제 타격이 발생하도록 하는 컴포넌트입니다. 공격 범위를 설정할 때는 `Attack()`과 `AttackFrom()` 두 가지 함수를 사용하며, 각각 다른 기준과 방식으로 범위를 지정합니다. ### 1. Attack() 함수 * **기준점:** 캐릭터의 현재 위치를 기준으로 공격 범위를 설정합니다. * **범위 형태:** `shape` 타입을 사용하여 다양한 형태의 공격 범위를 정의할 수 있습니다. * **사용 예시:** 캐릭터를 중심으로 특정 크기(`Vector2`)의 범위와 오 [followup] | Attack()에서 shape 타입으로 다양한 범위를 지정하는 구체적인 코드 예시와, AttackFrom()이 월드 좌표를 기준으로 사각형만 설정할 수 있는 이유를 설명해 주세요. [followup_answer] | 참고 문서에는 `Attack()` 함수에서 `shape` 타입을 사용하여 공격 범위 형태를 설정할 수 있다고 명시되어 있지만, 구체적인 shape 타입의 값이나 다양한 범위를 지정하는 실제 코드 인자 예시는 포함되어 있지 않습니다. 문서에 있는 예제에서는 `Vector2` 타입의 `attackSize`와 `attackOffset`을 전달하는 방식만 확인할 수 있으며, shape 파라미터에 대한 구체적인 사용법은 문서에 언급되어 있지 않습니다. ```lua Method: [server only] void AttackNormal () [scores] | [judge_reason] | [source_doc] | # 학습 과정 소개 메이플스토리에서 플레이어는 모험가가 되어 메이플 세계를 탐험하고 몬스터를 만나 전투를 합니다. 전투 과정에서 다른 몬스터를 공격하거나 피격당합니다. 메이플스토리뿐만 아니라 다양한 게임에서 공격과 피격은 중요한 개념입니다. 이번 과정에서는 공격과 피격을 구현하는 방법을 학습합니다. ##### 참고 가이드/레퍼런스 [AttackComponent](/apiReference/Components/AttackComponent) [HitComponent](/apiReference/Components/HitComponent