효과
개요
EffectsSystem
은 특정 엔티티(일반적으로 액터)와 관련된 사용자 지정 동작을 실행할 수 있는 방법을 제공합니다. 각각의 효과는 그 자체의 엔티티이며 그 수명을 통제할 수 있습니다; 그것은 본질적으로 영원히 또는 소유하고 있는 엔티티가 없어질 때까지 지속될 수 있습니다. 런타임에 효과를 추가하거나 제거할 수 있습니다. 효과는 엔티티이므로 동일한 엔티티 프로토타입을 여러 번 스폰하고 원하는 대로 추가/보관할 수 있습니다.
EffectController
는 모든 효과에 대한 실행 흐름의 핵심에 있습니다. 사용자 정의 효과를 만들려면 EffectController
에서 상속한 효과의 컨트롤러를 가지고 API를 구현하면 됩니다.
구현 예제
이 절에서는 FPS 템플릿에 포함된 HealthEffectController
구현에서 EffectsSystem
의 예를 보여 줍니다.
C#
public unsafe class HealthEffectController : EffectController, ITimedEffectController
{
//========== PUBLIC MEMBERS ===================================================================================
public EHealthAction Action = EHealthAction.Add;
public EHealthType Type = EHealthType.Health;
public FP AbsoluteValuePerTick = FP._10;
public FP PercentualValuePerTick = FP._0;
public FP TickInterval = FP._1;
public int TickCount = 1;
public bool TickOnApply = true;
//========== EffectController INTERFACE =======================================================================
internal override bool IsFinished(Frame frame, EntityRef entity)
{
// 1. The effect is active until all ticks are consumed
HealthEffect* healthEffect = frame.GetComponent<HealthEffect>(entity);
return healthEffect->TicksRemaining <= 0;
}
internal override bool Activate(Frame frame, EntityRef entity, EntityRef target)
{
if (TickCount <= 0)
{
throw new ArgumentException(nameof(TickCount));
}
Health* health = frame.TryGetComponent<Health>(target);
if (health == null)
return false;
// 2. Add HealthEffect component to support custom rollbackable data, set initial values
HealthEffect* healthEffect = frame.TryGetOrAddComponent<HealthEffect>(entity);
healthEffect->TimeToNextTick = TickInterval;
healthEffect->TicksRemaining = TickCount;
if (TickOnApply == true)
{
Tick(frame, entity);
}
return true;
}
internal override void Update(Frame frame, EntityRef entity)
{
// 3. Ticking in intervals
HealthEffect* healthEffect = frame.GetComponent<HealthEffect>(entity);
healthEffect->TimeToNextTick -= frame.DeltaTime;
if (healthEffect->TimeToNextTick <= FP._0)
{
Tick(frame, entity);
healthEffect->TimeToNextTick += TickInterval;
}
}
//========== ITimedEffectController INTERFACE =================================================================
FP ITimedEffectController.GetTotalTime(Frame frame, EntityRef entity)
{
int ticks = TickOnApply == true ? TickCount : (TickCount - 1);
return ticks * TickInterval;
}
FP ITimedEffectController.GetRemainingTime(Frame frame, EntityRef entity)
{
HealthEffect* healthEffect = frame.GetComponent<HealthEffect>(entity);
return healthEffect->TimeToNextTick + (healthEffect->TicksRemaining - 1) * TickInterval;
}
//========== PRIVATE METHODS ==================================================================================
private void Tick(Frame frame, EntityRef entity)
{
EntityRef owner = frame.GetOwnerEntity(entity);
Health* health = frame.TryGetComponent<Health>(owner);
// 4. Process only on entities which are alive
if (health == null || health->IsAlive == false)
return;
// 5. Calcualte health amount
FP desiredAmount = AbsoluteValuePerTick;
if (PercentualValuePerTick > FP._0)
{
if (Type == EHealthType.Health || Type == EHealthType.HealthAndShield)
{
desiredAmount += health->MaxHealth * PercentualValuePerTick * FP._0_01;
}
else if (Type == EHealthType.Shield)
{
desiredAmount += health->MaxShield * PercentualValuePerTick * FP._0_01;
}
}
if (desiredAmount <= FP._0)
return;
Effect* effect = frame.GetComponent<Effect>(entity);
// 6. Prepare hit and health data
HitData hitData = new HitData()
{
Target = owner,
Source = effect->Source,
Instigator = frame.TryGetOwnerEntity(effect->Source),
Position = frame.GetComponent<Transform3D>(owner)->Position,
BodyPartType = EBodyPartType.None,
HitSource = EHitSource.Effect,
};
HealthData healthData = new HealthData
{
Action = Action,
DesiredAmount = desiredAmount,
Target = hitData.Target,
Source = hitData.Source,
Instigator = hitData.Instigator,
Type = Type,
};
// 7. Invoke hit and decrease remaining tick count
frame.Signals.Hit(hitData, healthData);
if (TickCount > 0)
{
HealthEffect* healthEffect = frame.GetComponent<HealthEffect>(entity);
healthEffect->TicksRemaining -= 1;
}
}
}
새로운 효과 생성하기
다음은 FPS 템플릿에 새로운 효과 를 생성하는 단계별 절차입니다.
- Quantum 솔루션에서
EffectController
에서 상속받은 새로운MyEffectController
를 생성합니다. - 컨트롤러에 직렬화 가능한 필드들을 추가합니다 (구성).
- 필요시,
MyEffect
컴포넌트와 롤백 가능한 데이터와 같은 기타 데이터 구조체의 정의를 포함하고 있는MyEffect.qtn
을 생성합니다. - 필요시 기본 속성들을 오버라이드하고, 컨트롤러에서
Activate()
,Deactivate()
,Update()
그리고IsFinished()
를 구현합니다. - 사용 가능한 컨트롤러 인터페이스를 구현합니다 (예,
IAttributesProvider
, ...). quantum.code
솔루션을 다시 컴파일합니다.- 유니티에서 빈 게임 오브젝트를 생성하고 하위 객체로 비주얼 컴포넌트 / 모델을 추가합니다.
- 게임 오브젝트에
Entity
,EntityPrototype
,EntityComponentController
그리고EntityComponentEffect
스크립트를 추가합니다. Entity
컴포넌트에서 다음을 설정합니다:Transform Synchronization
을Simple
또는Error Correction
으로 설정Require Owner
를true
로 설정 - 소유자 엔티티 없이는 효과는 존재할 수 없습니다
EntityComponentController
컴포넌트에서,EffectController
드롭 다운 메뉴에서MyEffectController
를 선택하고 정의된 속성들을 채웁니다.EntityComponentEffect
컴포넌트에서, 정의된 속성들을 채웁니다.- 엔티티의 행동을 정의하기 위해서 다른 엔티티 컴포넌트를 추가합니다 (
EntityComponentMyEffect
, ...) - 엔티티의 프리팹을 생성합니다.
Quantum > Generate Asset Resources
메뉴를 통해 에셋 리소스 생성을 실행합니다.- 능력을 이제 사용할 수 있습니다 - 참조 엔티티 프로토타입.