소소한 나의 하루들

2d 종스크롤 슈팅(3) - 적 비행기 만들기 본문

개발/유니티

2d 종스크롤 슈팅(3) - 적 비행기 만들기

소소한 나의 하루 2024. 2. 16. 12:08

출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared

 

📚 유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com

이전에는 플레이어 비행기가 총알을 쏘는 것을 구현했는데, 이번에는 총알을 맞아주는 적 비행기 시스템을 구현해본다.


#1. 준비하기

제공받은 에셋의 sprite 설정을 해본다. 2d 도트그래픽이니까 Pixels per Units은 기준 크기에 맞게 24, Filter Mode는 Point (no filter), Compression은 None으로 설정한다. 그리고 sprite 크기가 다 다르므로 Sprite Editor에서 slice는 Automatic으로 설정하고 slice해준다. 설정이 끝나면 Apply까지 꼭 한다.

 

적용까지 끝났으면 에셋의 적 비행기 sprite를 찾아서 Scene에 배치해본다. 플레이어가 쏜 총알에 맞았을 때 트리거 이벤트를 위해서 콜라이더 컴포넌트를 생성한다.

적 비행기 sprite 중 세모 모양의 날렵한 비행기는 Box Collider 2D 컴포넌트를 적용하면, 앞쪽에 남는 빈 공간이 생겨버린다. 그래서 우리가 원하는 모양을 만들기 위한 콜라이더인 Polygon Collider 2D 컴포넌트를 적용한다. 그리고 살짝 큰 히트박스를 Sprite Editor에서 조절해준다.

Sprite Editor에서 Custom Physics Shape를 선택해주고, 조정해주고자하는 sprite를 선택한 뒤 Generate를 클릭한다.

*Sprite Editor창에서 Generate 버튼이 안보이면 창 크기를 키워준다.

*스프라이트는 기본적으로 Physics Shape를 가지고 있다. 폴리곤 콜라이더 모양은 Physics Shape를 따라간다.

 

스프라이트 히트박스를 조절해줬으면 Apply 한 다음, 기존에 있던 해당 오브젝트를 지운 뒤 다시 수정한 sprite를 Scene에 추가해주고 Polygon Collider 2D 컴포넌트도 추가해주면 수정된 히트박스가 새로 적용된다.

 

적 비행들도 움직이는 속도가 있으니까 Rigidbody 2D 컴포넌트를 추가하고 Gravity Scale을 0으로 설정한다. Tag도 추가해준다. (Enemy)


#2. 적 기체 프리펩

적 비행기의 행동을 관리해줄 수 있게 Enemy 스크립트도 생성하고 적 비행기 오브젝트들에 컴포넌트로 추가해준다.

public class Enemy : MonoBehaviour
{
    public float speed;
    public int health;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer; 
    Rigidbody2D rigid;

}

적 비행기에게 필요한 것은 무엇일까? 우선 speed도 필요하고, 몇번을 맞아야 죽는지 health도 필요할 것이다. 속력도 바꿔야하기 때문에 Rigidbody 2D도 추가하고, 죽을 때 모습도 바꿀 것이기 때문에 SpriteRender 컴포넌트도 추가한다. SpriteRenderer에서 Sprite도 바꾸려면 sprite도 바꿀 것이기 때문에 Sprite도 있어야한다.

: 적 비행기의 구성 요소를 변수로 구체화한다.

 

rigidbody.AddForce() 와 rigidbody.velocity 차이

https://unitybeginner.tistory.com/24

 

유니티 AddForce와 Velocity 차이점

안녕하세요 유니티 비기너입니다. 이번 시간에는 물체를 이동시키는 AddForce와 Velocity의 차이점을 비교해보겠습니다. 화면 구성 먼저 테스트를 진행하기 위해 다음과 같이 오브젝트를 구성하였

unitybeginner.tistory.com

 

rigidbody.AddForce()는 같은 힘을 연속해서 가하면 점점 가속도가 붙는다. [등가속 운동]

rigidbody.velocity는 같은 힘을 가해도 동일한 속도로 달릴 수 있도록 물리엔진이 자동으로 계산해준다. [등속운동]

private void Awake()
{
    spriteRenderer = GetComponent<SpriteRenderer>();
    rigid = GetComponent<Rigidbody2D>();

    rigid.velocity = Vector3.down * speed;
}

초기화까지 해줬으면, 적 비행기들이 할 일은 플레이어의 총알을 맞아주는 역할이다.

private void OnHit(int damage)
{
    health -= damage;

    if (health <= 0)
        Destroy(gameObject);
}

플레이어 총알의 데미지만큼 피가 깎이는 함수 OnHit()을 만들어준다. 적 비행기의 피(health)가 0보다 작거나 같게되면 = 파괴된다.

 

이제 적 비행기가 플레이어 총알에 피격당했을 때의 상태를 색상으로 표현한다. 보통 게임에서 피격당했을 때 순간적으로 색상이 살짝 불투명해지거나 하얀색으로 바뀐다. 그것을 구현해본다.

평소 스프라이트는 0, 피격 시 스프라이트는 1로 바꿔준다.

private void OnHit(int damage)
{
    health -= damage;
    spriteRenderer.sprite = sprites[1];
    //ReturnSprite(); //이렇게 하면 1프레임 내에서 즉시 다시 원래 sprite[0]으로 바꾸기 때문에 실제로 바뀌지 않은 것처럼 보인다
    Invoke("ReturnSprite", 0.1f);
    
    if (health <= 0)
        Destroy(gameObject);
}

void ReturnSprite()
{
    spriteRenderer.sprite = sprites[0];
}

이렇게 코드를 작성하게되면 1프레임 내에 피격당해 sprite[1]로 바뀌었다가 다시 sprite[0]으로 돌아오기 때문에 게임 내에서는 피격당했을 때에 이미지가 그대로 유지되는 것처럼 보인다.

따라서 바꾼 스프라이트를 돌리기 위해 시간차 함수를 호출한다.

//Enemy
private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject);
    else if (collision.gameObject.tag == "PlayerBullet")
    {
        Bullet bullet = collision.gameObject.GetComponent<Bullet>();
        OnHit(bullet.damage);
    }
        
}

//Bullet
public class Bullet : MonoBehaviour
{
    public int damage;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "BorderBullet")
        {
            Destroy(gameObject);
        }
    }
}

 이제 OnTriggerEnter2D()를 통해 이벤트 로직을 작성한다. 적 비행기가 플레이어를 지나가서 (총알과 마찬가지로) 바깥으로 나간 후에는 (총알 경계지점을 만나면) 삭제한다.

그리고 적 비행기가 플레이어가 쏜 총알을 맞았을 때의 이벤트를 구현하기 위해 우선 총알 프리펩에는 "PlayerBullet" 태그를 달아준다. 적 비행기가 아직 총알 경계지점을 만나지 않았을 때 적 비행기가 총알에 맞으면 피가 닳고, 체력이 0 이하면 제거되는 OnHit() 함수를 실행시켜준다. 

호출하면서 OnHit()에 총알의 데미지를 전달해줘야한다.

 

스크립트 작성이 끝나면, 이제 실행하기 전에 적 비행기의 체력(health)과 속도(speed), 총알의 데미지(프리펩 A/B) 값을 입력해줘야한다. 그리고 피격당했을 때 sprite와 피격당하지 않을 때 sprite도 적용시켜준다.

: public으로 설정한 변수들의 초기값을 잊지말고 꼭 작성해줘야한다.

적끼리 충돌을 막기위해 콜라이더의 Is Trigger 옵션을 체크해준다.

*이번 프로젝트는 Dynamic 물리 충돌을 거의 활용하지 않는다.

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject);
    else if (collision.gameObject.tag == "PlayerBullet")
    {
        Bullet bullet = collision.gameObject.GetComponent<Bullet>();
        OnHit(bullet.damage);
        //collision.gameObject.SetActive(false);
        Destroy(collision.gameObject);
    }
        
}

적 비행기가 피격 시 플레이어 총알을 삭제하는 로직을 추가한다.

 

스크립트 작성이 다 끝났으면, 적 비행기 오브젝트들을 프리펩으로 저장한다. 그리고 저장이 끝나면 Scene에서 적 비행기 오브젝트들은 삭제해준다.

*프리펩으로 저장한 후에는 Transform이 Scene에 배치했던 좌표로 저장되기 때문에 Transform을 0으로 초기화해준다.


#3. 적 비행기 생성

플레이어로부터 총알이 발사된 것처럼 적 비행기 프리펩도 생성해줘야하는데, 어디서 관리해줄까?

이러한 적 생성은 GameManager에서 생성하고 관리해준다. GameManager 오브젝트와 스크립트를 생성해준다.

 

GameManager에게 필요한 것은 무엇일까?

public class GameManager : MonoBehaviour
{
    public GameObject[] ememyObjs;
    public Transform[] spawnPoints;

    public float maxSpawnDelay;
    public float curSpawnDelay;
}

일단 적 비행기 프리펩 3개를 만들었기 때문에, 그것들을 변수화시켜야한다. : 프리펩 변수화

적 비행기 프리펩 오브젝트가 3개이니까 배열로 만들어서 관리해주도록 한다.

그리고 적 비행기 프리펩을 생성시켜줄 위치가 있어야하는데, 5개의 좌표를 잡아서 랜덤하게 소환하도록 한다.

 

생성 위치 배열 변수를 선언한다. 위치의 타입은 Transform이다. 그리고 처음 총알 발사할때처럼 1초에 60번씩 소환시키면 안된다. 앞에서 총알 발사 시 딜레이를 구현해줬던 것처럼 적 생성 딜레이 변수를 선언해준다. 참고

private void Update()
{
    curSpawnDelay += Time.deltaTime;

    if (curSpawnDelay > maxSpawnDelay)
    {
        SpawnEnemy();
        maxSpawnDelay = Random.Range(0.5f, 3f);
        curSpawnDelay = 0;
    }
        
}

curSpawnDelay에는 항상 Time.DeltaTime을 가산한다. 만약 curSpawnDelay > maxSpawnDelay도바 크다면, 적을 소환한다. 적 생성 후에는 꼭 딜레이 변수를 0으로 초기화한다.

 

생성 주기를 랜덤하게 잡기 위해 Random 클래스의 Range() 함수를 사용한다.

RandomRange()는 현재 사용하지 않는 함수이다.

Range() : 정해진 범위 내의 랜덤한 숫자를 반환한다. 

*인수로 float형 또는 int형을 받음

void SpawnEnemy()
{
    int ranEnemy = Random.Range(0, 3);
    int ranPoint = Random.Range(0, 5);

    Instantiate(ememyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);

}

그리고 소환될 적과 소환될 위치도 랜덤하게 소환한다. 랜덤으로 정해진 적 프리펩생성위치로 생성로직을 작성한다.

실행시키기 전에 GameManger에 적 프리펩을 적용시켜주고, Scene에서 빈 오브젝트로 생성위치를 생성한다. 그리고 Scene에서 총알 경계박스 안쪽으로 전체 위치를 y축 방향으로 높여주고, 각 좌표값을 조절해준다.

*이때 빈 오브젝트는 태그 아이콘으로 Scene에서 위치를 확인할 수 있다.

 

MaxSpawnDelay에 초기값을 넣어주고 실행시키면, 그 이후부터는 랜덤으로 값이 설정되어서 랜덤한 주기로 랜덤한 위치에서 랜덤한 적 비행기가 생성되는 것을 확인할 수 있다.

Comments