(유니티 2D) 플라이버드 점수 누적 카운트 (+간단 버그 수정)

  • by

지난번 기사에서 Bird가 파이프에 부딪치면 동작을 멈추게 했다.

이번에는 Bird가 파이프를 통과 할 때 한 점씩 점수를 얻으려고합니다.

그 전에 이전 기사에서 Bird가 멈추었지만 막상 파이프의 반복 생성을 막는 것을 놓쳤다.

그리고 오류 하나, 또 놓치고 ㅠㅠ

Bird가 초반 Ready 상태일 때도 파이프가 계속 생성되고 있다.

그걸 먼저 잡아 가야 해..,

매우 간단합니다.

(간단한 것을 놓쳤다 ~)

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

public class SpawnPoint : MonoBehaviour
{
    // 파이프 프리팹
    public GameObject pipePrefab;
    // 스폰 시간
    (SerializeField) float spawnTime = 1;
    // 타이머
    private float countdown = 0;

    // 랜덤하게 나올 스폰 타임
    (SerializeField) float spawnMaxTime = 1.1f;
    (SerializeField) float spawnMinTime = 0.9f;
    // 랜덤하게 나올 스폰 위치
    (SerializeField) float spawnMaxY = 2.3f;
    (SerializeField) float spawnMinY = -1.3f;
 
    void Update()
    {
        // Death 상태이거나 Start 전이면 프리팹 생성을 멈춘다 
        if(GameManager.isStart == false || GameManager.isDeath == true)
        {
            return;
        }
        // 타이머 구현
        countdown += Time.deltaTime;
        if (countdown >= spawnTime)
        {
            SpawnPipe();
            countdown = 0;
            // 시간을 랜덤하게 스폰하기
            spawnTime = Random.Range(spawnMinTime, spawnMaxTime);
        }
    }

    void SpawnPipe()
    {
        // 위치를 랜덤하게 스폰하기
        float spawnY = transform.position.y + Random.Range(spawnMinY, spawnMaxY);
        Vector3 randomPosition = new Vector3(transform.position.x, spawnY, transform.position.z);
        // 파이프 프리팹 생성 
        Instantiate(pipePrefab, randomPosition, Quaternion.identity);
    }
}

단지 조건에 맞추어 return시켜 주면 된다.


Ready 상태에서는 파이프가 생성되지 않는 것을 확인할 수 있다.


게임 종료시에 파이프가 생성되지 않는 것을 확인할 수 있다.

그런 다음 파이프를 통과 할 때 점수를 누적하여 점수를 얻습니다.

UI를 활용해 만들 예정이다.

이 게임의 UI를 관리하는 캔버스를 만듭니다.

(모든 UI가 화면에 표시되려면 무조건 캔버스에 표시해야 합니다.

)


히라키 창에서 오른쪽 클릭 → UI → Canvas를 클릭


빈 객체로 PlayUI와 UI 폴더를 별도로 나누었다.

(나중에 혼란을 주기 위해 미리 폴더를 정리합시다.

)

숫자를 입력하기 위해 TextMeshPro사용합니다.

히라기 윈도우 오른쪽 클릭 → UI → Text – TextMeshPro를 클릭


텍스트의 위치를 ​​설정할 수 있습니다.


텍스트에 다양한 효과를 줄 수 있습니다.

개인적으로 UI는 결국 개인 센스 감각에 따라 바뀌었다고 생각한다.

물론 어느 정도 기술과 지식은 당연히 가지고 있어야 하지만..^_ㅠ


나도 일단 대략 장식해 보았다… .. . ..

위의 숫자 00은 누적된 점수가 표시되는 곳입니다.

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

public class GameManager : MonoBehaviour
{
    // 플레이어 죽음 체크하기 
    public static bool isDeath = false;
    // 플레이어 시작 체크하기
    public static bool isStart = false;
    // 스코어
    public static int score = 0;
    void Start()
    {
        isDeath = false;
        isStart = false;
        score = 0;
    }
}

GameManager에서는 통상 score, life, bool형 등 대규모를 관리한다.

점수도 마찬가지 Static을 사용했으므로 0으로 항상 초기화한다.

using UnityEngine;
using TMPro;

public class PlayUI : MonoBehaviour
{
    public TextMeshProUGUI scoreText;
    void Update()
    {
        scoreText.text = GameManager.score.ToString();
    }
}


PlayUI 스크립트를 작성하고 text를 누적하여 수신할 변수를 선언합니다.

인스펙터 윈도우에서 자신을 드래그해 넣어준다.

(텍스트를 필드 선언 할 때 using TMPro;를 추가하십시오.)

그리고 GameManager에서 선언한 점수를 문자열 형식으로 받습니다.

void GetScore()
    {
        GameManager.score++;
    }

    void OnTriggerEnter2D(Collider2D coll)
    {
        if(coll.gameObject.tag == "Score")
        {
            GameManager.isDeath = false;
            GetScore();
        }
    }

그리고 플레이어 스크립트로 돌아와 Bird가 파이프를 통과했을 때

GameManager 점수가 하나씩 누적 추가되도록 메서드 GetScore를 만들고 만들고,

Trigger가 호출될 때 GetScore 메서드가 실행되도록 구현했습니다.


점수가 1씩 올라가는 것을 확인할 수 있다.


완전한 코드

더 보기

SpawnPoint.cs

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

public class SpawnPoint : MonoBehaviour
{
    // 파이프 프리팹
    public GameObject pipePrefab;
    // 스폰 시간
    (SerializeField) float spawnTime = 1;
    // 타이머
    private float countdown = 0;

    // 랜덤하게 나올 스폰 타임
    (SerializeField) float spawnMaxTime = 1.1f;
    (SerializeField) float spawnMinTime = 0.9f;
    // 랜덤하게 나올 스폰 위치
    (SerializeField) float spawnMaxY = 2.3f;
    (SerializeField) float spawnMinY = -1.3f;
 
    void Update()
    {
        // Death 상태이거나 Start 전이면 프리팹 생성을 멈춘다 
        if(GameManager.isStart == false || GameManager.isDeath == true)
        {
            return;
        }
        // 타이머 구현
        countdown += Time.deltaTime;
        if (countdown >= spawnTime)
        {
            SpawnPipe();
            countdown = 0;
            // 시간을 랜덤하게 스폰하기
            spawnTime = Random.Range(spawnMinTime, spawnMaxTime);
        }
    }

    void SpawnPipe()
    {
        // 위치를 랜덤하게 스폰하기
        float spawnY = transform.position.y + Random.Range(spawnMinY, spawnMaxY);
        Vector3 randomPosition = new Vector3(transform.position.x, spawnY, transform.position.z);
        // 파이프 프리팹 생성 
        Instantiate(pipePrefab, randomPosition, Quaternion.identity);
    }
}

GameManager.cs

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

public class GameManager : MonoBehaviour
{
    // 플레이어 죽음 체크하기 
    public static bool isDeath = false;
    // 플레이어 시작 체크하기
    public static bool isStart = false;
    // 스코어
    public static int score = 0;
    void Start()
    {
        isDeath = false;
        isStart = false;
        score = 0;
    }
}

PlayUI.cs

using UnityEngine;
using TMPro;

public class PlayUI : MonoBehaviour
{
    public TextMeshProUGUI scoreText;
    void Update()
    {
        scoreText.text = GameManager.score.ToString();
    }
}

Player.cs

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

public class Player : MonoBehaviour
{
    // Jump 구현
    Rigidbody2D rb;
    (SerializeField) float Speed = 5f;
    private bool keyJump = false;

    // Rotate 구현
    (SerializeField) private float upRotate = 5f;
    (SerializeField) private float downRotate = -5f;
    private Vector3 birdRotation;

    // Move 구현
    (SerializeField) private float moveSpeed = 5f;

    // Ready 구현
    (SerializeField) private float readyPower = 0.3f;

    void Start()
    {   // RigidBody2D 컴포넌트를 rb라는 변수를 통해 가져온다
        rb = this.GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        
        InputBird();

        if(GameManager.isStart == false)
        {
            ReadyBird();
            return;
        }
        
        RotateBird();
        MoveBird();
    }

    private void FixedUpdate()
    {
        // 게임 물리 연산은 FixedUpdate 메서드 안에서 진행한다 
        // 입력받은 Jump값이 true라면
        if (keyJump == true)
        {
            // JumpBird메서드 받기
            JumpBird();
            // 그리고 다시 keyJump값은 false로 만들어준다
            keyJump = false;
        }
    }

    void ReadyBird()
    {
        if (rb.velocity.y < 0)
        {
            rb.velocity = Vector2.up * readyPower;
        }
    }

    void JumpBird()
    {   // Rigidbody의 velocity 는 Speed 만큼 올라간다 
        rb.velocity = Vector3.up * Speed;
    }

    void InputBird()
    {
        if(GameManager.isDeath == true)
        {
            return;
        }
        // 스페이스바 혹은 마우스 좌버튼을 눌렀을 때
        keyJump |= Input.GetKeyDown(KeyCode.Space);
        keyJump |= Input.GetMouseButtonDown(0);

        // Bird가 시작했을 때 내려오지 않는 버그 수정
        if(GameManager.isStart == false && keyJump == true)
        {
            GameManager.isStart = true;
        }
    }

    void RotateBird()
    {
        // upRotate와 downRotate를 누적해서 저장할 변수 degree 초기화
        float degree = 0;
        if(rb.velocity.y > 0)
        {
            // upRotate 누적
            degree = upRotate;
        }
        else if(rb.velocity.y < 0)
        {
            // downRotate 누적
            degree = downRotate;
        }

        // birdRotation을 오일러각으로 변환하여 최댓값 30, 최솟값 -90을 받는다 
        birdRotation = new Vector3(0, 0, Mathf.Clamp((birdRotation.z + degree), -90, 30));
        transform.eulerAngles = birdRotation;
    }

    void MoveBird()
    {
        if(GameManager.isDeath == true)
        {
            return;
        }
        // Bird가 앞으로 이동하는 것 구현하기
        transform.Translate(Vector3.right * moveSpeed * Time.deltaTime, Space.World);
    }

    void GetScore()
    {
        GameManager.score++;
    }

    void OnTriggerEnter2D(Collider2D coll)
    {
        if(coll.gameObject.tag == "Score")
        {
            GameManager.isDeath = false;
            GetScore();
        }
    }

    void OnCollisionEnter2D(Collision2D coll)
    {
        if(coll.gameObject.tag == "Pipe" || coll.gameObject.tag == "Ground")
        {
            GameManager.isDeath = true;
        }
    }
}