341 lines
13 KiB
C#
341 lines
13 KiB
C#
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using TMPro;
|
|
using UnityEngine.UI;
|
|
|
|
// 게임 오버 상태를 표현하고, 게임 점수와 UI를 관리하는 게임 매니저
|
|
// 씬에는 단 하나의 게임 매니저만 존재할 수 있다.
|
|
public class GameManager : MonoBehaviour {
|
|
public static GameManager instance; // 싱글톤을 할당할 전역 변수
|
|
|
|
public bool isGameover = false; // 게임 오버 상태
|
|
public TextMeshProUGUI scoreText; // 점수를 출력할 UI 텍스트
|
|
public GameObject gameoverUI; // 게임 오버시 활성화 할 UI 게임 오브젝트
|
|
|
|
private int score = 0; // 게임 점수
|
|
private int currentLife; // 현재 남은 목숨
|
|
private int stageMileage = 0; // 현재 플레이 중 획득한 마일리지
|
|
private int totalMileage = 0; // 누적 보유 마일리지
|
|
private string tutorialMessage = ""; // 튜토리얼 안내 문구
|
|
private Image[] lifeIcons; // 왼쪽 HUD의 목숨 도장 표시
|
|
private TextMeshProUGUI tutorialText; // 화면 하단 튜토리얼 안내
|
|
|
|
public int maxLife = 3; // 시작 목숨
|
|
public int initialMileage = 500; // 첫 시작시 지급할 초기 마일리지
|
|
public bool grantInitialMileage = true; // 초기 마일리지 지급 여부
|
|
public bool saveMileageImmediately = true; // 스테이지 정산 전까지는 획득 즉시 누적한다.
|
|
|
|
private const string TotalMileageKey = "UniRun.TotalMileage";
|
|
private const string InitialMileageGrantedKey = "UniRun.InitialMileageGranted";
|
|
private const string CharacterFaceResourcePath = "UI_JJ_Face";
|
|
private const string PassportPanelResourcePath = "UI_PassportPanel";
|
|
private const string LifeStampResourcePath = "UI_LifeStamp";
|
|
private const string PassportItemSlotResourcePath = "UI_PassportItemSlot";
|
|
|
|
// 게임의 스피드를 올리는 변수
|
|
public float gameSpeed = 1f;
|
|
public float minGameSpeed = 1f;
|
|
public float speedIncreaseRate = 0.05f;
|
|
public float maxGameSpeed = 2f;
|
|
public float hitSpeedPenalty = 0.3f;
|
|
|
|
// 게임 시작과 동시에 싱글톤을 구성
|
|
void Awake() {
|
|
// 싱글톤 변수 instance가 비어있는가?
|
|
if(instance == null)
|
|
{
|
|
// instance가 비어있다면(null) 그곳에 자기 자신을 할당
|
|
instance = this;
|
|
}
|
|
else
|
|
{
|
|
// instance에 이미 다른 GameManager 오브젝트가 할당되어 있는 경우
|
|
|
|
// 씬에 두개 이상의 GameManager 오브젝트가 존재한다는 의미.
|
|
// 싱글톤 오브젝트는 하나만 존재해야 하므로 자신의 게임 오브젝트를 파괴
|
|
Debug.LogWarning("씬에 두개 이상의 게임 매니저가 존재합니다!");
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
private void Start() {
|
|
currentLife = maxLife;
|
|
totalMileage = PlayerPrefs.GetInt(TotalMileageKey, 0);
|
|
|
|
if(grantInitialMileage && PlayerPrefs.GetInt(InitialMileageGrantedKey, 0) == 0)
|
|
{
|
|
totalMileage += initialMileage;
|
|
PlayerPrefs.SetInt(TotalMileageKey, totalMileage);
|
|
PlayerPrefs.SetInt(InitialMileageGrantedKey, 1);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
BuildRuntimeHUD();
|
|
UpdateScoreUI();
|
|
}
|
|
|
|
void Update() {
|
|
// 게임 오버 상태에서 게임을 재시작할 수 있게 하는 처리
|
|
if(isGameover && Input.GetMouseButtonDown(0))
|
|
{
|
|
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
|
|
}
|
|
|
|
if(!isGameover)
|
|
{
|
|
// 게임 진행시 게임 속도는 시간에 따라 증가
|
|
gameSpeed += speedIncreaseRate * Time.deltaTime;
|
|
// 게임 최대 속도는 max를 넘어서지 못하게
|
|
gameSpeed = Mathf.Min(gameSpeed, maxGameSpeed);
|
|
UpdateScoreUI();
|
|
}
|
|
}
|
|
|
|
// 점수를 증가시키는 메서드
|
|
public void AddScore(int newScore) {
|
|
|
|
if(!isGameover)
|
|
{
|
|
score += newScore;
|
|
UpdateScoreUI();
|
|
}
|
|
|
|
}
|
|
|
|
public void SetTutorialMessage(string message) {
|
|
tutorialMessage = message;
|
|
UpdateScoreUI();
|
|
}
|
|
|
|
// 마일리지 토큰을 먹었을 때 호출되는 메서드
|
|
public void AddMileage(int amount) {
|
|
if(isGameover)
|
|
{
|
|
return;
|
|
}
|
|
|
|
stageMileage += amount;
|
|
score += Mathf.Max(1, amount / 10);
|
|
|
|
if(saveMileageImmediately)
|
|
{
|
|
totalMileage += amount;
|
|
PlayerPrefs.SetInt(TotalMileageKey, totalMileage);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
UpdateScoreUI();
|
|
}
|
|
|
|
// 플레이어가 장애물에 부딪혔을 때 목숨과 속도를 줄이는 메서드
|
|
public bool TakeDamage(int damage = 1) {
|
|
if(isGameover)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
currentLife -= damage;
|
|
currentLife = Mathf.Max(currentLife, 0);
|
|
|
|
gameSpeed -= hitSpeedPenalty;
|
|
gameSpeed = Mathf.Max(gameSpeed, minGameSpeed);
|
|
|
|
UpdateScoreUI();
|
|
|
|
return currentLife <= 0;
|
|
}
|
|
|
|
// 플레이어 캐릭터가 사망시 게임 오버를 실행하는 메서드
|
|
public void OnPlayerDead() {
|
|
isGameover = true;
|
|
gameoverUI.SetActive(true);
|
|
}
|
|
|
|
private void UpdateScoreUI() {
|
|
if(scoreText != null)
|
|
{
|
|
scoreText.text = "SCORE " + score
|
|
+ "\nMILE " + stageMileage
|
|
+ "\nTOTAL " + totalMileage
|
|
+ "\nSPD " + gameSpeed.ToString("0.0");
|
|
}
|
|
|
|
if(lifeIcons != null)
|
|
{
|
|
for(int i = 0; i < lifeIcons.Length; i++)
|
|
{
|
|
if(lifeIcons[i] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool hasLife = i < currentLife;
|
|
lifeIcons[i].color = hasLife
|
|
? Color.white
|
|
: new Color(0.34f, 0.34f, 0.34f, 0.72f);
|
|
}
|
|
}
|
|
|
|
if(tutorialText != null)
|
|
{
|
|
tutorialText.text = tutorialMessage;
|
|
tutorialText.gameObject.SetActive(!string.IsNullOrEmpty(tutorialMessage));
|
|
}
|
|
}
|
|
|
|
private void BuildRuntimeHUD() {
|
|
if(scoreText == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Transform canvasTransform = scoreText.transform.parent;
|
|
|
|
ConfigureRightInfoText();
|
|
CreateStatusHUD(canvasTransform);
|
|
CreateTutorialText(canvasTransform);
|
|
}
|
|
|
|
private void ConfigureRightInfoText() {
|
|
RectTransform rectTransform = scoreText.rectTransform;
|
|
rectTransform.anchorMin = new Vector2(1f, 1f);
|
|
rectTransform.anchorMax = new Vector2(1f, 1f);
|
|
rectTransform.pivot = new Vector2(1f, 1f);
|
|
rectTransform.anchoredPosition = new Vector2(-12f, -9f);
|
|
rectTransform.sizeDelta = new Vector2(132f, 62f);
|
|
|
|
scoreText.alignment = TextAlignmentOptions.TopRight;
|
|
scoreText.fontSize = 9.5f;
|
|
scoreText.lineSpacing = -10f;
|
|
scoreText.color = new Color(0.94f, 0.97f, 1f, 0.92f);
|
|
scoreText.raycastTarget = false;
|
|
scoreText.textWrappingMode = TextWrappingModes.NoWrap;
|
|
}
|
|
|
|
private void CreateStatusHUD(Transform canvasTransform) {
|
|
GameObject hudObject = new GameObject("Status HUD", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
|
hudObject.transform.SetParent(canvasTransform, false);
|
|
|
|
RectTransform hudRect = hudObject.GetComponent<RectTransform>();
|
|
hudRect.anchorMin = new Vector2(0f, 1f);
|
|
hudRect.anchorMax = new Vector2(0f, 1f);
|
|
hudRect.pivot = new Vector2(0f, 1f);
|
|
hudRect.anchoredPosition = new Vector2(10f, -8f);
|
|
hudRect.sizeDelta = new Vector2(176f, 54f);
|
|
|
|
Image hudBackground = hudObject.GetComponent<Image>();
|
|
hudBackground.sprite = Resources.Load<Sprite>(PassportPanelResourcePath);
|
|
hudBackground.color = hudBackground.sprite != null
|
|
? Color.white
|
|
: new Color(0.04f, 0.08f, 0.11f, 0.68f);
|
|
hudBackground.raycastTarget = false;
|
|
|
|
CreateFaceIcon(hudObject.transform);
|
|
CreateLifeIcons(hudObject.transform);
|
|
CreateItemSlots(hudObject.transform);
|
|
}
|
|
|
|
private void CreateFaceIcon(Transform parent) {
|
|
GameObject faceFrame = new GameObject("Face Frame", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
|
faceFrame.transform.SetParent(parent, false);
|
|
|
|
RectTransform frameRect = faceFrame.GetComponent<RectTransform>();
|
|
frameRect.anchorMin = new Vector2(0f, 1f);
|
|
frameRect.anchorMax = new Vector2(0f, 1f);
|
|
frameRect.pivot = new Vector2(0f, 1f);
|
|
frameRect.anchoredPosition = new Vector2(9f, -10f);
|
|
frameRect.sizeDelta = new Vector2(29f, 29f);
|
|
|
|
Image frameImage = faceFrame.GetComponent<Image>();
|
|
frameImage.color = new Color(0.84f, 0.87f, 0.80f, 0.58f);
|
|
frameImage.raycastTarget = false;
|
|
|
|
GameObject faceObject = new GameObject("Character Face", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
|
faceObject.transform.SetParent(faceFrame.transform, false);
|
|
|
|
RectTransform faceRect = faceObject.GetComponent<RectTransform>();
|
|
faceRect.anchorMin = Vector2.zero;
|
|
faceRect.anchorMax = Vector2.one;
|
|
faceRect.offsetMin = new Vector2(2f, 2f);
|
|
faceRect.offsetMax = new Vector2(-2f, -2f);
|
|
|
|
Image faceImage = faceObject.GetComponent<Image>();
|
|
faceImage.sprite = Resources.Load<Sprite>(CharacterFaceResourcePath);
|
|
faceImage.preserveAspect = true;
|
|
faceImage.raycastTarget = false;
|
|
}
|
|
|
|
private void CreateLifeIcons(Transform parent) {
|
|
Sprite lifeSprite = Resources.Load<Sprite>(LifeStampResourcePath);
|
|
int iconCount = Mathf.Max(maxLife, 1);
|
|
lifeIcons = new Image[iconCount];
|
|
|
|
for(int i = 0; i < iconCount; i++)
|
|
{
|
|
GameObject lifeObject = new GameObject("Life Stamp " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
|
lifeObject.transform.SetParent(parent, false);
|
|
|
|
RectTransform lifeRect = lifeObject.GetComponent<RectTransform>();
|
|
lifeRect.anchorMin = new Vector2(0f, 1f);
|
|
lifeRect.anchorMax = new Vector2(0f, 1f);
|
|
lifeRect.pivot = new Vector2(0f, 1f);
|
|
lifeRect.anchoredPosition = new Vector2(50f + (i * 21f), -10f);
|
|
lifeRect.sizeDelta = new Vector2(18f, 18f);
|
|
|
|
Image lifeImage = lifeObject.GetComponent<Image>();
|
|
lifeImage.sprite = lifeSprite;
|
|
lifeImage.color = Color.white;
|
|
lifeImage.preserveAspect = true;
|
|
lifeImage.raycastTarget = false;
|
|
lifeIcons[i] = lifeImage;
|
|
}
|
|
}
|
|
|
|
private void CreateItemSlots(Transform parent) {
|
|
Sprite slotSprite = Resources.Load<Sprite>(PassportItemSlotResourcePath);
|
|
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
GameObject slotObject = new GameObject("Item Slot " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
|
slotObject.transform.SetParent(parent, false);
|
|
|
|
RectTransform slotRect = slotObject.GetComponent<RectTransform>();
|
|
slotRect.anchorMin = new Vector2(0f, 1f);
|
|
slotRect.anchorMax = new Vector2(0f, 1f);
|
|
slotRect.pivot = new Vector2(0f, 1f);
|
|
slotRect.anchoredPosition = new Vector2(50f + (i * 21f), -31f);
|
|
slotRect.sizeDelta = new Vector2(18f, 18f);
|
|
|
|
Image slotImage = slotObject.GetComponent<Image>();
|
|
slotImage.sprite = slotSprite;
|
|
slotImage.color = slotSprite != null
|
|
? new Color(1f, 1f, 1f, 0.92f)
|
|
: new Color(0.92f, 0.97f, 1f, 0.22f);
|
|
slotImage.preserveAspect = true;
|
|
slotImage.raycastTarget = false;
|
|
}
|
|
}
|
|
|
|
private void CreateTutorialText(Transform canvasTransform) {
|
|
GameObject tutorialObject = new GameObject("Tutorial Text", typeof(RectTransform), typeof(CanvasRenderer));
|
|
tutorialObject.transform.SetParent(canvasTransform, false);
|
|
|
|
RectTransform tutorialRect = tutorialObject.GetComponent<RectTransform>();
|
|
tutorialRect.anchorMin = new Vector2(0.5f, 0f);
|
|
tutorialRect.anchorMax = new Vector2(0.5f, 0f);
|
|
tutorialRect.pivot = new Vector2(0.5f, 0f);
|
|
tutorialRect.anchoredPosition = new Vector2(0f, 18f);
|
|
tutorialRect.sizeDelta = new Vector2(360f, 24f);
|
|
|
|
tutorialText = tutorialObject.AddComponent<TextMeshProUGUI>();
|
|
tutorialText.font = scoreText.font;
|
|
tutorialText.alignment = TextAlignmentOptions.Center;
|
|
tutorialText.fontSize = 9.5f;
|
|
tutorialText.fontStyle = FontStyles.Bold;
|
|
tutorialText.color = new Color(0.96f, 0.98f, 1f, 0.95f);
|
|
tutorialText.raycastTarget = false;
|
|
tutorialText.textWrappingMode = TextWrappingModes.NoWrap;
|
|
tutorialText.gameObject.SetActive(false);
|
|
}
|
|
}
|