RIT-B10 | RiteSeekers — Relic 효과를 공격 코드와 분리한 과정
처음 Relic 효과를 붙이려고 했을 때는 Melee Ability 안에서 바로 처리하는 게 가장 단순해 보였습니다.
Blood Relic을 장착했다면 공격이 맞았을 때 출혈을 넣고, Execution Relic을 장착했다면 Target의 Health를 확인해서 추가 Damage를 주는 방식입니다.
Text
Melee
→ Blood 장착 여부 확인
→ Execution 장착 여부 확인
→ 각 Rule 조건 판단
→ GameplayEffect 적용효과가 두세 개 정도라면 이 방식도 충분히 동작합니다.
그런데 Rule을 하나씩 붙이다 보니 Melee가 점점 많은 걸 알아야 했습니다. 실제 공격 처리뿐 아니라 Blood가 어떤 조건에서 터지는지, Execution은 어느 Health 구간에서 동작하는지까지 Melee 안으로 들어오게 됩니다.
그래서 공격 자체와, 공격이 끝난 뒤 그 결과를 이용하는 추가 Rule을 나누는 쪽으로 바꿨습니다.
Text
Melee
→ 실제 공격과 적중 결과 처리
CombatAugment
→ 적중 이후 추가 Rule 처리둘 사이의 Event 경계로 사용한 것이 GameplayEvent.Combat.HitConfirmed입니다.
Relic을 장착했다고 바로 Rule이 실행되는 것은 아니었다
Relic이 Inventory나 Equipment에 들어 있다고 해서 그 순간 Combat 효과가 바로 동작하는 것은 아닙니다.
실제로는 AbilitySet과 ASC를 거쳐 Passive Ability가 Runtime에 들어와 있어야 했습니다.
Text
Relic 장착
→ AbilitySet Grant
→ ASC
→ Passive Ability
→ 실제 Hit
→ HitConfirmed
→ Blood / Execution
→ GameplayEffect
→ Health / Status / DeathBlood를 예로 들면:
Text
Blood Relic
→ Blood AbilitySet Grant
→ ASC에 Passive AbilitySpec 생성
→ Blood Passive Active
→ HitConfirmed 대기까지 이어집니다.
CombatAugment Passive는 OnSpawn Activation Policy를 사용해서 AbilitySet을 통해 ASC에 들어온 뒤 별도의 공격 입력을 기다리는 Ability가 아니라, 활성화된 상태에서 HitConfirmed Event를 기다리도록 두었습니다.
이 흐름을 보고 나서 Relic이 장비창에 있다는 상태와 실제 Combat Rule이 동작할 수 있는 상태를 같은 것으로 보지 않게 됐습니다.
이 상태를 보여주기 위해 Editor 설정 Screenshot을 별도로 하나 더 넣지는 않았습니다.
뒤의 Runtime 검증에서 Execution Relic이 실제로 들어왔을 때:
Text
Stage=RelicGranted
Granted=true
Active=true
Stage=PassiveActivated
Relic=Execution이 기록되는 것을 확인했고, 이어서 실제 HitConfirmed와 Execution Rule까지 연결되는 흐름을 영상과 Output Log에서 같이 확인했습니다.
즉 여기서 중요하게 본 것은 Asset이 설정되어 있다는 사실보다 그 설정이 실제 Runtime 상태로 이어졌는가였습니다.
HitConfirmed는 실제 Hit가 나온 뒤에 보냈다
Animation이 재생됐다고 적을 맞춘 것은 아니고, Trace가 실행됐다고 해서 반드시 유효한 Target이 생긴 것도 아닙니다.
그래서 Blood나 Execution을 공격 시작 시점에 바로 실행시키지는 않았습니다.
Melee 쪽에서 실제 Target과 HitResult를 확보한 뒤 Combat 결과가 만들어졌을 때 HitConfirmed를 보냈습니다.
Text
Melee Ability
→ Trace
→ TargetData / HitResult
→ Base Combat Result
→ GameplayEvent.Combat.HitConfirmed그다음부터 Blood와 Execution이 같은 Event를 받아 자기 조건을 확인합니다.
Text
Melee
→ HitConfirmed 발행
Blood / Execution
→ HitConfirmed 수신
→ 각자의 Rule 처리이렇게 나눈 뒤에는 Melee가 현재 어떤 Relic이 장착돼 있는지 알 필요가 없어졌습니다.
Melee는 실제로 무엇을 맞췄는지까지만 만들고, 그 결과를 받은 Passive 쪽에서 후속 효과를 결정합니다.

🔡 Debug Evidence 1. 실제 Melee Hit 이후 GameplayEvent.Combat.HitConfirmed를 전달하기 직전에 Breakpoint를 걸고, Runtime에서 Instigator, Target, EventTag, Damage 값이 Event Payload에 구성된 상태를 확인한 화면.
실제 Source에서는 FGameplayEventData HitConfirmedEvent에 EventTag와 Instigator, Target, Damage 값을 채운 뒤:
Text
SourceASC->HandleGameplayEvent(HitConfirmedEvent.EventTag,
&HitConfirmedEvent);로 전달합니다.
Debug 시점의 조사식에서는 실제로:
Text
EventTag
→ GameplayEvent.Combat.HitConfirmed
Instigator
→ B_RiteSeekerCharacter_Base_C_0
Target
→ RSCombatTrainingDummy_0
EventMagnitude
→ 22.0을 확인했습니다.
즉 Source에 코드가 존재한다는 것만 본 것이 아니라, 실제 Melee Hit에서 만들어진 Event Payload에 Player와 Training Dummy, GameplayEvent.Combat.HitConfirmed가 들어간 상태까지 확인한 것입니다.
핵심 흐름은 다음과 같습니다.
Text
실제 HitResult
→ FGameplayEventData 구성
→ GameplayEvent.Combat.HitConfirmed
→ SourceASC->HandleGameplayEvent(...)여기에서 중요한 점은 Melee 쪽이 Blood나 Execution의 세부 Rule을 직접 참조하지 않는다는 점입니다.
Melee의 책임은 실제 적중 결과를 만드는 데서 끝나고, 그 이후 추가 Rule은 HitConfirmed를 받은 Passive 쪽에서 판단합니다.
Debug Evidence에서 확인한 EventMagnitude=22.0과 뒤의 Runtime Log에 나오는 DamageMagnitude=37.0은 같은 항목을 같은 지점에서 기록한 값으로 보지는 않았습니다.
Debug Screenshot의 22.0은 HitConfirmedEvent.EventMagnitude를 구성한 시점에서 직접 확인한 값이고, 뒤의 BaseDamage=22.0 / DamageMagnitude=37.0은 DamageExecution 단계에서 남긴 Runtime Log입니다.
두 값의 의미를 억지로 하나로 묶기보다, 각각의 단계에서 실제로 확인된 값으로 구분해서 남겼습니다.
Blood와 Execution에서 반복되는 부분은 Base로 올렸다
Blood와 Execution은 최종 결과는 다르지만 시작하는 과정은 비슷했습니다.
둘 다 HitConfirmed를 받고, 실제 Target이 있는지 확인하고, 처리 가능한 Authority 상태인지 확인해야 합니다.
이 부분을 각각 복사해서 넣는 대신 CombatAugmentPassiveBase 쪽으로 올렸습니다.
Text
GameplayEvent.Combat.HitConfirmed
→ CombatAugmentPassiveBase
→ Event 수신
→ Authority 확인
→ Target Validation
→ 개별 Rule 처리여기서부터 Blood와 Execution의 동작이 갈립니다.
Text
Blood
→ Bleed / Periodic Damage
Execution
→ Target Health Ratio 확인
→ Finish Rule 판단Base에서 Blood나 Execution의 실제 조건까지 처리하지는 않습니다.
공통으로 필요한 Event 수신, Authority, Target 확인까지만 처리하고, 각 Rule에만 필요한 조건은 그대로 개별 Passive에 남겼습니다.
CombatAugmentPassiveBase의 ActivateAbility()에서는 Definition.TriggerEventTag를 기준으로 UAbilityTask_WaitGameplayEvent를 생성하고, Event가 들어오면 OnCombatHitConfirmed()로 연결합니다.
Text
Definition.TriggerEventTag
→ WaitGameplayEvent
→ OnCombatHitConfirmed그리고 실제 Event를 받은 뒤에는 Authority와 Target을 공통으로 확인합니다.

🔡 Debug Evidence 2-A. OnCombatHitConfirmed()에서 HitConfirmed를 받은 뒤 Authority와 Avatar 상태를 확인하고, ResolveTargetActor()를 통해 실제 Combat Target을 해석하는 부분.
OnCombatHitConfirmed()에서는 먼저:
Text
if (HasAuthority(&CurrentActivationInfo)==false)
{return;
}로 실행 경로를 확인하고, Avatar가 유효한지 확인한 뒤 Payload에서 Target을 해석합니다.
Text
AActor*TargetActor =ResolveTargetActor(Payload);if (TargetActor==nullptr)
{return;
}if (TargetActor==AvatarActor)
{return;
}즉 Event가 들어왔다고 바로 Blood나 Execution을 실행하는 것이 아니라:
Text
HitConfirmed 수신
→ Authority 확인
→ Avatar 확인
→ Target Resolve
→ Invalid Target Guard
→ Self Target Guard까지 공통으로 처리합니다.

🔡 Debug Evidence 2-B. 같은 HitConfirmed 처리 흐름에서 Trigger와 Target 상태를 기록한 뒤 HandleConfirmedHit()로 개별 Rule을 넘기기 직전, 실제 Runtime 값을 조사식으로 확인한 화면.
Target 검증을 통과하면 Trigger와 Target을 Runtime 상태에 기록합니다.
Text
constFGameplayTagTriggerEventTag =Payload.EventTag.IsValid()
?Payload.EventTag
:Definition.TriggerEventTag;LastTriggerEventTag =TriggerEventTag;LastTargetName =GetNameSafe(TargetActor);HandleConfirmedHit(Payload,TargetActor);Breakpoint를 HandleConfirmedHit(Payload, TargetActor) 직전에 걸고 확인했을 때 조사식에서는:
Text
Payload.EventTag
→ GameplayEvent.Combat.HitConfirmed
Payload.Target
→ RSCombatTrainingDummy_0
TargetActor
→ RSCombatTrainingDummy_0
AvatarActor
→ B_RiteSeekerCharacter_Base_C_0
Definition.AugmentTag
→ Combat.Augment.Execution
LastTargetName
→ RSCombatTrainingDummy_0
LastTriggerEventTag
→ GameplayEvent.Combat.HitConfirmed를 확인했습니다.
첫 번째 디버깅 화면에서는 Melee가:
Text
GameplayEvent.Combat.HitConfirmed
Target = RSCombatTrainingDummy_0를 만들어 ASC에 전달했고,
다음 디버깅 화면에서는 같은 EventTag와 같은 Target이 실제 CombatAugmentPassiveBase까지 들어온 상태를 확인했습니다.
이 두 장을 같이 보면:
Text
Melee
→ HitConfirmed Payload 구성
→ ASC에 전달
→ CombatAugmentPassiveBase 수신
→ Authority / Target Validation
→ HandleConfirmedHit
→ 개별 Rule까지 한 흐름으로 이어집니다.
공통 검증을 통과한 뒤에는 HandleConfirmedHit()로 넘기고, 실제 Blood나 Execution 조건은 각 Passive에서 이어서 처리하도록 두었습니다.
정리하면 Base의 역할은:
Text
Event 수신과 공통 검증이고,
Blood와 Execution의 역할은:
Text
각자의 실제 Rule 판단입니다.
LastTargetName, LastTriggerEventTag, LastResultText, LastAppliedMagnitude, LastObservedHealthRatio 같은 값은 뒤에서 확인하는 Runtime Snapshot과도 연결됩니다.
Rule 정보는 Definition으로 따로 묶었다
Blood와 Execution을 나누면서 Rule 자체의 정보도 C++ 조건문 안에 흩어두지 않고 FRSCombatAugmentDefinition으로 묶었습니다.
현재 사용하는 값은 다음과 같습니다.
Text
AugmentTag
TriggerEventTag
RuleType
StackRule
CooldownSeconds
Priority
bRequiresTargetBlood와 Execution은 둘 다 HitConfirmed를 Trigger로 사용하지만 RuleType은 서로 다릅니다.
Text
Blood
→ PeriodicDamage
Execution
→ HealthThresholdFinish여기서 Priority와 StackRule은 설명할 때 특히 조심하고 있습니다.
현재 프로젝트에는:
Text
Priority 기반 중앙 Scheduler
StackRule 기반 자동 Conflict Resolution이 구현돼 있는 것은 아닙니다.
지금의 Priority와 StackRule은 Rule의 성격을 표현하고 Runtime에서 확인하기 위한 Metadata입니다.
Blood와 Execution은 같은 HitConfirmed를 각자 받고 독립적으로 자기 Rule을 처리합니다.
Definition Source까지 공개 Evidence를 하나 더 추가하지는 않았습니다.
B10에서 확인하고 싶은 핵심은:
Text
Melee
→ HitConfirmed
→ Common Base
→ 개별 Rule의 책임 분리이기 때문에, 이를 직접 보여주는 Debug Evidence와 실제 Runtime 영상에 확인 자료를 집중했습니다.
Blood는 같은 Hit를 출혈로 처리했다
Blood Passive는 HitConfirmed를 받으면 적중된 Target에 Bleed를 적용합니다.
Text
HitConfirmed
→ Blood Passive
→ Target Validation
→ Bleed GameplayEffect
→ Status.Bleed
→ Periodic Damage
→ HealthChanged여기서 Melee가 직접 Bleed GameplayEffect를 적용하지 않습니다.
Melee는 Hit 결과까지만 만들고, Blood Passive가 그 결과를 받아 자기 Rule을 실행합니다.
Runtime에서는 출혈이 한 번 발생하는 것만 확인하지 않았습니다.
Blood Relic을 장착했을 때는:
Text
Blood Relic Grant
→ Blood Active
→ HitConfirmed
→ BleedApplied
→ BleedDamageApplied가 이어졌고,
Relic을 제거한 뒤 같은 공격을 했을 때는:
Text
Blood Relic Remove
→ Attack
→ BleedApplied 없음
→ BleedDamageApplied 없음을 확인했습니다.
들어올 때만 보는 것보다 제거한 뒤 정말 더 이상 반응하지 않는지까지 보는 편이 훨씬 확실했습니다.
Execution은 Hit 이후 현재 Health를 다시 봤다
Execution도 HitConfirmed를 받지만 바로 Damage를 넣지는 않습니다.
먼저 현재 Target 상태를 다시 확인합니다.
Text
HitConfirmed
→ Execution Passive
→ Target Health 확인
→ Dead Target Guard
→ Health Ratio 계산
→ Threshold 판단
→ Skipped / Triggered
→ Execution DamageRuntime에서 확인했을 때 첫 번째 공격 뒤에는:
Text
Health 100 → 63
Ratio = 0.63
Result = Skipped였습니다.
그리고 그다음 별도의 공격에서:
Text
Health 63 → 26
Ratio = 0.26
Result = Triggered
BonusDamage = 30
Health 26 → 0
DeathState = true로 이어졌습니다.
여기서 0.63과 0.26은 같은 Hit에서 나온 두 값이 아닙니다.
첫 번째 Hit가 끝난 뒤 Target Health가 63이 됐고, 그다음 Hit에서 26까지 내려간 상태를 다시 확인한 뒤 Execution이 Trigger된 것입니다.
이 숫자는 면접에서 설명할 때도 꼭 순서를 나눠서 말하는 편이 좋습니다.
이미 죽은 Target에는 다시 Execution을 넣지 않았다
낮은 Health에서 Execution이 Trigger되는 것만 확인하면 끝이라고 생각하기 쉽습니다.
그런데 Target이 이미 죽어 있는 경우도 따로 막아야 했습니다.
Runtime에서는 다음 상태를 확인했습니다.
Text
Health = 0
Ratio = 0.00
Result = Skipped_TargetDead
ExecuteDamageApplied 없음이미 Death 상태에 들어간 Target에 Execution Damage가 다시 적용되지 않는 것까지 같이 봤습니다.
이 부분은 작은 조건처럼 보이지만, Finish Rule을 Runtime에서 끝까지 확인하려면 꼭 필요한 경우였습니다.
Execution Relic을 빼면 Base Melee만 남았다
Execution도 Remove 이후를 따로 확인했습니다.
Text
RemoveExecutionRelic
→ Attack
→ Base Melee Health 100 → 63
→ ExecuteCheck 없음
→ ExecuteDamageApplied 없음
→ Triggered 없음여기서 공격 Damage 자체가 없어지는 것은 아닙니다.
기본 Melee Damage는 그대로 들어가고 Execution이라는 추가 Rule만 빠집니다.
Remove 함수가 호출됐다는 로그만 보는 것보다, 그 뒤 같은 공격에서 Execution 쪽 반응이 실제로 사라졌다는 걸 확인하는 편이 훨씬 명확했습니다.
한 번 Trigger되는 것보다 장착부터 제거까지 같이 봤다
CombatAugment를 확인할 때 한 번 성공한 장면만 보고 끝내지는 않았습니다.
제가 확인한 순서는 다음과 같았습니다.
Text
Grant
→ Active
→ Trigger
→ Result
→ Remove
→ Negative Test
→ Edge CaseRSCombatTrainingDummy를 사용한 이유도 이 영상의 목적이 Enemy AI나 연출을 보여주는 것이 아니라, CombatAugment의 Runtime 흐름을 반복해서 확인하는 데 있었기 때문입니다.
일반 Enemy를 사용하면 AI, 이동, Animation 같은 상태가 함께 개입할 수 있어서, 테스트에서는 Target 상태를 최대한 고정하고 다음 값에 집중했습니다.
Text
공격 전 / 후 Health
Rule 실행 여부
Death State
Remove 이후 Rule 미발생따라서 이 영상에서 중요한 것은 더미를 공격하는 장면 자체가 아니라:
Text
Relic이 Runtime에 들어왔는가
실제 Hit에 반응했는가
Rule 조건에 따라 Skip / Trigger가 나뉘는가
결과가 Health / Death까지 이어졌는가
Remove 뒤 Rule이 실제로 사라졌는가를 반복해서 확인할 수 있다는 점이었습니다.
Runtime Snapshot도 같이 확인했다
Relic Icon만 보고 Passive Rule이 실제로 살아 있는지 판단하기는 어려웠습니다.
그래서 현재 CombatAugment 상태와 마지막 처리 결과를 확인할 수 있도록 Runtime Snapshot도 두었습니다.
대표적으로 확인하는 값은 다음과 같습니다.
Text
Granted
Active
RuleType
Trigger
StackRule
Priority
LastTarget
LastResult
LastMagnitude
LastHealthRatio실제 Source에서는 각각 LastTargetName, LastResultText, LastAppliedMagnitude, LastObservedHealthRatio 같은 값으로 보관하고, Debug Summary에서는 읽기 쉬운 형태로 묶어서 확인했습니다.
이 Snapshot을 둔 이유는 로그를 많이 쌓기 위해서라기보다:
Text
현재 Rule이 Runtime에 존재하는가
마지막 Hit에 반응했는가
반응했다면 어떤 결과였는가를 빠르게 보기 위해서였습니다.
앞의 Debug Evidence에서 LastTargetName과 LastTriggerEventTag를 같이 확인한 것도 이 Runtime Snapshot과 연결되는 부분입니다.
Runtime에서는 Blood와 Execution을 끝까지 다시 확인했다
🎥 Video 1. UE5 C++ RiteSeekers — GAS 기반 전투 Relic System 및 Execution Pipeline 구현

📸 Runtime Evidence 1. Execution Relic을 활성화한 뒤 일반 Damage와 HitConfirmed, ExecuteTriggered, ExecuteDamageApplied, DeathState까지 이어지는 Runtime Output Log.
Relic을 장착한 뒤 Blood / Execution이 HitConfirmed에 반응하고, Remove 이후에는 추가 Rule이 사라지며 Dead Target에서는 Execution이 다시 적용되지 않는 Runtime 결과를 기록한 영상입니다.
이 영상은 플레이 콘텐츠나 연출을 보여주기 위한 영상이라기보다 CombatAugment가 Source에서 의도한 Runtime 흐름대로 연결되는지 확인하기 위해 남긴 영상에 가깝습니다.
화면에서 Target이 죽는 결과만 본 것이 아니라, 플레이 화면과 Output Log를 함께 기록해 내부 상태 변화도 같이 확인했습니다.
영상에서 확인한 흐름은 다음과 같습니다.
Text
Blood
→ HitConfirmed
→ Bleed / Periodic Damage
→ Remove 후 미발생
Execution
→ 첫 Hit 0.63 Skip
→ 다음 Hit 0.26 Trigger
→ BonusDamage 30
→ Death
Execution Remove
→ Base Melee만 남음
Dead Target
→ Skipped_TargetDead영상에서는 플레이 화면과 Output Log를 함께 기록했습니다.
아래에는 Execution의 정상 Trigger 경로를 따라가기 쉽도록 실제 Runtime에서 기록한 원본 로그를 그대로 남겼습니다.
Execution 정상 Trigger 경로 — Raw Runtime Log
Text
==================================================
RiteSeekers Execution Relic Finish Augment
Runtime Verification Evidence
==================================================
[RS-EVIDENCE] Stage=DummySpawned
Dummy=RSCombatTrainingDummy_0
Location=V(X=20.34, Y=170.74, Z=91.65)
Player=B_RiteSeekerCharacter_Base_C_0
PlayerLocation=V(X=-279.36, Y=157.34, Z=91.65)
[RS-EVIDENCE] Stage=CaptureReady
Player=B_RiteSeekerCharacter_Base_C_0
PlayerLocation=V(X=-279.36, Y=157.34, Z=91.65)
PlayerForward=V(X=1.00, Y=0.04)
Dummy=RSCombatTrainingDummy_0
DummyLocation=V(X=20.34, Y=170.74, Z=91.65)
DummyHP=100
[RS-EVIDENCE] Stage=PassiveActivated
Relic=Execution
Ability=RSGameplayAbility_Relic_ExecutionPassive_0
Avatar=B_RiteSeekerCharacter_Base_C_0
[RS-EVIDENCE] Stage=RelicGranted
Relic=Execution
Avatar=B_RiteSeekerCharacter_Base_C_0
Granted=true
Active=true
==================================================
Normal Damage Phase #1
==================================================
[RS-EVIDENCE] Stage=DamageExecution
HitActor=RSCombatTrainingDummy_0
SourceGE=Default_GE_Damage_SetByCaller_C
BaseDamage=22.0
DamageMagnitude=37.0
[RS-EVIDENCE] Stage=HealthChanged
Dummy=RSCombatTrainingDummy_0
Old=100.00
New=63.00
Magnitude=37.00
[RS-EVIDENCE] Stage=HitConfirmed
Avatar=B_RiteSeekerCharacter_Base_C_0
Target=RSCombatTrainingDummy_0
Augment=Combat.Augment.Execution
==================================================
Normal Damage Phase #2
==================================================
[RS-EVIDENCE] Stage=DamageExecution
HitActor=RSCombatTrainingDummy_0
SourceGE=Default_GE_Damage_SetByCaller_C
BaseDamage=22.0
DamageMagnitude=37.0
[RS-EVIDENCE] Stage=HealthChanged
Dummy=RSCombatTrainingDummy_0
Old=63.00
New=26.00
Magnitude=37.00
[RS-EVIDENCE] Stage=HitConfirmed
Avatar=B_RiteSeekerCharacter_Base_C_0
Target=RSCombatTrainingDummy_0
Augment=Combat.Augment.Execution
==================================================
Execution Trigger Phase
==================================================
[RS-EVIDENCE] Stage=ExecuteTriggered
Target=RSCombatTrainingDummy_0
Health=26.00
MaxHealth=100.00
Ratio=0.26
Threshold=0.30
==================================================
Execution Damage Phase
==================================================
[RS-EVIDENCE] Stage=HealthChanged
Dummy=RSCombatTrainingDummy_0
Old=26.00
New=0.00
Magnitude=26.00
[RS-EVIDENCE] Stage=DeathState
Dummy=RSCombatTrainingDummy_0
Dead=true
Health=0.00
[RS-EVIDENCE] Stage=ExecuteDamageApplied
Target=RSCombatTrainingDummy_0
BonusDamage=30.00
HasAuthority=true
==================================================
Final Combat Evidence
==================================================
[RS-EVIDENCE] Stage=DamageExecution
HitActor=RSCombatTrainingDummy_0
SourceGE=Default_GE_Damage_SetByCaller_C
BaseDamage=22.0
DamageMagnitude=37.0
[RS-EVIDENCE] Stage=HitConfirmed
Avatar=B_RiteSeekerCharacter_Base_C_0
Target=RSCombatTrainingDummy_0
Augment=Combat.Augment.Execution
==================================================
Verification Result : PASS
==================================================이 로그에서는 먼저 Execution Passive가 RelicGranted / PassiveActivated 상태로 Runtime에 들어온 것을 확인했습니다.
첫 번째 일반 공격에서는 Damage가 적용되며 Health가:
Text
100 → 63으로 내려갔고,
다음 별도의 공격에서는:
Text
63 → 26으로 내려갔습니다.
그 뒤:
Text
Health=26
MaxHealth=100
Ratio=0.26
Threshold=0.30상태에서 ExecuteTriggered가 기록됐습니다.
Execution Damage 처리 이후에는:
Text
Health 26 → 0
Dead=true가 이어졌고,
마지막으로:
Text
BonusDamage=30.00
HasAuthority=true를 확인했습니다.
여기서 BonusDamage=30.00은 Execution Rule에 설정된 추가 Damage 값이고, 실제 남아 있던 Health는 26이었기 때문에 Runtime의 Health 변화는 26 → 0, Magnitude=26.00으로 기록됐습니다.
HasAuthority=true 역시 확인했지만, 이 값을 프로젝트 전체의 네트워크 구조를 증명하는 근거로 확대해서 설명하지는 않습니다.
여기서 확인한 범위는 Execution Damage가 Authority를 가진 실행 경로에서 처리됐다는 것입니다.
실제 CombatAugmentPassiveBase도 ServerOnly 실행 정책을 사용하고 OnCombatHitConfirmed()에서 다시 Authority를 확인하지만, 이것 역시 이 글에서는 해당 CombatAugment의 실행 경계를 설명하는 범위로만 보고 있습니다.
또 하나 구분할 부분이 있습니다.
위 Raw Log는 Execution의 정상 Trigger 경로를 보여주는 로그입니다.
마지막 Final Combat Evidence의 DamageExecution / HitConfirmed 두 줄만으로 Dead Target Guard를 증명하려고 하지는 않았고, 죽은 Target에서 Execution이 다시 적용되지 않는지는 별도의 Dead Target 검증 구간에서 확인했습니다.
기존 Runtime 검증에서 확인한:
Text
첫 Hit
Ratio = 0.63
Result = Skipped
Execution Remove
→ 추가 Execution 미발생
Dead Target
→ Skipped_TargetDead까지를 이 Raw Log에 없는 문자열로 새로 만들어 넣지는 않았습니다.
0.63 Skip, Remove Negative Test, Dead Target Guard는 기존 Runtime 영상과 해당 검증 구간에서 확인한 결과로 구분해서 남겼습니다.
화면에서 적이 죽는 장면만 봤다면 최종 결과만 알 수 있었겠지만, Output Log를 같이 본 덕분에:
Text
Relic Grant / Passive Active
→ DamageExecution
→ HealthChanged
→ HitConfirmed
→ Execution 조건 판단
→ Execute Damage
→ Death State까지 Runtime 흐름을 따라갈 수 있었습니다.
이 프로젝트에서 직접 손댄 Combat 영역
ASC, AbilitySet, GameplayEvent 같은 기반 구조 전체를 제가 처음부터 만든 것은 아닙니다.
Reference 범위는 B01에서 이미 따로 정리했습니다.
RiteSeekers에서 직접 확장한 Combat 쪽은 다음 부분입니다.
Text
Melee
→ 실제 적중 뒤 HitConfirmed 발행
CombatAugmentPassiveBase
→ 공통 Event / Authority / Target Validation
FRSCombatAugmentDefinition
→ Combat Rule 정보
Blood / Execution
→ 같은 Hit를 서로 다른 Rule로 처리
Runtime Snapshot
→ Rule 상태와 마지막 결과 확인
Runtime Verification
→ Grant / Trigger / Remove / Dead Target 확인그리고 현재 구현 범위에는:
Text
Priority 기반 중앙 Scheduler
StackRule 기반 자동 Conflict Resolution이 포함되어 있지 않습니다.
이 부분은 오히려 분명하게 적어두는 게 좋다고 봅니다.
구현한 것보다 더 크게 보이게 쓰는 것보다 실제로 확장하고 검증한 범위를 정확하게 보여주는 편이 낫습니다.
프로젝트를 진행하며 달라진 부분
처음에는 새로운 Relic 효과가 필요하면 Melee에 조건 하나를 더 넣는 게 제일 간단해 보였습니다.
Text
Blood면 Bleed 적용
Execution이면 Health Ratio 확인
다음 Rule이 생기면 조건 추가처음 몇 개를 빠르게 확인할 때는 이 방식이 편합니다.
그런데 Rule이 늘어날수록 Melee가 실제 공격과 관계없는 조건을 계속 알게 됐습니다.
CombatAugment를 나눈 뒤에는 이렇게 보게 됐습니다.
Text
Melee
→ 실제로 무엇을 맞췄는가
HitConfirmed
→ 실제 적중 결과가 생겼다는 신호
CombatAugment
→ 그 Hit에 어떤 추가 Rule을 적용할 것인가Blood나 Execution을 추가해도 기본 Melee가 각 Rule의 세부 조건까지 알 필요가 없다는 점이 가장 크게 달라진 부분이었습니다.
정리
RiteSeekers에서 Relic 효과를 만들면서 더 오래 고민한 건 Blood나 Execution의 조건식 자체가 아니었습니다.
실제 공격과, 그 공격 결과를 이용하는 추가 Combat Rule을 어디에서 나눌 것인가가 더 중요했습니다.
현재 흐름은 이렇게 정리할 수 있습니다.
Text
Relic / Loadout
→ 사용할 Combat Rule 결정
AbilitySet / ASC
→ Passive Ability Runtime 구성
Melee
→ 공격과 실제 Hit 결과 생성
HitConfirmed
→ 실제 적중 이후 Event
CombatAugment
→ 같은 Hit를 각 Rule에 맞게 처리
Blood / Execution
→ 각자의 GameplayEffect와 결과 처리
Runtime Verification
→ Grant부터 Remove와 Edge Case까지 확인처음에는 Melee 안에 Blood와 Execution을 직접 넣는 것이 가장 빠르게 보였습니다.
직접 나눠본 뒤에는 Melee가 공격 결과까지만 만들고, Blood와 Execution은 그 결과를 받아 자기 조건만 판단하게 두는 편이 Source를 다시 볼 때도 훨씬 이해하기 쉬웠습니다.
그리고 한 번 Trigger되는 장면만 확인하지 않았습니다.
Text
장착했을 때 실제로 들어오는가
실제 Hit에 반응하는가
조건에 따라 Skip / Trigger가 나뉘는가
Remove 뒤에는 Rule이 사라지는가
죽은 Target에서는 다시 실행되지 않는가까지 확인하면서 Relic이 들어오고 빠지는 Lifetime과 실제 Combat Rule의 Runtime 동작을 같이 볼 수 있었습니다.
결국 이 Runtime 영상에서 확인하려고 한 것은 더미를 죽이는 장면 자체가 아니라:
Text
Relic이 Runtime에 들어오고
실제 Hit 결과가 Event로 전달되고
CombatAugment가 자기 Rule을 판단하고
그 결과가 GameplayEffect와 Health / Death로 이어지고
Remove와 Edge Case에서도 의도한 경계가 유지되는가였습니다.
화면에 보이는 최종 결과와 Source 사이에 있는 Runtime 흐름까지 같이 확인해두니, 새로운 Combat Rule을 붙였을 때 어디까지 연결됐는지 훨씬 명확하게 따라갈 수 있었습니다.