mod에서는 entity event system(mod에서 이벤트 시스템을 쉽게 활용할 수 있도록 기본적으로 제공하는 API)을 제공.
evnet 3가지 구성요소
event: 로직 상에서 사건의 발생을 의미(event의 종류의 식별 정보, 추가 정보 소유)
handler: 해당 event를 받았을때 처리하는 행동의 주체
sender: 해당 이벤트를 발송하는 객체
event 시스템의 장단점
장점
다른 component나 기능 단위에서 결합성이 떨어짐.
행위에 대한 액션 추가 희망시 행위 수행하는 곳 수정 없이 추가 가능.
다른 compoenent의 정보를 필요로 하지 않음.
단점
사건 발생 시 전체적 플로우 찾기 어려움( 각각 처리하는 로직으로 인해 실행되는 시점에서 알 수 없음)
디버깅이 어려움
순차적 행위 수행이 어려움
Log event
우리가 이름 붙이는 event로 log 간의 이벤트를 집어 넣는데 사용될 예정임
event 생성하는 과정
1.LogEvent 객체 생성
2.Logevent에 message property생성
3.myComponent 생성 및 Handler와 function 추가
log로 이벤트를 주고 받는 과정
1.자신이 엔티티 쪽으로 이벤트를 쏨
2.로그 이벤트 받겠다는 핸들러 -> 자신의 엔티티쪽으로 등록
핸들러 등록 구조
각 엔티티들은 컴포넌트들을 포함하고있음.
엔티티 이벤트 시스템은 이와같이 작동함.
컴포넌트는 각 엔티티를 중계자로 사용할 수 있으며, 각 컴포넌트는 엔티티를 통해 핸들러를 등록함.
이벤트 발생 역시 엔티티를 통해 가능
sender역시 엔티티를 통해 이벤트를 발생하는 것이 가능하며 이때 엔티티는 handler들에게 해당 이벤트를 전송하는 연락을 함.
정리
어떤 event가 왔을때 이것을 처리하는 부분을 component내의 로직에 넣음. 그리고 이벤트 수신 등록을 register,addlistener등으로 하며, mod에서는 entity에 등록을 하는 구조.
이벤트를 주고 받을때
같은 엔티티에서 이벤트를 주고받을때
예를들어 component1에서 이벤트 발생시, component3의 이벤트를 실행시키고 싶을 때를 가정. 두 component간에는 연관이 없어서 component에서 바로 호출하지않고 엔티티를 통해 호출하는 구조.
1.compoenent1이 특정 타이밍에 의해 엔티티로 발송
2.로그 이벤트를 수신하겠다고 등록한 컴포넌트들에게 이벤트 발송
3.해당 컴포넌트들은 이벤트를 받아서 처리
다른 엔티티에서 이벤트를 주고 받을때
예를들어rabbitentity쪽으로 이벤트를 쏘면
rabbitcomponent가 수신해서 rabbitcomponent안의 로그 메시지가 출력되는 구조
1.신호를 보내는 엔티티쪽에서 component들이 이벤트 발송
2.받는 엔티티 쪽에서 신호를 받고 이벤트 수행
이를 구현하는 방법
1 번째 방법
1.rabbitcomponent에서 로그 이벤트 등록
2.mycomponent에서 rabbitentity쪽으로 로그 이벤트 발송 등록
2번째 방법
1.rabbitcomponent에서 로그 메시지 핸들 이벤트와 출력 등록(이때 beerentity로 이벤트 발송 등록 x)
2.beercomponent 에서 등록한 로그 이벤트를 rabbitentity로 보내 로그 출력
이렇게 우리는 분리되어있는 환경에서 통신을 할 수 있게 해주는 것을 event라고 함.
이벤트 처리
이벤트 처리 순서는
1.이벤트를 처리할 컴포넌트, 엔티티 생성
2. 핸들러 로직 추가 (핸들러 로직이 이루어지는 방법: entity event handler추가 -> 핸들러 상단 이벤트 중계자 설정 -> 이벤트 처리 로직)
3. 이벤트 발생 로직 추가
해가떴을때 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
}
이 코드에서 이벤트 발생 로직 추가의 경우 아래와 같이 해가 뜨고 지는 이벤트 로직을 추가 할 수 있음.
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
}
완성된 컴포넌트를 map에 addcomponent를 통해 등록시켜준 후 이벤트 호출을 위한 로직을 추가해줌.
huntercomponent에 handlekeydownevent를 추가해줌 z키보드 사용시 이벤트 호출되도록함.
--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
entity
엔티티 생성
mod에서는 엔티티를 생성할수 있는 함수인 _spawnservice를 제공해줌.
SpawnByEntityTemplate: 배치된 엔티티와 동일한 엔티티를 생성하는, 엔티티를 복제해주는 역할을 수행
맵상에 복제 대상이 되는 템플릿 엔티티가 반드시 존재해야함.
--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
SpawnByModelId: 워크 스페이스에 추가된 모델중 한가지 모델을 지정해 엔티티를 생성해주는 함수
모델리스트에 있는 모델을 엔티티로 생성하고자 할때 사용함.
-- void SpawnByModelId()
--SpawnByModelId의 파라미터값들을 설정합니다.
local id = "maplestorymapobject$002be76" -- 워크스페이스 -> Model 하위에 추가된 모델이 있으며, 모델 -> 우클릭 -> Copy Model ID로 ID를 복사해서 가져올 수 있습니다. 앞에 "model://"은 제거해줍니다.
local name = "SpawnedEntity" -- 생성될 엔티티의 이름을 설정합니다.
local spawnPosition = Vector3(0,0,0) -- 생성될 때의 위치 좌표를 설정합니다.
local parent = _EntityService:GetEntityByPath("/maps/map01") -- 생성될 엔티티의 부모 엔티티입니다.
local ownerId = nil -- 엔티티의 소유권을 가질 플레이어의 ID(Name)를 넣어줍니다. 일반적으로 nil로 설정합니다.
local spawnedEntity = _SpawnService:SpawnByModelId(id, name, spawnPosition, parent, ownerId) --스폰한 엔티티를 변수로 받으면, 해당 엔티티에 대한 후처리를 할 수 있습니다.
if isvalid(spawnedEntity) == false then log("Spawn Failed") end
엔티티 삭제
mod에서는 엔티티를 삭제 할 수 있는 함수인 _EntityService:Destroy , Entity:Destroy
제공 삭제하고자하는 엔티티를 밑에 코드처럼 지정해서 삭제해줄수있음.
--void OnUpdate(number delta) [server only]
if isvalid(self.SpawnedEntity) == false then return end
if self._T.time == nil then self._T.time = 0 end
self._T.time = self._T.time + delta
if self._T.time >= 3 then
_EntityService:Destroy(self.SpawnedEntity)
end
--void OnUpdate(number delta) [server only]
if isvalid(self.SpawnedEntity) == false then return end
if self._T.time == nil then self._T.time = 0 end
self._T.time = self._T.time + delta
if self._T.time >= 3 then
self.SpawnedEntity:Destroy() --_EntityService:Destroy 대신 Entity:Destroy로 교체.
end
엔티티 유효성 체크
mod에서는 isvalid를 사용해 유효성 체크
--void OnUpdate(number delta) [server only]
if isvalid(self.SpawnedEntity) == false then return end
if self._T.time == nil then self._T.time = 0 end
self._T.time = self._T.time + delta
if self._T.time >= 3 then
local isvalidValue = isvalid(self.SpawnedEntity)
log("삭제 전 : "..tostring(isvalidValue)) -- 콘솔 창에 "삭제 전: true" 출력
self.SpawnedEntity:Destroy()
isvalidValue = isvalid(self.SpawnedEntity)
log("삭제 후 : "..tostring(isvalidValue)) -- 콘솔 창에 "삭제 후: false" 출력
end
만약 entity를 생성하게 되면 서버와 클라이언트 모두 entity가 생성이 되는데 서버와 클라이언트 모두 네트워크로 연결이 되어있긴 하지만 다른 객체 이기 때문에 어디서 설정하느냐에 따라서 설정값이 달라질 수 있고 이로인해 동기화(어느 한쪽 값이 달라졌을때 양쪽의 값을 모두 바꾸는 행동을 취하는 것) 문제 발생 가능.
서버는 한개이지만 클라이언트는 여러개 일 수 있음. 즉 서버와 클라이언트는 1:n관계임.
서버에서 특정 property값을 바꾸면 그 property를 가지고 있는 클라이언트의 property또한 전부 바뀌는 반면에 클라이언트에서는 독자적 으로 값을 바꿔도 다른 클라이언트나 서버에 영향을 미치지 않음. 이유는 서버와 클라이언트에 저장된 각각의 엔티티는 네트워크로 연결되어있으나 엄연히 다른 엔티티이기 때문임.
mod에서는 어느 한쪽이 달라졌을때 다른 한쪽 또한 일일이 바꿔주는 행동을 수행하기 번거롭다고 생각하여 실행제어하는 개념을 도입함.
property 실행 제어
서버에서는 특정 property값을 바꾸면 그 property를 가지고있는 클라이언트의 property또한 전부 값이 변경된다고했고, 클라이언트에서는 property값을 바꿔도 서버의 property에 영향을 미치지않는다고 했다. 만약 클라이언트에서 변경한 property값들이 서버에도 반영된다면 클라이언트-서버 관계는 1:n의 관계임으로 서버에 과부화가 오게됨. 따라서 동기화는 서버 -> 클라이언트 단방향으로 진행됨.
한 오브젝트의 property에서 tweenlinecomponent를 통하여 object의 움직임을 제어할수있음 종료방식에서 one round trip으로 하면 설정한 위치만큼 이동했다가 다시 돌아오고 끝 Repeat round trip으로 설정하면 계속 반복적으로 이동함. 선형운동방식을 통하여 운동방식을 바꿀수있음 오브젝트를 움직이고 싶으면 tweenlinecomponent사용!!
Tweencircularcomponent 오브젝트를 회전시키게 만듬 회전 반경, 각도 속도 제어가능
Tweenfloatingcomponent 폭을 설정하여 오브젝트가 위아래로 운동할수있게 하는 componenent Rigidbodycomponent object에게 중력과 같은 물리엔진을 설정해주는 component임 이 component가 존재하지않는 오브젝트들은 중력의 영향을 받지않아서 공중에 떠있음 원점 정보가 다름에 따라 이상한 위치까지 떨어질때도있음. Isblockverticalline기능을 통하여 블록의 접근성 또한 제어 할 수 있음. Quarter view기능을 통하여 화면상에 떠서 움직이게 할 수있음 가속도들도 설정 가능(광장같은 맵에서 사용하면 어울릴거같음) Movement component를 이용해 이동속도와 점프력을 제어할수있음. 이 component를 없애면 이동이 불가능함.
기존에있는 trap entity들은 triggercomponent(충돌을 제어하는 component)가 없음 그래서 이 trap에 triggercomponent를 설정해준뒤 work space에 있는 my desk에서 create script>create component를 만들어준뒤 myplayer라는 component를 만들어주고 이 곳에
이런식으로 충돌했을때의 이벤트를 설정해주면 됨.
이렇게 설정하면 사용자가 아까 triggercomponent를 설정해줬던 trap이랑 충돌했을경우 충돌이라는 이벤트가 발생됨. 이때 아까 myplayer이라는 component를 설정해줬었는데 이 component를 사용자에게 적용해줘야하기 때문에 Defaultplayer에 꼭 이 component를 적용해줘야함.
충돌이 발생했을때 log를 통하여 서버에 충돌이라는 문자가 뜨는게 아닌 사용자가 trap과 닿았을때 닿은 방향 반대방향으로 5만큼 밀려나게 하기 위해 아까 물리엔진을 제어해주는 component인 rigidbodycomponent를 사용하여줄수도있음.
아까 학습했던 tweenlinecomponent를 활용하여 움직이는 trap들도 만들수있을거같음. Player component를 이용하여 사용자의 체력과 닉네임을 제어해줄수있는데 이때 아까 만들었던 trap에 닿으면 체력이 100이 깍이도록 설정해줄수도있음
-100으로하면 물체와 충돌했을때 체력 100이 깍임
또 다른 playercontrollercomponent를 이용하여 왼쪽으로부터 trap과 부딪혔을때 오른쪽으로 5만큼 튕겨지게하고 오른쪽으로부터 부딪혔을때 왼쪽으로5만큼 튕겨지게할수도있음. 또한 이 component를 이용하여 고정방향을 설정해줄수도있음.