기록 보관소

[Unity/유니티] 기초-뱀서라이크: 게임 시작과 종료[13] 본문

유니티 프로젝트/뱀서라이크

[Unity/유니티] 기초-뱀서라이크: 게임 시작과 종료[13]

JongHoon 2023. 7. 18. 21:20

개요

유니티 독학을 위해 아래 링크의 골드메탈님의 영상들을 보고 직접 따라 해보면서 진행 상황을 쓰고 배웠던 점을 요약한다.

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

 

📚유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com


뱀서라이크: 게임 시작과 종료[13]

1. 게임시작

GameStart 오브젝트 생성
GameStart 오브젝트 아래에 이미지를 생성해 타이틀로 만든다

  • Shadow 컴포넌트 : UI 그래픽을 기준으로 그림자를 생성하는 컴포넌트.

게임 시작 버튼 생성 및 수정
버튼의 Text도 변경

  • Outline 컴포넌트 : UI 그래픽을 기준으로 외각선을 그리는 컴포넌트.

Set Active는 OnClick 이벤트에서 바로 사용 가능
게임 시작시 HUD를 숨길 수 있도록 빈 오브젝트 HUD를 생성해 경험치, 레벨, 킬 카운터, 타이머, 체력을 자식 오브젝트로 옮긴다
HUD 오브젝트를 비활성화 하고, 게임 시작 버튼의 On Click에서 활성화되도록 설정

//GameManager Script

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

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	[Header("# Game Control")]
	public bool isLive;	//시간 정지 여부 확인 변수
	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
	[Header("# Player Info")]
	public int health;
	public int maxHealth = 100;
    public int level;
	public int kill;
	public int exp;
	public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
	public LevelUp uiLevelUp;

    void Awake() {
		instance = this;
	}

	public void GameStart() {
		health = maxHealth;
		uiLevelUp.Select(0);//임시 스크립트 (첫번째 캐릭터 선택)
        isLive = true;
	}

	void Update() {
		if (!isLive)
			return;

		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
		}
	}

	public void GetExp() {
		exp++;

		if (exp == nextExp[Mathf.Min(level, nextExp.Length - 1)]) {
			level++;
			exp = 0;
			uiLevelUp.Show();
		}
	}

	public void Stop() {
		isLive = false;
		Time.timeScale = 0;
	}

    public void Resume() {
        isLive = true;
        Time.timeScale = 1;	//값이 1보다 크면 그만큼 시간이 빠르게 흐름. 모바일 게임에서 시간 가속하는 것이 이것..
    }
}

게임 시작 버튼의 On Click에 Game Start() 함수를 실행하도록 추가
테스트 실행. 버튼을 누르기 전까지 게임이 시작되지 않는다. 캐릭터는 움직인다.
게임 시작 버튼을 누르니 게임이 시작되고, HUD가 나타난다.


2. 플레이어 피격

//GameManager Script

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

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	[Header("# Game Control")]
	public bool isLive;	//시간 정지 여부 확인 변수
	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
	[Header("# Player Info")]
	public float health;
	public float maxHealth = 100;
    public int level;
	public int kill;
	public int exp;
	public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
	public LevelUp uiLevelUp;

    void Awake() {
		instance = this;
	}

	public void GameStart() {
		health = maxHealth;
		uiLevelUp.Select(0);//임시 스크립트 (첫번째 캐릭터 선택)
        isLive = true;
	}

	void Update() {
		if (!isLive)
			return;

		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
		}
	}

	public void GetExp() {
		exp++;

		if (exp == nextExp[Mathf.Min(level, nextExp.Length - 1)]) {
			level++;
			exp = 0;
			uiLevelUp.Show();
		}
	}

	public void Stop() {
		isLive = false;
		Time.timeScale = 0;
	}

    public void Resume() {
        isLive = true;
        Time.timeScale = 1;	//값이 1보다 크면 그만큼 시간이 빠르게 흐름. 모바일 게임에서 시간 가속하는 것이 이것..
    }
}
//Player Script

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

public class Player : MonoBehaviour {
    public Vector2 inputVec;   //키보드 입력 값 변수
	public float speed; //속도 관리 변수
    public Scanner scanner;
    public Hand[] hands;

    Rigidbody2D rigid;
    SpriteRenderer spriter;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
		spriter = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        scanner = GetComponent<Scanner>();
        hands = GetComponentsInChildren<Hand>(true);    //인자의 true를 통해서 비활성화된 오브젝트도 가능
	}

    void FixedUpdate() {
        if (!GameManager.instance.isLive)
            return;

		//위치 이동
		Vector2 nextVec = inputVec * speed * Time.fixedDeltaTime;
		rigid.MovePosition(rigid.position + nextVec);
    }

    void OnMove(InputValue value) {
        inputVec = value.Get<Vector2>();
    }

    void LateUpdate() {
        if (!GameManager.instance.isLive)
            return;

        anim.SetFloat("Speed", inputVec.magnitude); //Magnitude : 벡터의 순수한 크기 값
        
        if (inputVec.x != 0) {
            spriter.flipX = inputVec.x < 0;
        }
    }

    void OnCollisionStay2D(Collision2D collision) {
        if (!GameManager.instance.isLive)
            return;

        GameManager.instance.health -= Time.deltaTime * 10;

        if (GameManager.instance.health < 0) {
            for (int index = 2; index < transform.childCount; index++) {
                transform.GetChild(index).gameObject.SetActive(false);
            }

            anim.SetTrigger("Dead");
        }
    }
}
  • 플레이어 피격시 체력이 잃는다. 체력이 0보다 낮아져서 죽으면 Dead 애니메이션이 출력된다.
    • deltaTime을 사용해서 float형이 되었기에 GameManager의 health도 float으로 변경한다.

테스트 실행. 몬스터에게 닿자 체력이 깎인다.
체력을 모두 잃자, 사망 모션이 나왔다.


3. 게임 오버

GameStart 오브젝트를 복사해서 게임이 끝났을 때의 UI인 GameResult를 만든다

//GameManager Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;	//장면 관리(Scene Manager 같은)를 사용하기 위한 네임스페이스.

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	[Header("# Game Control")]
	public bool isLive;	//시간 정지 여부 확인 변수
	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
	[Header("# Player Info")]
	public float health;
	public float maxHealth = 100;
    public int level;
	public int kill;
	public int exp;
	public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
	public LevelUp uiLevelUp;
	public GameObject uiResult;

    void Awake() {
		instance = this;
	}

	public void GameStart() {
		health = maxHealth;
		uiLevelUp.Select(0);//임시 스크립트 (첫번째 캐릭터 선택)
        isLive = true;
	}

	public void GameOver() {
		StartCoroutine(GameOverRoutine());
	}

	IEnumerator GameOverRoutine() {
		isLive = false;
		
		yield return new WaitForSeconds(0.5f);

		uiResult.SetActive(true);
		Stop();
	}

	public void GameRetry() {
		SceneManager.LoadScene(0);	//LoadScene() : 이름 혹은 인덱스로 장면을 새롭게 부르는 함수
	}

	void Update() {
		if (!isLive)
			return;

		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
		}
	}

	public void GetExp() {
		exp++;

		if (exp == nextExp[Mathf.Min(level, nextExp.Length - 1)]) {
			level++;
			exp = 0;
			uiLevelUp.Show();
		}
	}

	public void Stop() {
		isLive = false;
		Time.timeScale = 0;
	}

    public void Resume() {
        isLive = true;
        Time.timeScale = 1;	//값이 1보다 크면 그만큼 시간이 빠르게 흐름. 모바일 게임에서 시간 가속하는 것이 이것..
    }
}
//Player Script

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

public class Player : MonoBehaviour {
    public Vector2 inputVec;   //키보드 입력 값 변수
	public float speed; //속도 관리 변수
    public Scanner scanner;
    public Hand[] hands;

    Rigidbody2D rigid;
    SpriteRenderer spriter;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
		spriter = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        scanner = GetComponent<Scanner>();
        hands = GetComponentsInChildren<Hand>(true);    //인자의 true를 통해서 비활성화된 오브젝트도 가능
	}

    void FixedUpdate() {
        if (!GameManager.instance.isLive)
            return;

		//위치 이동
		Vector2 nextVec = inputVec * speed * Time.fixedDeltaTime;
		rigid.MovePosition(rigid.position + nextVec);
    }

    void OnMove(InputValue value) {
        inputVec = value.Get<Vector2>();
    }

    void LateUpdate() {
        if (!GameManager.instance.isLive)
            return;

        anim.SetFloat("Speed", inputVec.magnitude); //Magnitude : 벡터의 순수한 크기 값
        
        if (inputVec.x != 0) {
            spriter.flipX = inputVec.x < 0;
        }
    }

    void OnCollisionStay2D(Collision2D collision) {
        if (!GameManager.instance.isLive)
            return;

        GameManager.instance.health -= Time.deltaTime * 10;

        if (GameManager.instance.health < 0) {
            for (int index = 2; index < transform.childCount; index++) {
                transform.GetChild(index).gameObject.SetActive(false);
            }

            anim.SetTrigger("Dead");
            GameManager.instance.GameOver();
        }
    }
}

UI Result 변수에 GameResult 할당하기
게임 오버시 나타날 게임 재시작 버튼에 On Click 이벤트 할당
테스트 실행. 죽고 난 직후의 모습. 코루틴에서 0.5초 뒤에 UI를 띄우도록 설정했기 때문에 바로 뜨지는 않는다.
0.5초 뒤 정상적으로 UI가 떴다
재시작 버튼을 누르니 다시 게임 시작 화면으로 돌아왔다


4. 게임 승리

빈 오브젝트 EnemyCleaner 생성

  • EnemyCleaner는 게임 승리시, 이름처럼 남은 몬스터들을 모두 제거하기 위한 오브젝트.

콜라이더 크기와 데미지를 매우 크게 설정해서 맵에 남아 있는 몬스터들을 모두 제거할 수 있도록 만든다

  • 참고로 캡쳐한 시점에서 실수로 Tag를 지정하지 않았는데,Bullet을 지정해주어야 후에 테스트 실행시 EnemyCleaner가 정상 작동된다.

게임 승리 표시를 위해 GameResult UI를 다시 수정. Title을 복사해서 스프라이트를 변경하고, 구분짓기 위해 이름들을 변경.
위 타이틀 표시를 위한 Result 스크립트 생성

//Result Script

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

public class Result : MonoBehaviour {
    public GameObject[] titles;

    public void Lose() {
        titles[0].SetActive(true);
    }

    public void Win() {
        titles[1].SetActive(true);
    }
}
//GameManager Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;	//장면 관리(Scene Manager 같은)를 사용하기 위한 네임스페이스.

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	[Header("# Game Control")]
	public bool isLive;	//시간 정지 여부 확인 변수
	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
	[Header("# Player Info")]
	public float health;
	public float maxHealth = 100;
    public int level;
	public int kill;
	public int exp;
	public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
	public LevelUp uiLevelUp;
	public Result uiResult;

    void Awake() {
		instance = this;
	}

	public void GameStart() {
		health = maxHealth;
		uiLevelUp.Select(0);//임시 스크립트 (첫번째 캐릭터 선택)
        isLive = true;
	}

	public void GameOver() {
		StartCoroutine(GameOverRoutine());
	}

	IEnumerator GameOverRoutine() {
		isLive = false;
		
		yield return new WaitForSeconds(0.5f);

		uiResult.gameObject.SetActive(true);
		uiResult.Lose();
		Stop();
	}

	public void GameRetry() {
		SceneManager.LoadScene(0);	//LoadScene() : 이름 혹은 인덱스로 장면을 새롭게 부르는 함수
	}

	void Update() {
		if (!isLive)
			return;

		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
		}
	}

	public void GetExp() {
		exp++;

		if (exp == nextExp[Mathf.Min(level, nextExp.Length - 1)]) {
			level++;
			exp = 0;
			uiLevelUp.Show();
		}
	}

	public void Stop() {
		isLive = false;
		Time.timeScale = 0;
	}

    public void Resume() {
        isLive = true;
        Time.timeScale = 1;	//값이 1보다 크면 그만큼 시간이 빠르게 흐름. 모바일 게임에서 시간 가속하는 것이 이것..
    }
}

GameResult에 스크립트 삽입 및 Titles 변수 할당
GameManager의 UI Result 변수 할당

  • 게임 승리시 처리를 위해 GameManger를 추가 수정
//GameManager Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;	//장면 관리(Scene Manager 같은)를 사용하기 위한 네임스페이스.

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	[Header("# Game Control")]
	public bool isLive;	//시간 정지 여부 확인 변수
	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
	[Header("# Player Info")]
	public float health;
	public float maxHealth = 100;
    public int level;
	public int kill;
	public int exp;
	public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
	public LevelUp uiLevelUp;
	public Result uiResult;
	public GameObject enemyCleaner;

    void Awake() {
		instance = this;
	}

	public void GameStart() {
		health = maxHealth;
		uiLevelUp.Select(0);//임시 스크립트 (첫번째 캐릭터 선택)
		Resume();

    }

	public void GameOver() {
		StartCoroutine(GameOverRoutine());
	}

	IEnumerator GameOverRoutine() {
		isLive = false;
		
		yield return new WaitForSeconds(0.5f);

		uiResult.gameObject.SetActive(true);
		uiResult.Lose();
		Stop();
	}

    public void GameVictory() {
        StartCoroutine(GameVictoryRoutine());
    }

    IEnumerator GameVictoryRoutine() {
        isLive = false;
		enemyCleaner.SetActive(true);

        yield return new WaitForSeconds(0.5f);

        uiResult.gameObject.SetActive(true);
        uiResult.Win();
        Stop();
    }

    public void GameRetry() {
		SceneManager.LoadScene(0);	//LoadScene() : 이름 혹은 인덱스로 장면을 새롭게 부르는 함수
	}

	void Update() {
		if (!isLive)
			return;

		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
			GameVictory();
		}
	}

	public void GetExp() {
		if (!isLive)	//EnemyCleaner로 경험치를 못얻게 하기 위함
			return;

		exp++;

		if (exp == nextExp[Mathf.Min(level, nextExp.Length - 1)]) {
			level++;
			exp = 0;
			uiLevelUp.Show();
		}
	}

	public void Stop() {
		isLive = false;
		Time.timeScale = 0;
	}

    public void Resume() {
        isLive = true;
        Time.timeScale = 1;	//값이 1보다 크면 그만큼 시간이 빠르게 흐름. 모바일 게임에서 시간 가속하는 것이 이것..
    }
}

EnemyCleaner 변수 할당
최종 테스트 실행. 잘 작동한다.