[MOD 강좌 Chapter 8] Event와 컴포넌트 확장
Event 기반의 로직 구성
로직을 작성하다 보면 어떤 상태가 변하거나 어떤 액션이 있거나 특정 일이 주어졌을 때, 즉 특정 타이밍에 "무언가를" 해야 할 일이 생김. 그럴 경우 "행위가 일어난 주체"에서 무얼 할 것인지 결정해야 함
결국 이벤트 시스템과 일반 로직의 차이는 "행위가 일어난 주체"에서 일을 처리할 것인지, "행동을 실행해야 할 주체"에서 일을 처리할 것인지에 대한 차이
Event System의 구성 (3가지)
1. Event : 로직 상에서 어떤 사건의 발생
Event의 종류의 식별 정보 및 추가 정보 등을 들고 있는 자료형
2. Handler : 해당 Event를 받았을 때 처리하는 행동의 주체
해당 이벤트가 불렸을 때 불리는 함수 (listener, subscriber 과 비슷한 용어)
3. Sender : 해당 Event를 발송하는 객체
emmiter, dispatcher 등과 비슷한 용어
학급 전체에 공지문을 우편 배달한다고 가정해 볼때
Event 는 공지문이고, Handler는 학급 내 학생들, Sender 는 우편 배달원
Event System의 장단점
장점
- 다른 Component 나 기능단위에서 결합성이 떨어지므로 의존성을 배제할 수 있고 분산시스템이 용이해짐
- 행위에 대한 액션을 추가 하고 싶을 때 행위를 수행하는 곳을 수정 없이 편하게 추가 가능
- 다른 Component의 정보를 알 필요 없음
단점
- 어떤 사건이 발생 시 전체적인 플로우를 찾기가 힘듬(각각 처리 하므로 실행되는 시점에서는 알 수 없기 때문)
- 위와 같은 이유로 디버깅이 힘듬
- 또한 순차적인 행위를 하기에 어려움 (A → B → C→ D의 순으로 진행돼야 한다면 이벤트 시스템만으로는 힘듬)
MOD Event System ( Entity Event System )
MOD에서는 Entity를 중계소로 이용하고 있는 Entity Event System을 사용
MOD에는 Event System을 쉽게 활용할 수 있도록 기본으로 제공되는 API 존재
API를 사용하면 Event를 좀 더 쉽게 제어 가능
MOD에서는 Entity Event System을 통해 Event를 핸들링 가능
Component는 Entity를 중계자로 사용, 각각의 Component는 Entity를 통해 핸들러를 등록하고, 이벤트 발생도 Entity를 통해 가능
Component 들이 Entity에 핸들러를 등록하고 Sender 역시 Entity를 통해서 이벤트 발생. Entity 들은 핸들러들에게 해당 Event를 전송
기본적으로는 자기 자신의 Entity에 연결하는 경우가 대부분이지만, 아래 그림처럼 상황에 따라 다른 Entity로 연결하는 것도 가능
특히, Map Entity와 World Entity는 서로 간에 Event를 주고받는 경우가 많아 자주 사용됨
Sunrise Event 생성
Native 형 Event
Event 들은 Listen 하거나 이벤트를 발생 가능
추가로 직접 Event Type을 선언 가능
Workspace에서 새로운 Event Type을 선언 혹은 Import
1. Workspace에서 우클릭하고 팝업창이 나타나면 Create EventType을 선택
2. Event Type이 추가되면 이름을 SunriseEvent로 수정
3. 내부 Property로는 boolean Type인 isSunrise 를 사용, isSunrise를 통해 해가 뜨고 지는 상태를 True/False로 체크
이벤트를 처리할 컴포넌트와 엔티티 생성
실습) SunriseEvent를 이용해 Vampire vs Hunter 로직
등장하는 Vampire와 Hunter 들은 평소에는 일반적인 전투와 움직임을 하다가, SunriseEvent를 받게 됨
Sunrise 상태일 때, Hunter는 따스한 햇볕으로 Hp를 회복하고 Vampire는 Hp가 감소하게 만드는 게 목표
1. Sunrise 상태일 때의 로직을 구현하기 위해 VampireComponent와 HunterComponent를 추가
2. 컴포넌트가 동작할 Vampire NPC 엔티티와 Hunter NPC 엔티티를 다음과 같이 배치
3. 배치된 엔티티의 이름을 다음과 같이 설정
4. Vampire 엔티티에 VampireComponent를, Hunter 엔티티에 HunterComponent를 각각 추가
핸들러 로직
헌터와 뱀파이어 Entity에 넣어줄 HunterComponent, VampireComponent 제작
1. 두 컴포넌트의 스크립트 에디터에서 Entity Event Handler에서 +버튼을 누르고, SunriseEvent를 선택하여 이벤트 핸들러를 추가
2. SunriseEvent는 map에 의해 발생되므로 중계 Entity를 map01으로 설정
( 핸들러 상단의 이벤트 중계자를 map01로 설정)
3. Handler 추가 작업이 완료되었으면, 각자 받은 SunriseEvent를 처리하는 로직을 넣어야 함
- Hunter의 경우에는 Hp 회복 로직을, Vampire의 경우에는 Hp 감소 로직 삽입
해가 뜨면 Hp가 증가하는 HunterComponent 예제
Property :
[Sync]
boolean isSunrise = false
[Sync]
number Hp = 0
Method :
[server Only]
void OnUpdate (number delta)
{
if self.isSunrise == true then --해가 떴는지 체크합니다.
self.Hp = self.Hp + delta --해가 떠 있을 동안 Hp가 증가합니다.
log("Hunter Hp : "..self.Hp) --현재 체력을 Console 창에 표시합니다.
if self.Hp >= 200 then self.Hp = 200 end --Hp가 200까지 증가했다면 증가를 멈춥니다.
end
}
Entity Event Handler :
entity map01 (/maps/map01)
HandlerSunriseEvent(SunriseEvent event)
{
-- Parameters
local isSunrise = event.isSunrise
self.isSunrise = isSunrise
}
해가 뜨면 Hp가 감소하는 VampireComponent 예제
Property :
[Sync]
boolean isSunrise = false
[Sync]
number Hp = 0
Method :
[server Only]
void OnUpdate (number delta)
{
if self.isSunrise == true then --해가 떴는지 체크합니다.
self.Hp = self.Hp - delta --해가 떠 있을 동안 Hp가 감소합니다.
log("Vampire Hp : "..self.Hp) --현재 Hp를 Console 창에 표시합니다.
if self.Hp < 0 then self.Hp = 0 end --Hp가 0까지 감소했다면 감소를 멈춥니다.
end
}
Entity Event Handler :
Entity map01 (/maps/map01)
HandlerSunriseEvent(SunriseEvent event)
{
-- Parameters
local isSunrise = event.isSunrise
self.isSunrise = isSunrise
}
참고) 함수를 이용해 함수를 직접 연결할 수 있는 방법
self.Entity:ConnectEvent(self.OnSunrise) Method 예시 (HandleSunriseEvent와 동일하게 작동)
이벤트 발생 로직
해가 뜨고 지는 로직 생성 (특정 시간마다 해가 뜨고 지게 만드는 게 목표)
1. 해에 대한 시간을 관리해 줄 TimeManger Component를 새로 제작
2. TimeManager Component를 Map Entity에 넣어주면 해당 Map에서 시간을 관리
3. TimeManager에 OnUpdate Method를 통해서 해가 뜨고 짐을 판단
4. 하단에는 이벤트 발생 Method도 추가하여 SunriseEvent를 발생
Property :
[Sync]
boolean isSunrise = false
Method :
[server only]
void OnUpdate (number delta)
{
if self._T.Time == nil then self._T.Time = 0 end
self._T.Time = self._T.Time + delta
if self._T.Time >= 5 then --5초마다 번갈아 해가 뜨고 집니다.
self._T.Time = 0
if self.isSunrise == true then
self.isSunrise = false
else
self.isSunrise = true --해가 떠 있는 상태 외에 나머지 상태는 isSunrise가 false입니다.
end
log(self.isSunrise)
self:SendEvent(self.isSunrise)
end
}
[server]
void SendEvent (boolean isSunrise)
{
local event = SunriseEvent()
event.isSunrise = isSunrise
self.Entity:SendEvent(event)
self.isSunrise = isSunrise
self._T.Time = 0
}
5. 완성된 Component를 map01 엔티티에 AddComponent
+) Hunter 궁극기로 태양을 뜨게 하는 일출 스킬을 제작
Z 키를 이용해 이벤트를 호출 가능
HunterComponent에 HandleKeyDownEvent 추가한 결과 ▼
--HandleKeyDownEvent(KeyDownEvent event) [service : InputService]
-- Parameters
local key = event.key
--------------------------------------------------------------------------------
if key == KeyboardKey.Z then --Z 키를 누르면 `일출` 메시지가 Console 창에 나타납니다.
log("일출")
local timeManager = self.Entity.CurrentMap.TimeManager
timeManager:SendEvent(true) --Timemanager Component의 Event가 true가 되도록 이벤트를 발생시킵니다.
end
엔티티 생성
MOD는 _SpawnService와 _EntityService 등을 통해 엔티티를 생성하고 제거하는 여러 기능들을 제공
"_SpawnService"라는 서비스 : 여러 엔티티 생성 함수를 제공
주요 엔티티 생성 함수
SpawnByEntityTemplate
- 설명
- 배치된 엔티티와 동일한 엔티티를 생성(엔티티 "복제"의 의미로 이해)
- 따라서 맵 상에 "복제" 대상이 되는 템플릿 엔티티가 반드시 존재해야 함
- 파라미터
Parameter | Type | Description |
entityTemplate | Entity | 생성할 엔티티의 템플릿(복제할 대상)이 되는 엔티티를 넣어줍니다. |
name | string | 생성한 엔티티의 이름을 설정합니다. |
spawnPosition | Vector3 | 엔티티가 생성될 위치 좌표를 설정합니다. |
includeChild (Optional) |
bool | 템플릿 엔티티의 하위 엔티티도 함께 복제할지를 설정합니다. |
dynamic (Optional) |
bool | SpawnByEntityTemplate를 통해 생성된 엔티티가 동적으로 생성되었는지에 대한 여부를 설정합니다. 특수한 경우가 아니라면 true로 설정해 줍니다. |
needSync (Optional) |
bool | 생성된 엔티티의 동기화가 필요할 경우 true로 설정합니다. 해당 파라미터에 값을 넣지 않으면 기본적으로 true가 들어갑니다. |
nameEditable (Optional) |
bool | 생성된 후 동적으로 이름이 수정 가능한지에 대한 여부 설정합니다. 특수한 경우가 아니면 일반적으로 true로 설정합니다. |
- 반환 값
- 스폰 성공 시 MODEntity를 반환
- 스폰 실패 시 nil을 반환
- 사용 예시
- 간단하게 맵에 배치된 엔티티와 동일한 엔티티를 생성하는 방법
1.템플릿이 될 엔티티를 맵에 배치. 오브젝트에서 원하는 에셋을 골라 배치
2. 워크스페이스에서 SpawnManager라는 이름으로 컴포넌트를 하나 생성
3. "SpawnManager"를 더블클릭하여 스크립트에디터를 열고 "SpawnByEntityTemplate"이라는 이름의 New 함수를 생성
4. 스크립트 작성
--void SpawnByEntityTemplate()
--SpawnByEntityTemplate의 파라미터값들을 설정합니다.
local entityTemplate = _EntityService:GetEntityByPath("/maps/map01/object-49_1") -- 맵에 배치한 엔티티를 받아옵니다. 워크스페이스 -> 엔티티 -> 우클릭 -> Copy Entity Path로 패스를 가져올 수 있습니다.
local name = entityTemplate.Name .. "Copy" -- 생성될 엔티티의 이름을 설정합니다.
local spawnPosition = Vector3(0,0,0) -- 생성될 때의 위치 좌표를 설정합니다.
local spawnedEntity = _SpawnService:SpawnByEntityTemplate(entityTemplate, name, spawnPosition) --스폰한 엔티티를 변수로 받으면, 해당 엔티티에 대한 후처리를 할 수 있습니다.
if isvalid(spawnedEntity) == false then log("Spawn Failed") end
5. 'OnBeginPlay'를 추가하고 스크립트를 다음과 같이 작성
--void OnBeginPlay() [server only]
self:SpawnByEntityTemplate()
'게임개발 > NEXON PROJECT MOD' 카테고리의 다른 글
멋쟁이사자처럼 X 넥슨 MOD Supporters Hackathon 6주차 회고 (0) | 2022.07.01 |
---|---|
멋쟁이사자처럼 X 넥슨 MOD Supporters Hackathon 5주차 회고 (0) | 2022.07.01 |
멋쟁이사자처럼 X 넥슨 MOD Supporters Hackathon 3주차 회고 (0) | 2022.07.01 |
멋쟁이사자처럼 X 넥슨 MOD Supporters Hackathon 2주차 회고 (0) | 2022.07.01 |
멋쟁이사자처럼 X 넥슨 MOD Supporters Hackathon 1주차 회고 (0) | 2022.07.01 |
댓글