Fighter 시스템
개요
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);
}
초기화 및 업데이트
FighterSystem
은 RuntimeConfig.startingState
필드에 있는 상태를 사용하여 OnInit()
의 첫 번째 상태를 초기화합니다.
FighterSystem
의 Update()
에서는 플레이어 중 한 명이 현재 멈춰 있지 않는 한 현재 상태 업데이트도 실행됩니다.
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은 아래 그림에 설명된 대로 매치 플로우를 구현합니다.
RuntimeConfig
는 시작 상태
를 알고 FighterSystem
은 해당 에셋의 참조를 사용하여 게임을 초기화합니다. 다른 모든 에셋은 다음으로 전환할 수 있는 사용 가능한 상태에 대해서만 알고 있으며, 그 앞에 오는 상태는 알고 있지 않습니다.
대부분의 매치는 4_Match Active State에서 사용됩니다. 종료 조건 중 하나(타이머가 소진되었거나 한 명 이상이 KO임)를 충족하면 현재 경기가 종료되고
다음 상태로 이동합니다.
- 현재 경기에서 진행해야 할 라운드가 남아 있는 경우 7_Reset 상태는 플레이어를 재설정하고 다시 라운드를 시작합니다.
- 경기가 가능한 모든 라운드를 진행했거나 플레이어가 2회 연속 승리했을 경우 새로운 매치가 시작됩니다.
영역 내에 캐릭터 유지하기
캐릭터가 플레이 영역 내에 머물도록 FighterSystem
은 RuntimeConfig
에 정의된 minBounds
및 maxBounds
내에 캐릭터를 고정합니다. 이 작업은 KeepFightersInBounds()
메소드에서 수행됩니다.
또한 두 모델이 겹치지 않도록 캐릭터 구분도 관리합니다.
두 캐릭터에 대해 동시에 클램프 검사를 수행합니다. 두 가지 에지 사례를 처리하기 위해 수행됩니다.
- 캐릭터가 경기장 경계로 다시 이동되고 모델이 서로 가까이 있기 때문에 상대방의 캐릭터로 밀려나게 됩니다.
- 한 캐릭터에 다른 캐릭터보다 우선순위를 부여하면 첫 번째 캐릭터가 경기장 내에서 자유롭게 이동할 수 있고 두 번째 캐릭터는 끌 수 있지만 그 반대로 끌 수는 없습니다.
투사체 & 상호작용
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