ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Fire Knight
    게임 클라이언트 개발/MakeDNF 2024. 3. 19. 19:46

    이번 글에선 지금까지 구현한 Fire Knight 캐릭터의 스킬을 작성할 것이다. Fire Knight는 던전 앤 파이터의 여 귀검사 소드마스터를 모티브로 스킬을 구현했다. 

    Fire Knight의 전투 영상

     

    기본 공격

    기본 공격은 총 3타로 이루어져있다. 기본 공격 버튼(Default로 X키로 지정)을 입력할 경우 연속적으로 1타, 2타, 3타를 사용한다. 2타의 경우 방향키를 입력하지 않을 경우 약간 앞으로 전진하며, 캐릭터가 바라보는 방향의 반대 방향의 방향키를 입력할 경우 움직이지 않고 사용 가능하다. (제자리 평타, 흔히 제평이라 부르는 기능) 

    기본 공격 1타, 2타, 3타

     

    기본 공격을 구현하면서 고려했던 부분은 다음 공격으로 이어가기 위한 유저의 추가적인 키 입력 타이밍이였다. 각 스킬들의 싸이클은 크게 공격을 시전하기 전 선딜레이 (PreDelay), 히트 박스가 활성화되어 공격을 시전하는 단계 (HitboxActive), 히트 박스가 비활성화된 이후 남은 시전 동작 (MotionInProgress), 공격 시전 이후 후딜레이 (PostDelay)로 구성되어있다. 

    Predelay - HitboxActive - MotionInProgress - PostDelay

     

    키보드를 이용한 RPG의 경우, 유저들은 스킬을 사용하기 위해 키보드를 짧은 순간에 여러 번 입력한다. 그래서 PreDelay부터 키 입력을 받을 경우 기본 공격을 1타만 사용하고 싶어도 추가 키 입력으로 인식하여 2타까지 사용하는 상황이 발생했다. 또한 PostDelay에만 추가 키 입력을 받는다면 너무 짧은 시간 안에 키를 입력해야해서 연속적으로 키를 입력해야 겨우 2타를 사용할 수 있었다. 그래서 추가 키 입력은 HitboxActive, MotionInProgress 두 상태에서만 가능하도록 구현했다. 

    // 유저가 스킬 키를 추가적으로 입력했을 때 이벤트 함수
    public override void OnSkillButtonPressed()
    {
        if (phase != EStatePhase.HITBOXACTIVE && phase != EStatePhase.MOTIONINPROGRESS) return;
    
        isContinue = true;
        character.Animator.SetBool(continueHash, true);
    }

     

    Fire Knight의 기본공격

     

    악즉참 Swift Demon Slash

    악즉참 스킬은 기본 공격을 캔슬하고 사용할 수 있는 액티브 스킬이다. 총 12회의 검기를 생성하며 난무 후 마지막 피니쉬 검기를 생성하여 적을 공격한다. 채널링이 긴 스킬이기 때문에 스킬 사용 시 무적 상태가 되도록 구현했다.

    캐릭터에 흰색 테두리가 생기는 것을 확인할 수 있다.

     

    난무 중엔 총 4가지의 다른 검기를 생성하도록 구현하여 역동적으로 표현했다. 이전에 구현한 오브젝트 풀링을 위한 툴을 활용하여 쉽게 오브젝트를 생성할 수 있었고 생성한 검기 오브젝트에서 피격 판정을 수행했다.

    // 난무 중 현재 베기 횟수에 따라 다른 검기를 생성하는 로직
    switch (curSlash % 4)
    {
        case 0:
            GameManager.ObjectPool.SpawnFromPool(EObjectPoolList.Slash_3_FireKnight).GetComponent<Projectile>().Activate(character.DNFTransform);
            break;
    
        case 1:
            GameManager.ObjectPool.SpawnFromPool(EObjectPoolList.Slash_1_FireKnight).GetComponent<Projectile>().Activate(character.DNFTransform);
            break;
    
        case 2:
            GameManager.ObjectPool.SpawnFromPool(EObjectPoolList.Slash_5_FireKnight).GetComponent<Projectile>().Activate(character.DNFTransform);
            break;
    
        case 3:
            GameManager.ObjectPool.SpawnFromPool(EObjectPoolList.Slash_4_FireKnight).GetComponent<Projectile>().Activate(character.DNFTransform);
            break;
    }

     

    Fire Knight의 악즉참 스킬

     

    반월 Crescent

    반월 스킬은 기본 공격을 캔슬하고 사용할 수 있는 액티브 스킬이다. 전방으로 대쉬하면서 캐릭터 주변으로 검을 크게 휘두르며 총 3회 공격하는 스킬이다. 스킬 사용 중엔 캐릭터는 슈퍼 아머 상태가 된다.

    반월 스킬 시전 시 슈퍼 아머 상태가 되는 모습

     

    이전 글에서 작성했듯이 스킬 범위에 따라 히트 박스를 총 세 개 세팅했다. 또한 AnimatorStateInfo 구조체의 normalizedTime에 따라 각 히트 박스를 활성화하여 반월 스킬의 범위를 설정했다.

    반월 스킬 범위에 따른 히트 박스 범위

     

    반월 스킬 사용 시 앞으로 대쉬하는 기능을 추가했다. 이 또한 AnimatorStateInfo 구조체의 normalizedTime을 활용하여 가속도의 값을 Ease 함수를 통해 설정했다.

    // Crescent_Slash.cs
    // normalizedTime에 따라 캐릭터의 대쉬 가속도를 설정
    float dashRatio = EaseHelper.EaseInSine(1f, 0f, animatorStateInfo.normalizedTime);
    Vector3 dashDirection = Time.fixedDeltaTime * dashRatio * dashSpeed * this.dashDirection;
    
    // 설정된 가속도를 활용하여 캐릭터를 이동
    if (character.DNFRigidbody.enabled)
    {
        character.DNFRigidbody.MoveDirection(dashDirection);
    }
    
    // Ease.cs
    // Ease 함수
    public static float EaseInSine(float start, float end, float value)
    {
        end -= start;
        return -end * Mathf.Cos(value * (Mathf.PI * 0.5f)) + end + start;
    }

     

    다양한 Ease 함수는 아래 깃허브 링크에서 확인할 수 있다.

    https://gist.github.com/cjddmut/d789b9eb78216998e95c

     

    Easing Functions for Unity3D

    Easing Functions for Unity3D. GitHub Gist: instantly share code, notes, and snippets.

    gist.github.com

     

    Fire Knight의 반월 스킬

     

    회피 Dodge

    회피 스킬은 기본 공격이나 다른 스킬을 캔슬하고 사용할 수 있는 액티브 스킬이다. 무적 상태가 되어 앞으로 일정 거리 이동한다.

     

    스킬을 캔슬하는 기능을 위해 각 스킬에 캔슬 가능한 스킬 리스트를 맴버 변수로 작성하였다. SkillStat 클래스는 단순히 스킬에 대한 정보만 갖고 있는 클래스로 추후 ScriptableObject나 JSON 형태의 파일로 수정할 예정이다.

    // 스킬에 대한 정보를 포함하고 있는 클래스
    [Serializable]
    public class SkillStat
    {
        // .. Other member variables
        
        /// <summary>
        /// The list of the skills that can be canceld while in use.
        /// </summary>
        public List<Skill> CancelList = new();
    }

     

    해당 맴버 변수에 있는 스킬 클래스를 GetHashCode() 함수를 통해 int형으로 변환하여 List로 저장한다. 이는 참조 타입 변수인 클래스 간의 비교를 피하고 값 타입 변수인 int형으로 비교하기 위함이다.

    // Skill.cs
    // 스킬을 구현하기 위한 원형 클래스
    // 해당 클래스를 상속받은 ActiveSkill과 PassiveSkill이 존재
    public abstract class Skill : MonoBehaviour
    {
        // .. Other members
        
        /// <summary>
        /// The hash code of the skill.
        /// </summary>
        public abstract int SkillCode { get; }
        
        // .. Other members
    }
    
    // SwiftDemonSlash.cs
    // 악즉참 스킬을 구현한 클래스
    public partial class SwiftDemonSlash : ActiveSkill
    {
        // .. Other members
    
        public override int SkillCode => typeof(SwiftDemonSlash).GetHashCode();
        
        // .. Other members
    }
    
    // Dodge.cs
    // 회피 스킬을 사용할 수 있는지 검사하는 함수
     public override bool CheckCanUseSkill(Skill activeSkill)
    {
        // .. Other conditions
        
        // 캐릭터가 현재 공격 행동 중인 경우 (스킬을 사용하고 있는 경우)
        // Cancel List에 activeSkill을 포함하고 있지 않다면 스킬을 사용할 수 없음. 
        if (character.CurBehaviourCode == BehaviourCodeList.ATTACK_BEHAVIOUR_CODE && !CancelList.Contains(activeSkill.SkillCode)) return false;
    
        return true;
    }

     

    굳이 Skill 클래스를 int형으로 변환하여 비교를 한 이유는 참조 타입 변수 간 비교를 제대로 수행하지 않는다면, 잘못된 결과를 초래할 수 있기 때문이다. 참조 타입 변수 간 비교를 제대로 수행하기 위해선 해당 참조 내 모든 멤버들도 비교를 수행해야 한다. 간단한 예시로 문자열 변수, string 타입의 변수도 참조 타입의 변수이다. 그래서 문자열을 동등 연산자(==)로 수행할 경우 잘못된 결과를 출력할 수도 있다.

    string text1 = "Hello";
    object text2 = new string("Hello");
    
    Debug.Log("text1 == text2 : " + (text1 == text2 ? "Equal" : "Not Equal"));
    Debug.Log("text1.Equals(text2) : " + (text1.Equals(text2) ? "Equal" : "Not Equal"));

    동등 연산자와 Equals 함수를 통한 string 비교

     

     

    Fire Knight의 회피 스킬

     

    극 귀검술 : 파검무 Blade Waltz

    극 귀검술 : 파검무 스킬은 기본 공격이나 다른 스킬을 캔슬하고 사용할 수 있는 액티브 스킬이다. 전방으로 대쉬하며 베고 뒤돌아 다시 베면서 폭발을 일으키는 스킬이다.

     

    파검무 스킬은 사용 도중 다른 스킬로 캔슬할 경우 가운데에 설치된 검들이 미리 폭발하는 메커니즘을 갖고 있다. 이를 미루어 보아 생성한 검들은 스킬 클래스에 포함되어있는 이펙트가 아닌 새로운 오브젝트를 소환하는 것이라고 추측할 수 있었다. 구현한 스킬에서도 첫 베기 모션에서 폭발 오브젝트를 소환하여 맴버 변수로 저장했고, 스킬이 캔슬됬을때 오브젝트가 아직 폭발하지 않았다면, 해당 오브젝트를 폭발시키도록 구현하였다.

    // BladeWaltz.cs
    public partial class BladeWaltz : ActiveSkill, IAttackable
    {
        // .. Other members
        
        /// <summary>
        /// Explosion object activated when the character slashes or cancels a skill.
        /// </summary>
        private BladeWaltzProjectile.Explosion explosion = null;
        
        // .. Other members
    }
    
    // BladeWaltz_Second.cs
    // 스킬이 캔슬됬을때 호출되는 이벤트 함수
    public override void OnCancel()
    {
        // explosion 오브젝트가 아직 활성화되기 전이라면 활성화시킴
        if (phase == EStatePhase.PREDELAY)
        { 
            stateController.explosion.TriggerExplosion();
        }
        
        // .. Other cancel logic
    }

     

    또한 캐릭터가 직접 이동하는 것인지, 아님 캐릭터의 이미지만 이동하는 것인지 확인할 필요가 있었다. 이는 스킬 사용 도중 캔슬했을때의 캐릭터 위치로 판별할 수 있었다. 파검무 스킬을 사용 중 강화 벡스탭으로 스킬을 캔슬할 경우 캐릭터 이미지 위치에서 스킬이 캔슬되지만, 블레이드 직업의 95제 스킬인 풀 바디 스킬을 캔슬할 경우 시전한 위치로 돌아오는 것을 확인할 수 있었다. 이를 미루어 보아 파검무 스킬은 캐릭터의 위치 자체를 변동시키는 스킬임을 알 수 있다.

    대쉬 이후 강화 벡스텝으로 스킬을 캔슬했을때의 캐릭터 위치 (위 : 파검무, 아래 : 풀바디)

     

     

    Fire Knight의 극 귀검술 : 파검무 스킬

     

    마검 발현 Magic Sword Medley

    마검 발현 스킬은 Fire Knight가 기본 공격이나 스킬로 적을 공격했을때 투사체를 생성하는 패시브 스킬이다. 기본 공격이 적중했을 경우 작은 타격이 생성되며, 스킬로 적을 공격했을 경우 적의 위치에 메테오가 떨어지는 스킬이다.

     

    기본 공격과 스킬에 의한 공격을 나눌 필요성이 생겨 캐릭터의 공격 타입을 총 세 가지로 나눴다. BaseAttack은 기본 공격에 피격된 경우, Skill은 스킬에 피격된 경우, Additional은 오브젝트나 추가 피해에 의해 피격된 경우를 표현한다. Additional 타입이 존재하는 이유는 인게임 사례를 통해 알아볼 수 있다. 던전 앤 파이터의 선계 시즌 초반에 자연의 수호자 세팅(자수셋)의 오브젝트로 적을 피격했을때 공격 이벤트가 반복적으로 발생하여 적을 공격하지 않아도 오브젝트가 계속 생성되는 문제점이 존재했었다. 이를 방지하기 위해 Additional 타입의 공격일 경우 오브젝트를 생성하지 않도록 구현하기위해 추가했다.

    public enum EAttackType { NONE = -1, BASEATTACK, SKILL, ADDITIONAL }
    
    // BaseAttack.cs
    // 캐릭터의 기본 공격에 피격됬는지 체크하는 함수
    public void CalculateOnHit(List<IDamagable> targets)
    {
        // .. Calculate whether the target is hit by BaseAttack
        
        character.OnAttack(target.DefenderDNFTransform, EAttackType.BASEATTACK, EHitType.DIRECT);
    }
            
    // BladeWaltz.cs
    // 캐릭터의 BladeWaltz 스킬에 피격됬는지 체크하는 함수
    public void CalculateOnHit(List<IDamagable> targets)
    {
        // .. Calculate whether the target is hit by BladeWaltz
        
        character.OnAttack(target.DefenderDNFTransform, EAttackType.SKILL, EHitType.INDIRECT);
    }

     

    Fire Knight의 마검 발현 스킬

     

Designed by Tistory.