[Unity] Knife Hit (5)

suhan0304·2024년 8월 27일

유니티 - Knife Hit

목록 보기
5/9
post-thumbnail

Score & Stage System

Stage는 어느 정도 구현해놨는데, Score도 올라가게 구현해보자. Score은 Knife가 Target에 OnHit 될 때 1 증가하도록 해주자.

Events에 OnHitTarget 델리게이트를 하나 새로 만들어주고 해당 이벤트가 실행될 때 기존의 OnHit과 ScoreUpdate가 실행되도록 수정해주었다.

UIManager.cs

private void OnEnable() {
    if (Instance == null) {
        Instance = this;
        DontDestroyOnLoad(this);
    }
    else {
        Destroy(gameObject);
    }

    Events.OnStartStage += OnStartStage;
    Events.OnAllKnivesOnHit += OnAllKnivesOnHit;
    Events.OnHitTarget += UpdateScore;
}

private void OnDisable() {
    Events.OnStartStage -= OnStartStage;
    Events.OnAllKnivesOnHit -= OnAllKnivesOnHit;
    Events.OnHitTarget -= UpdateScore;
}

public void UpdateScore() {
    scoreText.text = GameManager.Instance.ScoreNum.ToString();
}

GameManager.cs

public void Start() {
    ScoreNum = 0;
    stageNum = 1;
    
    UIManager.Instance.Initialize();
    StartStage();
}

Knife.cs

private void OnTriggerEnter2D(Collider2D collision) {
    gameObject.GetComponent<TrailRenderer>().enabled = false;
    Events.OnTouchScreen -= FireKnife;   

    if (collision.gameObject.CompareTag("Target") && !hasInteracted) {
        hasInteracted = true;

        rb.velocity = Vector3.zero;
        transform.position = new Vector3(0f, collision.gameObject.transform.position.y - knifeOffsetY, 0f);
        transform.SetParent(collision.transform, true);

        particle.Play();
        
        GameManager.Instance.ScoreNum++;

        Events.OnHitTarget.Invoke();
        
        if ( GameManager.Instance.RemainKnives > 0) {
            GameManager.Instance.SpawnKnife();
        }
    }
 	//생략   
}

Target.cs

private void OnEnable() {
    Events.OnAllKnivesOnHit += DestroyTarget;
    Events.OnHitTarget += OnHitTarget;
}

private void OnDisable() {
    Events.OnAllKnivesOnHit -= DestroyTarget;
    Events.OnHitTarget -= OnHitTarget;
}

[Button("OnHitTarget")]
public void OnHitTarget() {
    transform.DOPunchPosition(Vector3.right * shakeStrength, shakeDuration, vibrato, randomness);
    FlashWhiteRenderer.DOFade(1f, flashDuration / 2)
        .OnComplete(() => FlashWhiteRenderer.DOFade(0f, flashDuration /2));

    if (GameManager.Instance.RemainKnives == 0) {
        Events.OnAllKnivesOnHit.Invoke();
    }

    UIManager.Instance.UpdateScore();
}

중요한 점은 ScoreUpdate를 OnHitTarget 동일한 델리게이트 객체가 참조하고 있기 때문에 원래는 등록된 순서로 실행되는데, 이러면 UIManager와 GameManager의 Start 문의 실행 순서까지 봐야하기 때문에, 그냥 Invoke 하기 전에 Score를 1 증가 시켜주도록 별도로 작성해주었다.

점수와 스테이지가 잘 증가하는 것을 확인할 수 있다.


GameOver

이제 게임 오버를 구현해보자. 게임 오버시에 할 일은 간단하다.

  • GameOver UI 띄우기
  • Best Score, Best Stage 업데이트

이 두가지가 전부인데 일단 UI 먼저 만들어보자. 일단 실제 인게임에서 GameOver 창을 확인해보자.

점수와 스테이지가 나오면서 최고 점수이면 NEW BEST를 추가로 보여준다. 그 다음 인게임 재화인 사과를 더 얻는 광고 버튼과, RESTART 버튼이 있다. 하단 메뉴는 기존 TitleScene에서의 하단 메뉴와 동일하므로 나중에 구현해보자.

일단 인게임 재화인 사과를 구현하지 않아서 사과를 더 받는 광고 버튼이 아니라 다시 한 번 살아날 수 있는 Continue 버튼을 추가하는 것으로 대체했다.

그럴싸해 보인다! 이제 GameOver 로직을 구현해보자.

UIManager.cs

public void OnGameOver() {
    CanvasGroup canvasGroup = gameOverUI.GetComponent<CanvasGroup>();
    canvasGroup.alpha = 0f; 

    gameoverStageText.text = GameManager.Instance.stageNum.ToString();
    gameoverScoreText.text = GameManager.Instance.scoreNum.ToString();
    
    gameOverUI.SetActive(true);
    canvasGroup.DOFade(1f, gameOverFadeDuration); 
}

이렇게 했는데 canvasGroup에 DOFade가 작동하지 않는다.. 원인을 찾아보자.

-> 찾앗는데, Target.cs에서 DOTween KillAll을 해버려서 UI의 DOTween도 모두 중지되어버렸다.

Target.cs

public void OnGameOver() {
    StopCoroutine(rotateCoroutine);
    rotateTween?.Kill();

    DOTween.Sequence()
        .AppendInterval(1f)
        .OnComplete(() => {
            DOTween.Kill(gameObject);
            Destroy(gameObject);
        });
}

그 다음에 Restart에는 간단한 다시 시작을 넣어줬다.

public void RestartButton() {
    CanvasGroup canvasGroup = gameOverUI.GetComponent<CanvasGroup>();
    canvasGroup.DOFade(0f, gameOverFadeDuration); 
    gameOverUI.SetActive(false);

    Events.OnRestartButton.Invoke();
}

Continue 버튼은 추후에 Google Ads나 Unity Ads를 넣어보자.

잘 작동한다, 이제 HOME 화면을 돌아가면?

최고 스테이지, 점수도 잘 적용된 모습이다.

참고로 점수의 저장은 아래와 같이 처리했다.

GameManager.cs

public void OnGameOver() {
    if (scoreNum > bestScoreNum) {
        PlayerPrefs.SetInt("BestScore", scoreNum);
        PlayerPrefs.SetInt("BestStage", stageNum);

        Events.OnNewBestScore?.Invoke();
    }
}

PlayerPrefs 원래 자체적으로 간단한 데이터만 저장할 용도이기 때문에 보안성이 굉장히 취약하다. 실제 게임을 만들 때 점수나 인게임 재화는 데이터 위조나 변조를 방지하기 위해 PlayerPrefs 보다는 다른 별도의 데이터로 저장하고 관리하는게 좋다.


SceneFader

TitleScene에서 MainScene, MainScene에서 TitleScne으로 LoadScene할 때 뚝뚝 끊기는 느낌이 있는 것을 중간에 SceneFader라는 연결부를 만들어줘서 해결할 수 있다.

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SceneFader : MonoBehaviour
{
    public Image img;
    public AnimationCurve curve;
    private void Start() {
        StartCoroutine(FadeIn());
    }

    public void FadeTo(string scene) {
        StartCoroutine(FadeOut(scene));
    }

    IEnumerator FadeIn() {
        float t = 1f;

        while (t > 0f) {
            t -= Time.deltaTime;
            float a = curve.Evaluate(t);
            img.color = new Color(0f, 0f, 0f, a);
            yield return 0;
        }
    }
    IEnumerator FadeOut(string scene) //FadeIn 반대로 설정
    {
        float t = 0f;

        while (t < 1f)
        {
            t += Time.deltaTime;
            float a = curve.Evaluate(t);
            img.color = new Color(0f, 0f, 0f, a); 
            yield return 0;
        }

        SceneManager.LoadScene(scene);
    }
}

흠... DOTween을 사용해보자.

using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SceneFader : MonoBehaviour
{
    public Image img;
    
    private void Start() {
        img.DOFade(0f, 0.5f);
    }

    public void FadeTo(string scene) {
        img.DOFade(1f, 0.5f)
            .OnComplete(() => {
                DOTween.Kill(gameObject);
                SceneManager.LoadScene(scene);
            }
            );
    }
}

여기에 싱글톤을 살짝 섞으면 어디서든 FadeTo를 사용할 수 있다.

public class SceneFader : MonoBehaviour
{
    public static SceneFader Instance;

    
    private void OnEnable() {
        if (Instance == null) {
            Instance = this;
        }
        else {
            Destroy(gameObject);
        }
    }

    public Image img;
    public float fadeDuration = 0.25f;
    private void Start() {
        img.DOFade(0f, fadeDuration);
    }

    public void FadeTo(string scene) {
        img.DOFade(1f, 0.25f)
            .OnComplete(() => {
                DOTween.Kill(gameObject);
                SceneManager.LoadScene(scene);
            }
            );
    }
}

Animation Curve 넣고 싶으면 Ease를 적용시켜주면 되는데 Fade 시간도 짧은데 굳이 넣을까 싶어서 제외했다. 이제 각 Scene마다 SceneManager를 넣어주자.

중요한건 image의 Raycast Target을 체크 해제해주어야 한다.

그래야 다른 UI 버튼이나 화면 클릭이 작동한다. 이제 LoadScene 해주는 FadeTo로 Scene 전환을 해준다.

public class PlayButton : MonoBehaviour
{
    public TitleAnimation titleAnimation;
    public void Play() {
        titleAnimation.StopTitleAnimation();
        SceneFader.Instance.FadeTo("MainScene");
    }
}

생각해보니깐 DontDestroyOnLoad로 해서 별도의 Start 실행이 안되서 따로 FadeIn도 실행해줘야된다. 최종적으로 완성된 SceneFader는 아래와 같다.

SceneFader.cs

public class SceneFader : MonoBehaviour
{
    public static SceneFader Instance;

    
    private void OnEnable() {
        if (Instance == null) {
            Instance = this;
            DontDestroyOnLoad(this);
        }
        else {
            Destroy(gameObject);
        }
    }

    public Image img;
    public float fadeDuration = 0.25f;
    private void Start() {
        img = GetComponentInChildren<Image>();
        FadeIn();
    }

    public void FadeIn() {
        img.DOFade(0f, fadeDuration);
    }

    public void FadeTo(string scene) {
        img.DOFade(1f, 0.25f)
            .OnComplete(() => {
                DOTween.Kill(gameObject);
                SceneManager.LoadScene(scene);
                FadeIn();
            }
            );
    }
}

이런 방식의 SceneFader 말고 로딩창 자체는 DontDestroyOnLoad로 로딩창을 구현해놓고 맵 전환시에 해당 로딩창을 띄어놓고 다음 씬을 로드해오는 방식도 있다.

아래와 같이 완성됐다.

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글