티스토리 뷰

반응형

 

Flappy Bird 두 번째 포스트입니다! 아직 초보 개발자이니 부족한 부분은 의견 주시면 감사하겠습니당😊😊

 

 

 

그전에 첫번째 포스트를 못보신 분은 아래 링크 참고해 주세요!

GitHub 코드와 APK파일은 마지막 글에 있습니다.

 

2020/03/11 - [Unity 게임 개발] - [Unity 게임 개발 고수 되기 #1. Flappy Bird ] 01. 플레이어와 바닥 움직임,기둥과 점수존의 오브젝트 풀링 구현

 

[Unity 게임 개발 고수 되기 #1. Flappy Bird ] 01. 플레이어와 바닥 움직임,기둥과 점수존의 오브젝트 풀링 구현

Unity로 예전에 인기가 많았던 모바일 게임 Flappy Bird를 개발해보려고 해요! 다들 해보셨던 기억이 있나요? 화면을 터치하면 새가 점프하고 기둥이나 바닥에 부딪히지 않고 기둥 사이를 잘 지나가면 점수가 올라..

codingwell.tistory.com

 

 

 

유니티로 구현하기

 

1. 기둥, ScoreZone 오브젝트 스폰하고 비활성화하기

윗기둥, 아랫기둥, ScoreZone을 적절한 위치에 스폰하기 위해서 GameManager이라는 빈 오브젝트를 만들고 스크립트 또한 만들어주세요! 윗기둥과 아랫기둥은 랜덤한 높이로 스폰되기 위해서 랜덤함수를 사용하는 것이 좋겠죠? 

그 전에, 기둥과 ScoreZone은 각 스크립트를 만들어 넣습니다. 

 

 

일단 , GameManager 코드 일부를 보면

  private void Update()
    {
        if (player.isStart)  //플레이어가 게임을 시작했으면
        {
            //기둥, 점수존을 일정시간 간격으로 스폰함
            curSpawnDelay += Time.deltaTime;
            if (curSpawnDelay > nextSpawnDelay && !player.isDie)
            //스폰할 일정시간이 지났고 플레이어가 죽지않았다는 조건 하에
            {
                SpawnPillar();
                curSpawnDelay = 0;
            }
        }      
    }


    void SpawnPillar()  //기둥, 점수존을 일정 위치에 일정 속도로 움직이도록 스폰함
    {
        float randomIndex = Random.Range(4f,8.1f);
        Vector2 moveDir = new Vector2(-2.3f, 0);

        GameObject pillarUp = om.MakeObj("pillarUp");  //윗 기둥
        GameObject pillarDown = om.MakeObj("pillarDown");  //아랫 기둥
        GameObject scoreZone = om.MakeObj("scoreZone");  //점수 획득 존

        //스폰 위치
        pillarUp.transform.position = new Vector3(3.5f, randomIndex);
        pillarDown.transform.position = new Vector3(3.5f, randomIndex - 9.82f);
        scoreZone.transform.position = new Vector3(3.5f, randomIndex - 4.9f);

        Rigidbody2D rigidU = pillarUp.GetComponent<Rigidbody2D>();
        Rigidbody2D rigidD = pillarDown.GetComponent<Rigidbody2D>();
        Rigidbody2D rigidS = scoreZone.GetComponent<Rigidbody2D>();

        Pillar pillarULogic = pillarUp.GetComponent<Pillar>();
        Pillar pillarDLogic = pillarDown.GetComponent<Pillar>();
        ScoreZone scoreLogic = scoreZone.GetComponent<ScoreZone>();

        //기둥과 점수존을 왼쪽으로 일정하게 움직이도록 함
        rigidU.velocity = moveDir;
        rigidD.velocity = moveDir;
        rigidS.velocity = moveDir;
    }

 

일정한 시간마다 스폰하기 위해서 저는 nextSpawnDelay 변수를 사용하였고, 1.5로 할당하였어요. Update문에서 일정 시간이 지나면 SpawnPillar 함수를 호출합니다. 

 

SpawnPillar 함수는 기둥을 Random클래스를 이용하여 랜덤한 높이로 스폰하고, 왼쪽으로 움직이도록 합니다. 

첫번째 포스트에서 작성했던 ObjectManager의 MakeObj 함수를 호출하여 오브젝트를 활성화시킵니다.

 

그 다음, 스폰 위치를 정해줍니다. 저는 씬에서 기둥을 움직여가면서 높이의 최대 최소를 정했습니다. 

점수존은 기둥 사이에 위치하도록 합니다. 

각 오브젝트 리지드바디의 velocity에 moveDir을 할당하여 왼쪽으로 일정하게 움직이도록 합니다.

 

 

 

 

스폰을 한 후에 활성화된 기둥과 점수존이 왼쪽으로 움직이다가 화면에서 사라지게 됩니다.

이 때 해당 오브젝트를 비활성화를 시켜야겠죠? 이를 위해 Border라는 빈 오브젝트를 하나 만들어주세요.

Rigidbody2D를 Static으로 , Box Collider2D는 isTrigger을 체크하여 넣어주세요.

 

그리고 맵 왼쪽에 적절한 곳에 위치해 주시면 됩니다.

 

Border 오브젝트가 맵 왼쪽에 위치하게 합니다.

 

 

 

기둥이나 점수존이 왼쪽으로 움직이다가 Border 오브젝트와 닿으면 비활성화되도록 하겠습니다.

기둥 스크립트(Pillar) 와 점수존 스크립트 (ScoreZone) 를 수정하겠습니다.

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

public class Pillar : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Border")
        {
            gameObject.SetActive(false);
        }
    }
}

기둥 스크립트입니다. OnTriggerEnter2D 함수를 사용해서 Border과 충돌이 있을 경우 해당 오브젝트를

비활성화 하도록 했습니다. 여기서 Border 오브젝트의 tag를 설정해주셔야 합니다.

 

 

 

 

오브젝트의 이름 밑에 Tag가 있죠? 여기서 Add Tag를 눌러서 설정해주세요.

 

 

 

 

ScoreZone 또한 같습니다.

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

public class ScoreZone : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Border")
        {
            gameObject.SetActive(false);
        }
    }
}

 

 

 

 

활성화되어 스폰되고 Border와 접촉할 때 비활성화가 잘 되는 것을 볼 수 있습니다.

기둥 사이의 ScoreZone 또한 잘 스폰된 것이 보입니다.

 

 

 

 

 

 

 

 

2. 플레이어가 기둥과 바닥에 충돌할 경우 게임 오버 구현하기

일단, 게임 오버가 되면 모든 오브젝트가 멈추고 플레이어가 바닥에 -90도 각도로 회전하며 곤두박질치게 됩니다.

저번에 만들어두었던 GameManager 스크립트에 GameOver 함수를 작성합니다.

public void GameOver()
    {       
        //모든 오브젝트의 움직임을 멈춤
        om.StopObject("pillarUp");
        om.StopObject("pillarDown");
        om.StopObject("scoreZone");

        player.isStart = false;
        player.isDie = true;
        player.transform.rotation = Quaternion.Euler(0, 0, -90); 
    }

GameObjectManager의 StopObject 함수를 구현했었죠? 그 함수를 호출하여 활성화되어 있는 기둥들과 점수존의 움직임을 멈추도록 합니다. 

 

그리고 플레이어의 Rotation z축은 -90이 되어 땅으로 곤두박질치게 합니다.

 

 

 

 

 

 

플레이어가 바닥이나 기둥에 닿을 시에 GameManager의 GameOver함수를 호출하도록 합니다.

플레이어의 스크립트를 수정합니다.

 private void OnTriggerEnter2D(Collider2D collision)
    {
        //바닥에 닿을 시에 게임오버와 물리적 요소 제거
        if (collision.gameObject.tag == "Floor")
        {
            gm.GameOver();
            rigid.simulated = false;
        }

        //기둥에 닿을 시에 게임오버
        if (collision.gameObject.tag == "Pillar")
            gm.GameOver();        
    }

바닥과 기둥 오브젝트의 태그를 각각 만들어 적용합니다.

OnTriggerEnter2D 함수를 사용하여 충돌을 감지하면 GameOver를 호출합니다. 그리고 플레이어가 계속 중력이 작용하여 끝없이 떨어지지 않아야 하기때문에 플레이어가 바닥에 닿을 시에 리지드바디의 simulated를 false로 할당하여 물리적 요소가 소용이 없도록 했습니다.

 

 

 

 

 

 

3. 점수 획득과 점수UI

플레이어가 기둥 사이를 잘 지나가면 점수가 올라가야겠죠?

저번 포스트에서 기둥 사이에 점수 획득 판별을 위한 ScoreZone 오브젝트를 두었습니다.

플레이어가 ScoreZone에 닿을 경우 점수가 올라가도록 해보겠습니다.

 

일단, 점수를 띄울 Text UI를 만들어보겠습니다.

Hierarchy 에서 Create - UI - Text 를 눌러주세요. 그러면 Canvas가 Hierarchy창에 생기죠? 

Canvas 인스펙터를 수정해보겠습니다.

 

Canvas 인스펙터에서 원래 Render Mode가 Screen Space - Overlay로 되어있었던 것을 Screen Space - Camera로 바꾸어주세요. Render Camera에 지금 Hierarchy에 있는 메인카메라를 드래그하여 넣어줍니다. 이렇게 하면 카메라 크기에 맞춰 Canvas가 조정됩니다. 

그리고 UI는 항상 가장 위에 보여야하기 때문에 Order in Layer를 크게 10으로 지정해주세요.

 

Canvas Scaler 에서 UI Scale Mode를 다음과 같이 바꾸고 Reference Resolution도 바꾸어주세요!

 

 

 

 

 

 

이제 Canvas 안에 있는 저희가 만든 Text를 건드려보겠습니다. 저는 ScoreText로 이름을 바꾸었어요.

 

ScoreText 인스펙터

 

 

 

 

저 동그라미 속의 네모 칸을 누르고 Shift (Pivot) + Alt (Position) 키를 동시에 누르면 

 

다음과 같이 나타납니다. 이 때 저는 가운데 위로 설정하였습니다.

이는 앵커 프리셋을 설정하는 것인데, 기준점을 설정하며 해상도에 대응하게 하는 것입니다.

 

앵커 프리셋을 설정했다면 위의 사진처럼 위치, Width와 Height, 글자 크기/배열 등을 설정합니다.

 

 

 

 

 

 

 

 

다음과 같이 ScoreText가 UI로 나타납니다. 이제 스크립트를 작성해보겠습니다.

 

 

 

 

 

 

먼저 , GameManager에서 상단에 using UnityEngine.UI; 를 써주세요.

그리고 변수를 선언해주세요. screText 는 해당 Text UI를 유니티에서 꼭! 할당시켜주어야 합니다.

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

public class GameManager : MonoBehaviour
{
	public int score;
  	public Text scoreText;

 

 

점수 UI를 업데이트할 함수를 만듭니다. 

public void ScoreUpdate() 
{
        //UI의 Text에 현재 점수를 업데이트함
        scoreText.text = score.ToString();
}

 

 

 

다음은 ScoreZone 스크립트입니다. 다음과 같이 코드를 수정합니다.

private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Border")
        {
            gameObject.SetActive(false);
        }

        if(collision.gameObject.tag == "Player")
        {
            //점수 증가
            gm.score++;

            //점수 UI 업데이트
            gm.ScoreUpdate();
        }
    }

플레이어에 태그를 달아주고, 플레이어와 충돌할 시에  GameManager의 score를 증가시키고, UI 업데이트를 위해 scoreUpdate 함수를 호출합니다.

 

 

 

 

 

 

 

 

4. 게임 시작 직전 UI

게임 시작 직전에 다음과 같은 UI가 나타나있고, 플레이어는 애니메이션을 실행하고 있습니다.

스페이스바를 눌러 점프를 시작하면 (모바일에서는 터치), UI가 사라지고 플레이어는 애니메이션을 중지합니다.

 

 

 

 

 

일단, Canvas 안에 다음과 같이 빈 오브젝트를 만들고(BeforeStartUI) 그 자식으로 Image 두 개를 만들어주세요.

Image 하나는 Get Ready! 글자가 써있는 sprite를 할당해주시고, 다른 Image 하나는 TAP하라는 그림이 있는 sprite를 할당해주세요.

 

 

 

 

 

점프가 시작되면, UI 전체가 투명해지며 안보이는 애니메이션을 만들겠습니다.

두 Image를 감싸고 있는 BeforeStartUI에 Animator를 만들어주시고, Animation을 다음과 같이 만들어주세요.

Add Property를 눌러서 두 Image의 Color를 추가합니다. 처음에는 (r,g,b,a)를 (1,1,1,1)로 설정하여 불투명하게하고,

 

 

 

 

0.25초 정도에 (r,g,b,a)를 (1,1,1,0)으로 설정하여 투명하게합니다. 이렇게하면 UI가 투명해지는 애니메이션이 완성됩니다.

 

 

 

 

 

Animator를 수정해보겠습니다.

Animator에서는 저 +를 눌러 Trigger를 만듭니다.

그리고 아까 만들었던 애니메이션은 Any State와 연결하고, 빈 애니메이션을 하나 만들어 Default로 설정합니다.

 

 

 

 

 

그럼 이제  GameManager스크립트를 수정해보겠습니다.

상단에 다음과 같이 선언합니다. anim는 유니티에서 꼭 할당시켜주어야 합니다. 애니메이터가 들어있는 오브젝트를 드래그하여 할당하면 됩니다.

bool tryStart = true;
public Animator anim;

 

 

 

Update문을 수정하겠습니다. 애니메이션이 단 한번, 시작할때만 실행되어야 하기 때문에 다음과 같이 로직을 작성하였습니다. SetTrigger함수를 호출하며 아까 유니티 엔진에 만들었던 Trigger 이름을 인수로 보내고, 한번 실행했으니 tryStart를 false로 바꿉니다. 

private void Update()
    {
        if (player.isStart)  //플레이어가 게임을 시작했으면
        {
            //기둥을 일정시간 간격으로 스폰함
            curSpawnDelay += Time.deltaTime;
            if (curSpawnDelay > nextSpawnDelay && !player.isDie)
            //스폰할 일정시간이 지났고 플레이어가 죽지않았다는 조건 하에
            {
                SpawnPillar();
                curSpawnDelay = 0;
            }

            //터치를 시작하면 처음 UI가 투명해지는 애니메이션 실행
            if (tryStart)
                anim.SetTrigger("BeforeStartFade");
            tryStart = false;
        }
    }

 

 

 

 

 

 

 

 

 

 

 

이번 포스트는 여기까지 하고,

다음 포스트에서는 게임 오버 후의 UI와 PlayerPrefs를 이용한 점수 저장을 구현해보겠습니당😊!

 

읽어주셔서 감사합니당 😍~

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday