기존 Enemy의 OnTriggerEnter2D를 수정해서 피격 이벤트를 구현했다. 이 때 Enemy의 애니메이션에서 Hit 애니메이션을 재생할 것이다. Trigger Parameter로 Hit를 기존에 생성해 놓았기 때문에 setTrigger 함수를 호출하여 애니메이션 상태를 변경한다.
피격 시 HP가 0보다 크면 피격 리액션을, 0보다 작으면 Dead 리액션을 재생하도록 할 것이다.
anim.SetTrigger("Hit");
넉백은 Coroutine을 사용해서 구현했다. 이때 코루틴은 생명주기와는 비동기로 별도로 진행되기 때문에 현재 어떤 스크립트가 작동하고 있는지에 상관없이 별도로 진행되도록 할 수 있다. 따라서 넉백 코루틴이 실행되면 몬스터를 넉백 시키는 동시에 몬스터가 플레이어를 향하는 로직은 그대로 진행되도록 구현했다.
코루틴(Coroutine) : 생명 주기와 비동기처럼 실행되는 함수
IEnumerator : 코루틴만의 반환형 인터페이스
yield return : 코루틴만의 반화 키워드로 없으면 오류가 발생한다.
- yield return new null; // 1프레임 쉬기
- yeild return new waitForSeconds(2f); //2초간 쉬기
물리 프레임 단위로 쉬기 위해서 WaitForFixedUpdate 변수를 선언 및 초기화를 해준 후 yield return에 해당 변수를 사용해주었다.
넉백은 플레이어와 반대 방향으로 밀어내주도록 로직을 구현했다. 이 때 넉백의 AddForce를 해줄 때 FixedUpdate에서의 힘을 주지 못하도록 현재 애니메이션의 현재 정보 중 Name이 Hit일때는 FixedUpdate가 return 되도록 수정했다.
IEnumerator KnockBack()
{
yield return wait; // 다음 하나의 물리 프레임을 딜레이
Vector3 playerPos = GameManager.Instance.player.transform.position; //Player의 Postion
Vector3 dirVec = transform.position - playerPos; //Player에서 Enemy로의 방향(=넉백 방향)
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse); //순간적인 힘이므로 ForceMode2D.Impulse 실행
}
GetCurrentAnimatorStateInfo(num) : 현재 상태 정보를 가죠오는 함수
매개변수 num : 애니메이션의 레이어 인덱스
죽었을 때 실행할 수행할 작업
Dead 애니메이션의 1초 실행 후 사망 리액션 로직이 실행 되도록 했따. DeadEnemy 1~3의 애니메이션에서 키 이펙트를 1초 구간에 하나 복사 붙여넣기 해준 후 1초 구간에 Add Evemt 버튼을 통해 애니메이션 이벤트를 추가해주었다.
이후에 Enemy 프리팹을 더블 클릭해서 애니메이션의 아까 추가해준 이벤트에 인스펙터에서 Dead 함수를 연결해준다.
시간차 함수 실행을 애니메이터에게 맞길 수 있다.
특정 애니메이션이 몇 초간 실행 후 함수 실행하도록 애니메이션 이벤트를 추가해서 함수를 실행하도록 구현할 수 있다.
void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.CompareTag("Bullet")) // 충돌한 collision이 Bullet인지를 먼저 확인
return;
health -= collision.GetComponent<Bullet>().damage; //Bullet 스크립트 컴포넌트에서 damage를 가져와서 체력에서 깍는다.
StartCoroutine(KnockBack());
if (health > 0)
{
// .. 아직 살아있음 -> Hit Action
anim.SetTrigger("Hit");
}
else
{
// .. 체력이 0보다 작음 -> Die
// 아래에서 설정한 변수들은 재활용에서 또 다시 사용할 때를 위해 init에 초기화 코드를 추가 작성
coll.enabled = false; //충돌 방지를 위해 collider 2D 비활성화
rigid.simulated = false; //물리적 활동 방지를 위해 rigidbody 2D 비활성화
spriter.sortingOrder = 1; //활동중이 몬스터들을 가리지 않도록 sortingLayer을 낮춘다
anim.SetBool("Dead", true); //Dead 애니메이션을 실행
}
}
게임 매니저에 레벨, 킬수, 경험치를 관리할 수 있도록 변수로 선언해주었다.
게임 매니저 인스펙터를 정리해주기 위해 [Header]를 사용해 정리해 주었다.
public static GameManager Instance;
[Header("# Game Control")]
public float gameTime; //게임 시간
public float maxGameTime = 2 * 10f; //게임 최대 시간 (test용 20초)
[Header("# Player Info")]
public PoolManager pool;
public Player player;
[Header("# Game Object")]
public int level; //레벨
public int kill; //킬수
public int exp; //경험치
public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
이제 경험치를 늘려줄 GetExp 함수를 게임매니저에 작성했다. 경험치가 꽉 차면 레벨업이 되도록 구현했다.
킬수와 경험치는 Enemy의 Dead 부분에서 구현했다. 이 때 사망로직이 연달아 실행되는 것을 방자기하기 위해 isLive가 False이면 바로 return 되도록 하였다. 살아있을때 hp가 0보다 작아진 그 순간 한번만 kill++과 GetExp 함수가 실행된다.
[Header("# Player Info")]
public int level; //레벨
public int kill; //킬수
public int exp; //경험치
public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
~~(생략)~~
public void GetExp()
{
exp++;
if(exp == nextExp[level])
{
level++;
exp = 0;
}
}
void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.CompareTag("Bullet") || !isLive) // 충돌한 collision이 Bullet인지를 먼저 확인
return;
health -= collision.GetComponent<Bullet>().damage; //Bullet 스크립트 컴포넌트에서 damage를 가져와서 체력에서 깍는다.
StartCoroutine(KnockBack());
if (health > 0)
{
// .. 아직 살아있음 -> Hit Action
anim.SetTrigger("Hit");
}
else
{
// .. 체력이 0보다 작음 -> Die
// 아래에서 설정한 변수들은 재활용에서 또 다시 사용할 때를 위해 init에 초기화 코드를 추가 작성
coll.enabled = false; //충돌 방지를 위해 collider 2D 비활성화
rigid.simulated = false; //물리적 활동 방지를 위해 rigidbody 2D 비활성화
spriter.sortingOrder = 1; //활동중이 몬스터들을 가리지 않도록 sortingLayer을 낮춘다
anim.SetBool("Dead", true); //Dead 애니메이션을 실행
GameManager.Instance.kill++;
GameManager.Instance.GetExp();
}
}
IEnumerator KnockBack()
{
yield return wait; // 다음 하나의 물리 프레임을 딜레이
Vector3 playerPos = GameManager.Instance.player.transform.position; //Player의 Postion
Vector3 dirVec = transform.position - playerPos; //Player에서 Enemy로의 방향(=넉백 방향)
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse); //순간적인 힘이므로 ForceMode2D.Impulse 실행
}