Unity - 2D 종스크롤 슈팅 : 보스 만들기

TXMAY·2023년 10월 17일
0

Unity 튜토리얼

목록 보기
32/33
post-thumbnail

이번 강좌는 보스 만들기에 관한 강좌다.


준비하기

'Boss' 스프라이트를 배치한다.
그리고 애니메이션과 Collider를 추가하고 Is Trigger를 활성화한다.

총알 프리펩

'Enemy Bullet' 프리펩을 복사하여 'Enemy Bullet C/D'로 만들고 스프라이트를 변경한다.
그리고 보스가 위에서 아래로 총알을 발사하기 때문에 Flip에 Y축을 활성화해 뒤집어 주고, Is Trigger를 활성화한다.
그런 다음 오브젝트 풀링을 위해 'ObjectManager.cs'에 다음과 같이 추가한다.

...

public GameObject bulletBossAPrefab;
public GameObject bulletBossBPrefab;
...

GameObject[] bulletBossA;
GameObject[] bulletBossB;
...

void Awake()
{
    ...
    
    bulletBossA = new GameObject[50];
    bulletBossB = new GameObject[1000];
	...
    
}

void Generate()
{
    ...

    for (int index = 0; index < bulletBossA.Length; index++)
    {
        bulletBossA[index] = Instantiate(bulletBossAPrefab);
        bulletBossA[index].SetActive(false);
    }

    for (int index = 0; index < bulletBossB.Length; index++)
    {
        bulletBossB[index] = Instantiate(bulletBossBPrefab);
        bulletBossB[index].SetActive(false);
    }
}

public GameObject MakeObj(string type)
{
    switch (type)
    {
        ...
        
        case "BulletBossA":
            targetPool = bulletBossA;
            break;
        case "BulletBossB":
            targetPool = bulletBossB;
            break;
    }
    ...
    
}

public GameObject[] GetPool(string type)
{
    switch (type)
    {
        ...
        
        case "BulletBossA":
            targetPool = bulletBossA;
            break;
        case "BulletBossB":
            targetPool = bulletBossB;
            break;
    }
	...
    
}

그리고 보스 패턴을 추가하기 위해 'Bullet.cs'에 다음과 같이 코드를 추가한다.

...

public bool isRotate;

void Update()
{
    if (isRotate)
    {
        transform.Rotate(Vector3.forward * 10);
    }
}
...

그리고 'Enemy Bullet A'에 isRotate를 활성화한다.

보스 기본 로직

Enemy 스크립트를 넣고 값을 넣는다.
그리고 'Enemy.cs'에 다음과 같이 코드를 추가하고 수정한다.

...

Animator anim;

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

    if (enemyName == "B")
    {
        anim = GetComponent<Animator>();
    }
}
...

void Update()
{
    if (enemyName == "B")
    {
        return;
    }
	...
    
}
...

public void OnHit(int dmg)
{
    ...
    
    if (enemyName == "B")
    {
        anim.SetTrigger("OnHit");
    }
    else
    {
        spriteRenderer.sprite = sprites[1];
        Invoke("ReturnSprite", 0.1f);
    }

    if (health <= 0)
    {
        ...

        int ran = enemyName == "B" ? 0 : Random.Range(0, 10);
        ...
        
    }
}
...

void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.gameObject.tag == "BorderBullet" && enemyName != "B")
    {
        ...
        
    }
    ...
    
}

그런 다음 Rigidbody를 추가하고 Gravity Scale을 0으로 설정하고 프리펩으로 만든다.

오브젝트 풀링 사용

총알과 똑같이 코드를 작성한다.

// ObjectManager.cs
public GameObject enemyBPrefab;
...

GameObject[] enemyB;
...

void Awake()
{
    enemyB = new GameObject[1];
    ...
    
}

void Generate()
{
    for (int index = 0; index < enemyB.Length; index++)
    {
        enemyB[index] = Instantiate(enemyBPrefab);
        enemyB[index].SetActive(false);
    }
    ...
    
}

public GameObject MakeObj(string type)
{
    switch (type)
    {
        case "EnemyB":
            targetPool = enemyB;
            break;
        ...
    }
    ...
    
}

public GameObject[] GetPool(string type)
{
    switch (type)
    {
        case "EnemyB":
            targetPool = enemyB;
            break;
        ...
	}
    ...
    
}

// GameManager.cs
...

void Awake()
{
    ...
    
    enemyObjs = new string[] { "EnemyS", "EnemyM", "EnemyL", "EnemyB" };
    ...
    
}
...

void SpawnEnemy()
{
    ...
    
    switch (spawnList[spawnIndex].type)
    {
        ...
        
        case "B":
            enemyIndex = 3;
            break;
    }
    ...
    
}

패턴 흐름

'Enemy.cs'에 다음과 같이 코드를 추가한다.

...

public int patternIndex;
public int curPatternCount;
public int[] maxPatternCount;
...

void OnEnable()
{
    switch (enemyName)
    {
        case "B":
            health = 3000;
            Invoke("Stop", 2);
            break;
        ...
        
    }
}

void Stop()
{
    if (!gameObject.activeSelf)
    {
        return;
    }

    Rigidbody2D rigid = GetComponent<Rigidbody2D>();
    rigid.velocity = Vector2.zero;

    Invoke("Think", 2);
}

void Think()
{
    patternIndex = patternIndex == 3 ? 0 : patternIndex + 1;
    curPatternCount = 0;
    switch (patternIndex)
    {
        case 0:
            FireFoward();
            break;
        case 1:
            FireShot();
            break;
        case 2:
            FireArc();
            break;
        case 3:
            FireAround();
            break;
    }
}

void FireFoward()
{
    curPatternCount++;

    if (curPatternCount < maxPatternCount[patternIndex])
    {
        Invoke("FireFoward", 2);
    }
    else
    {
        Invoke("Think", 3);
    }
}

void FireShot()
{
    curPatternCount++;

    if (curPatternCount < maxPatternCount[patternIndex])
    {
        Invoke("FireShot", 3.5f);
    }
    else
    {
        Invoke("Think", 3);
    }
}

void FireArc()
{
    curPatternCount++;

    if (curPatternCount < maxPatternCount[patternIndex])
    {
        Invoke("FireArc", 0.15f);
    }
    else
    {
        Invoke("Think", 3);
    }
}

void FireAround()
{
    curPatternCount++;

    if (curPatternCount < maxPatternCount[patternIndex])
    {
        Invoke("FireAround", 0.7f);
    }
    else
    {
        Invoke("Think", 3);
    }
}
...

패턴 구현

다음과 같이 코드를 추가한다.

...

void FireFoward()
{
    if (health <= 0)
    {
        return;
    }

    GameObject bulletR = objectManager.MakeObj("BulletBossA");
    bulletR.transform.position = transform.position + Vector3.right * 0.3f;
    GameObject bulletRR = objectManager.MakeObj("BulletBossA");
    bulletRR.transform.position = transform.position + Vector3.right * 0.45f;
    GameObject bulletL = objectManager.MakeObj("BulletBossA");
    bulletL.transform.position = transform.position + Vector3.left * 0.3f;
    GameObject bulletLL = objectManager.MakeObj("BulletBossA");
    bulletLL.transform.position = transform.position + Vector3.left * 0.45f;

    Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
    Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
    Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
    Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();

    rigidR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
    rigidRR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
    rigidL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
    rigidLL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
	...
    
}

void FireShot()
{
    if (health <= 0)
    {
        return;
    }

    for (int index = 0; index < 5; index++)
    {
        GameObject bullet = objectManager.MakeObj("BulletEnemyB");
        bullet.transform.position = transform.position;

        Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
        Vector2 dirVec = player.transform.position - transform.position;
        Vector2 ranVec = new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(0f, 2f));
        dirVec += ranVec;
        rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
    }
    ...
    
}

void FireArc()
{
    if (health <= 0)
    {
        return;
    }

    GameObject bullet = objectManager.MakeObj("BulletEnemyA");
    bullet.transform.position = transform.position;
    bullet.transform.rotation = Quaternion.identity;

    Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
    Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 10 * curPatternCount / maxPatternCount[patternIndex]), -1);
    rigid.AddForce(dirVec.normalized * 5, ForceMode2D.Impulse);
    ...
    
}

void FireAround()
{
    if (health <= 0)
    {
        return;
    }

    int roundNumA = 50;
    int roundNumB = 40;
    int roundNum = curPatternCount % 2 == 0 ? roundNumA : roundNumB;

    for (int index = 0; index < roundNum; index++)
    {
        GameObject bullet = objectManager.MakeObj("BulletBossB");
        bullet.transform.position = transform.position;
        bullet.transform.rotation = Quaternion.identity;

        Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
        Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 2 * index / roundNum), Mathf.Sin(Mathf.PI * 2 * index / roundNum));
        rigid.AddForce(dirVec.normalized * 2, ForceMode2D.Impulse);

        Vector3 rotVec = Vector3.forward * 360 * index / roundNum + Vector3.forward * 90;
        bullet.transform.Rotate(rotVec);
    }
    ...
    
}
...

public void OnHit(int dmg)
{
    ...

    if (health <= 0)
    {
        ...
        
        CancelInvoke();
        ...
        
    }
}

예전에 탄막을 구현할 때, 보스를 회전시켜서 발사했는데 이런 식으로 구현이 돼서 신기했다.

profile
게임 개발 공부하는 고양이

0개의 댓글