기록 보관소

[Unity/유니티] 기초-2D 종스크롤 슈팅: 아이템과 필살기 구현하기[B32] 본문

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

[Unity/유니티] 기초-2D 종스크롤 슈팅: 아이템과 필살기 구현하기[B32]

JongHoon 2022. 3. 4. 23:27

개요

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

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

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 종스크롤 슈팅: 아이템과 필살기 구현하기[B32]

1. 준비하기

아이템으로 사용할 스프라이트 3개 추가

//Item 스크립트 파일

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

public class Item : MonoBehaviour {
    public string type;
    Rigidbody2D rigid;
    
    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        rigid.velocity = Vector2.down * 0.1f;
    }
}

Item Power 설정
Item Boom 설정
Item Coin 설정
Boom 애니메이션 생성
Power 애니메이션 생성
Coin 애니메이션 생성
애니메이션이 적용되면서 아래로 내려가는 모습
이제 플레이어가 트리거를 통해서 아이템과 상호작용 하도록 Item 태그를 만들어서 설정해준다


2. 충돌 로직

//Player 스크립트 파일

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

public class Player : MonoBehaviour {
    public GameManager manager;
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    Animator anim;

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

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

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

    void Update() {
        Move();
        Fire();
        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 = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                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;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.35f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.35f, transform.rotation);
                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 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--;
            manager.UpdateLifeIcon(life);

            if (life == 0) {
                manager.GameOver();
            }
            else {
                manager.RespawnPlayer();
            }
            gameObject.SetActive(false);
            Destroy(collision.gameObject);
        }
        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++;
                    break;
                case "Boom":

                    break;
            }
        }
    }

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

Player 설정
파워 아이템과 동전에 닿으니 점수와 파워가 올랐다

  • 추가 작업이 필요한 필살기(Item Boom)를 제외한 파워 아이템과 동전은 간단하게 태그를 활용해서 OnTriggerEnter2D에서 작업을 했다. 하지만 아직 아이템에 닿아도 사라지지 않는 등 일부 미완성으로, 필살기 작업이 끝나면 로직을 완성할 것으로 보인다.
    • 다만 아이템이 사라지게 하는건 그냥 간단하게 Switch 문이 끝난 다음에 Destroy(collision.gameObject);를 입력해주기만 하면 된다.
  • 또한 Power를 float형 변수에서 int형으로 바꾸어주었다. 변수 타입이 바뀌니 0으로 초기화되어서 다시 설정해주었다.

3. 필살기

필살기 Boom 스프라이트 설정

  • 필살기 크기를 키우기 위해서 Pixels Per Unit의 값을 24에서 15정도로 내렸다.

Boom 스프라이트 추가
BoomEffect 애니메이션 생성
애니메이션 속도를 3으로 설정해서 빠르게 설정한다
약간 투명화하고 비활성화 한다.

//Player 스크립트 파일

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

public class Player : MonoBehaviour {
    public GameManager manager;
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject boomEffect;
    Animator anim;

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

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

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

    void Update() {
        Move();
        Fire();
        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 = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                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;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.35f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.35f, transform.rotation);
                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 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--;
            manager.UpdateLifeIcon(life);

            if (life == 0) {
                manager.GameOver();
            }
            else {
                manager.RespawnPlayer();
            }
            gameObject.SetActive(false);
            Destroy(collision.gameObject);
        }
        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++;
                    break;
                case "Boom":
                    //Boom 활성화
                    boomEffect.SetActive(true);
                    Invoke("OffBoomEffect", 4f);

                    //모든 적 처치
                    GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
                    for (int index = 0; index < enemies.Length; index++) {
                        Enemy enemyLogic = enemies[index].GetComponent<Enemy>();
                        enemyLogic.onHit(1000);
                    }

                    //모든 적 총알 제거
                    GameObject[] bullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
                    for (int index = 0; index < enemies.Length; index++) {
                        Destroy(bullets[index]);
                    }

                    break;
            }

            Destroy(collision.gameObject);
        }
    }

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

    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 스크립트 파일

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 player;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;

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

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

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

        if (enemyName == "S") {
            GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
            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 = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
            GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);

            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) {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

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

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

    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.dmg);
            Destroy(collision.gameObject);  //플레이어 총알 삭제
        }
    }
}
  • Enemy 스크립트는 Boom 사용시 적 기체를 Destroy하는 것이 아닌 모든 적에게 1000의 큰 피해를 주어 점수를 얻도록 하기위해서 onHit 메소드를 public으로 변경해주었다.

 

 

Boom Effect에 Boom 추가
동전을 먹으니 점수 1000점을 얻었다
파워를 먹으니 총알이 2개, 즉 파워가 2단계가 되었다.
필살기 Boom을 먹기 직전
Boom을 먹은 직후. 모든 적이 처치되어 점수를 얻었고, 총알도 사라졌다.
시간이 지나니 이펙트도 사라졌다

  • GameObject.FindGameObjectsWithTag(string tag) : 태그로 장면(Scene)의 모든 오브젝트를 추출
  • 다만 이렇게 되면 플레이어가 필살기를 쓰고 싶을때 쓰지 못하고 아이템을 먹은 순간 바로 자동으로 사용하게 된다. 이를 바꿔보자.

Boom Icon 3개 추가. 앵커는 오른쪽 상단으로 잡는다

//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

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

    public float maxSpawnDelay;
    public float curSpawnDelay;

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

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

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

    void SpawnEnemy() {
        int ranEnemy = Random.Range(0, 3);
        int ranPoint = Random.Range(0, 9);
        GameObject enemy = Instantiate(enemyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);
        Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
        Enemy enemyLogic = enemy.GetComponent<Enemy>();
        enemyLogic.player = player;

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

    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);
    }
}
//Player 스크립트 파일

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

public class Player : MonoBehaviour {
    public GameManager manager;
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject boomEffect;
    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 = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                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;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.35f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.35f, transform.rotation);
                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;
        manager.UpdateBoomIcon(boom);

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

        //모든 적 처치
        GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
        for (int index = 0; index < enemies.Length; index++)
        {
            Enemy enemyLogic = enemies[index].GetComponent<Enemy>();
            enemyLogic.onHit(1000);
        }

        //모든 적 총알 제거
        GameObject[] bullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
        for (int index = 0; index < enemies.Length; index++) {
            Destroy(bullets[index]);
        }
    }

    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--;
            manager.UpdateLifeIcon(life);

            if (life == 0) {
                manager.GameOver();
            }
            else {
                manager.RespawnPlayer();
            }
            gameObject.SetActive(false);
            Destroy(collision.gameObject);
        }
        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++;
                    break;
                case "Boom":
                    if (boom == maxBoom)
                        score += 500;
                    else {
                        boom++;
                        manager.UpdateBoomIcon(boom);
                    }
                    break;
            }

            Destroy(collision.gameObject);
        }
    }

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

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

Player 설정
GameManager 설정
게임 시작시 필살기 아이콘은 투명해야하므로 알파값을 0으로 만들어준다.
Boom 아이템에 닿으니 하나가 생겼다
적이 나타나고
Alt키를 누르니 아이콘과 기존의 적들이 모두 없어졌다.


4. 아이템 드랍

이제 완성된 아이템들은 모두 프리펩으로 만들고 삭제해준다

//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 Sprite[] sprites;

    SpriteRenderer spriteRenderer;

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

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

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

        if (enemyName == "S") {
            GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
            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 = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
            GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);

            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) {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

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

            //랜덤 아이템 드랍
            int ran = Random.Range(0, 10);
            if (ran < 3)    //드랍 없음 30%
                Debug.Log("Not Item");
            else if (ran < 6)   //동전 드랍 30%
                Instantiate(itemCoin, transform.position, itemCoin.transform.rotation);
            else if (ran < 8)   //파워 드랍 20%
                Instantiate(itemPower, transform.position, itemPower.transform.rotation);
            else if (ran < 10)  //붐 드랍 20%
                Instantiate(itemBoom, transform.position, itemBoom.transform.rotation);

            Destroy(gameObject);
        }
    }

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

    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.dmg);
            Destroy(collision.gameObject);  //플레이어 총알 삭제
        }
    }
}

각 Enemy에 아이템 프리펩들을 채워넣는다
Enemy S를 처치하니 나온 동전
그런데 아이템이 동시에 2개가 나오는 경우가 생겼다

  • 이 아이템 2개가 동시에 나온 이유는, 플레이어의 파워가 2 이상 올라가면 탄환이 2개 이상이 되어 onHit가 두번 발동해서 발생한 것이다. 이전 글에서 다뤘던 라이프가 2개 동시에 깎이는 상황과 같다.

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 GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;
    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;

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

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

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

        if (enemyName == "S") {
            GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
            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 = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
            GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);

            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;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

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

            //랜덤 아이템 드랍
            int ran = Random.Range(0, 10);
            if (ran < 3)    //드랍 없음 30%
                Debug.Log("Not Item");
            else if (ran < 6)   //동전 드랍 30%
                Instantiate(itemCoin, transform.position, itemCoin.transform.rotation);
            else if (ran < 8)   //파워 드랍 20%
                Instantiate(itemPower, transform.position, itemPower.transform.rotation);
            else if (ran < 10)  //붐 드랍 20%
                Instantiate(itemBoom, transform.position, itemBoom.transform.rotation);

            Destroy(gameObject);
        }
    }

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

    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.dmg);
            Destroy(collision.gameObject);  //플레이어 총알 삭제
        }
    }
}

계속 플레이 해봐도 더이상 같은 현상이 발생하지 않는다

  • 굉장히 간단하게 onHit 메소드 앞에 적 기체의 체력을 if문에 사용해서 이미 체력이 0이하라면, 즉 이미 죽은 상태라면 바로 메소드를 종료시켜 아이템을 생성하지 않도록 만들었다.