다음과 같은 엔티티 시스템 변형을 구현하고 있습니다.
Entity 클래스 ID를보다 조금 더 그 바인딩 구성 요소를 함께
"구성 요소 논리"가없고 데이터 만 있는 여러 구성 요소 클래스
많은 시스템 클래스 (일명 "서브 시스템", "관리자"). 이것들은 모든 엔티티 로직 처리를 수행합니다. 대부분의 경우 시스템은 관심있는 엔티티 목록을 반복하고 각 엔티티에 대해 조치를 수행합니다.
모든 게임 시스템에서 공유 하는 MessageChannel 클래스 개체 입니다. 각 시스템은 특정 유형의 메시지를 구독하여들을 수 있으며 채널을 사용하여 다른 시스템으로 메시지를 브로드 캐스트 할 수도 있습니다.
시스템 메시지 처리의 초기 변형은 다음과 같습니다.
- 각 게임 시스템에서 순차적으로 업데이트 실행
시스템이 구성 요소에 어떤 작업을 수행하고 해당 작업이 다른 시스템에 관심이있는 경우 시스템은 적절한 메시지를 보냅니다 (예 : 시스템 호출).
messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition))
실체가 움직일 때마다)
특정 메시지를 구독 한 각 시스템은 메시지 처리 방법을 얻습니다.
시스템이 이벤트를 처리하고 있고 이벤트 처리 로직이 다른 메시지를 브로드 캐스트해야하는 경우 메시지가 즉시 브로드 캐스트되고 다른 메시지 처리 방법 체인이 호출됩니다.
이 변형은 충돌 감지 시스템 최적화를 시작할 때까지 괜찮습니다 (엔티티 수가 증가함에 따라 실제로 느려졌습니다). 처음에는 간단한 무차별 강제 알고리즘을 사용하여 각 엔티티 쌍을 반복합니다. 그런 다음 특정 셀 영역 안에있는 엔터티를 저장하는 셀 격자가있는 "공간 인덱스"를 추가하여 인접 셀의 엔터티 만 검사 할 수 있습니다.
엔티티가 움직일 때마다 충돌 시스템은 엔티티가 새로운 위치의 무언가와 충돌하는지 확인합니다. 그렇다면 충돌이 감지됩니다. 충돌하는 두 엔티티가 모두 "물리적 객체"인 경우 (둘 다 RigidBody 구성 요소를 가지고 있고 동일한 공간을 차지하지 않도록 서로 밀도록 의도 된 경우) 전용 강체 분리 시스템은 움직임 시스템에 엔티티를 일부로 이동하도록 요청합니다 그들을 분리 할 특정 위치. 이로 인해 이동 시스템은 변경된 엔티티 위치를 알리는 메시지를 보냅니다. 충돌 감지 시스템은 공간 인덱스를 업데이트해야하기 때문에 반응합니다.
셀이 반복되는 동안 셀의 내용 (C #의 일반 Entity 오브젝트 목록)이 수정되어 반복자가 예외를 발생시키기 때문에 문제가 발생하는 경우가 있습니다.
그렇다면 충돌을 검사하는 동안 충돌 시스템이 중단되는 것을 어떻게 방지 할 수 있습니까?
물론 셀 내용이 올바르게 반복되도록하는 "영리한"/ "까다로운"논리를 추가 할 수는 있지만 충돌 시스템 자체에 문제가있는 것은 아니라고 생각합니다 (다른 시스템에서도 비슷한 문제가 있음). 메시지는 시스템에서 시스템으로 이동할 때 처리됩니다. 내가 필요한 것은 특정 이벤트 처리 방법이 중단없이 작동하도록하는 방법입니다.
내가 시도한 것 :
- 수신 메시지 대기열 . 일부 시스템이 메시지를 브로드 캐스트 할 때마다 메시지가 관심이있는 시스템의 메시지 큐에 추가됩니다. 이 메시지는 시스템 업데이트가 각 프레임에서 호출 될 때 처리됩니다. 문제 : 시스템 A가 시스템의 B 대기열에 메시지를 추가하면 시스템 B가 시스템 A보다 나중에 동일한 게임 프레임에서 업데이트되어야하는 경우 잘 작동합니다. 그렇지 않으면 메시지가 다음 게임 프레임을 처리하게합니다 (일부 시스템에서는 바람직하지 않음)
- 발신 메시지 대기열 . 시스템이 이벤트를 처리하는 동안 브로드 캐스트하는 모든 메시지가 발신 메시지 큐에 추가됩니다. 메시지는 시스템 업데이트가 처리 될 때까지 기다릴 필요가 없습니다. 초기 메시지 처리기가 작업을 마치면 "바로"처리됩니다. 메시지 처리로 인해 다른 메시지가 브로드 캐스트되면 발신 큐에도 추가되므로 모든 메시지가 동일한 프레임으로 처리됩니다. 문제: 엔터티 수명 시스템 (시스템으로 엔터티 수명 관리를 구현 한 경우)이 엔터티를 생성하면 일부 시스템 A 및 B에이를 알립니다. 시스템 A가 메시지를 처리하는 동안 메시지 체인이 생성되어 결국에는 생성 된 엔티티가 손상됩니다 (예 : 총알 엔티티가 장애물과 충돌 할 때 생성되어 총알 자체 파괴). 메시지 체인이 해결되는 동안 시스템 B는 엔티티 작성 메시지를받지 않습니다. 따라서 시스템 B가 엔티티 삭제 메시지에도 관심이있는 경우이를 가져오고 "체인"분석이 완료된 후에 만 초기 엔티티 작성 메시지를받습니다. 이로 인해 삭제 메시지가 무시되고 작성 메시지가 "수락"됩니다.
편집-질문, 의견에 대한 답변 :
- 충돌 시스템이 반복하는 동안 셀의 내용을 수정하는 사람은 누구입니까?
충돌 시스템이 일부 엔티티와 인접 엔티티에서 충돌 검사를 수행하는 동안 충돌이 감지 될 수 있으며 엔티티 시스템은 다른 시스템에 의해 즉시 반응 할 메시지를 보냅니다. 메시지에 대한 반응으로 다른 메시지가 생성되어 즉시 처리 될 수도 있습니다. 따라서 일부 다른 시스템은 충돌 충돌 검사가 아직 완료되지 않았어도 충돌 시스템이 즉시 처리해야한다는 메시지를 생성 할 수 있습니다 (예 : 엔티티가 이동하여 충돌 시스템이 공간 인덱스를 업데이트해야 함).
- 글로벌 발신 메시지 대기열로 작업 할 수 없습니까?
최근에 단일 글로벌 대기열을 시도했습니다. 새로운 문제가 발생합니다. 문제 : 탱크 엔티티를 벽 엔티티로 옮깁니다 (탱크는 키보드로 제어 됨). 그런 다음 탱크 방향을 변경하기로 결정했습니다. 탱크와 벽을 각 프레임을 분리하기 위해 CollidingRigidBodySeparationSystem은 탱크를 벽에서 가장 작은 양만큼 이동시킵니다. 분리 방향은 탱크의 이동 방향의 반대 방향이어야합니다 (게임 그림이 시작될 때 탱크는 절대로 벽으로 이동하지 않은 것처럼 보입니다). 그러나 방향은 NEW 방향과 반대가되어 탱크를 처음과 다른 벽면으로 이동시킵니다. 문제가 발생하는 이유 : 메시지가 현재 처리되는 방식입니다 (간단한 코드).
public void Update(int deltaTime)
{
m_messageQueue.Enqueue(new TimePassedMessage(deltaTime));
while (m_messageQueue.Count > 0)
{
Message message = m_messageQueue.Dequeue();
this.Broadcast(message);
}
}
private void Broadcast(Message message)
{
if (m_messageListenersByMessageType.ContainsKey(message.GetType()))
{
// NOTE: all IMessageListener objects here are systems.
List<IMessageListener> messageListeners = m_messageListenersByMessageType[message.GetType()];
foreach (IMessageListener listener in messageListeners)
{
listener.ReceiveMessage(message);
}
}
}
코드는 다음과 같이 흐릅니다 (첫 번째 게임 프레임이 아니라고 가정합니다).
- 시스템이 TimePassedMessage 처리를 시작합니다
- InputHandingSystem은 키 누름을 엔티티 조치로 변환합니다 (이 경우 왼쪽 화살표는 MoveWest 조치로 바)). 엔티티 조치는 ActionExecutor 구성 요소에 저장됩니다.
- 엔티티 조치에 대한 조치로 ActionExecutionSystem 은 MovementDirectionChangeRequestedMessage를 메시지 큐의 끝에 추가합니다.
- MovementSystem 은 Velocity 컴포넌트 데이터를 기반으로 엔티티 위치를 이동하고 PositionChangedMessage 메시지를 큐의 끝에 추가합니다. 이전 프레임의 이동 방향 / 속도를 사용하여 이동합니다 (북쪽이라고합시다).
- 시스템이 TimePassedMessage 처리를 중지합니다
- 시스템이 MovementDirectionChangeRequestedMessage 처리를 시작합니다
- MovementSystem 은 요청에 따라 엔티티 속도 / 이동 방향을 변경합니다
- 시스템이 MovementDirectionChangeRequestedMessage 처리를 중지합니다
- 시스템이 PositionChangedMessage 처리를 시작합니다
- CollisionDetectionSystem 은 엔티티가 이동했기 때문에 다른 엔티티 (탱크가 벽 안으로 들어가는)에 부딪 혔음을 감지합니다. CollisionOccuredMessage를 대기열에 추가합니다.
- 시스템이 PositionChangedMessage 처리를 중지합니다
- 시스템이 CollisionOccuredMessage 처리를 시작합니다
- 충돌 RigidBodySeparationSystem 은 탱크와 벽을 분리하여 충돌에 반응합니다. 벽은 정적이므로 탱크 만 이동합니다. 탱크의 이동 방향은 탱크의 출처를 나타내는 지표로 사용됩니다. 반대 방향으로 오프셋
BUG : 탱크가이 프레임을 움직였을 때, 이전 프레임의 이동 방향을 사용하여 이동했지만, 분리 될 때이 프레임의 이동 방향은 이미 다르지만 사용되었습니다. 그것이 작동하는 방식이 아닙니다!
이 버그를 방지하려면 이전 이동 방향을 어딘가에 저장해야합니다. 이 특정 버그를 수정하기 위해 일부 구성 요소에 추가 할 수 있지만이 경우 메시지를 처리하는 근본적으로 잘못된 방법을 나타내지 않습니까? 분리 시스템이 어떤 이동 방향을 사용해야합니까? 이 문제를 어떻게 우아하게 해결할 수 있습니까?
- gamadu.com/artemis를 읽고 Aspects에서 수행 한 작업을 확인하고, 어느 측면에서 현재 문제를 해결하고 있는지 확인할 수 있습니다.
사실, 나는 꽤 오랫동안 Artemis에 익숙했습니다. 소스 코드를 조사하고 포럼 등을 읽었습니다. 그러나 "Aspects"가 몇 군데에서만 언급 된 것을 보았습니다. 이해하는 한, 기본적으로 "Systems"를 의미합니다. 그러나 아르테미스 측이 어떻게 내 문제를 어떻게 해결하는지 알 수 없습니다. 심지어 메시지를 사용하지 않습니다.
- "엔터티 통신 : 메시지 큐 대 발행 / 구독 대 신호 / 슬롯"도 참조하십시오.
엔티티 시스템에 관한 모든 gamedev.stackexchange 질문을 이미 읽었습니다. 이것은 내가 직면 한 문제를 논의하지 않는 것 같습니다. 뭔가 빠졌습니까?
- 그리드를 업데이트 할 때 충돌 시스템의 일부이므로 이동 메시지에 의존 할 필요가 없습니다.
무슨 말인지 잘 모르겠습니다. CollisionDetectionSystem의 이전 구현은 업데이트에서 충돌을 확인하지만 (TimePassedMessage가 처리 된 경우) 성능으로 인해 가능한 한 확인을 최소화해야했습니다. 따라서 엔터티가 움직일 때 충돌 검사로 전환했습니다 (게임에서 대부분의 엔터티는 정적 임).