오늘 학습 키워드

최종 팀 프로젝트

오늘 학습 한 내용을 나만의 언어로 정리하기

추가 스킬 만들기

AdditionalSkillState

public class AdditionalAttackState : IPlayerState  
{  
    public void Enter(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 추가 스킬 입력됨");  
        controller.Skill.CurrentSkill.Enter(controller);  
    }  
  
    public void HandleInput(PlayerController controller)  
    {  
        controller.Skill.CurrentSkill.HandleInput(controller);  
    }  
  
    public void LogicUpdate(PlayerController controller)  
    {  
        controller.Skill.CurrentSkill.LogicUpdate(controller);  
    }  
  
    public void Exit(PlayerController controller)  
    {  
        controller.Skill.CurrentSkill.Exit(controller);  
    }  
}
  • 스킬 자체는 controller.skill.CurrentSkill에 있음

WindSlash

public class WindSlash : SkillBase  
{  
    // 프레임 쪼개기  
    private const float ANIMATION_FRAME_RATE = 20f;  
    // 앞으로 날라가는 기준 시간  
    private const float RUN_FRONT_TIME = (1.0f / ANIMATION_FRAME_RATE) * 8f;  
      
    // 날라가기 관련  
    private bool isMoved = false;  
    private Vector2 attackDirection;  
    private float runDistance = 3f;  
    private Vector2 startPos;  
    private Vector2 targetPos;  
  
    // 시간 쪼개기   
private float startStateTime;  
    private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float attackAnimationLength;  
      
    // 쿨타임  
    private float lastUsedTime = 0f;  
  
  
      
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 지상  
        if (!controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 WindSlash는 지상에서만 사용 가능");  
            controller.ChangeState<IdleState>();  
            return;  
        }  
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 WindSlash는 쿨타임 중");  
            controller.ChangeState<IdleState>();            return;        }*/        Debug.Log("[플레이어] 스킬 WindSlash 사용!");  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Move.rb.velocity = Vector2.zero;  
        controller.Condition.isCharge = true;  
        /*controller.Animator.ClearTrigger();  
        controller.Animator.ClearInt();                controller.Animator.ClearBool();*/  
        animRunningTime = 0f;  
        startStateTime = Time.time;  
        attackAnimationLength =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "WindSlash").length;  
        controller.Attack.SetDamageList(new []{damage, damage2});  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
          
  
        attackDirection = controller.transform.localScale.x < 0 ? Vector2.left : Vector2.right;  
        startPos = controller.transform.position;  
        targetPos = startPos + (attackDirection * runDistance);  
        isMoved = false;  
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
  
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        animRunningTime += Time.deltaTime;  
  
        if (animRunningTime >= RUN_FRONT_TIME && !isMoved)  
        {  
            isMoved = true;  
            Vector2 direction = (targetPos - startPos).normalized;  
              
            RaycastHit2D hit = Physics2D.Raycast(startPos, direction, runDistance, controller.Move.groundMask);  
  
            if (hit.collider != null)  
            {  
                controller.Move.rb.MovePosition(hit.point - direction * 0.01f);  
            }  
            else  
            {  
                controller.Move.rb.MovePosition(targetPos);  
            }  
              
        }  
          
        if (Time.time - startStateTime > startAttackTime)  
        {  
            AnimatorStateInfo curAnimInfo = controller.Animator.animator.GetCurrentAnimatorStateInfo(0);  
  
            if (curAnimInfo.IsName("SpecialAttack"))  
            {   
                float animTime = curAnimInfo.normalizedTime;  
  
                if (animTime >= 1.0f)  
                {  
                    if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                    else controller.ChangeState<FallState>();  
                    return;  
                }  
            }  
  
            if (animRunningTime >= attackAnimationLength)  
            {  
                if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                else controller.ChangeState<FallState>();  
                return;  
            }  
                  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 WindSlash 종료");  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}

SuperCrash

public class SuperCrash : SkillBase  
{  
    // SuperCrash -> WhileSuperCrash -> EndSuperCrash  
    // SuperCrash 진행할 동안만 공중에서 멈춰있고 나머지 재생해주면 됨  
    // 시간 쪼개기   
private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float inAirAnimationLength;  
      
    // 쿨타임  
    private float lastUsedTime = 0f;  
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 공중  
        if (controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 SuperCrash는 공중에서만 사용 가능");  
            controller.ChangeState<IdleState>();  
            return;  
        }  
  
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 SuperCrash는 쿨타임 중");  
            controller.ChangeState<FallState>();            return;        }*/        Debug.Log("[플레이어] 스킬 SuperCrash 사용!");  
          
        // 시점 고정  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Condition.isCharge = true;  
          
        animRunningTime = 0f;  
        inAirAnimationLength =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "SuperCrash").length;  
        controller.Attack.SetDamageList(new []{damage, damage2});  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
          
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        animRunningTime += Time.deltaTime;  
        if (animRunningTime < inAirAnimationLength)  
        {  
            controller.Move.rb.velocity = Vector2.zero;  
            return;  
        }  
        else  
        {  
            controller.Move.rb.gravityScale = 10f;  
            if (controller.Move.isGrounded)  
            {  
                controller.ChangeState<IdleState>();  
                return;  
            }  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 SuperCrash 종료");  
        controller.Move.rb.gravityScale = 1f;  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}
 

ScrewAttack

public class ScrewAttack : SkillBase  
{  
    // 시간 쪼개기   
private float startStateTime;  
    private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float attackAnimationLength;  
  
    // 쿨타임  
    private float lastUsedTime = 0f;  
      
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 지상  
        if (!controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 ScrewAttack은 지상에서만 사용 가능");  
            controller.ChangeState<FallState>();  
            return;  
        }  
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 ScrewAttack는 쿨타임 중");  
            controller.ChangeState<IdleState>();            return;        }*/        Debug.Log("[플레이어] 스킬 ScrewAttack 사용!");  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Move.rb.velocity = Vector2.zero;  
        controller.Condition.isCharge = true;  
          
        animRunningTime = 0f;  
        startStateTime = Time.time;  
        attackAnimationLength =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "ScrewAttack").length;  
        controller.Attack.SetDamageList(damages);  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
          
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
          
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        if (Time.time - startStateTime > startAttackTime)  
        {  
            AnimatorStateInfo curAnimInfo = controller.Animator.animator.GetCurrentAnimatorStateInfo(0);  
  
            if (curAnimInfo.IsTag("AdditionalAttack"))  
            {   
                float animTime = curAnimInfo.normalizedTime;  
  
                if (animTime >= 1.0f)  
                {  
                    if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                    else controller.ChangeState<FallState>();  
                    return;  
                }  
            }  
  
            if (animRunningTime >= attackAnimationLength)  
            {  
                if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                else controller.ChangeState<FallState>();  
                return;  
            }  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 ScrewAttack 종료");  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}

SpeedSting

public class SpeedSting : SkillBase  
{  
    // 프레임 쪼개기  
    private const float ANIMATION_FRAME_RATE = 20f;  
    // 앞으로 날라가는 기준 시간  
    private const float RUN_FRONT_TIME = (1.0f / ANIMATION_FRAME_RATE) * 3f;  
  
    // 날라가기 관련  
    private bool isMoved = false;  
    private Vector2 attackDirection;  
    private float runDistance = 5f;  
    private Vector2 startPos;  
    private Vector2 targetPos;  
      
    // 시간 쪼개기   
private float startStateTime;  
    private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float attackAnimationLength;  
      
    // 쿨타임  
    private float lastUsedTime = 0f;  
      
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 지상  
        if (!controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 SpeedSting는 지상에서만 사용 가능");  
            controller.ChangeState<FallState>();  
            return;  
        }  
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 SpeedSting는 쿨타임 중");  
            controller.ChangeState<IdleState>();            return;        }*/        Debug.Log("[플레이어] 스킬 SpeedSting 사용!");  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Move.rb.velocity = Vector2.zero;  
        controller.Condition.isCharge = true;  
        /*controller.Animator.ClearTrigger();  
        controller.Animator.ClearInt();  
        controller.Animator.ClearBool();*/        animRunningTime = 0f;  
        startStateTime = Time.time;  
        attackAnimationLength =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "SpeedSting").length;  
        controller.Attack.SetDamageList(damages);  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
          
  
        attackDirection = controller.transform.localScale.x < 0 ? Vector2.left : Vector2.right;  
        startPos = controller.transform.position;  
        targetPos = startPos + (attackDirection * runDistance);  
        isMoved = false;  
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
   
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        animRunningTime += Time.deltaTime;  
  
        if (animRunningTime >= RUN_FRONT_TIME && !isMoved)  
        {  
            isMoved = true;  
            Vector2 direction = (targetPos - startPos).normalized;  
              
            RaycastHit2D hit = Physics2D.Raycast(startPos, direction, runDistance, controller.Move.groundMask);  
  
            if (hit.collider != null)  
            {  
                controller.Move.rb.MovePosition(hit.point - direction * 0.01f);  
            }  
            else  
            {  
                controller.Move.rb.MovePosition(targetPos);  
            }  
              
        }  
          
        if (Time.time - startStateTime > startAttackTime)  
        {  
            AnimatorStateInfo curAnimInfo = controller.Animator.animator.GetCurrentAnimatorStateInfo(0);  
  
            if (curAnimInfo.IsTag("AdditionalAttack"))  
            {   
                float animTime = curAnimInfo.normalizedTime;  
  
                if (animTime >= 1.0f)  
                {  
                    if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                    else controller.ChangeState<FallState>();  
                    return;  
                }  
            }  
  
            if (animRunningTime >= attackAnimationLength)  
            {  
                if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                else controller.ChangeState<FallState>();  
                return;  
            }  
                  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 SpeedSting 종료");  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}

PowerUp

public class PowerUp : SkillBase  
{  
    // 애니메이션 재생 끝나면 공격력 증가해주면 됨  
    // 시간 쪼개기   
private float startStateTime;  
    private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float buffAnimationTime;  
  
      
    // 쿨타임  
    private float lastUsedTime = 0f;  
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 지상  
        if (!controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 PowerUp는 지상에서만 사용 가능");  
            controller.ChangeState<FallState>();  
            return;  
        }  
          
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 PowerUp는 쿨타임 중");  
            controller.ChangeState<FallState>();            return;        }*/        Debug.Log("[플레이어] 스킬 PowerUp 사용!");  
          
        // 시점 고정  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Condition.isCharge = true;  
          
        animRunningTime = 0f;  
        buffAnimationTime =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "PowerUp").length;  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
   
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        animRunningTime += Time.deltaTime;  
        if (Time.time - startStateTime > startAttackTime)  
        {  
            AnimatorStateInfo curAnimInfo = controller.Animator.animator.GetCurrentAnimatorStateInfo(0);  
  
            if (curAnimInfo.IsTag("AdditionalAttack"))  
            {   
                float animTime = curAnimInfo.normalizedTime;  
  
                if (animTime >= 1.0f)  
                {  
                    controller.Attack.BuffDamage(buffValue, duration);  
                    if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                    else controller.ChangeState<FallState>();  
                    return;  
                }  
            }  
  
            if (animRunningTime >= buffAnimationTime)  
            {  
                controller.Attack.BuffDamage(buffValue, duration);  
                if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                else controller.ChangeState<FallState>();  
                return;  
            }  
                  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 PowerUp 종료");  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}

Unbreakable

public class Unbreakable : SkillBase  
{  
    // 애니메이션 재생 끝나면 무적처리 해주면 됨  
    // 시간 쪼개기   
private float startStateTime;  
    private float startAttackTime = 0.01f;  
    private float animRunningTime = 0f;  
    private float buffAnimationTime;  
  
      
    // 쿨타임  
    private float lastUsedTime = 0f;  
      
      
    public override void Enter(PlayerController controller)  
    {  
        // 발동 조건 체크 : 지상  
        if (!controller.Move.isGrounded)  
        {  
            Debug.Log("[플레이어] 스킬 Unbreakable는 지상에서만 사용 가능");  
            controller.ChangeState<FallState>();  
            return;  
        }  
          
        // 쿨타임 체크  
        /*if (Time.time - lastUsedTime < cooldown)  
        {            Debug.Log("[플레이어] 스킬 Unbreakable는 쿨타임 중");  
            controller.ChangeState<FallState>();            return;        }*/        Debug.Log("[플레이어] 스킬 Unbreakable 사용!");  
          
        // 시점 고정  
        controller.isLookLocked = false;  
        controller.Move.ForceLook(controller.transform.localScale.x < 0);  
        controller.isLookLocked = true;  
        controller.Condition.isCharge = true;  
          
        animRunningTime = 0f;  
        buffAnimationTime =   
            controller.Animator.animator.runtimeAnimatorController  
                .animationClips.First(c => c.name == "Unbreakable").length;  
        controller.Animator.SetIntAniamtion(AnimatorHash.PlayerAnimation.AdditionalAttackID, skillId);  
        controller.Animator.SetTriggerAnimation(AnimatorHash.PlayerAnimation.AdditionalAttack);  
        controller.PlayerInputDisable();  
    }  
  
    public override void HandleInput(PlayerController controller)  
    {  
   
    }  
  
    public override void LogicUpdate(PlayerController controller)  
    {  
        animRunningTime += Time.deltaTime;  
        if (Time.time - startStateTime > startAttackTime)  
        {  
            AnimatorStateInfo curAnimInfo = controller.Animator.animator.GetCurrentAnimatorStateInfo(0);  
  
            if (curAnimInfo.IsTag("AdditionalAttack"))  
            {   
                float animTime = curAnimInfo.normalizedTime;  
  
                if (animTime >= 1.0f)  
                {  
                    controller.Condition.SetInvincible(duration);  
                    if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                    else controller.ChangeState<FallState>();  
                    return;  
                }  
            }  
  
            if (animRunningTime >= buffAnimationTime)  
            {  
                controller.Condition.SetInvincible(duration);  
                if (controller.Move.isGrounded) controller.ChangeState<IdleState>();  
                else controller.ChangeState<FallState>();  
                return;  
            }  
                  
        }  
    }  
  
    public override void Exit(PlayerController controller)  
    {  
        Debug.Log("[플레이어] 스킬 Unbreakable 종료");  
        controller.PlayerInputEnable();  
        lastUsedTime = Time.time;  
        controller.Condition.isCharge = false;  
    }  
}

코드 리뷰

  • 코루틴도 try-catch-finally 가 된다.
    • 특히 finally의 경우 StopCoroutine하면 작동함.
  • IEnumerable : List와 Dictionary의 공통 인터페이스. 열거 가능한.
  • 파이어스토어가 이해하는건 Dictionary<string, object> 임