기록 보관소

[Unity/유니티] 기초-2D 종스크롤 슈팅: 적 전투와 피격 이벤트 만들기[B30] 본문

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

[Unity/유니티] 기초-2D 종스크롤 슈팅: 적 전투와 피격 이벤트 만들기[B30]

JongHoon 2022. 3. 2. 23:20

개요

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

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

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 종스크롤 슈팅: 적 전투와 피격 이벤트 만들기[B30]

1. 적 생성 추가

이전 포스트에서 만들어두었던 포인트를 복사해서 옆에도 배치해 준다
GameManager에 추가

//GameManager 스크립트 파일

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

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

    public float maxSpawnDelay;
    public float curSpawnDelay;

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

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

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

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

    }
}
//Enemy 스크립트 파일

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

public class Enemy : MonoBehaviour {
    public float speed;
    public int health;

    public Sprite[] sprites;

    SpriteRenderer spriteRenderer;

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

    void onHit(int dmg) {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

        if (health <= 0)
            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);  //플레이어 총알 삭제
        }

    }
}

실행하니 옆에서도 잘 생성된다

  • 이제 포인트가 옆에도 추가되었으므로, 포인트를 관리하는 GameManager에서 랜덤하게 주어지는 포인트 범위를 늘려야하며, 더이상 적 기체가 무조건 아래로만 내려가도록 하면 안되고 옆으로 이동할 수 있도록 해야한다. 그래서 Enemey 스크립트 파일에서 무조건 아래로 이동했던 로직을 아예 다 지우고, GameManager에서 이들을 불러와 포인트 위치에 따라 스프라이트와 이동 방향을 바뀌게 한다.

2. 적 공격

적 총알 프리펩 생성을 위해 기존에 만들어뒀던 총알을 복사한다
Open Prefab 클릭
Scene에 프리펩 화면이 떴다. 이제 프리펩 복사본 수정도 가능하다.
프리펩 수정 완료
다른 타입의 총알도 만들어 준다
EnemyBullet 태그 추가 및 적용

//GameManager 스크립트 파일

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

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

    public float maxSpawnDelay;
    public float curSpawnDelay;

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

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

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

    }
}
//Enemy 스크립트 파일

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

public class Enemy : MonoBehaviour {
    public string enemyName;
    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 * 10, 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 * 10, ForceMode2D.Impulse);
            rigidL.AddForce(dirVecL.normalized * 10, ForceMode2D.Impulse);
        }

        curShotDelay = 0;
    }

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

    void onHit(int dmg) {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

        if (health <= 0)
            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);  //플레이어 총알 삭제
        }

    }
}

Player 넣어주기
이름과 재장전 시간, 총알을 입력해준다.
적이 총알을 발사하는 모습

  • Vector3 Vec.normalized : 벡터가 단위 값(1)로 변환된 변수. 방향은 그대로 유지하되 크기는 1로 단위 벡터가 된다.
  • Player를 GameManager에 굳이 넣어서 연결시켜준 이유는, 프리펩은 이미 Scene에 올라온 오브젝트에 접근이 불가능하기 때문이다. 따라서 GameManager를 통해서 적이 생성된 후에 플레이어를 넘겨줌으로써 해결해준다.

3. 피격 이벤트

//GameManager 스크립트 파일

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

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

    public float maxSpawnDelay;
    public float curSpawnDelay;

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

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

    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 RespawnPlayer() {
        Invoke("RespawnPlayerExe", 2f);
    }

    void RespawnPlayerExe() {
        player.transform.position = Vector3.down * 3.5f;    //플레이어 위치 초기화
        player.SetActive(true);
    }
}
//Enemy 스크립트 파일

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

public class Enemy : MonoBehaviour {
    public string enemyName;
    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;
    }

    void onHit(int dmg) {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
        Invoke("ReturnSprite", 0.1f);

        if (health <= 0)
            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);  //플레이어 총알 삭제
        }
    }
}

GameManager 넣어주기
총알에 맞기 직전
맞자마자 플레이어가 비활성화 되었다
다시 원래 자리로 복귀한 플레이어

  • 적 기체에 닿거나 적 총알에 맞게되면 Player가 비활성화되므로 GameManager를 통해서 플레이어를 복귀 시키는 함수를 만들어 실행시킨다. Invoke 함수를 통해서 2초정도의 부활 시간을 주었다.