턴제 게임에서 엔터티 구성 요소 게임 상태를 진행하는 방법은 무엇입니까?


9

지금까지 내가 사용한 엔터티 구성 요소 시스템은 대부분 Java의 아르테 미처럼 작동했습니다.

  • 구성 요소의 모든 데이터
  • 상태 비 저장 독립 시스템 (적어도 초기화시 입력이 필요하지 않은 정도까지)은 특정 시스템이 관심있는 구성 요소 만 포함하는 각 엔티티를 반복합니다.
  • 모든 시스템은 엔티티를 하나의 틱으로 처리 한 다음 전체를 다시 시작합니다.

이제 게임을 진행하기 전에 서로에 대해 정해진 순서로 발생해야하는 수많은 이벤트와 응답으로 턴 기반 게임에 이것을 처음으로 적용하려고합니다. 예를 들면 :

플레이어 A는 칼로 피해를받습니다. 이에 대응하여, A의 방어구는 공격을 받아 피해를 줄입니다. A의 이동 속도도 약해져서 낮아집니다.

  • 받는 피해는 전체 상호 작용을 설정하는 것입니다
  • 플레이어에게 데미지를 적용하기 전에 방어력을 계산하고 들어오는 데미지에 적용해야합니다
  • 이동 속도 감소는 최종 피해량에 의존하기 때문에 실제로 피해가 처리 된 후에야 유닛에 적용 할 수 없습니다.

이벤트는 다른 이벤트를 트리거 할 수도 있습니다. 갑옷을 사용하여 검의 피해를 줄이면 검이 부서 질 수 있습니다 (손상이 완료되기 전에 이루어져야 함). 이에 따라 추가로 사건이 발생할 수 있습니다.

대체로 이것은 몇 가지 문제를 일으키는 것으로 보입니다.

  1. 많은 낭비되는 처리주기 : 대부분의 시스템 (렌더링과 같이 항상 실행되는 것들을 위해 저장)은 작동하기 위해 "그들의 차례"가 아닐 때해야 할 일이 없으며, 대부분의 시간이 게임이 들어 오기를 기다리는 데 소비합니다. 유효한 작업 상태. 이것은 게임에 더 많은 상태가 추가 될수록 크기가 계속 커지는 수표로 그러한 모든 시스템을 비산합니다.
  2. 시스템이 게임에 존재하는 엔티티를 처리 할 수 ​​있는지 확인하려면, 관련없는 다른 엔티티 / 시스템 상태를 모니터 할 방법이 필요합니다 (손상을 담당하는 시스템은 갑옷의 적용 여부를 알아야합니다). 이로 인해 여러 가지 책임이있는 시스템이 혼란스러워 지거나 각 처리주기 후에 엔티티 콜렉션을 스캔하고 리스너 세트와 통신하여 작업을 수행 할 수있는 시간을 알려주는 것 외에 다른 목적이없는 추가 시스템이 필요합니다.

위의 두 지점은 시스템이 동일한 엔티티 세트에서 작동하고 구성 요소의 플래그를 사용하여 상태를 변경한다고 가정합니다.

이를 해결하는 또 다른 방법은 게임 상태를 진행하기 위해 단일 시스템 작업의 결과로 구성 요소를 추가 / 제거하거나 완전히 새로운 엔티티를 만드는 것입니다. 이는 시스템이 실제로 일치하는 엔티티를 가질 때마다 시스템이 처리 할 수 ​​있음을 알고 있음을 의미합니다.

그러나 이로 인해 시스템이 후속 시스템을 트리거해야하므로 단일 시스템 상호 작용의 결과로 버그가 표시되지 않으므로 프로그램 동작에 대해 추론하기가 어렵습니다. 새로운 시스템을 추가하는 것은 다른 시스템에 어떤 영향을 미치는지 정확히 알지 못하고 구현할 수 없기 때문에 더 어려워집니다 (그리고 새로운 시스템이 관심있는 상태를 트리거하기 위해 이전 시스템을 수정해야 할 수도 있음). 단일 작업으로.

이것이 내가 살아야 할 것입니까? 내가 본 모든 ECS 예제는 실시간이었으며, 이러한 경우 게임당 루프가 어떻게 작동하는지 쉽게 알 수 있습니다. 그리고 렌더링에 여전히 필요합니다. 무언가가 발생할 때마다 대부분의 측면을 일시 중지시키는 시스템에는 적합하지 않은 것 같습니다.

게임 상태를 앞으로 나아갈 수있는 디자인 패턴이 있습니까? 아니면 모든 로직을 루프 밖으로 옮기고 필요할 때만 트리거해야합니까?


이벤트가 발생하기 위해 실제로 폴링하고 싶지 않습니다. 이벤트는 발생할 때만 발생합니다. 아르테미스는 시스템이 서로 통신하는 것을 허용하지 않습니까?
Sidar

그러나 메소드를 사용하여 결합함으로써 만 가능합니다.
Aeris130

답변:


3

여기서 조언은 컴포넌트 시스템을 사용한 RPG 프로젝트의 과거 경험에서 비롯된 것입니다. 스파게티 코드이기 때문에 게임 사이드 코드에서 작업하는 것을 싫어한다고 말할 것입니다. 그래서 나는 여기에 많은 대답을하지 않고 단지 관점을 제시합니다.

플레이어에게 칼의 피해를 다루기 위해 설명하는 논리는 ... 하나의 시스템이 그 모든 것을 담당 하는 것 같습니다 .

어딘가에 HandleWeaponHit () 함수가 있습니다. 플레이어 엔티티의 ArmorComponent에 액세스하여 관련 갑옷을 얻습니다. 공격하는 무기 엔티티의 WeaponComponent에 액세스하여 무기를 산산조각냅니다. 최종 피해를 계산 한 후 플레이어가 이동 감소를 달성하기 위해 MovementComponent를 터치합니다.

낭비되는 처리 사이클에 관해서는 ... HandleWeaponHit ()은 필요할 때만 트리거되어야합니다 (도검 적중 감지시).

어쩌면 내가하려고하는 요점은 분명히 : 당신은 중단 점을두고, 타격 한 다음 칼 타격이 발생할 때 실행되어야하는 모든 논리를 단계별로 진행할 수있는 코드의 장소를 원할 것입니다. 다시 말해, 논리는 여러 시스템의 tick () 함수 전체에 분산되어서는 안됩니다.


이 방법을 사용하면 더 많은 동작이 추가 될 때 hit () 함수가 baloon이됩니다. 칼이 시선 내에있는 대상 (모든 대상)을 칠 때마다 웃으면 서 쓰러지는 적이 있다고 가정 해 봅시다. HandleWeaponHit이 실제로 트리거를 담당해야합니까?
Aeris130

1
전투 순서가 엄격하게 정해져 있으므로 적중시 효과를 발동합니다. 모든 것이 작은 시스템으로 나뉘어 질 필요는 없습니다. 이 시스템이 실제로는 "전투 시스템"이기 때문에 이것을 처리하게하세요. 그리고 ... 전투 ...
Patrick Hughes

3

그것은 1 년 된 질문이지만 지금은 ECS를 공부하는 동안 집에서 만든 게임과 같은 끔찍한 문제에 직면하고 있습니다. 바라건대 토론이나 최소한의 의견으로 끝날 것입니다.

ECS 개념을 위반하는지 확실하지 않지만 다음과 같은 경우에는 어떻게됩니까?

  • 시스템이 이벤트 오브젝트를 발행 / 구독 할 수 있도록 EventBus를 추가하십시오 (실제로 순수한 데이터이지만 구성 요소는 아닙니다)
  • 각 중간 상태에 대한 구성 요소 작성

예:

  • UserInputSystem이 [DamageDealerEntity, DamageReceiverEntity, Skill / Weapon used info] 로 공격 이벤트를 시작합니다 .
  • CombatSystem이 구독하고 DamageReceiver의 회피 확률을 계산합니다. 회피가 실패하면 동일한 매개 변수로 Damage 이벤트를 트리거합니다
  • DamageSystem이 그러한 이벤트에 등록되어 트리거됩니다.
  • DamageSystem은 Strength, BaseWeapon 피해, 유형 등을 사용하여 [DamageDealerEntity, FinalOutgoingDamage, DamageType] 을 사용하여 새 IncomingDamageComponent에 쓰고 데미지 수신기 엔티티 / 엔티티에 첨부합니다.
  • DamageSystem에서 나가는 손상을 발생시킵니다.
  • ArmorSystem에 의해 트리거되고, 수신기 엔티티를 가져 오거나 엔티티에서이 IncomingDamage 측면으로 검색하여 IncomingDamageComponent (마지막 하나는 확산으로 여러 공격에 더 적합 할 수 있음)를 선택하고 적용된 갑옷과 데미지를 계산합니다. 검 산산조각 이벤트를 선택적으로 트리거
  • ArmorSystems는 각 엔티티에서 IncomingDamageComponent를 제거하고 DamageReceivedComponent로 대체하여 최종 계산 된 숫자로 HP와 상처의 속도 감소에 영향을줍니다.
  • ArmorSystems는 IncomingDamageCalculated 이벤트를 보냅니다.
  • 속도 시스템을 구독하고 속도를 다시 계산
  • HealthSystem을 구독하고 실제 HP를 줄입니다.
  • 기타
  • 어떻게 든 정리

장점 :

  • 복잡한 체인 이벤트에 대한 중간 데이터를 제공하는 시스템 트리거
  • EventBus에 의한 디커플링

단점 :

  • 나는 이벤트 매개 변수와 임시 구성 요소의 두 가지 전달 방법을 혼합한다고 생각합니다. 약한 곳일 수 있습니다. 이론적으로 일을 균등하게 유지하기 위해 시스템이 측면에서 엔티티의 구성 요소에서 묵시적 매개 변수를 찾을 수 있도록 데이터가없는 열거 이벤트 만 발생시킬 수 있습니다.
  • 잠재적으로 관심이있는 모든 SystemsHave가 IncomingDamageCalculated를 처리하여 정리하고 다음 차례에 발생할 수 있는지 확인하는 방법을 모르겠습니다. 어쩌면 CombatSystem에서 일종의 검사가 될 수 있습니다 ...

2

Yakovlev와 비슷한 솔루션을 게시했습니다.

기본적으로 나는 턴에 대한 논리를 따르는 것이 매우 직관적이기 때문에 이벤트 시스템을 사용했습니다. 이 시스템은 턴 기반 논리 (플레이어, 몬스터 및 상호 작용할 수있는 모든 것)를 준수하는 게임 내 장치를 담당하게되었으며 렌더링 및 입력 폴링과 같은 실시간 작업은 다른 곳에 배치되었습니다.

시스템은 이벤트와 엔티티를 입력으로 받아서 엔티티가 이벤트를 수신했음을 알리는 onEvent 메소드를 구현합니다. 또한 모든 시스템은 특정 구성 요소 세트로 이벤트 및 엔티티를 구독합니다. 시스템에서 사용할 수있는 유일한 상호 작용 지점은 엔터티 관리자 싱글 톤으로 엔터티에 이벤트를 보내고 특정 엔터티에서 구성 요소를 검색하는 데 사용됩니다.

엔티티 관리자는 전송 된 엔티티와 연결된 이벤트를 수신하면 이벤트를 큐의 뒷면에 배치합니다. 큐에 이벤트가있는 동안 가장 중요한 이벤트가 검색되어 이벤트를 구독하고 이벤트를 수신하는 엔티티의 구성 요소 세트에 관심이있는 모든 시스템으로 전송됩니다. 이러한 시스템은 엔터티의 구성 요소를 처리하고 관리자에게 추가 이벤트를 보낼 수 있습니다.

예 : 플레이어가 피해를 입으므로 플레이어 개체에 피해 이벤트가 전송됩니다. DamageSystem은 상태 구성 요소를 사용하여 모든 엔터티에 전송 된 손상 이벤트를 구독하며 이벤트에 지정된 양만큼 엔터티 구성 요소의 상태를 줄이는 onEvent (entity, event) 메서드를 갖습니다.

이를 통해 방어구 구성 요소로 엔티티에 전송 된 손상 이벤트를 구독하는 방어구 시스템을 쉽게 삽입 할 수 있습니다. onEvent 메소드는 구성 요소의 방어력으로 이벤트의 피해를 줄입니다. 이것은 방어 시스템이 작동하기 위해 피해 시스템보다 먼저 피해 이벤트를 처리해야하기 때문에 시스템이 이벤트를받는 순서를 지정하면 게임 로직에 영향을 미칩니다.

그러나 때때로 시스템은 수신 엔터티 외부로 나가야합니다. Eric Undersander에 대한 저의 반응을 계속하려면 게임 맵에 액세스하고 엔티티의 x 공간 내에서 FallsDownLaughingComponent로 엔티티를 찾고 시스템에 FallDownLaughingEvent를 보내는 시스템을 추가하는 것이 간단합니다. 이 시스템은 손상 시스템 이후에 이벤트를 수신하도록 스케줄되어야합니다. 그 시점에서 손상 이벤트가 취소되지 않은 경우 손상이 처리되었습니다.

제기 된 한 가지 문제는 일부 응답이 추가 응답을 생성 할 수 있다는 점에서 응답 이벤트가 전송 된 순서대로 처리되도록하는 방법이었습니다. 예:

플레이어는 이동하여 플레이어 개체로 이동 이벤트가 전송되고 이동 시스템에 의해 선택됩니다.

대기열에서 : 이동

움직임이 허용되면 시스템은 플레이어 위치를 조정합니다. 그렇지 않은 경우 (플레이어가 장애물로 이동을 시도한 경우) 이벤트가 취소 된 것으로 표시되어 엔티티 관리자가 후속 시스템으로 보내지 않고이를 버립니다. 이벤트에 관심이있는 시스템 목록의 끝에는 TurnFinishedSystem이 있는데, 이는 플레이어가 캐릭터 이동에 자신의 차례를 보냈으며 이제 자신의 차례가 끝났음을 확인합니다. 결과적으로 턴 오버 이벤트가 플레이어 엔티티로 전송되고 대기열이 배치됩니다.

대기열에서 : 전환

이제 플레이어가 함정을 밟아 손상을 입었다 고 가정하십시오. TrapSystem은 TurnFinishedSystem 이전에 이동 메시지를 수신하므로 피해 이벤트가 먼저 전송됩니다. 이제 대기열은 다음과 같습니다.

대기열에서 : 데미지, 턴 오버

지금까지 모든 것이 정상이며, 데미지 이벤트가 처리 된 다음 턴이 끝납니다. 그러나 손상에 대한 응답으로 추가 이벤트가 전송되면 어떻게됩니까? 이제 이벤트 큐는 다음과 같습니다.

대기열에서 : Damage, TurnOver, ResponseToDamage

즉, 피해에 대한 대응이 처리되기 전에 차례가 끝날 것입니다.

이 문제를 해결하기 위해 send (event, entity) 및 respond (event, eventToRespondTo, entity)의 두 가지 이벤트 전송 방법을 사용했습니다.

모든 이벤트는 응답 체인에서 이전 이벤트의 기록을 유지하며 respond () 메소드가 사용될 때마다 응답하는 이벤트 (및 해당 응답 체인의 모든 이벤트)는 응답하십시오. 초기 움직임 이벤트에는 그러한 이벤트가 없습니다. 후속 피해 대응은 이동 이벤트가 목록에 있습니다.

또한 가변 길이 배열은 여러 이벤트 큐를 포함하는 데 사용됩니다. 관리자가 이벤트를 수신 할 때마다 이벤트는 응답 체인의 이벤트 양과 일치하는 배열의 인덱스에있는 큐에 추가됩니다. 따라서 초기 이동 이벤트는 [0]에 대기열에 추가되고, 손상 및 TurnOver 이벤트는 모두 이동에 대한 응답으로 전송되었으므로 [1]에 별도의 대기열에 추가됩니다.

피해 이벤트에 대한 응답이 전송되면 해당 이벤트에는 피해 이벤트 자체와 이동이 모두 포함되어 인덱스의 대기열에 배치됩니다 [2]. 인덱스 [n]이 큐에 이벤트를 가지고있는 한, 해당 이벤트는 [n-1]로 이동하기 전에 처리됩니다. 처리 순서는 다음과 같습니다.

이동-> 데미지 [1]-> ResponseToDamage [2]-> [2]가 비어 있음-> 전환 [1]-> [1]이 비어 있음-> [0]이 비어 있음

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.