ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 전투 시스템 구현 (Feat. 메이플 스토리 월드)
    게임 클라이언트 개발/MakeDNF 2024. 3. 6. 16:49

    이번 글에선 히트박스와 충돌 시스템을 구현한 내용을 바탕으로 공격과 피격 로직을 어떻게 구현했는지에 대해 작성하고자 한다. 

     

    메이플 스토리 월드

    이전에 멋쟁이사자처럼에서 주최한 해커톤 Super Hackathon 2022에 참여하였고 메이플 스토리 월드 플랫폼을 활용하여 "데드 바이 데이라이트"와 비슷한 술래잡기 콘텐츠를 제작했었다. 콘텐츠를 제작하기 위해 메이플 스토리 월드의 API를 하나하나 뜯어보며 어떻게 구현되어 있는지 확인했었다. 특히 메이플 스토리 리소스를 기반으로 제작된 플랫폼이어서 공격과 피격에 대한 로직이 작성되어 있었고 이를 참고하여 이번 프로젝트의 공격 및 피격 로직을 설계 및 구현하였다.

    메이플 스토리 월드의 공격 시스템 로직

     

    메이플 스토리 월드에선 GameObject를 Entity로 지칭한다. 공격 로직을 작성할 Entity의 AttackComponent에서 Attack() 함수를 호출하면 설정된 범위 내에 존재하는 Entity 중 HitComponent가 부착된 Entity에 공격을 시도하며 AttackEvent를 발생시킨다. HitComponent가 부착된 Entity가 피격됐다면 OnHit() 함수를 호출하고 HitEvent를 발생시킨다. 

    // AttackComponent
    // 설정된 범위 내에 존재하는 Entity 중 HitComponent가 부착된 Entity를 공격
    table<Component> Attack(Vector2 size, Vector2 offset, string attackInfo, CollisionGroup collisionGroup = nil)
    
    // AttackEvent
    // Entity가 공격할 때 발생하는 이벤트
    // AttackComponent에서 발생함
    AttackEvent(Entity defenderEntity = nil)
    
    // HitComponent
    // Entity가 피격됬을때 호출되는 함수
    void OnHit(Entity attacker, integer damage, boolean isCritical, string attackInfo, int32 hitCount)
    
    // HitEvent
    // Entity가 피격됬을때 발생하는 이벤트
    HitEvent(
        Vector2 attackCentor = Vector2(0, 0), 
        Entity attackerEntity = nil,
        List<integer> damages = nil,
        any extra = nil,
        HitFeedbackAction feedbackAction = 0,
        boolean isCritical = false,
        integer totalDamage = 0)

     

    IAttackable 인터페이스

    IAttackable 인터페이스는 공격을 수행할 수 있는 클래스를 위한 인터페이스이다.

    /// <summary>
    /// Interface representing an subject capable of performing attacks.
    /// </summary>
    public interface IAttackable
    {
        /// <summary>
        /// DNFTransform component of the attacking subject.
        /// </summary>
        public DNFTransform AttackerDNFTransform { set;  get; }
    
        /// <summary>
        /// Hitbox controller component for representing the attack range.
        /// </summary>
        public HitboxController AttackerHitboxController{ set; get; }
        
        /// <summary>
        /// The list of targets hit after the attacker hitbox is activated.
        /// </summary>
        public List<IDamagable> AlreadyHitTargets { set; get; }
    
        /// <summary>
        /// Check if the targets' hitbox is within the attack range.
        /// </summary>
        /// <param name="targets">The list of objects that can be hit</param>
        public void CalculateOnHit(List<IDamagable> targets);
    }

     

    첫 번째로 AttackerDNFTransform은 공격을 수행하는 주체의 DNFTransform 컴포넌트이다. 해당 인터페이스를 상속하는 오브젝트에서 공격 로직이 수행되지만, 그 공격을 수행하는 주체는 다른 오브젝트일 수 있기에 추가하였다. 예를 들어 A라는 스킬을 사용하여 적을 공격한다면, 스킬 A 클래스가 IAttackable 인터페이스를 상속받고 HitboxController의 스킬 범위를 캐릭터의 DNFTransform 컴포넌트를 기반으로 설정해줘야 한다. 이때 캐릭터의 DNFTransform 컴포넌트가 AttackerDNFTransform이 된다.

    public class Crescent : MonoBehaviour, IAttackable
    {
        private Character character = null;
        
        #region IAttackable Implementation
    
        public DNFTransform AttackerDNFTransform { set; get; }
    
        // ... Other Member Implementation
        
        #endregion IAttackable Implementation
        
        private void Awake()
        {
            character = GetComponent<Character>();
            
            // Set the attacker DNF transform component as character's DNF Transform
            AttackerDNFTransform = character.DNFTransform;
        }
    }

     

    두 번째로 AttackerHitboxController는 공격을 수행할 범위를 설정하기 위한 HitboxController 컴포넌트이다. 해당 컴포넌트로 Editor에서 스킬 범위와 히트박스를 설정하고 이후 공격을 수행하는 주체에서 해당 컴포넌트로 실질적인 범위를 설정한다.

    Edit 모드에서 Crescent 스킬 범위를 설정한 후 Play 모드에서 캐릭터의 위치에 기반하여 적용되는 범위

     

    세 번째로 AlreadyHitTargets는 히트박스가 활성화된 이후 피격된 오브젝트들을 저장하는 리스트이다. 현재 구현되어 있는 충돌 로직은 HitboxController 컴포넌트끼리 매 프레임 충돌 검사를 수행한다. 또한 스킬들의 HitboxController는 한 프레임만 활성화가 되는 것이 아닌 일정 프레임동안 활성화되기 때문에 아무런 조치를 취하지 않는다면 스킬의 HitboxController가 활성화되어 있는 동안 범위 내 몬스터는 매 프레임 피격될 것이다. 이를 방지하기 위해 이미 피격된 몬스터는 AlreadyHitTargets에 추가하여 충돌 로직에서 제외하였고 나머지 몬스터들에 한해 충돌 로직을 수행하도록 구현하였다. 

     

    마지막으로 CalculateOnHit은 IAttackable을 상속받는 클래스를 통해 타겟을 공격했는지 검사하는 로직이다. 해당 함수를 통해 1회 타격 로직이나 장판과 같은 다단 히트 로직을 작성했다. 해당 함수에서 공격 성공 시 이벤트와 피격된 대상(IDamagable)에서 피격 이벤트를 발생시킨다.

     

    IDamagable 인터페이스

    IDamagable 인터페이스는 공격에 피격될 수 있는 클래스를 위한 인터페이스이다.

    /// <summary>
    /// Interface representing an object capable of receiving damage.
    /// </summary>
    public interface IDamagable
    {
        /// <summary>
        /// DNFTransform component of the defending object.
        /// </summary>
        public DNFTransform DefenderDNFTransform { set;  get; }
    
        /// <summary>
        /// Hitbox controller component used to check whether the object has been hit.
        /// </summary>
        public HitboxController DefenderHitboxController { set; get; }
    
        /// <summary>
        /// The event method called when the object is hit.
        /// </summary>
        /// <param name="attacker">The DNFTransform component of the attacker</param>
        /// <param name="damages">The list of damamges</param>
        /// <param name="knockBackPower">The power value for the knock back effect</param>
        /// <param name="knockBackDirection">The normalized direction value for the knock back effect</param>
        public void OnDamage(DNFTransform attacker, List<int> damages, float knockBackPower, Vector3 knockBackDirection);
    }

     

    첫 번째로 DefenderDNFTransform은 IAttackable과 같은 이유로 선언한 공격에 피격된 객체의 DNFTransform 컴포넌트이다. 

    public class Character : MonoBehaviour, IDamagable
    {
        private DNFTransform dnfTransform = null;
    
        #region IDamagable Implementation
    
        public DNFTransform DefenderDNFTransform { set; get; }
    
        // .. Other Member Implementation
        
        #endregion IDamagable Implementation
        
        private void Awake()
        {
            dnfTransform = GetComponent<DNFTransform>();
            
            // Set the defender DNF transform component as character's DNF transform
            DefenderDNFTransform = dnfTransform;
        }
    }

     

    두 번째로 DefenderHitboxController는 피격 범위를 설정하기 위한 HitboxController 컴포넌트이다. 해당 컴포넌트로 Editor에서 스킬 범위와 히트박스를 설정하고 이후 스킬을 사용하는 주체에서 해당 컴포넌트로 실질적인 범위를 설정한다.

    Edit 모드에서 캐릭터의 피격 범위를 설정한 후 Play 모드에서 캐릭터가 점프했을때 피격 범위

     

    마지막으로 피격 로직을 처리할 OnDamage 함수이다. 데미지 계산 로직 및 넉백 이펙트를 위한 변수들을 전달받아 피격 시 행동을 처리한다. 추후 캐릭터가 슈퍼 아머 상태일 때의 로직을 추가할 예정이다. 무적 상태일 경우 피격 판정 자체(즉, OnDamage 함수)를 호출하지 않을 예정이다.

    public class Character : MonoBehaviour, IDamagable
    {
        #region IDamagable Implementation
    
        // .. Other Member Implementation
        
        public void OnDamage(DNFTransform attacker, List<int> damages, float knockBackPower, Vector3 knockBackDirection)
        {
            curBehaviour.OnCancel();
    
            hitBehaviour.Hit(knockBackPower * 0.2f, knockBackDirection * knockBackPower);
        }
        
        #endregion IDamagable Implementation
    }

     

     

     

Designed by Tistory.