[Unity][2D-Game] Undead Survivor (9)

suhan0304·2023년 11월 8일
0

유니티 - Undead Survivor

목록 보기
11/21
post-thumbnail

Review

  • 자동 원거리 공격을 구현했다.
  • 가장 가까운 몬스터를 찾기 위해 Enemy 레이어를 만들고 해당 레이어에 CircleCastAll로 범위 내 모든 객체들을 반환받았다.
  • 반환 받은 객체들을 foreach로 하나씩 방문하면서 거리가 최소가 되는 오브젝트를 찾았다.
  • 해당 Nearest 오브젝트로 원거리 공격 발사되도록 로직을 구현했다.

강의영상 (9) - 타격감 있는 몬스터 처치 만들기


개발

피격 리액션

기존 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 : 애니메이션의 레이어 인덱스


사망 리액션

죽었을 때 실행할 수행할 작업

  • 여러 로직을 제어하는 isLive 변수를 false로 변경한다.
  • 충돌을 방지하기 위해 콜라이더를 비활성화
    - 비활성화 시켜주기 위해 콜라이더 2D 변수를 생성 및 초기화 해주었다.
  • 이후 추가적인 활동도 비활성화 시키기 위해 리지드바디의 물리적 비활성화
    - 리지드바디의 비활성화는 .simulated = false
  • 활동 몬스터를 가리지 않게 레이어를 낮추기
  • Dead 애니메이션을 실행

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 함수가 실행된다.

GameManager.cs

[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;
    }
}

Enemy.cs

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 실행
}

결과물

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글