/ UNITY

미니게임 제작 09

앞으로는 간단한 게임들을 만들어보며 추가적인 기능에 대해 공부를 해보려한다. 간단한 것부터 만들기 시작하여 만든 결과물을 레퍼런스 하여 덩치를 조금씩 키워나가는 과정을 밟아보자. 지식이 많지 않아 부족할 수 있지만, 하는데까지 해보겠다.

현재 구상 하고 있는 것은 예전에 잠깐 화제가 되었던 모바일 게임 매직 서바이벌 과 스팀에서 최근 붐이 일었던 게임 뱀파이어 서바이벌 의 장르를 본딴 생존 서바이벌 슈팅 게임이다.

이번에는 게임 매니저의 hpDown 메서드를 구현하고, 게임이 종료되었을 때 특정 키 입력을 통해 재시작 할 수 있도록 하겠다. 가능하면 스코어까지 건드려보도록 하자.

game_66

먼저 게임오버 시 애니메이션을 만들었다. 가지고 있는 스프라이트를 플레이어 오브젝트에 드래그&드롭하여 생성하였다.

game_67

애니메이션은 위 그림과 같이 작동할 것이다.

애니메이션은 생성되었고, 이제 애니메이터를 건드려서 죽었을 때 해당 애니메이션을 재생하도록 해보겠다.

game_68

먼저 bool 파라미터 isDie를 만들었다. 그 후 AnyState로부터 트랜잭션을 PlayerDie 애니메이션에 연결했다.

game_69

트랜잭션 설정은 인스펙터 창에서 다음과 같이 설정했다.

애니메이터는 모두 설정 완료했다. 이제 각 스크립트를 변경해야한다. PlayerControl.cs 파일을 수정해보자.

// 죽었는지 여부 확인할 변수
bool isDie = false;

먼저 죽어있는 상태인지 확인할 논리형 변수를 하나 생성했다.

void MoveSet()
{
    H = Input.GetAxisRaw("Horizontal");
    V = Input.GetAxisRaw("Vertical");
    if (!isDie)
    {
        //Animation
        if (anim.GetInteger("vAxisRaw") != V)
        {
            if (anim.GetInteger("hAxisRaw") != 0 && anim.GetInteger("vAxisRaw") == 0)
            {
                anim.SetInteger("hAxisRaw", (int)H);
                anim.SetBool("isChange", true);
            }
            else
            {
                anim.SetInteger("vAxisRaw", (int)V);
                anim.SetBool("isChange", true);
            }
        }
        else if (anim.GetInteger("hAxisRaw") != H)
        {
            anim.SetInteger("hAxisRaw", (int)H);
            anim.SetBool("isChange", true);
        }
        else
        {
            anim.SetBool("isChange", false);
        }
    }
}

또한, 이동 애니메이션 재생은 죽지 않은 상태에서만 이루어질 수 있도록 조건문을 추가해주었다.

public void die()
{
    // 플레이어 사망 애니메이션 재생
    anim.SetBool("isDie", true);

    isDie = true;
    // 조작 막기
    speed = 0;
}

Hp가 0이 되었을 때 GameManager에 의해 호출되는 die 메서드이다.

플레이어 사망애니메이션을 재생하고, isDie의 값을 true로 바꾼다. 그리고 이동하지 못하게 speed를 0으로 설정한다.

이제 플레이어 사망 애니메이션 처리는 완료되었다. 다음으로 EnemySpawner.cs 파일을 수정해보자.

public void gameover()
{
    gameObject.SetActive(false);
}

EnemySpawner에는 gameover메서드를 추가해주었다. SetActive를 이용해 해당 오브젝트를 비활성화하면 더이상 적 개체가 생성되지 않을 것이다.

EnemySpawner의 gameover 메서드를 호출하려면 GameManager에 해당 컴포넌트를 할당해주어야 한다.

// EnemySpawner 변수
public EnemySpawner enemySpawner;

GameManager 스크립트에 변수를 하나 추가해주었다.

public void hpDown()
{
    if (health > 1)
    {
        // Hp 감소 및 hpBar 갱신
        hpBar.setHp(--health);
    }
    else
    {
        // Hp 감소 및 hpBar 갱신
        hpBar.setHp(--health);

        // 플레이어의 사망 스크립트 수행(애니메이션 재생)
        playerControl.die();

        // EnemySpawner 정지
        enemySpawner.gameover();

        // 게임오버, 점수 UI 적용
        gameoverText.gameObject.SetActive(true);
        // 다시 시작 UI 적용

    }
}

또한, hpDown 메서드에서 EnemySpawner의 gameover 메서드를 호출하도록 했다.

game_70

게임매니저 오브젝트에 EnemySpawner를 할당해주었다. 이제 잘 적용 되는지 테스트해보자.

game_71

잘 적용되는 모습이다.

다음으로 R 키를 눌렀을 때 게임을 재시작 할 수 있도록 하겠다. 게임 다시시작은 해당 게임 Scene를 다시 불러오는 것을 통해 구현해보자.

game_72

먼저 GameOver 텍스트를 다음과 같이 바꾸었다.

using UnityEngine.SceneManagement;

GameManager 스크립트에 위 패키지를 Import 시켜주었다.

void restart()
{
    // Scene1 씬을 로드
    SceneManager.LoadScene("Scene1");
}

R키 입력시 실행되는 restart 메서드이다. SceneManager 클래스의 LoadScene메서드를 통해 현재 씬인 Scene1 씬을 불러온다.

game_73

game_74

game_75

game_76

하지만 이렇게 씬을 불러오면 문제가 하나 생긴다. DontDestroyOnLoad로 싱글턴 패턴을 반영한 GameManager에 할당되어있는 객체가 전부 소실된다는 점이다.

game_77

재시작 후 정상적으로 진행되지 않는 게임의 모습.

이 게임에서 나는 재시작 할 때 외에는 씬 변경을 하지 않으려고 한다. 따라서 게임 매니저를 OndestroyOnLoad화 시키는 것은 오히려 있을 경우 문제가 생길 수 있기 때문에, GameManager 스크립트에서 OndestroyOnLoad 시키는 부분을 제거하였다.

void Awake()
{
    // 게임 오브젝트 생성 시 객체생성.
    if (instance == null)
    {
        instance = this;

    }
}

GameManager 스크립트의 싱글턴 패턴을 적용하는 Awake 함수를 다음과 같이 바꾸었다.

game_78

정상적으로 재시작이 이루어지는 모습이다.

전체 소스코드는 다음과 같다.

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

public class PlayerControl : MonoBehaviour
{
    // rigidbody 선언
    Rigidbody2D playerRigidbody;
    float H;
    float V;

    // 죽었는지 여부 확인할 변수
    bool isDie = false;

    public float speed = 8;



    Animator anim;
    GameManager instance;
    void Start()

    {
        // rigidbody 컴포넌트 가져와 할당하기
        playerRigidbody = GetComponent<Rigidbody2D>();
        // Animator 컴포넌트 가져와 할당하기
        anim = GetComponent<Animator>();
        instance = GameManager.Instance;
    }

    void Update()
    {
        MoveSet();
    }

    void FixedUpdate()
    {
        Move();

    }

    void MoveSet()
    {
        H = Input.GetAxisRaw("Horizontal");
        V = Input.GetAxisRaw("Vertical");
        if (!isDie)
        {
            //Animation
            if (anim.GetInteger("vAxisRaw") != V)
            {
                if (anim.GetInteger("hAxisRaw") != 0 && anim.GetInteger("vAxisRaw") == 0)
                {
                    anim.SetInteger("hAxisRaw", (int)H);
                    anim.SetBool("isChange", true);
                }
                else
                {
                    anim.SetInteger("vAxisRaw", (int)V);
                    anim.SetBool("isChange", true);
                }
            }
            else if (anim.GetInteger("hAxisRaw") != H)
            {
                anim.SetInteger("hAxisRaw", (int)H);
                anim.SetBool("isChange", true);
            }
            else
            {
                anim.SetBool("isChange", false);
            }
        }
    }


    public void die()
    {
        // 플레이어 사망 애니메이션 재생
        anim.SetBool("isDie", true);

        isDie = true;
        // 조작 막기
        speed = 0;
    }

    void Move()
    {
        Vector2 dirVec = new Vector2(H, V);

        playerRigidbody.velocity = dirVec * speed;
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.gameObject.tag == "Enemy")
        {
            instance.hpDown();
            Destroy(other.gameObject);
        }
    }

}
EnemySpawner
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class EnemySpawner : MonoBehaviour
{
    // 몬스터
    public GameObject[] enemys;
    // 플레이어 위치
    public Transform playerTransform;
    // 최대거리
    public float maxDist = 8f;
    // 최소거리
    public float minDist = 3f;
    public float timeSpawnMax = 5f; // 최대 주기
    public float timeSpawnMin = 2f; // 최소 주기
    public float timeSpawn;
    private float lastSpawn; // 가장 최근 생성
    void Start()
    {
        // 생성 주기 최소와 최대 사이에서 랜덤하게 초기화
        timeSpawn = Random.Range(timeSpawnMin, timeSpawnMax);
        lastSpawn = 0;
    }
    void Update()
    {
        if (Time.time >= lastSpawn + timeSpawn)
        {
            // 최근 생성 시간 갱신
            lastSpawn = Time.time;
            // 생성주기 랜덤 변경
            timeSpawn = Random.Range(timeSpawnMin, timeSpawnMax);

            Spawn();
        }
    }
    void Spawn()
    {
        Vector2 spawnPos = GetRandomPoint(playerTransform.position, maxDist);
        GameObject selectedEnemy = enemys[Random.Range(0, enemys.Length)];
        GameObject enemy = Instantiate(selectedEnemy, spawnPos, Quaternion.identity);
        Enemy es = enemy.GetComponent<Enemy>();
        es.init(playerTransform);
    }
    Vector2 GetRandomPoint(Vector2 center, float distance)
    {
        // 중앙을 중심으로 반지름이 maxDist인 원 안에서 랜덤 위치 저장
        // Random.insideUnitCircle은 반지름이 distance에서
        Vector2 randomPos = Random.insideUnitCircle * distance + center;
        return randomPos;
    }

    public void gameover()
    {
        gameObject.SetActive(false);
    }

}
GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{

    // 플레이어 Hp
    int health = 4;
    // 점수
    int score = 0;
    // 게임 오버 체크 변수
    bool gameover = false;
    // hp Bar 객체
    public HpBar hpBar;

    // 플레이어 컨트롤 변수
    public PlayerControl playerControl;

    // EnemySpawner 변수
    public EnemySpawner enemySpawner;
    // 게임오버 텍스트 변수
    public Text gameoverText;

    // Private 기본생성자 : sigleton
    private GameManager() { }

    // 인스턴스 변수 생성 : sigleton
    private static GameManager instance = null;

    void Awake()
    {
        // 게임 오브젝트 생성 시 객체생성.
        if (instance == null)
        {
            instance = this;

        }
    }
    public static GameManager Instance
    {
        get
        {
            if (instance == null)
            {
                return null;
            }

            return instance;
        }
    }

    void Update()
    {
        scoreUpdate();

        if (gameover && Input.GetKeyDown(KeyCode.R))
        {
            restart();
        }
    }

    public void hpDown()
    {

        if (health > 1)
        {
            // Hp 감소 및 hpBar 갱신
            hpBar.setHp(--health);
        }
        else
        {
            // Hp 감소 및 hpBar 갱신
            hpBar.setHp(--health);

            // 플레이어의 사망 스크립트 수행(애니메이션 재생)
            playerControl.die();

            // EnemySpawner 정지
            enemySpawner.gameover();

            // 게임오버, 점수 UI 적용
            gameoverText.gameObject.SetActive(true);

            // 게임오버 체크 변수설정
            gameover = true;

            // 다시 시작 UI 적용

        }

    }

    // 점수 갱신 메서드 : 시간에 따라 점수 증가 및 UI 표시
    void scoreUpdate()
    {

    }

    // 게임 재시작 메서드 : 플레이어 사망 후 특정 조작을 통해 게임 재시작 : R키
    void restart()
    {
        // Scene1 씬을 로드
        SceneManager.LoadScene("Scene1");
    }


}

오늘은 저번에 플레이어 사망에 따른 사망 애니메이션 재생 및 게임오버 처리, 그리고 게임 재시작을 구현하였다.

다음에는 점수를 실시간으로 증가시키고, 게임이 종료되었을 때 점수를 표시해보도록 하겠다.