엔티티의 생성과 삭제, 유효성 체크

# 학습 과정 소개

엔티티의 생성과 삭제는 월드를 제작하면서 가장 많이 사용하는 기능입니다. 이번 과정에서는 엔티티 생성과 삭제 방법, 그리고 엔티티 존재 여부를 확인하는 유효성 체크 기능을 살펴봅니다.

# 엔티티 생성

SpawnService는 엔티티 생성 함수를 제공합니다. 주요 엔티티 생성 함수인 `SpawnByEntity`와 `SpawnByModelId`를 알아보겠습니다.

## SpawnByEntity

엔티티를 바탕으로 새로운 엔티티를 생성합니다.

#### 매개 변수

[{"Parameter":"entity","Type":"Entity","Description":"복제할 엔티티를 지정합니다."},{"Parameter":"name","Type":"string","Description":"생성할 엔티티의 이름을 설정합니다."},{"Parameter":"spawnPosition","Type":"Vector3","Description":"엔티티가 생성될 위치 좌표를 설정합니다."},{"Parameter":"parent(Optional)","Type":"Entity","Description":"생성된 엔티티의 부모가 될 엔티티를 넣어줍니다."},{"Parameter":"includeChild(Optional)","Type":"boolean","Description":"복제할 엔티티의 하위 엔티티도 함께 복제할 것인지 설정합니다."}]

#### 반환값

  * 스폰 성공하면 Entity를 반환합니다.
  * 스폰 실패하면 nil을 반환합니다.


    
    
    

#### 사용 예시

간단하게 맵에 배치된 엔티티와 동일한 엔티티를 생성하는 방법을 알아봅시다.

  1. Preset List에서 복제할 엔티티를 맵에 배치합니다. [png](16602756252223e544189296248be9e622a6f6aede534)

  2. 새 스크립트 컴포넌트 SpawnManager를 생성합니다. SpawnManager 스크립트를 더블클릭하여 스크립트 에디터를 열고 `SpawnByEntity` 함수를 작성합니다.
         
         @Component
         script EntityDuplicator extends Component
         method void SpawnByEntity()
         local entity = _EntityService:GetEntityByPath("/maps/map01/monster-8") -- 배치된 엔티티 우클릭 - Copy Entity Path를 선택해 Path를 가져옵니다.
         local name = entity.Name .. "Copy" -- 생성될 엔티티 이름 설정
         local spawnPosition = Vector3(0,0,0) -- 생성될 위치 설정
         
         local spawnEntity = _SpawnService:SpawnByEntity(entity,name,spawnPosition)
         if isvalid(spawnEntity) == false then 
             log("Spawn Failed")
         end
         end
         end
         



    
    
    3. 작성이 완료되었으면 `OnBeginPlay` 함수를 추가하고 다음과 같이 작성합니다.
        ```lua
    @Component
    script EntitySpawner extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        self:SpawnByEntity()
        end
    end
    

  4. SpawnManager 스크립트 컴포넌트를 common에 추가합니다. [png](1659601195286fbb9556ae01c4adb8b44b90ab8e68840)
  5.  [시작] 버튼을 눌러 맵에 배치했던 것과 같은 엔티티가 생성되는지 확인합니다. [png](1655870326527ec7ce48fd9b74ea799d7072cf1ac7f5f)



## SpawnByModelId

#### 설명

Workspace에 추가한 모델 중 한 가지 모델을 지정하여 엔티티로 생성합니다.

#### 매개 변수

[{"Parameter":"id","Type":"string","Description":"워크스페이스에 추가된 모델 중 새로 생성할 엔티티의 템플릿이 될 Entry ID를 넣어줍니다.Entry ID는 Workspace - 모델(생성할 엔티티의 템플릿이 되는) - Copy Entry ID를 선택해 가져올 수 있습니다."},{"Parameter":"name","Type":"string","Description":"생성할 엔티티의 이름을 설정합니다."},{"Parameter":"spawnPosition","Type":"Vector3","Description":"엔티티가 생성될 위치 좌표를 설정합니다."},{"Parameter":"parent","Type":"Entity","Description":"생성된 엔티티의 부모가 될 엔티티를 넣어줍니다."}]

#### 반환 값

  * 스폰 성공하면 Entity를 반환합니다.
  * 스폰 실패하면 nil을 반환합니다.



#### 사용 예시

  1. Preset List에서 원하는 모델의 콘텍스트 메뉴를 연 뒤, Add to Workspace를 눌러 Workspace에 모델을 추가합니다. [png](165995860982314bcc1601e8c44d885b2067b34ea32f3)
  2. 추가한 모델의 콘텍스트 메뉴에서 Copy Entry ID를 눌러 Entry ID를 클립보드에 복사합니다. [png](1687760366731d075f28ddc3b4f14ac0791f36257437a)
  3. SpawnManager 컴포넌트를 열고 `SpawnByModelId` 함수를 생성합니다. 
         
         @Component
         script EntitySpawner extends Component
         method void SpawnByModelId()
         local id = "maplestorymonster$0108737afb9548309c03795430b20c05" -- Copy Entry ID를 눌러 복사했던 ID를 붙여넣습니다. 앞에 "model://"은 제거합니다.
         local name = "SpawnedEntity" -- 생성될 엔티티 이름 설정
         local spawnPosition = Vector3(0,0,0) -- 생성될 위치 설정
         local parent = _EntityService:GetEntityByPath("/maps/map01") --생성될 엔티티의 부모 엔티티
         
         local spawnedEntity = _SpawnService:SpawnByModelId(id, name, spawnPosition, parent)
         if isvalid(spawnedEntity) == false then
             log("Spawn Failed")
         end
         end
         end
         



    
    
    4. `OnBeginPlay` 함수를 아래와 같이 수정합니다.
        ```lua
    @Component
    script ModelSpawner extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        -- self:SpawnByEntity()
        self:SpawnByModelId()
        end
    end
    

  4.  [시작] 버튼을 눌러 Workspace의 모델과 동일한 엔티티가 생성되는지 확인합니다. [png](1655816153693283ae21166b6476ea65044a4ffde1a21)



# 엔티티 제거

메이플스토리 월드에서는 엔티티를 제거할 수 있는 `_EntityService:Destroy`와 `Entity:Destroy` 두 가지 방법을 제공합니다. `_EntityService:Destroy`는 매개 변수로 넘겨 받은 엔티티를, 그리고 `Entity:Destroy`는 Destroy를 호출한 엔티티 자신을 삭제합니다. 따라서 두 함수는 대상만 다를 뿐 삭제 동작은 동일합니다.

## Destroy

#### 매개 변수

[{"index":"Parameter","0":"entity"},{"index":"Description","0":"제거 대상이 되는 엔티티를 넣어 줍니다."}]

#### 사용 예시

SpawnService의 스폰 기능을 통해 엔티티를 생성하고, 생성된 후 3초 후에 제거되도록 합니다. 예제는 위 엔티티 생성에서 사용한 SpawnManager 스크립트 컴포넌트를 활용합니다.

  1. 프로퍼티를 추가하고, 타입과 이름을 다음과 같이 설정합니다. 
         
         @Component
         script SpawnedEntityHolder extends Component
         property any SpawnedEntity = nil
         end
         



    
    
        
    2. `SpawnByModelId` 함수를 다음과 같이 수정합니다.
    
        ```lua
    @Component
    script ModelIdSpawner extends Component
        method void SpawnByModelId()
        local id = "maplestorymonster$0108737afb9548309c03795430b20c05"
        local name = "SpawnedEntity"
        local spawnPosition = Vector3(0,0,0)
        local parent = _EntityService:GetEntityByPath("/maps/map01")
    
        -- 아래 부분을 수정합니다.
        self.SpawnedEntity = _SpawnService:SpawnByModelId(id, name, spawnPosition, parent)
        if isvalid(self.SpawnedEntity) == false then
            log("Spawn Failed")
        end
        end
    end
    
    
    
    

  3. `OnUpdate` 함수를 추가하고 다음과 같이 작성합니다. 엔티티 생성 3초 후 해당 엔티티를 삭제하는 스크립트를 작성합니다.
         
         @Component
         script TimedDestroyer extends Component
         @ExecSpace("ServerOnly")
         method void OnUpdate(number delta)
         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
         end
         end
         



    
    
    5.  **[시작]** 버튼을 눌러 엔티티가 생성되고 3초 뒤 사라지는 것을 확인합니다
    
        >**Tip**
        > `Entity:Destroy`를 활용해 엔티티를 삭제할 수도 있습니다. `Entity:Destroy`를 사용한 스크립트는 다음과 같습니다.
    
        ```lua
    @Component
    script TimedDestroyer extends Component
        @ExecSpace("ServerOnly")
        method void OnUpdate(number delta)
        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
        end
    end
    

# 엔티티 유효성 체크

엔티티가 생성 또는 삭제될 때, 엔티티의 상태를 확인합니다. 보통 엔티티의 nil 체크를 하는데, 삭제 대기 중 같은 상태에서는 원하는 값을 제대로 얻지 못할 수도 있습니다. 이럴 때 엔티티 유효성 체크 기능을 사용합니다. 글로벌 함수인 `isvalid`를 통해 엔티티의 유효성을 체크할 수 있습니다.

## isvalid

`isvalid` 함수는 엔티티가 현재 삭제 대기 중인지 또는 이미 삭제되었는지를 확인하여 true 또는 false 값을 반환합니다.

#### 매개 변수

[{"index":"Parameter","0":"entity"},{"index":"Description","0":"유효성 체크 대상이 되는 엔티티를 넣어줍니다."}]

#### 반환 값

  * 매개 변수로 넘겨진 엔티티가 존재하면 true를 반환합니다.
  * 엔티티가 nil이거나 삭제 대기 중, 또는 삭제된 엔티티라면 false를 반환합니다.



#### 사용 예시

위 `Destroy` 함수 예제에 이어서 엔티티 삭제 전후로 `isvalid` 함수가 어떤 값을 반환하는지 확인하고 실제 사용법을 살펴봅니다.

  1. SpawnManager 스크립트 컴포넌트의 `OnUpdate` 함수를 다음과 같이 수정합니다.
         
         @Component
         script DelayedEntityDestroyer extends Component
         @ExecSpace("ServerOnly")
         method void OnUpdate(number delta)
         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("Before deletion : "..tostring(isvalidValue))
             self.SpawnedEntity:Destroy()
             isvalidValue = isvalid(self.SpawnedEntity)
             log("After deletion : "..tostring(isvalidValue))
         end
         end
         end
         



    
    
            
    2.  **[시작]** 버튼을 눌러 테스트합니다. 콘솔 창에서 엔티티 삭제 전후 로그를 확인합니다.
        [png](1655869031532a1ede31bc9bd46d8b521d0e7b328442f)
    
        > **Tip**
        > 유효성 체크는 글로벌 함수인 `isvalid`를 주로 이용하지만, `_EntityService:IsValid(entity)` 함수 또한 동일한 기능을 수행합니다.
        > 따라서 위 스크립트를 아래와 같이 수정해도 결과는 같습니다.
    
        ```lua
    @Component
    script TimedEntityDestroyer extends Component
        @ExecSpace("ServerOnly")
        method void OnUpdate(number delta)
        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 = _EntityService:IsValid(self.SpawnedEntity)
            log("Before deletion : "..tostring(isvalidValue))
            self.SpawnedEntity:Destroy()
            isvalidValue = _EntityService:IsValid(self.SpawnedEntity)
            log("After deletion : "..tostring(isvalidValue))
        end
        end
    end