
오늘 학습 키워드
최종 팀 프로젝트
오늘 학습 한 내용을 나만의 언어로 정리하기
추가 스킬 만들기
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> 임