This document is about: QUANTUM 2
SWITCH TO

효과

개요

EffectsSystem은 특정 엔티티(일반적으로 액터)와 관련된 사용자 지정 동작을 실행할 수 있는 방법을 제공합니다. 각각의 효과는 그 자체의 엔티티이며 그 수명을 통제할 수 있습니다; 그것은 본질적으로 영원히 또는 소유하고 있는 엔티티가 없어질 때까지 지속될 수 있습니다. 런타임에 효과를 추가하거나 제거할 수 있습니다. 효과는 엔티티이므로 동일한 엔티티 프로토타입을 여러 번 스폰하고 원하는 대로 추가/보관할 수 있습니다.

EffectController는 모든 효과에 대한 실행 흐름의 핵심에 있습니다. 사용자 정의 효과를 만들려면 EffectController에서 상속한 효과의 컨트롤러를 가지고 API를 구현하면 됩니다.

effects
효과 실행 흐름과 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;
        }
    }
}
healing over time effect
시간 경과 효과에 따른 치료.

새로운 효과 생성하기

다음은 FPS 템플릿에 새로운 효과 를 생성하는 단계별 절차입니다.

  1. Quantum 솔루션에서 EffectController에서 상속받은 새로운 MyEffectController를 생성합니다.
  2. 컨트롤러에 직렬화 가능한 필드들을 추가합니다 (구성).
  3. 필요시, MyEffect 컴포넌트와 롤백 가능한 데이터와 같은 기타 데이터 구조체의 정의를 포함하고 있는 MyEffect.qtn을 생성합니다.
  4. 필요시 기본 속성들을 오버라이드하고, 컨트롤러에서 Activate(), Deactivate(), Update() 그리고 IsFinished()를 구현합니다.
  5. 사용 가능한 컨트롤러 인터페이스를 구현합니다 (예, IAttributesProvider, ...).
  6. quantum.code 솔루션을 다시 컴파일합니다.
  7. 유니티에서 빈 게임 오브젝트를 생성하고 하위 객체로 비주얼 컴포넌트 / 모델을 추가합니다.
  8. 게임 오브젝트에 Entity, EntityPrototype, EntityComponentController 그리고 EntityComponentEffect 스크립트를 추가합니다.
  9. Entity 컴포넌트에서 다음을 설정합니다:
    • Transform SynchronizationSimple 또는 Error Correction으로 설정
    • Require Ownertrue로 설정 - 소유자 엔티티 없이는 효과는 존재할 수 없습니다
  10. EntityComponentController 컴포넌트에서, EffectController 드롭 다운 메뉴에서 MyEffectController를 선택하고 정의된 속성들을 채웁니다.
  11. EntityComponentEffect 컴포넌트에서, 정의된 속성들을 채웁니다.
  12. 엔티티의 행동을 정의하기 위해서 다른 엔티티 컴포넌트를 추가합니다 (EntityComponentMyEffect, ...)
  13. 엔티티의 프리팹을 생성합니다.
  14. Quantum > Generate Asset Resources 메뉴를 통해 에셋 리소스 생성을 실행합니다.
  15. 능력을 이제 사용할 수 있습니다 - 참조 엔티티 프로토타입.
Back to top