public class UIController : MonoBehaviour
{
[SerializeField]
private GameController gameController;
[Header("Main UI")]
[SerializeField]
private GameObject mainPanel;
[SerializeField]
private TextMeshProUGUI textMainGrade;
[Header("Game UI")]
[SerializeField]
private GameObject gamePanel;
[SerializeField]
private TextMeshProUGUI textScore;
[Header("Result UI")]
[SerializeField]
private GameObject resultPanel;
// 결과 화면에서 현재 점수, 등급, 등급에 따른 대사, 최고점수 설정을 위한 변수 선언
[SerializeField]
private TextMeshProUGUI textResultScore;
[SerializeField]
private TextMeshProUGUI textResultGrade;
[SerializeField]
private TextMeshProUGUI textResultTalk;
[SerializeField]
private TextMeshProUGUI textResultHighScore;
[Header("Result UI Animation")]
[SerializeField]
private ScaleEffect effectGameOver;
[SerializeField]
private CountingEffect effectResultScore;
[SerializeField]
private FadeEffect effectResultGrade;
public void Awake()
{
// 처음 씬이 시작되어 Main UI가 true일 때 최고 등급 불러오기
textMainGrade.text = PlayerPrefs.GetString("HIGHGRADE");
}
// "Start" 버튼을 눌러 게임을 시작할 때 GameStart() 메소드에서 Main UI는 비활성화하고, Game UI는 활성화한다.
public void GameStart()
{
mainPanel.SetActive(false); // Main 화면 비활성화
gamePanel.SetActive(true); // Game 화면 활성화
}
// 플레이어의 체력이 0이 되었을 때 호출하는 메소드
public void GameOver()
{
int currentScore = (int)gameController.CurrentScore;
// 현재 등급 출력, 현재 등급에 해당하는 대사 출력
CalculateGradeAndTalk(currentScore);
// 최고 점수 출력
CalculateHighScore(currentScore);
gamePanel.SetActive(false); // Game 화면 비활성화
resultPanel.SetActive(true); // Result 화면 활성화
// "Game Over" 텍스트 크기 축소 애니메이션
effectGameOver.Play(500, 200);
// 현재 점수를 0부터 카운팅하는 애니메이션
// 카운팅 애니메이션 종료 후 등급 Fade In 애니메이션 재생
effectResultScore.Play(0, currentScore, effectResultGrade.FadeIn);
}
// "Main" 버튼을 클릭했을 때 호출하는 메소드
public void GoToMainMenu()
{
// 플레이어의 위치, 점수, 체력 등 초기화할 게 많기 때문에 그냥 현재씬을 다시 로드
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
// "Notion" 버튼을 클릭했을 때 호출하는 메소드
public void GoToNotion()
{
Application.OpenURL("<https://www.notion.so/DodgeGame-0184af897628482497050e31184d8486/@unitynote>");
}
void Update()
{
// 현재 점수를 소수점 자리수가 없는 실수(F0)로 출력
textScore.text = gameController.CurrentScore.ToString("F0");
}
private void CalculateGradeAndTalk(int score)
{
if ( score < 500 )
{
textResultGrade.text = "C+";
textResultTalk.text = "I got C+";
}
else if ( score < 1000 )
{
textResultGrade.text = "B0";
textResultTalk.text = "I got B0";
}
else if ( score < 1500 )
{
textResultGrade.text = "B+";
textResultTalk.text = "I got B+";
}
else if ( score < 2000 )
{
textResultGrade.text = "A0";
textResultTalk.text = "I got A0";
}
else
{
textResultGrade.text = "A+";
textResultTalk.text = "I got A+";
}
}
private void CalculateHighScore(int score)
{
int highScore = PlayerPrefs.GetInt("HIGHSCORE");
// 최고 점수보다 높은 점수를 획득
if ( score > highScore )
{
// 최고 등급 갱신(string)
PlayerPrefs.SetString("HIGHGRADE", textResultGrade.text);
// 최고 점수 갱신(int)
PlayerPrefs.SetInt("HIGHSCORE", score);
textResultHighScore.text = score.ToString();
}
else
{
textResultHighScore.text = highScore.ToString();
}
}
}
public class GameController : MonoBehaviour
{
[SerializeField]
private UIController uiController;
[SerializeField]
private GameObject pattern01;
private readonly float scoreScale = 20; // 점수 증가 계수 (읽기전용)
// 플레이어 점수 (죽지않고 버틴 시간)
public float CurrentScore { private set; get; } = 0;
// bool, 게임 진행 여부
// false일 때, 점수 증가, 플레이어 제어에 제약을 받음
public bool IsGamePlay { private set; get; } = false;
// "Start"버튼을 눌렀을 때 호출하는 메소드
public void GameStart()
{
// uiController에 있는 GameStart() 메소드 호출
uiController.GameStart();
// 적 출현 패턴 활성화
pattern01.SetActive(true);
// true: 플레이어 제어, 점수 증가 허용
IsGamePlay = true;
}
// "Exit"버튼을 눌렀을 때 호출하는 메소드
public void GameExit()
{
// 에디터 모드일 때, 플레이 중지
#if UNITY_EDITOR
UnityEditor.EditorApplication.ExitPlaymode();
// 실행 파일일 때는 앱을 종료
#else
Application.Quit();
#endif
}
// 게임 오버 시 호출되는 메소드
public void GameOver()
{
uiController.GameOver();
pattern01.SetActive(false);
IsGamePlay = false;
}
void Update()
{
// isGamePlay(false): 메인 화면, 결과 화면일 때
// 점수가 증가하지 않도록 false 상태 return
if (IsGamePlay == false) return;
// 버틴 시간을 점수로 환산하기 때문에 Update() 메소드에서
// CurrentScore 프로퍼티에 Time.deltaTime * scoreScale을 더함
CurrentScore += Time.deltaTime * scoreScale;
}
}
public class PlayerController : MonoBehaviour
{
[SerializeField]
private KeyCode jumpKey = KeyCode.Space;
[SerializeField]
private GameController gameController;
private MovementRidgidbody2D movement2D;
private PlayerHP playerHP;
private void Awake()
{
movement2D = GetComponent<MovementRidgidbody2D>();
playerHP = GetComponent<PlayerHP>();
}
// Update is called once per frame
void Update()
{
// IsGamePlay가 false일 때는 플레이어의 이동, 점프가 불가능
if (gameController.IsGamePlay == false) return;
// 메소드 실행
UpdateMove();
UpdateJump();
}
private void UpdateMove()
{
// left, a = -1 / none = 0 / right, d = +1
float x = Input.GetAxisRaw("Horizontal");
// 좌우 이동
movement2D.MoveTo(x);
}
private void UpdateJump()
{
if (Input.GetKeyDown(jumpKey))
{
movement2D.JumpTo();
}
else if (Input.GetKey(jumpKey))
{
movement2D.IsLongJump = true;
}
else if (Input.GetKeyUp(jumpKey))
{
movement2D.IsLongJump = false;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if ( collision.CompareTag("Obstacle"))
{
bool isDie = playerHP.TakeDamage();
if ( isDie == true )
{
GetComponent<Collider2D>().enabled = false; // 충돌처리 비활성화
gameController.GameOver(); // GameOver() 메소드 호출 >> 게임오버
}
}
}
}
public class PlayerHP : MonoBehaviour
{
[SerializeField]
private GameObject[] imageHP;
private int currentHP;
[SerializeField]
private float invincibilityDuration; // 무적 지속시간
private bool isInvincibility = false; // 무적 여부
private SoundController soundController;
private SpriteRenderer spriteRenderer; // 플레이어의 색상 변겅
private Color originColor; // 최초 플레이어의 색상 변수
private void Awake()
{
soundController = GetComponentInChildren<SoundController>();
spriteRenderer = GetComponent<SpriteRenderer>();
currentHP = imageHP.Length;
originColor = spriteRenderer.color;
}
public bool TakeDamage()
{
// 무적 상태일 때는 체력이 감소하지 않는다
if ( isInvincibility == true ) return false;
if ( currentHP > 1 )
{
soundController.Play(0);
StartCoroutine(nameof(OnInvincibility));
// 무슨 의미?
currentHP--;
imageHP[currentHP].SetActive(false);
}
else
{
// 플레이어의 체력이 없어 사망할 땐 true 반환
return true;
}
return false;
}
private IEnumerator OnInvincibility()
{
isInvincibility = true;
float current = 0;
float percent = 0;
float colorSpeed = 10;
while ( percent < 1 )
{
current += Time.deltaTime;
percent = current / invincibilityDuration; // invincibilityDuration: 무적 시간
// 무적 시간동안 플레이어의 색상이 기본 색상(255, 180, 0, 255)에서 (255, 0, 200, 255) 사이로 왔다갔다 하도록 한다.
spriteRenderer.color = Color.Lerp(originColor, Color.red, Mathf.PingPong(Time.time * colorSpeed, 1));
yield return null;
}
// 무적을 종료할 때 플레이어의 색상을 원래 색상(originColor)로 설정하고,
// isInvincibility를 false로 설정한다.
spriteRenderer.color = originColor;
isInvincibility = false;
}
}
public class SoundController : MonoBehaviour
{
[SerializeField]
private AudioClip[] sounds;
private AudioSource audioSource;
private void Awake()
{
audioSource = GetComponent<AudioSource>();
}
public void Play(int index=0)
{
audioSource.Stop();
audioSource.clip = sounds[index];
audioSource.Play();
}
}
public class ScaleEffect : MonoBehaviour
{
[SerializeField]
[Range(0.01f, 10f)]
private float effectTime; // 크기 확대/축소 되는 시간
private TextMeshProUGUI effectText; // 크기 확대/축소 효과에 사용되는 텍스트
private void Awake()
{
effectText = GetComponent<TextMeshProUGUI>();
}
public void Play(float start, float end)
{
StartCoroutine(Process(start, end));
}
private IEnumerator Process(float start, float end)
{
float current = 0;
float percent = 0;
while ( percent < 1 )
{
current += Time.deltaTime;
percent = current / effectTime;
effectText.fontSize = Mathf.Lerp(start, end, percent);
yield return null;
}
}
}
using System.Collections;
using UnityEngine;
using TMPro;
using UnityEngine.Events;
public class CountingEffect : MonoBehaviour
{
[SerializeField]
[Range(0.01f, 10f)]
private float effectTime;
private TextMeshProUGUI effectText;
private void Awake()
{
effectText = GetComponent<TextMeshProUGUI>();
}
public void Play(int start, int end, UnityAction action=null)
{
StartCoroutine(Process(start, end, action));
}
private IEnumerator Process(int start, int end, UnityAction action)
{
float current = 0;
float percent = 0;
while ( percent < 1)
{
current += Time.deltaTime;
percent = current / effectTime;
effectText.text = Mathf.Lerp(start, end, percent).ToString("F0");
yield return null;
}
// action이 null이 아니면 action에 저장되어 있는 메소드 실행
action?.Invoke();
}
}
using System.Collections;
using UnityEngine;
using TMPro;
public class FadeEffect : MonoBehaviour
{
[SerializeField]
[Range(0.01f, 10f)]
private float effectTime; // fade 되는 시간
private TextMeshProUGUI effectText; // fade 효과에 사용되는 텍스트
private void Awake()
{
effectText = GetComponent<TextMeshProUGUI>();
}
public void FadeIn()
{
StartCoroutine(Fade(0, 1));
}
public void FadeOut()
{
StartCoroutine(Fade(1, 0));
}
private IEnumerator Fade(float start, float end)
{
float current = 0;
float percent = 0;
while ( percent < 1 )
{
current += Time.deltaTime;
percent = current / effectTime;
Color color = effectText.color;
color.a = Mathf.Lerp(start, end, percent);
effectText.color = color;
yield return null;
}
}
}
using UnityEngine;
public class PatternController : MonoBehaviour
{
[SerializeField]
private GameController gameController;
[SerializeField]
private GameObject[] patterns; // 보유하고 있는 모든 패턴
private GameObject currentPattern; // 현재 패턴
private int[] patternIndexs; // 겹치지 않는 patterns.Length 개수의 숫자
private int current = 0; // patternIndexs 배열의 순번
}