기록 보관소

[Unity/유니티] 기초-2D 종스크롤 슈팅: 탄막을 뿜어대는 보스 만들기[B37] 본문

유니티 프로젝트/2D 종스크롤 슈팅

[Unity/유니티] 기초-2D 종스크롤 슈팅: 탄막을 뿜어대는 보스 만들기[B37]

JongHoon 2022. 3. 11. 23:33

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

유니티 강좌 기초 채널 Basic

유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤

www.youtube.com


2D 종스크롤 슈팅: 탄막을 뿜어대는 보스 만들기[B37]

1. 준비하기

보스 스프라이트 추가
Boss_Idle 애니메이션 추가
Boss_Hit 애니메이션 추가
Boss_Hit 애니메이션은 Animation창을 통해서 앞의 스프라이트를 제거해 반투명 1프레임으로 변경
Animator창. Trigger형 매개변수 OnHit를 하나 추가하고, Transition을 설정해준다

  • Any State -> Boss_Hit일때만 Conditions에 OnHit를 쓰고, Boss_Hit -> Boss_Idle일때는 사용하지 않는다.
  • 이전까지의 과정과 마찬가지로 Has Exit Time를 제거하고 Transition Duration을 0으로 설정해준다. Boss_Hit -> Boss_Idle에서는 Transition Duration만 바꿔주면 된다.

Capsule Collider 2D와 RigidBody 2D 컴포넌트를 추가하고, 각각 충돌크기와 Gravity Scale을 설정해준다


2. 총알 프리펩

Enemy Bullet B 프리펩 복사
복사했던 적 총알을 Enemy Bullet C로 설정. 캡쳐에서는 빠져있지만 Is Trigger도 체크해둔다.
Enemy Bullet C를 복사하여 Enemy Bullet D 만들기

//ObjectManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectManager : MonoBehaviour {
    public GameObject enemyLPrefab;
    public GameObject enemyMPrefab;
    public GameObject enemySPrefab;

    public GameObject itemCoinPrefab;
    public GameObject itemPowerPrefab;
    public GameObject itemBoomPrefab;

    public GameObject bulletPlayerAPrefab;
    public GameObject bulletPlayerBPrefab;
    public GameObject bulletEnemyAPrefab;
    public GameObject bulletEnemyBPrefab;
    public GameObject bulletFollowerPrefab;
    public GameObject bulletBossAPrefab;
    public GameObject bulletBossBPrefab;

    GameObject[] enemyL;
    GameObject[] enemyM;
    GameObject[] enemyS;

    GameObject[] itemCoin;
    GameObject[] itemPower;
    GameObject[] itemBoom;

    GameObject[] bulletPlayerA;
    GameObject[] bulletPlayerB;
    GameObject[] bulletEnemyA;
    GameObject[] bulletEnemyB;
    GameObject[] bulletFollower;
    GameObject[] bulletBossA;
    GameObject[] bulletBossB;

    GameObject[] targetPool;

    void Awake() {
        enemyL = new GameObject[10];
        enemyM = new GameObject[10];
        enemyS = new GameObject[20];

        itemCoin = new GameObject[20];
        itemPower = new GameObject[10];
        itemBoom = new GameObject[10];

        bulletPlayerA = new GameObject[100];
        bulletPlayerB = new GameObject[100];
        bulletEnemyA = new GameObject[100];
        bulletEnemyB = new GameObject[100];
        bulletFollower = new GameObject[100];
        bulletBossA = new GameObject[50];
        bulletBossB = new GameObject[1000];

        Generate();
    }

    void Generate() {
        //적 기체
        for (int index = 0; index < enemyL.Length; index++) {
            enemyL[index] = Instantiate(enemyLPrefab);
            enemyL[index].SetActive(false);
        }
        for (int index = 0; index < enemyM.Length; index++) {
            enemyM[index] = Instantiate(enemyMPrefab);
            enemyM[index].SetActive(false);
        }
        for (int index = 0; index < enemyS.Length; index++) {
            enemyS[index] = Instantiate(enemySPrefab);
            enemyS[index].SetActive(false);
        }
        
        //아이템
        for (int index = 0; index < itemCoin.Length; index++) {
            itemCoin[index] = Instantiate(itemCoinPrefab);
            itemCoin[index].SetActive(false);
        }
        for (int index = 0; index < itemPower.Length; index++) {
            itemPower[index] = Instantiate(itemPowerPrefab);
            itemPower[index].SetActive(false);
        }
        for (int index = 0; index < itemBoom.Length; index++) {
            itemBoom[index] = Instantiate(itemBoomPrefab);
            itemBoom[index].SetActive(false);
        }

        //총알
        for (int index = 0; index < bulletPlayerA.Length; index++) {
            bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
            bulletPlayerA[index].SetActive(false);
        }
        for (int index = 0; index < bulletPlayerB.Length; index++) {
            bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
            bulletPlayerB[index].SetActive(false);
        }
        for (int index = 0; index < bulletEnemyA.Length; index++) {
            bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
            bulletEnemyA[index].SetActive(false);
        }
        for (int index = 0; index < bulletEnemyB.Length; index++) {
            bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
            bulletEnemyB[index].SetActive(false);
        }
        for (int index = 0; index < bulletFollower.Length; index++) {
            bulletFollower[index] = Instantiate(bulletFollowerPrefab);
            bulletFollower[index].SetActive(false);
        }
        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 "EnemyL":
                targetPool = enemyL;
                break;
            case "EnemyM":
                targetPool = enemyM;
                break;
            case "EnemyS":
                targetPool = enemyS;
                break;
            case "ItemCoin":
                targetPool = itemCoin;
                break;
            case "ItemPower":
                targetPool = itemPower;
                break;
            case "ItemBoom":
                targetPool = itemBoom;
                break;
            case "BulletPlayerA":
                targetPool = bulletPlayerA;
                break;
            case "BulletPlayerB":
                targetPool = bulletPlayerB;
                break;
            case "BulletEnemyA":
                targetPool = bulletEnemyA;
                break;
            case "BulletEnemyB":
                targetPool = bulletEnemyB;
                break;
            case "BulletFollower":
                targetPool = bulletFollower;
                break;
            case "BulletBossA":
                targetPool = bulletBossA;
                break;
            case "BulletBossB":
                targetPool = bulletBossB;
                break;
        }

        for (int index = 0; index < targetPool.Length; index++) {
            if (!targetPool[index].activeSelf) {    //비활성화된 오브젝트에 접근
                targetPool[index].SetActive(true);  //해당 오브젝트를 활성화 후
                return targetPool[index];   //오브젝트 반환
            }
        }

        return null;
    }

    public GameObject[] GetPool(string type) {
        switch (type) {
            case "EnemyL":
                targetPool = enemyL;
                break;
            case "EnemyM":
                targetPool = enemyM;
                break;
            case "EnemyS":
                targetPool = enemyS;
                break;
            case "ItemCoin":
                targetPool = itemCoin;
                break;
            case "ItemPower":
                targetPool = itemPower;
                break;
            case "ItemBoom":
                targetPool = itemBoom;
                break;
            case "BulletPlayerA":
                targetPool = bulletPlayerA;
                break;
            case "BulletPlayerB":
                targetPool = bulletPlayerB;
                break;
            case "BulletEnemyA":
                targetPool = bulletEnemyA;
                break;
            case "BulletEnemyB":
                targetPool = bulletEnemyB;
                break;
            case "BulletFollower":
                targetPool = bulletFollower;
                break;
            case "BulletBossA":
                targetPool = bulletBossA;
                break;
            case "BulletBossB":
                targetPool = bulletBossB;
                break;
        }

        return targetPool;
    }
}

Object Manager 설정

//Bullet 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour {
    public int dmg;
    public bool isRotate;

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

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "BorderBullet") {
            gameObject.SetActive(false);
        }
    }
}

Enemy Bullet A의 Is Rotate 변수 체크

  • 이전에 만들어두었던 총알을 보스 패턴으로 사용하기 위해서, bool형 변수를 활용해 Enemy Bullet A를 스스로 돌아가는 총알로 만들어준다.

3. 보스 기본 로직

//Enemy 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public float maxShotDelay;
    public float curShotDelay;

    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;
    public ObjectManager objectManager;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;
    Animator anim;

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

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

    void OnEnable() {
        switch(enemyName) {
            case "L":
                health = 40;
                break;
            case "M":
                health = 10;
                break;
            case "S":
                health = 3;
                break;
        }
    }

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

        Fire();
        Reload();
    }

    void Fire() {
        if (curShotDelay < maxShotDelay)    //장전 중이라면
            return;

        if (enemyName == "S") {
            GameObject bullet = objectManager.MakeObj("BulletEnemyA");
            bullet.transform.position = transform.position;
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;    //목표물로 방향 = 목표물 위치 - 자신의 위치
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
        }
        else if (enemyName == "L") {
            GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
            bulletR.transform.position = transform.position + Vector3.right * 0.3f;
            GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
            bulletL.transform.position = transform.position + Vector3.left * 0.3f;
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();

            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);

            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
        }

        curShotDelay = 0;
    }

    void Reload() {
        curShotDelay += Time.deltaTime;
    }

    public void onHit(int dmg) {
        if (health <= 0)    //이미 죽은 상태라면
            return;

        health -= dmg;
        if (enemyName == "B") {
            anim.SetTrigger("OnHit");
        }
        else {
            spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
            Invoke("ReturnSprite", 0.1f);
        }

        if (health <= 0) {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;

            //랜덤 아이템 드랍. 보스는 드랍하지 않음
            int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
            if (ran < 3) {  //드랍 없음 30%
                //아이템 없음
            }
            else if (ran < 6) { //동전 드랍 30%
                GameObject itemCoin = objectManager.MakeObj("ItemCoin");
                itemCoin.transform.position = transform.position;
            }
            else if (ran < 8) {     //파워 드랍 20%
                GameObject itemPower = objectManager.MakeObj("ItemPower");
                itemPower.transform.position = transform.position;
            }
            else if (ran < 10) {    //붐 드랍 20%
                GameObject itemBoom = objectManager.MakeObj("ItemBoom");
                itemBoom.transform.position = transform.position;
            }

            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
    }

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

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") {   //보스가 아닌 적이 맵 경계로 가면
            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
        else if (collision.gameObject.tag == "PlayerBullet")
        {    //플레이어 총알에 닿으면
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            onHit(bullet.dmg);
            collision.gameObject.SetActive(false);  //플레이어 총알 삭제
        }
    }
}

Enemy B 설정
피해를 받으니 애니메이션이 발동하면서 바뀌고, 체력도 잘 깎인다.

  • 기존 Enemy의 스크립트를 재활용 하되, 일반적인 적들과는 조금 다르므로 if문의 조건으로 구분하였다.

4. 오브젝트 풀링 사용

스프라이트 Enemy B를 프리펩으로 만들고, 스프라이트는 제거

//ObjectManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectManager : MonoBehaviour {
    public GameObject enemyBPrefab;
    public GameObject enemyLPrefab;
    public GameObject enemyMPrefab;
    public GameObject enemySPrefab;

    public GameObject itemCoinPrefab;
    public GameObject itemPowerPrefab;
    public GameObject itemBoomPrefab;

    public GameObject bulletPlayerAPrefab;
    public GameObject bulletPlayerBPrefab;
    public GameObject bulletEnemyAPrefab;
    public GameObject bulletEnemyBPrefab;
    public GameObject bulletFollowerPrefab;
    public GameObject bulletBossAPrefab;
    public GameObject bulletBossBPrefab;

    GameObject[] enemyB;
    GameObject[] enemyL;
    GameObject[] enemyM;
    GameObject[] enemyS;

    GameObject[] itemCoin;
    GameObject[] itemPower;
    GameObject[] itemBoom;

    GameObject[] bulletPlayerA;
    GameObject[] bulletPlayerB;
    GameObject[] bulletEnemyA;
    GameObject[] bulletEnemyB;
    GameObject[] bulletFollower;
    GameObject[] bulletBossA;
    GameObject[] bulletBossB;

    GameObject[] targetPool;

    void Awake() {
        enemyB = new GameObject[1];
        enemyL = new GameObject[10];
        enemyM = new GameObject[10];
        enemyS = new GameObject[20];

        itemCoin = new GameObject[20];
        itemPower = new GameObject[10];
        itemBoom = new GameObject[10];

        bulletPlayerA = new GameObject[100];
        bulletPlayerB = new GameObject[100];
        bulletEnemyA = new GameObject[100];
        bulletEnemyB = new GameObject[100];
        bulletFollower = new GameObject[100];
        bulletBossA = new GameObject[50];
        bulletBossB = new GameObject[1000];

        Generate();
    }

    void Generate() {
        //적 기체
        for (int index = 0; index < enemyB.Length; index++) {
            enemyB[index] = Instantiate(enemyBPrefab);
            enemyB[index].SetActive(false);
        }
        for (int index = 0; index < enemyL.Length; index++) {
            enemyL[index] = Instantiate(enemyLPrefab);
            enemyL[index].SetActive(false);
        }
        for (int index = 0; index < enemyM.Length; index++) {
            enemyM[index] = Instantiate(enemyMPrefab);
            enemyM[index].SetActive(false);
        }
        for (int index = 0; index < enemyS.Length; index++) {
            enemyS[index] = Instantiate(enemySPrefab);
            enemyS[index].SetActive(false);
        }
        
        //아이템
        for (int index = 0; index < itemCoin.Length; index++) {
            itemCoin[index] = Instantiate(itemCoinPrefab);
            itemCoin[index].SetActive(false);
        }
        for (int index = 0; index < itemPower.Length; index++) {
            itemPower[index] = Instantiate(itemPowerPrefab);
            itemPower[index].SetActive(false);
        }
        for (int index = 0; index < itemBoom.Length; index++) {
            itemBoom[index] = Instantiate(itemBoomPrefab);
            itemBoom[index].SetActive(false);
        }

        //총알
        for (int index = 0; index < bulletPlayerA.Length; index++) {
            bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
            bulletPlayerA[index].SetActive(false);
        }
        for (int index = 0; index < bulletPlayerB.Length; index++) {
            bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
            bulletPlayerB[index].SetActive(false);
        }
        for (int index = 0; index < bulletEnemyA.Length; index++) {
            bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
            bulletEnemyA[index].SetActive(false);
        }
        for (int index = 0; index < bulletEnemyB.Length; index++) {
            bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
            bulletEnemyB[index].SetActive(false);
        }
        for (int index = 0; index < bulletFollower.Length; index++) {
            bulletFollower[index] = Instantiate(bulletFollowerPrefab);
            bulletFollower[index].SetActive(false);
        }
        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 "EnemyB":
                targetPool = enemyB;
                break;
            case "EnemyL":
                targetPool = enemyL;
                break;
            case "EnemyM":
                targetPool = enemyM;
                break;
            case "EnemyS":
                targetPool = enemyS;
                break;
            case "ItemCoin":
                targetPool = itemCoin;
                break;
            case "ItemPower":
                targetPool = itemPower;
                break;
            case "ItemBoom":
                targetPool = itemBoom;
                break;
            case "BulletPlayerA":
                targetPool = bulletPlayerA;
                break;
            case "BulletPlayerB":
                targetPool = bulletPlayerB;
                break;
            case "BulletEnemyA":
                targetPool = bulletEnemyA;
                break;
            case "BulletEnemyB":
                targetPool = bulletEnemyB;
                break;
            case "BulletFollower":
                targetPool = bulletFollower;
                break;
            case "BulletBossA":
                targetPool = bulletBossA;
                break;
            case "BulletBossB":
                targetPool = bulletBossB;
                break;
        }

        for (int index = 0; index < targetPool.Length; index++) {
            if (!targetPool[index].activeSelf) {    //비활성화된 오브젝트에 접근
                targetPool[index].SetActive(true);  //해당 오브젝트를 활성화 후
                return targetPool[index];   //오브젝트 반환
            }
        }

        return null;
    }

    public GameObject[] GetPool(string type) {
        switch (type) {
            case "EnemyB":
                targetPool = enemyB;
                break;
            case "EnemyL":
                targetPool = enemyL;
                break;
            case "EnemyM":
                targetPool = enemyM;
                break;
            case "EnemyS":
                targetPool = enemyS;
                break;
            case "ItemCoin":
                targetPool = itemCoin;
                break;
            case "ItemPower":
                targetPool = itemPower;
                break;
            case "ItemBoom":
                targetPool = itemBoom;
                break;
            case "BulletPlayerA":
                targetPool = bulletPlayerA;
                break;
            case "BulletPlayerB":
                targetPool = bulletPlayerB;
                break;
            case "BulletEnemyA":
                targetPool = bulletEnemyA;
                break;
            case "BulletEnemyB":
                targetPool = bulletEnemyB;
                break;
            case "BulletFollower":
                targetPool = bulletFollower;
                break;
            case "BulletBossA":
                targetPool = bulletBossA;
                break;
            case "BulletBossB":
                targetPool = bulletBossB;
                break;
        }

        return targetPool;
    }
}

ObjectManager 설정

//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.IO;    //파일 읽기용 라이브러리. C#에서 지원

public class GameManager : MonoBehaviour {
    string[] enemyObjs;
    public Transform[] spawnPoints;
    public GameObject player;
    public Text scoreText;
    public Image[] lifeImage;
    public Image[] boomImage;
    public GameObject gameOverSet;
    public ObjectManager objectManager;

    public float nextSpawnDelay;
    public float curSpawnDelay;

    public List<Spawn> spawnList;
    public int spawnIndex;
    public bool spawnEnd;

    void Awake() {
        enemyObjs = new string[] { "EnemyS", "EnemyM", "EnemyL", "EnemyB" };
        spawnList = new List<Spawn>();
        ReadSpawnFile();
    }

    void ReadSpawnFile() {
        //변수 초기화
        spawnList.Clear();
        spawnIndex = 0;
        spawnEnd = false;

        //텍스트 파일 읽기
        TextAsset textFile = Resources.Load("Stage 0") as TextAsset;    //텍스트 파일 Stage 0 불러오기
        StringReader stringReader = new StringReader(textFile.text);

        while (stringReader != null) {
            string line = stringReader.ReadLine();

            if (line == null)
                break;

            //스폰 데이터 생성
            Spawn spawnData = new Spawn();
            spawnData.delay = float.Parse(line.Split(',')[0]);
            spawnData.type = line.Split(',')[1];
            spawnData.point = int.Parse(line.Split(',')[2]);
            spawnList.Add(spawnData);
        }

        //텍스트 파일 닫기
        stringReader.Close();

        //첫번째 스폰 딜레이 적용
        nextSpawnDelay = spawnList[0].delay;
    }

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

        if (curSpawnDelay > nextSpawnDelay && !spawnEnd) {
            SpawnEnemy();
            curSpawnDelay = 0;
        }

        //UI 점수 업데이트
        Player playerLogic = player.GetComponent<Player>();
        scoreText.text = string.Format("{0:n0}", playerLogic.score);
    }

    void SpawnEnemy() {
        int enemyIndex = 0;
        switch(spawnList[spawnIndex].type) {
            case "S":
                enemyIndex = 0;
                break;
            case "M":
                enemyIndex = 1;
                break;
            case "L":
                enemyIndex = 2;
                break;
            case "B":
                enemyIndex = 3;
                break;
        }
        int enemyPoint = spawnList[spawnIndex].point;
        GameObject enemy = objectManager.MakeObj(enemyObjs[enemyIndex]);
        enemy.transform.position = spawnPoints[enemyPoint].position;
        Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
        Enemy enemyLogic = enemy.GetComponent<Enemy>();
        enemyLogic.player = player;
        enemyLogic.objectManager = objectManager;

        if (enemyPoint == 5 || enemyPoint == 6) {   //오른쪽 포인트
            enemy.transform.Rotate(Vector3.back * 90);  //적 기체 스프라이트 돌리기
            rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
        }
        else if (enemyPoint == 7 || enemyPoint == 8) {  //왼쪽 포인트
            enemy.transform.Rotate(Vector3.back * (-90));  //적 기체 스프라이트 돌리기
            rigid.velocity = new Vector2(enemyLogic.speed, -1);
        }
        else {  //중앙 앞쪽 포인트
            rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
        }

        //스폰 인덱스 증가
        spawnIndex++;
        if (spawnIndex == spawnList.Count) {
            spawnEnd = true;
            return;
        }

        //다음 리스폰 딜레이 갱신
        nextSpawnDelay = spawnList[spawnIndex].delay;
    }

    public void UpdateLifeIcon(int life) {
        //UI 라이프 모두 투명화
        for (int index = 0; index < 3; index++) {
            lifeImage[index].color = new Color(1, 1, 1, 0);
        }

        //UI 남은 라이프 수만큼 활성화
        for (int index = 0; index < life; index++) {
            lifeImage[index].color = new Color(1, 1, 1, 1);
        }
    }

    public void UpdateBoomIcon(int boom) {
        //UI 붐 모두 투명화
        for (int index = 0; index < 3; index++) {
            boomImage[index].color = new Color(1, 1, 1, 0);
        }

        //UI 남은 붐 수만큼 활성화
        for (int index = 0; index < boom; index++) {
            boomImage[index].color = new Color(1, 1, 1, 1);
        }
    }

    public void RespawnPlayer() {
        Invoke("RespawnPlayerExe", 2f);
    }

    void RespawnPlayerExe() {
        player.transform.position = Vector3.down * 3.5f;    //플레이어 위치 초기화
        player.SetActive(true);

        Player playerLogic = player.GetComponent<Player>();
        playerLogic.isHit = false;
    }

    public void GameOver() {
        gameOverSet.SetActive(true);
    }

    public void GameRetry() {
        SceneManager.LoadScene(0);
    }
}

테스트를 위해 스테이지 텍스트 변경
보스가 생성되어 내려오는 모습
하지만 계속 내려가서 지나가버리는 문제가 있다


5. 패턴 흐름

//Enemy 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public float maxShotDelay;
    public float curShotDelay;
    public int patternIndex;
    public int curPatternCount;
    public int[] maxPatternCount;

    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;
    public ObjectManager objectManager;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;
    Animator anim;

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

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

    void OnEnable() {
        switch(enemyName) {
            case "B":
                health = 3000;
                Invoke("Stop", 2);
                break;
            case "L":
                health = 40;
                break;
            case "M":
                health = 10;
                break;
            case "S":
                health = 3;
                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() {
        Debug.Log("앞으로 4발 발사.");
        curPatternCount++;

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

    void FireShot() {
        Debug.Log("플레이어 방향으로 발사.");
        curPatternCount++;

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

    void FireArc() {
        Debug.Log("부채 모양으로 발사.");
        curPatternCount++;

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

    void FireAround() {
        Debug.Log("원 형태로 전체 공격.");
        curPatternCount++;

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

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

        Fire();
        Reload();
    }

    void Fire() {
        if (curShotDelay < maxShotDelay)    //장전 중이라면
            return;

        if (enemyName == "S") {
            GameObject bullet = objectManager.MakeObj("BulletEnemyA");
            bullet.transform.position = transform.position;
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;    //목표물로 방향 = 목표물 위치 - 자신의 위치
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
        }
        else if (enemyName == "L") {
            GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
            bulletR.transform.position = transform.position + Vector3.right * 0.3f;
            GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
            bulletL.transform.position = transform.position + Vector3.left * 0.3f;
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();

            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);

            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
        }

        curShotDelay = 0;
    }

    void Reload() {
        curShotDelay += Time.deltaTime;
    }

    public void onHit(int dmg) {
        if (health <= 0)    //이미 죽은 상태라면
            return;

        health -= dmg;
        if (enemyName == "B") {
            anim.SetTrigger("OnHit");
        }
        else {
            spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
            Invoke("ReturnSprite", 0.1f);
        }

        if (health <= 0) {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;

            //랜덤 아이템 드랍. 보스는 드랍하지 않음
            int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
            if (ran < 3) {  //드랍 없음 30%
                //아이템 없음
            }
            else if (ran < 6) { //동전 드랍 30%
                GameObject itemCoin = objectManager.MakeObj("ItemCoin");
                itemCoin.transform.position = transform.position;
            }
            else if (ran < 8) {     //파워 드랍 20%
                GameObject itemPower = objectManager.MakeObj("ItemPower");
                itemPower.transform.position = transform.position;
            }
            else if (ran < 10) {    //붐 드랍 20%
                GameObject itemBoom = objectManager.MakeObj("ItemBoom");
                itemBoom.transform.position = transform.position;
            }

            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
    }

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

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") {   //보스가 아닌 적이 맵 경계로 가면
            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
        else if (collision.gameObject.tag == "PlayerBullet")
        {    //플레이어 총알에 닿으면
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            onHit(bullet.dmg);
            collision.gameObject.SetActive(false);  //플레이어 총알 삭제
        }
    }
}

Enemy B 설정
더이상 보스가 내려오지 않고, 패턴도 콘솔창에 잘 출력된다.
시간이 지나니 다시 패턴이 처음부터 시작되었다


6. 패턴 구현

//Enemy 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public float maxShotDelay;
    public float curShotDelay;
    public int patternIndex;
    public int curPatternCount;
    public int[] maxPatternCount;

    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;
    public ObjectManager objectManager;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;
    Animator anim;

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

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

    void OnEnable() {
        switch(enemyName) {
            case "B":
                health = 3000;
                Invoke("Stop", 2);
                break;
            case "L":
                health = 40;
                break;
            case "M":
                health = 10;
                break;
            case "S":
                health = 3;
                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() {
        //총알 앞으로 4발 발사
        GameObject bulletR = objectManager.MakeObj("BulletBossA");
        bulletR.transform.position = transform.position + Vector3.right * 0.65f + Vector3.down * 1;
        GameObject bulletRR = objectManager.MakeObj("BulletBossA");
        bulletRR.transform.position = transform.position + Vector3.right * 0.8f + Vector3.down * 1;
        GameObject bulletL = objectManager.MakeObj("BulletBossA");
        bulletL.transform.position = transform.position + Vector3.left * 0.65f + Vector3.down * 1;
        GameObject bulletLL = objectManager.MakeObj("BulletBossA");
        bulletLL.transform.position = transform.position + Vector3.left * 0.8f + Vector3.down * 1;

        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);

        //패턴 횟수 갱신
        curPatternCount++;

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

    void FireShot() {
        //플레이어를 향해 방사형 발사
        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);
        }

        //패턴 횟수 갱신
        curPatternCount++;

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

    void FireArc() {
        //부채 형태로 연속 발사
        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 * 3, ForceMode2D.Impulse);

        //패턴 횟수 갱신
        curPatternCount++;

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

    void FireAround() {
        //원 형태로 광역 공격
        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);
        }

        //패턴 횟수 갱신
        curPatternCount++;

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

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

        Fire();
        Reload();
    }

    void Fire() {
        if (curShotDelay < maxShotDelay)    //장전 중이라면
            return;

        if (enemyName == "S") {
            GameObject bullet = objectManager.MakeObj("BulletEnemyA");
            bullet.transform.position = transform.position;
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;    //목표물로 방향 = 목표물 위치 - 자신의 위치
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
        }
        else if (enemyName == "L") {
            GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
            bulletR.transform.position = transform.position + Vector3.right * 0.3f;
            GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
            bulletL.transform.position = transform.position + Vector3.left * 0.3f;
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();

            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);

            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
        }

        curShotDelay = 0;
    }

    void Reload() {
        curShotDelay += Time.deltaTime;
    }

    public void onHit(int dmg) {
        if (health <= 0)    //이미 죽은 상태라면
            return;

        health -= dmg;
        if (enemyName == "B") {
            anim.SetTrigger("OnHit");
        }
        else {
            spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
            Invoke("ReturnSprite", 0.1f);
        }

        if (health <= 0) {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;

            //랜덤 아이템 드랍. 보스는 드랍하지 않음
            int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
            if (ran < 3) {  //드랍 없음 30%
                //아이템 없음
            }
            else if (ran < 6) { //동전 드랍 30%
                GameObject itemCoin = objectManager.MakeObj("ItemCoin");
                itemCoin.transform.position = transform.position;
            }
            else if (ran < 8) {     //파워 드랍 20%
                GameObject itemPower = objectManager.MakeObj("ItemPower");
                itemPower.transform.position = transform.position;
            }
            else if (ran < 10) {    //붐 드랍 20%
                GameObject itemBoom = objectManager.MakeObj("ItemBoom");
                itemBoom.transform.position = transform.position;
            }

            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
    }

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

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") {   //보스가 아닌 적이 맵 경계로 가면
            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
        else if (collision.gameObject.tag == "PlayerBullet")
        {    //플레이어 총알에 닿으면
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            onHit(bullet.dmg);
            collision.gameObject.SetActive(false);  //플레이어 총알 삭제
        }
    }
}

패턴 1
패턴 2
패턴 3
패턴 4까지 모두 잘 사용한다

  • Mathf.Cos(float) : 삼각함수 cos.
  • Mathf.Sin(float) : 삼각함수 sin. Cos 대신 사용하면 패턴 첫 시작이 반대 방향으로 된다.
  • Mathf.PI : 원주율 상수(3.14...).
  • 추가적으로, 패턴 3의 경우 총알이 짝수개로 있으면 같은 위치에 총알이 발사되어 플레이어가 가만히 있으면 다 피할 수 있다. 그래서 100개에서 99개로 총알 개수를 홀수로 바꾸어 주었다.

번외. 보스 처치 후

보스를 처치해도 계속 패턴을 사용하는 문제가 있다.
플레이어가 보스와 충돌하면 보스가 사라지는 문제도 있다

//Enemy 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public float maxShotDelay;
    public float curShotDelay;
    public int patternIndex;
    public int curPatternCount;
    public int[] maxPatternCount;

    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;
    public ObjectManager objectManager;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;
    Animator anim;

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

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

    void OnEnable() {
        switch(enemyName) {
            case "B":
                health = 3000;
                Invoke("Stop", 2);
                break;
            case "L":
                health = 40;
                break;
            case "M":
                health = 10;
                break;
            case "S":
                health = 3;
                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() {
        if (health <= 0)    //이미 죽은 상태라면 멈추기
            return;

        //총알 앞으로 4발 발사
        GameObject bulletR = objectManager.MakeObj("BulletBossA");
        bulletR.transform.position = transform.position + Vector3.right * 0.65f + Vector3.down * 1;
        GameObject bulletRR = objectManager.MakeObj("BulletBossA");
        bulletRR.transform.position = transform.position + Vector3.right * 0.8f + Vector3.down * 1;
        GameObject bulletL = objectManager.MakeObj("BulletBossA");
        bulletL.transform.position = transform.position + Vector3.left * 0.65f + Vector3.down * 1;
        GameObject bulletLL = objectManager.MakeObj("BulletBossA");
        bulletLL.transform.position = transform.position + Vector3.left * 0.8f + Vector3.down * 1;

        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);

        //패턴 횟수 갱신
        curPatternCount++;

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

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

        //패턴 횟수 갱신
        curPatternCount++;

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

    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 * 3, ForceMode2D.Impulse);

        //패턴 횟수 갱신
        curPatternCount++;

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

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

        //패턴 횟수 갱신
        curPatternCount++;

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

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

        Fire();
        Reload();
    }

    void Fire() {
        if (curShotDelay < maxShotDelay)    //장전 중이라면
            return;

        if (enemyName == "S") {
            GameObject bullet = objectManager.MakeObj("BulletEnemyA");
            bullet.transform.position = transform.position;
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;    //목표물로 방향 = 목표물 위치 - 자신의 위치
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
        }
        else if (enemyName == "L") {
            GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
            bulletR.transform.position = transform.position + Vector3.right * 0.3f;
            GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
            bulletL.transform.position = transform.position + Vector3.left * 0.3f;
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();

            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);

            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
        }

        curShotDelay = 0;
    }

    void Reload() {
        curShotDelay += Time.deltaTime;
    }

    public void onHit(int dmg) {
        if (health <= 0)    //이미 죽은 상태라면
            return;

        health -= dmg;
        if (enemyName == "B") {
            anim.SetTrigger("OnHit");
        }
        else {
            spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
            Invoke("ReturnSprite", 0.1f);
        }

        if (health <= 0) {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;

            //랜덤 아이템 드랍. 보스는 드랍하지 않음
            int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
            if (ran < 3) {  //드랍 없음 30%
                //아이템 없음
            }
            else if (ran < 6) { //동전 드랍 30%
                GameObject itemCoin = objectManager.MakeObj("ItemCoin");
                itemCoin.transform.position = transform.position;
            }
            else if (ran < 8) {     //파워 드랍 20%
                GameObject itemPower = objectManager.MakeObj("ItemPower");
                itemPower.transform.position = transform.position;
            }
            else if (ran < 10) {    //붐 드랍 20%
                GameObject itemBoom = objectManager.MakeObj("ItemBoom");
                itemBoom.transform.position = transform.position;
            }

            if (enemyName == "B")
                CancelInvoke();
            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
    }

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

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") {   //보스가 아닌 적이 맵 경계로 가면
            gameObject.SetActive(false);
            transform.rotation = Quaternion.identity;
        }
        else if (collision.gameObject.tag == "PlayerBullet")
        {    //플레이어 총알에 닿으면
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            onHit(bullet.dmg);
            collision.gameObject.SetActive(false);  //플레이어 총알 삭제
        }
    }
}
//Player 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour {
    public GameManager gameManager;
    public ObjectManager objectManager;
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject boomEffect;
    public GameObject[] followers;
    Animator anim;

    public int life;
    public int score;
    public float speed;
    public int maxPower;
    public int power;
    public int maxBoom;
    public int boom;
    public float maxShotDelay;
    public float curShotDelay;

    public bool isTouchTop;
    public bool isTouchBottom;
    public bool isTouchLeft;
    public bool isTouchRight;
    public bool isHit;
    public bool isBoomTime;

    void Awake() {
        anim = GetComponent<Animator>();
    }

    void Update() {
        Move();
        Fire();
        Boom();
        Reload();
    }

    void Move() {   //플레이어 이동 함수
        float h = Input.GetAxisRaw("Horizontal");
        if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
            h = 0;

        float v = Input.GetAxisRaw("Vertical");
        if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
            v = 0;

        Vector3 curPos = transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;

        transform.position = curPos + nextPos;

        if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
            anim.SetInteger("Input", (int)h);
    }

    void Fire() {   //플레이어 총알 발사 함수
        if (!Input.GetButton("Fire1"))  //Ctrl 키, 마우스 좌클릭
            return;

        if (curShotDelay < maxShotDelay)    //장전 중이라면
            return;

        switch(power) {
            case 1:
                GameObject bullet = objectManager.MakeObj("BulletPlayerA");
                bullet.transform.position = transform.position;
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
                bulletR.transform.position = transform.position + Vector3.right * 0.1f;
                GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
                bulletL.transform.position = transform.position + Vector3.left * 0.1f;
                Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
                rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            default:
                GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
                bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
                GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
                bulletCC.transform.position = transform.position;
                GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
                bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
                Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
                rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
        }
        
        curShotDelay = 0;
    }

    void Reload() {
        curShotDelay += Time.deltaTime;
    }

    void Boom() {
        if (!Input.GetButton("Fire2"))  //Alt 키, 마우스 우클릭
            return;

        if (isBoomTime) //Boom 재사용 대기시간 중이라면
            return;

        if (boom == 0)  //필살기가 없다면
            return;

        boom--;
        isBoomTime = true;
        gameManager.UpdateBoomIcon(boom);

        //Boom 활성화
        boomEffect.SetActive(true);
        Invoke("OffBoomEffect", 4f);

        //모든 적 처치
        GameObject[] enemiesL = objectManager.GetPool("EnemyL");
        GameObject[] enemiesM = objectManager.GetPool("EnemyM");
        GameObject[] enemiesS = objectManager.GetPool("EnemyS");
        for (int index = 0; index < enemiesL.Length; index++)
            if (enemiesL[index].activeSelf) {   //활성화된 적만 처치
                Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
                enemyLogic.onHit(1000);
            }
        for (int index = 0; index < enemiesM.Length; index++)
            if (enemiesM[index].activeSelf) {   //활성화된 적만 처치
                Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
                enemyLogic.onHit(1000);
            }
        for (int index = 0; index < enemiesS.Length; index++)
            if (enemiesS[index].activeSelf) {   //활성화된 적만 처치
                Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
                enemyLogic.onHit(1000);
            }

        //모든 적 총알 제거
        GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
        GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
        for (int index = 0; index < bulletsA.Length; index++)
            if (bulletsA[index].activeSelf)     //활성화된 총알만 제거
                bulletsA[index].SetActive(false);
        for (int index = 0; index < bulletsB.Length; index++)
            if (bulletsB[index].activeSelf)     //활성화된 총알만 제거
                bulletsB[index].SetActive(false);
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Border") {
            switch(collision.gameObject.name) {
                case "Top":
                    isTouchTop = true;
                    break;
                case "Bottom":
                    isTouchBottom = true;
                    break;
                case "Left":
                    isTouchLeft = true;
                    break;
                case "Right":
                    isTouchRight = true;
                    break;
            }
        }
        else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
            if (isHit)  //이미 맞으면 다시 맞지 않도록 return
                return;

            isHit = true;
            life--;
            gameManager.UpdateLifeIcon(life);

            if (life == 0) {
                gameManager.GameOver();
            }
            else {
                gameManager.RespawnPlayer();
            }
            gameObject.SetActive(false);
            if (collision.gameObject.tag == "Enemy") {
                Enemy enemyObject = collision.gameObject.GetComponent<Enemy>();
                if (enemyObject.enemyName == "B")
                    return;
                else
                    collision.gameObject.SetActive(false);
            }
        }
        else if (collision.gameObject.tag == "Item") {
            Item item = collision.gameObject.GetComponent<Item>();
            switch (item.type) {
                case "Coin":
                    score += 1000;
                    break;
                case "Power":
                    if (power == maxPower)
                        score += 500;
                    else {
                        power++;
                        AddFollower();
                    }
                    break;
                case "Boom":
                    if (boom == maxBoom)
                        score += 500;
                    else {
                        boom++;
                        gameManager.UpdateBoomIcon(boom);
                    }
                    break;
            }

            collision.gameObject.SetActive(false);
        }
    }

    void OffBoomEffect() {
        boomEffect.SetActive(false);
        isBoomTime = false;
    }

    void AddFollower() {
        if (power == 4)
            followers[0].SetActive(true);
        else if (power == 5)
            followers[1].SetActive(true);
        else if (power == 6)
            followers[2].SetActive(true);

    }

    void OnTriggerExit2D(Collider2D collision) {
        if (collision.gameObject.tag == "Border") {
            switch (collision.gameObject.name) {
                case "Top":
                    isTouchTop = false;
                    break;
                case "Bottom":
                    isTouchBottom = false;
                    break;
                case "Left":
                    isTouchLeft = false;
                    break;
                case "Right":
                    isTouchRight = false;
                    break;
            }
        }
    }
}

이제 플레이어가 몸이 닿아도 보스는 사라지지 않는다
보스를 처치해도
더이상 보스가 패턴을 사용하지 않는다

  • 보스가 죽어도 패턴을 계속 사용하는 것은 Enemy에서 위에서 추가했던 4가지 패턴 함수 앞에 health가 0보다 같거나 작으면 return;하도록 if문을 하나 추가하고, CancleInvoke(); 함수를 onHit의 if (health <= 0) { .. }에 넣음으로써 Think함수나 패턴 함수가 Invoke하는 것을 막으면서 해결하였다.
    • CancleInvoke에 if문을 넣어서 보스(B)를 구분한 이유는, 일반 적 기체는 보스와는 달리 Invoke함수를 통해 피해 입은 스프라이트가 되었다가 0.1초 뒤에 돌아오는 형식으로 되어있어서, 구분하지 않으면 피해 입은 스프라이트가 된채로 다시 활성화되기때문이다.
  • 플레이어가 보스에 닿으면 보스도 비활성화 되는 것은 if 문으로 부딪힌 오브젝트의 tag가 Enemy일때, Enemy Name을 확인해서 보스(B)라면 비활성화 하지 않고 바로 return;하도록 만들고 보스가 아니라면 비활성화 하는 방식으로 해결하였다.