티스토리 뷰
🕹️ 실습 (2D 횡스크롤 스테이트머신)
Attack
// Entity.cs
[Header("Collision Info")]
public Transform attackCheck;
public float attackCheckRadius;
protected virtual void OnDrawGizmos()
{
Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);
}
public virtual void TakeDamage()
{
Debug.Log($"{gameObject} gets damage");
}
// PlayerAnimationTriggers.cs
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius); // 범위 안에 있는 모든 Collider를 가져옴
foreach(var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
hit.GetComponent<Enemy>().TakeDamage();
}
}
}
Damage Effect
- Material 생성 후 GUI/TextShader로 변경한다
- Player Animator의 Material에 넣어준다
- Player에 EntityFX 스크립트를 붙여준 후 FlashFXMaterial을 넣어준다
public class EntityFX : MonoBehaviour
{
private SpriteRenderer sr;
[Header("Flash FX")]
[SerializeField] private Material hitMat;
private Material originMat;
void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();
originMat = sr.material;
}
private IEnumerator FlashFX()
{
sr.material = hitMat;
yield return new WaitForSeconds(0.2f);
sr.material = originMat;
}
}
// Entity.cs
public EntityFX fx { get; private set; }
protected virtual void Start()
{
fx = GetComponent<EntityFX>();
}
public virtual void TakeDamage()
{
fx.StartCoroutine("FlashFX");
Debug.Log($"{gameObject.name} took damage");
}
Knockback
// Entity.cs
[Header("Knockback Info")]
[SerializeField] protected Vector2 knockbackDirection;
[SerializeField] private float knockbackDuration;
protected bool isKnocked;
public virtual void TakeDamage()
{
fx.StartCoroutine("FlashFX");
StartCoroutine("HitKnockBack");
Debug.Log($"{gameObject.name} took damage");
}
protected virtual IEnumerator HitKnockBack()
{
isKnocked = true;
rb.linearVelocity = new Vector2(knockbackDirection.x * -facingDir, knockbackDirection.y);
yield return new WaitForSeconds(knockbackDuration);
isKnocked = false;
}
public void SetZeroVelocity()
{
if (isKnocked)
{
return;
}
rb.linearVelocity = new Vector2(0, 0);
}
public void SetVelocity(float _xVelocity, float _yVelocity)
{
if (isKnocked)
{
return;
}
rb.linearVelocity = new Vector2(_xVelocity, _yVelocity);
HandleFlip(_xVelocity);
}
Stun
public class SkeletonStunnedState : EnemyState
{
protected Skeleton enemy;
public SkeletonStunnedState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Skeleton _enemy)
: base(_enemyBase, _stateMachine, _animBoolName)
{
enemy = _enemy;
}
public override void Eneter()
{
base.Eneter();
stateTimer = enemy.stunDuration;
rb.linearVelocity = new Vector2(-enemy.facingDir * enemy.stunDirection.x, enemy.stunDirection.y);
}
public override void Update()
{
base.Update();
enemy.fx.InvokeRepeating("RecolorBlink", 0, 0.1f);
if (stateTimer < 0)
{
stateMachine.ChangeState(enemy.idleState);
}
}
public override void Exit()
{
base.Exit();
enemy.fx.Invoke("CancelRedBlink", 0);
}
}
// EntityFX.cs
private void RecolorBlink()
{
sr.color = (sr.color != Color.white) ? Color.white : Color.red;
}
private void CancelRedBlink()
{
CancelInvoke();
sr.color = Color.white;
}
protected override void Awake()
{
counterAttackState = new PlayerCounterAttackState(this, stateMachine, "CounterAttack");
}
Counter Attack과 Stun
// Player.cs
public float counterAttackDuration = 0.2f;
public PlayerCounterAttackState counterAttackState { get; private set; }
// PlayerGroundedState.cs
if (Input.GetKeyDown(KeyCode.M))
{
stateMachine.ChangeState(player.counterAttackState);
}
public class PlayerCounterAttackState : PlayerState
{
public PlayerCounterAttackState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName)
: base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
stateTimer = player.counterAttackDuration;
player.anim.SetBool("SucceedCounterAttack", false);
}
public override void Update()
{
base.Update();
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
if (hit.GetComponent<Enemy>().CanBeStunned())
{
stateTimer = 10;
player.anim.SetBool("SucceedCounterAttack", true);
}
}
}
if (stateTimer < 0 || triggerCalled) // 시간 내에 못 쳤을 경우 idle로 가라
{
stateMachine.ChangeState(player.idleState);
}
}
public override void Exit()
{
base.Exit();
player.anim.SetBool("SucceedCounterAttack", false);
}
}
// Enemy.cs
public virtual void OpenCounterAttackWindow() // Animation Event와 연결
{
canBeStunned = true;
counterImage.SetActive(true);
}
public virtual void CloseCounterAttackWindow() // Animation Event와 연결
{
canBeStunned = false;
counterImage.SetActive(false);
}
public virtual bool CanBeStunned()
{
if (canBeStunned)
{
CloseCounterAttackWindow();
return true;
}
return false;
}
// Skeleton.cs
public override bool CanBeStunned()
{
if (base.CanBeStunned())
{
stateMachine.ChangeState(stunnedState);
return true;
}
return false;
}
Enemy가 canBeStunned가 true일 때 Player가 반격하면, Enemy가 Stun상태로 된다
'Unity > 멋쟁이사자처럼' 카테고리의 다른 글
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 37일차 회고 (0) | 2025.04.17 |
---|---|
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 36일차 회고 (0) | 2025.04.17 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 34일차 회고 (0) | 2025.04.10 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 33일차 회고 (0) | 2025.04.09 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 32일차 회고 (0) | 2025.04.09 |