This document is about: QUANTUM 2
SWITCH TO

Fighter 시스템


Available in the Gaming Circle and Industries Circle
Circle

개요

FighterSystem은 게임 플레이를 지배하는 모든 개념이 들어있는 곳입니다. 따라서 Fighting Template의 유일한 다목적 시스템이며 게임 루프의 핵심입니다. 게임 플레이 커스터마이징은 여기서 이루어집니다.

매치

FighterSystem은 이 게임 인스턴스와 관련된 RuntimeConfig를 읽어 OnInit()의 전역 매치 설정을 초기화합니다.

상태 에셋

모든 일치 상태는 MatchStateData 에셋 기본 클래스의 구체적인 구현에 의해 정의됩니다. 상태가 서로 연결되어 매치 흐름을 만듭니다.

C#

public abstract unsafe partial class MatchStateData{
   public FP timerLength = FP._0;
   public bool ignoreInput;

   public void OnEnter(Frame f){
       f.Global->ignoreInput = ignoreInput;
       f.Global->matchStateTimer = timerLength;
       OnStateEnter(f);
   }

   public virtual void OnStateEnter(Frame f) { }

   public void OnUpdate(Frame f) {
       f.Global->matchStateTimer -= f.DeltaTime;
       if (f.Global->matchStateTimer <= FP._0) {
           OnStateTimerComplete(f);
       } else {
           OnStateUpdate(f);
       }
   }

   public virtual void OnStateTimerComplete(Frame f) { }
   public virtual void OnStateUpdate(Frame f) { }
   public virtual void OnExit(Frame f) { }

   public void GoToState(Frame f, AssetRefMatchStateData nextStateAsset) {
       OnExit(f);
       f.Global->matchState = nextStateAsset;
       var nextState = f.FindAsset<MatchStateData>(nextStateAsset.Id);
       nextState.OnEnter(f);
   }

   public static void GoToStateCall(Frame f, AssetRefMatchStateData nextStateAsset) {
       var state = f.FindAsset<MatchStateData>(f.Global->matchState.Id);
       state.GoToState(f, nextStateAsset);
   }

초기화 및 업데이트

FighterSystemRuntimeConfig.startingState 필드에 있는 상태를 사용하여 OnInit()의 첫 번째 상태를 초기화합니다.

FighterSystemUpdate()에서는 플레이어 중 한 명이 현재 멈춰 있지 않는 한 현재 상태 업데이트도 실행됩니다.

C#

var matchState = f.FindAsset<MatchStateData>(f.Global->matchState.Id);
var freezeList = f.ResolveList(f.Global->freeze);
if (freezeList[0] <= FP._0 && freezeList[1] <= FP._0){
   matchState.OnUpdate(f);
}

freezeList는 히트 스톱을 처리하는 데 사용됩니다. 즉, 플레이어가 맞은 후 플레이어들이 맞았음음 인식하기 위해 게임을 잠시 정지시킵니다.
흐름

Fighting Template은 아래 그림에 설명된 대로 매치 플로우를 구현합니다.

Match State Flow
Fighting Template에서 구현된 기본 매치 상태 흐름.

RuntimeConfig시작 상태를 알고 FighterSystem은 해당 에셋의 참조를 사용하여 게임을 초기화합니다. 다른 모든 에셋은 다음으로 전환할 수 있는 사용 가능한 상태에 대해서만 알고 있으며, 그 앞에 오는 상태는 알고 있지 않습니다.
대부분의 매치는 4_Match Active State에서 사용됩니다. 종료 조건 중 하나(타이머가 소진되었거나 한 명 이상이 KO임)를 충족하면 현재 경기가 종료되고
다음 상태로 이동합니다.

  • 현재 경기에서 진행해야 할 라운드가 남아 있는 경우 7_Reset 상태는 플레이어를 재설정하고 다시 라운드를 시작합니다.
  • 경기가 가능한 모든 라운드를 진행했거나 플레이어가 2회 연속 승리했을 경우 새로운 매치가 시작됩니다.

영역 내에 캐릭터 유지하기

캐릭터가 플레이 영역 내에 머물도록 FighterSystemRuntimeConfig에 정의된 minBoundsmaxBounds 내에 캐릭터를 고정합니다. 이 작업은 KeepFightersInBounds() 메소드에서 수행됩니다.
또한 두 모델이 겹치지 않도록 캐릭터 구분도 관리합니다.
두 캐릭터에 대해 동시에 클램프 검사를 수행합니다. 두 가지 에지 사례를 처리하기 위해 수행됩니다.

  1. 캐릭터가 경기장 경계로 다시 이동되고 모델이 서로 가까이 있기 때문에 상대방의 캐릭터로 밀려나게 됩니다.
  2. 한 캐릭터에 다른 캐릭터보다 우선순위를 부여하면 첫 번째 캐릭터가 경기장 내에서 자유롭게 이동할 수 있고 두 번째 캐릭터는 끌 수 있지만 그 반대로 끌 수는 없습니다.

투사체 & 상호작용

Fighting Template에서는 플레이어가 특수 이동을 수행할 때 발사체가 생성됩니다. 이 이동의 애니메이션 상태 동작과 관련된 QTABCreateProjectile 에셋이 있습니다. UpdateProjectiles()HitBox 컴포넌트로 활성 발사체를 필터링하고 Projectile 컴포넌트에서 참조하는 ProjectileData 에셋을 사용하여 업데이트합니다.

C#

private static void UpdateProjectiles(Frame f){
   var projectiles = f.Filter<Projectile, HitBox, Transform3D>();
   while (projectiles.NextUnsafe(out EntityRef proj, out var p, out var hb, out var t)){
       var projData = f.FindAsset<ProjectileData>(p->data.Id);
       projData.Update(f, proj, p, t, hb);
   }
}

수집품, 환경 조치 및 기타 참가자의 상호작용 가능한 요소도 유사한 방식으로 처리해야 합니다. 중요한 부분은 히트 관련 메소드가 실행되기 전에 히트 기록에 영향을 미칠 수 있는 컴포넌트를 업데이트하는 것입니다.

히트

FighterSystem은 맞춤형 타격 감지/충돌 점검을 구현합니다. 현재 애니메이션 상태에 대해 설정된 공격의 히트박스와 상대의 상처박스 간의 오버랩 검사를 수행합니다(자세한 내용은 상태 동작 편집기 페이지 참조).

히트 등록은 다음 3단계로 수행됩니다.

  • 히트 탐지
  • KO 검사
  • 공격 데미지 애플리케이션

히트를 실행할 수 있는 애니메이션(예,발사체(QTABCreateProjectile)를 쏘거나 공격(ATABAttackState))이 수행될 때마다 HitBox 컴포넌트를 가진 엔티티가 생성됩니다. 이것은 히트 감지를 위해 오버랩을 수행하는 데 사용됩니다.

C#

// Extracted from QTABAttackState.cs
var hitBoxEntity = frame.Create();
var hitBoxComponent = new HitBox();

중첩이 발견되는 경우 다음을 수행합니다.

  • 상대에게 방금 맞은 공격의 데이터가 제공됩니다. 그리고,
  • 현재 공격에 대한 히트박스 데이터를 가지고 있는 이미 떨어진 엔티티는 동일한 공격을 여러 번 받지 않기 위해 파괴됩니다.

C#

var hitboxFilter = f.Filter<HitBox>();
while (hitboxFilter.Next(out EntityRef e, out HitBox hb)){
   if (hb.active == false)
       continue;

   if (!f.Unsafe.TryGetPointer(hb.target, out Fighter* targetFighter)) continue;
   var hurtBoxList = f.ResolveList(targetFighter->hurtBoxList);

   for (int h = 0; h < hurtBoxList.Count && targetFighter->hitByAttack == false; h++){
       if (!hb.bounds.Intersects(hurtBoxList[h])) continue;
       if (!f.FindAsset<AttackData>(hb.attackData.Id).CanTestCollision(f, targetFighter)) continue;
      
       targetFighter->hitByAttack = true;
       targetFighter->hitPosition = FP._0_50 * (hb.bounds.Center + hurtBoxList[h].Center);
       targetFighter->hitByAttackData = hb.attackData;
       f.Destroy(e);
       break;
   }
}

플레이어가 동일한 프레임에서 던지기 공격에 대한 에지 사례를 피하기 위해 AttackData.Priority에서 각 공격에 우선순위가 부여되며 공격이 시작되었는지 확인하기 전에 우선순위를 평가합니다.

마지막으로 TestIfPlayerHitByAttack() 메소드로 데미지를 적용합니다.

C#

private static void TestIfPlayerHitByAttack(Frame f, ref QList<FP> freezeList, ref bool kod, EntityRef pE, Fighter* pF, AttackData p1AtkDataHitBy){
   if (!pF->hitByAttack) return;
  
   var pA = f.Unsafe.GetPointer<CustomAnimator>(pE);
   var pT = f.Unsafe.GetPointer<Transform3D>(pE);
   if (p1AtkDataHitBy.OnHit(ref freezeList, pF->index, f, pE, pF, pA, pT)) {
       kod = true;
   }

   pF->hitByAttack = false;
}
Back to top