
세피리아의 첫 스테이지 몬스터들을 보면 여러 유형의 돌진(근접)형 몬스터들이 존재한다.
따라서 재사용 할 수 있도록 돌진 유형 스크립트를 구성해서, 대표적인 3가지 타입의 돌진형 몬스터를 구현해주었다.
코드는 몬스터별 설정에 맞춰서 공격 가능 거리를 확인하고, 그에 맞춰 공격 준비를 진행하고, 준비가 끝나면 돌격하면서 공격하는 형태로 되어있다.
따라서 위 세 몬스터의 차이는 설정값에서 발생한다.
[SerializeField] Vector2 attackHitboxSize; //공격 히트박스 크기
[SerializeField] float attackDistance; //공격 시작 사거리
[SerializeField] float attackMoveDistance; //공격하면서 이동할 거리
[SerializeField] float attackDuration; //공격 진행 시간
[SerializeField] float attackPrepareDuration; //공격 준비 시간
[SerializeField] float attackEndDelay; //공격 후 딜레이 시간
[SerializeField] float warningWidth; //공격 안내선 좌우 폭
[SerializeField] float warningStartLength; //안내선 시작 길이
[SerializeField] float warningEndLength; //안내선 최종 길이
돌진 공격의 유형을 나누기 위한 핵심적인 설정들은 위와 같다.
| 옵션 | 표준형 | 거대형 | 사거리형 |
|---|---|---|---|
| attackHitboxSize (공격 히트박스 크기) | 보통 | 큼 | 작음 |
| attackDistance (공격 사거리) | 보통 | 보통 | 긺 |
| attackMoveDistance (공격시 이동 거리) | 보통 | 보통 | 매우 긺 |
| attackDuration (공격 시간) | 보통 | 보통 | 보통 이상 |
| attackPrepareDuration (공격 준비 시간) | 보통 | 긺 | 보통 |
| attackEndDelay (공격 후 딜레이) | 보통 | 긺 | 긺 |
| warningWidth (공격 안내선의 폭) | 보통 | 큼 | 작음 |
| warningStartLength (공격 안내선 시작 길이) | 보통 | 보통 | 긺 |
| warningEndLength (공격 안내선 최종 길이) | 보통 | 보통 | 매우 긺 |
각 옵션들을 위의 표와 같은 느낌으로 설정해줘서 표준 돌진 몬스터, 대형 돌진 몬스터, 사거리가 긴 돌진 몬스터를 각각 구현해주었다.
몬스터는 지정된 사거리(attackDistance)내에 플레이어가 존재하는지 확인하고, 사거리 내에 플레이어가 들어오면 공격 준비상태로 전환되도록 되어있다.
공격 준비 상태로 전환되면서 1회 실행된다.
//공격 준비 시작
public override void StartAttackPrepare()
{
//공격 준비 시간 초기화
attackPrepareTimer = 0f;
//attackWarning 객체 부모 재지정
attackWarning.SetParent(transform, true);
attackWarning.localPosition = warningOffset;
//공격 방향 지정
attackDirection = ((Vector2)target.position - (Vector2)transform.position).normalized;
//공격 각도 계산
float angle = Mathf.Atan2(attackDirection.y, attackDirection.x) * Mathf.Rad2Deg - 90f;
//구한 각도를 안내선 및 히트박스 z축에 적용해서 회전 진행
attackWarning.rotation = Quaternion.Euler(0f, 0f, angle);
attackHitbox.transform.rotation = Quaternion.Euler(0f, 0f, angle);
//이펙트가 존재한다면 이펙트 또한 회전 진행
if (attackEffect != null)
attackEffect.transform.rotation = Quaternion.Euler(0f, 0f, angle);
//공격 안내선 세팅
SetupAttackWarning();
}
공격 준비가 시작되면, 공격 준비 시간을 초기화 하고, 공격라인의 부모를 트랜스폼 즉 현재 객체로 다시 지정해준다.
이후 진행될 공격 과정에서 공격 라인이 현 객체를 벗어나게 되기 때문에, 이전에 공격을 이미 진행했을 경우를 대비하여, 공격 시작 전 복귀시켜주는 과정이 꼭 필요하다.
이후, 공격 대상의 위치와 자신의 위치를 비교해서 공격을 진행할 방향을 잡고, 방향을 기준으로 실제 공격 각도를 조정해준다.
각도 조정에 -90도를 보정해주는 이유는 히트박스 및 공격 안내선이 기본적으로 위쪽을 바라보는 형태로 되어있기 때문이다.
각도를 구해주었으면 안내선, 히트박스, 이펙트에 그 각도를 적용해주고, 공격 안내선 세팅을 진행한다.
안내선 세팅은 단순히 미리 만들어둔 안내선들에게 개별 시작, 도착 위치 및 길이를 전달해주어 이후 준비 진행 단계 연출을 준비해준다.
공격 준비 상태에서 계속해서 실행된다.
//공격 준비 진행
public override bool ExecuteAttackPrepare()
{
//타이머 처리
attackPrepareTimer += Time.fixedDeltaTime;
//시간 경과에 따른 진행도 전달
float progress = Mathf.Clamp01(attackPrepareTimer / attackPrepareDuration);
//각 공격 선들에 대한 진행도 지정
leftOuter.SetProgress(progress);
leftInner.SetProgress(progress);
rightInner.SetProgress(progress);
rightOuter.SetProgress(progress);
//진행도가 100%가 되면 true 반환
return progress >= 1f;
}
공격 준비 단계는 미리 정해놓은 공격 준비시간동안 점점 진행도를 늘려주도록 되어있다.
이 진행도는 각 공격 안내선들에 적용되는데, 안내선은 진행도를 받으면 기존에 설정되어 있던대로 선의 위치를 이동하고, 선의 길이도 점점 길어지게 된다.
진행도가 100%가 되면 true를 반환하게 되고, 몬스터의 상태가 공격 준비에서 공격 상태로 전환된다.
공격 상태로 전환되면서 1회 실행된다.
//공격 시작
public override void StartAttack()
{
//공격 타이머 초기화
attackTimer = 0f;
//공격 시작, 끝 위치 지정
attackStartPosition = rb.position;
attackEndPosition = attackStartPosition + attackDirection * attackMoveDistance;
//부모를 분리해서 공격 시작 위치에 공격 범위 표시 고정
attackWarning.SetParent(null, true);
//공격 이펙트 활성화
if (attackEffect != null)
{
attackEffect.transform.position = (Vector2)transform.position + attackDirection * attackEffectOffset;
attackEffect.SetActive(true);
}
//히트박스 목록 초기화
attackHitbox.ResetHitTargets();
}
공격 타이머를 초기화 해주고, 공격의 시작 ~ 끝 위치를 설정해주며, 공격 안내선의 분리를 진행한다.
공격 안내선을 월드에 분리해두지 않고 몬스터가 계속해서 갖고있게 되면, 몬스터가 공격을 진행하면서 위치가 이동될 때 안내선이 계속해서 이동하게 되기 때문에 플레이어에게 유의미한 안내를 주기가 어렵다.
이후, 공격 이펙트의 활성화와 기존 공격의 히트박스 타겟 목록을 초기화 하고, 실제 공격 진행 단계로 넘어간다.
공격 상태에서 계속해서 실행된다.
//공격 진행
public override bool ExecuteAttack()
{
//타이머 처리
attackTimer += Time.fixedDeltaTime;
//진행도 확보
float progress = Mathf.Clamp01(attackTimer / attackDuration);
//진행도에 따른 몬스터 위치 이동
Vector2 attackPosition = Vector2.Lerp(attackStartPosition, attackEndPosition, progress);
rb.MovePosition(attackPosition);
//공격 대상 확보
DetectAttackTarget();
//진행도에 따른 공격 표시 페이드아웃
leftOuter.SetFadeProgress(progress);
leftInner.SetFadeProgress(progress);
rightInner.SetFadeProgress(progress);
rightOuter.SetFadeProgress(progress);
//진행도가 100%가 되면 true 반환
if (progress >= 1f)
{
leftOuter.Hide();
leftInner.Hide();
rightInner.Hide();
rightOuter.Hide();
return true;
}
return false;
}
공격 진행 또한 진행도를 확보하고, 그 진행도를 기반으로 동작한다.
진행도에 맞춰서 몬스터를 이동시켜 실질적인 돌진처리를 만들고, 이 과정에서 계속해서 공격 대상을 확보한다.
대상이 확보되는 즉시, 기존의 공격 로직에 맞춰 공격처리가 진행되기 때문에 오브젝트나 플레이어가 공격 범위에서 확인되면, 오브젝트를 파괴하거나 플레이어에게 데미지를 입힌다.
또한 공격 표시의 페이드아웃 처리를 같이 진행하다가, 진행도가 100%에 달하게 되면 공격표시 선들을 전부 숨김처리하고 true를 반환한다.
true가 반환되면, 공격 처리를 종료하는 과정에서 몬스터별로 설정해둔 공격 후딜레이를 적용해주고, Idle 상태로 전환되며, 후딜레이 시간이 종료되면 다시 Move상태로 전환되어 플레이어를 추적하고, 사거리 내에 들면 다시 공격하는 공격 사이클이 완성된다.