게임 스테이지를 전부 클리어했을때 표시할 엔딩장면을 만들어 봅시다. Result 라는 이름의 씬을 만들어 봅니다.

결과화면  UI 만들기 

배경 이미지 배치

+UI>Image를 추가하고 자동추가된 Canvas 컴포넌트의 Render Mode값을 ScreenSpace-Camera로 설정하고 Render Camara Main Camera를 선택해 줍니다.  tile_back이미지를 Image컴포넌트에 끌어다 놓습니다. 이미지 크기를 화면에 맞게 조정합니다. 하이라키에서 image를 선택하고Anchor Presets를 열어 ALT를 누른상태에서 stetch를 선택해도 됩니다.

 

타이틀로 돌아가는 버튼 배치

하이라키의+를 클릭해 UI>Legacy>Button을 추가합니다. 프로젝트뷰 Image폴더의 Button스프라이트를 Image컴포넌트의  Source에 끌어다 놓습니다. Preserve Aspect / SetNativeSize후 적당히 위치를 잡습니다.

자식으로 Text도 추가하고 타이틀로 돌아가기를 입력해줍니다. 사이즈는 48정도 입니다.

Button에 ChangeScene 스크립트를 끌어다 추가해주고 Scene Name은 Title로 설정해줍니다.

OnClick()에 +를 눌러 리스트를 하나 만들고 하이라키의 Button을 끌어다 연결후 None Function을 눌러 ChangeScene>Load()를 선택해줍니다.

 

점수표시용 UI만들기

하이라키의+를 클릭해 UI>Image을 추가합니다. 프로젝트뷰 Image폴더의 ScoreBoard스프라이트를 Image컴포넌트의  Source에 끌어다 놓습니다. Preserve Aspect / SetNativeSize후 적당히 위치를 잡습니다. 이름을 ScoreBoard 로 바꿉니다.

자식으로 Text도 추가하고 타이틀로 돌아가기를 입력해줍니다. 사이즈는 48정도 입니다. 이름을 ScoreText로 바꿉니다.

전체 점수를 표시할 스크립트 만들기

ResultManager스크립트를 만들어 Canvas에 어태치합니다.

ResultManager.cs
0.00MB

실행되면 GameManager의 totalScore를 ScoreBoard자식인 scoreText에 표시하는게 다입니다.

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

public class ResultManager : MonoBehaviour
{
    public GameObject scoreText;

    // Start is called before the first frame update
    void Start() {
        scoreText.GetComponent<Text>().text = GameManager.totalScore.ToString();
    }
}

 Canvas에 어태치된 ResultManager스크립트 컴포넌트의  scoreText변수에 ScoreText를 끌어다 스크립트에 연결해 줍니다.

시간처리를 위한 변수를 추가한다.

// +++ 시간 제한 추가 +++
public GameObject timeBar;          // 시간 표시 이미지 
public GameObject timeText;         // 시간 텍스트
TimeController timeCnt;             // TimeController 스크립트 참조

 

점수처를 위한 변수를 추가한다.

// +++ 점수 추가 +++
public GameObject scoreText;        // 점수 텍스트  
public static int totalScore;       // 점수 총합
public int stageScore = 0;          // 스테이지 점수

싱글톤정적변수인 gameState를 체크해 게임중이라면 player게임오브젝트의 playerController스크립트의 score변수가 0이 아니라면 stageScore를 추가해주고 0으로 변경한후 점수를 처리하기 위한 UpdateScore()를 호출한다.

else if (PlayerController.gameState == "playing")
    {
        // 게임 중
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        // PlayerController 가져오기
        PlayerController playerCnt = player.GetComponent<PlayerController>();
        // +++ 시간 제한 추가 +++
        // 시간 갱신
        if(timeCnt != null)
        {
			~시간 종료 처리
        }
        // +++ 점수 추가 +++
        if (playerCnt.score != 0)
        {
            stageScore += playerCnt.score;
            playerCnt.score = 0;
            UpdateScore();
        }
    }

UpdateScore()에서는 public으로 연결된 scoreText게임오브젝트의 Text컴포넌트를 갱신해준다. 재미있는건 하이라키의 모든건 게임오브젝트라 Text마저도 GameObject이다. Text 내용을 변경하기 위해서는 귀찮아도 다시 GetComponent<Text>를 통해 test에 접근해야 한다 

void UpdateScore()
{
    int score = stageScore + totalScore;
    scoreText.GetComponent<Text>().text = score.ToString();
}

using UnityEngine.UI;  간 선언되어 있기 때문에  Text타입을 이용해 좀더 편하게 사용할 수도 있다.

public Text ScoreText;
ScoreText.text =  score.ToString();

시간은 TimeController에서 관리되여 있고 여기서는 시간을 표시하고 게임을 관리하기 위해서 사용한다. 소스를 읽어보면 이해할수 있으리라 믿습니다.

GameManager.cs
0.00MB

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI を使うのに必要

public class GameManager : MonoBehaviour
{
    public GameObject mainImage;        // 이미지를 담아두는 GameObject
    public Sprite gameOverSpr;          // GAME OVER 이미지
    public Sprite gameClearSpr;         // GAME CLEAR 이미지
    public GameObject panel;            // 패널
    public GameObject restartButton;    // RESTART 버튼
    public GameObject nextButton;       // NEXT 버튼
    Image titleImage;                   // 이미지를 표시하고있는 Image 컴포넌트

    // +++ 시간 제한 추가 +++
    public GameObject timeBar;          // 시간 표시 이미지 
    public GameObject timeText;         // 시간 텍스트
    TimeController timeCnt;             // TimeController

    // +++ 점수 추가 +++
    public GameObject scoreText;        // 점수 텍스트  
    public static int totalScore;       // 점수 총합
    public int stageScore = 0;          // 스테이지 점수

    // Start is called before the first frame update
    void Start()
    {
        // 이미지 숨기기
        Invoke("InactiveImage", 1.0f);
        // 버튼(패널)을 숨기기
        panel.SetActive(false);

        // +++ 시간 제한 추가 +++
        // TimeController 가져옴
        timeCnt = GetComponent<TimeController>();
        if(timeCnt != null)
        {
            if(timeCnt.gameTime == 0.0f)
            {
                timeBar.SetActive(false);   // 시간 제한이 없으면 숨김
            }
        }

        // +++ 점수 추가 +++
        UpdateScore();
    }

    // Update is called once per frame
    void Update()
    {
        if (PlayerController.gameState == "gameclear")
        {
            // 게임 클리어
            mainImage.SetActive(true); // 이미지 표시
            panel.SetActive(true); // 버튼(패널)을 표시
            // RESTART 버튼 무효화 
            Button bt = restartButton.GetComponent<Button>();
            bt.interactable = false;
            mainImage.GetComponent<Image>().sprite = gameClearSpr;
            PlayerController.gameState = "gameend";

            // +++ 시간 제한 추가 +++
            if (timeCnt != null)
            {
                timeCnt.isTimeOver = true; // 시간 카운트 중지

                // +++ 점수 추가 +++
                // 정수에 할당하여 소숫점 버림
                int time = (int)timeCnt.displayTime;
                totalScore += time * 10;        // 남은 시간을 점수에 더한다.
            }
            // +++ 점수 추가 +++
            totalScore += stageScore;
            stageScore = 0;
            UpdateScore();// 점수 갱신
        }
        else if (PlayerController.gameState == "gameover")
        {
            // 게임 오버
            mainImage.SetActive(true);      // 이미지 표시하기
            panel.SetActive(true);          // 버튼(패널)표시하기
            // NEXT 버튼 비활성
            Button bt = nextButton.GetComponent<Button>();
            bt.interactable = false;
            mainImage.GetComponent<Image>().sprite = gameOverSpr;
            PlayerController.gameState = "gameend";

            // +++ 시간 제한 추가 +++
            if (timeCnt != null)
            {
                timeCnt.isTimeOver = true; // 시간 카운트 중지
            }
        }
        else if (PlayerController.gameState == "playing")
        {
            // 게임 중
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            // PlayerController 가져오기
            PlayerController playerCnt = player.GetComponent<PlayerController>();
            // +++ 시간 제한 추가 +++
            // 시간 갱신
            if(timeCnt != null)
            {
                if (timeCnt.gameTime > 0.0f)
                {
                    // 정수에 할당하여 소수점 이하를 버림
                    int time = (int)timeCnt.displayTime;
                    // 시간 갱신
                    timeText.GetComponent<Text>().text = time.ToString();
                    // 타임 오버
                    if (time == 0)
                    {
                        playerCnt.GameOver();   // 게임 오버
                    }
                }
            }
            // +++ 점수 추가 +++
            if (playerCnt.score != 0)
            {
                stageScore += playerCnt.score;
                playerCnt.score = 0;
                UpdateScore();
            }
        }
    }
    // 이미지 숨김
    void InactiveImage()
    {
        mainImage.SetActive(false);
    }
    // +++ 점수 추가 +++
    void UpdateScore()
    {
        int score = stageScore + totalScore;
        scoreText.GetComponent<Text>().text = score.ToString();
    }
}

 

시간을 계측하는 스크립트 만들기

카운트다운과 카운트업을 모두 할수 있는 스크립트를 만들고 이름을 TimeController로 합니다.

프로젝트뷰의 Prefab폴더에서 Canvs 프리팹을 더블클릭후 편집모드 상태에서 TimeController 스크립트를 어태치합니다.

분석을  해보면

변수

times가 시계다.

public bool isCountDown = true; // true= 카운트 다운으로 시간 측정
public float gameTime = 0;      // 게엠의 최대 시간
public bool isTimeOver = false; // true= 타이머 정지
public float displayTime = 0;   // 표시 시간
float times = 0;                // 현재 시간

Start()

최대게임시간을 displayTime으로 지정한다.

void Start(){
    if (isCountDown){
        // 카운트다운
        displayTime = gameTime;
    }
}

Update()

times를 업데이트해주고 이시간을 이용해 displayTime을 갱신한다.

void Update(){
    if (isTimeOver == false){
        times += Time.deltaTime;
        if (isCountDown){
            // 카운트다은
            displayTime = gameTime - times;
            }
        }else{
            // 카운트업
            displayTime = times;
        }
    }
}

스크립트를 Canvas에 어태치한후 인스펙트뷰의 GameTime값을 60으로 IsCount Down을 체크합니다.

TimeController.cs
0.00MB

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

public class TimeController : MonoBehaviour{
    public bool isCountDown = true; // true= 카운트 다운으로 시간 측정
    public float gameTime = 0;      // 게엠의 최대 시간
    public bool isTimeOver = false; // true= 타이머 정지
    public float displayTime = 0;   // 표시 시간

    float times = 0;                // 현재 시간

    // Start is called before the first frame update
    void Start(){
        if (isCountDown){
            // 카운트다운
            displayTime = gameTime;
        }
    }

    // Update is called once per frame
    void Update(){
        if (isTimeOver == false){
            times += Time.deltaTime;
            if (isCountDown){
                // 카운트다은
                displayTime = gameTime - times;
                if (displayTime <= 0.0f){
                    displayTime = 0.0f;
                    isTimeOver = true;
                }
            }else{
                // 카운트업
                displayTime = times;
                if (displayTime >= gameTime){
                    displayTime = gameTime;
                    isTimeOver = true;
                }
            }
            Debug.Log("TIMES: " + displayTime);
        }
    }
}

 

게임에 시간제한 UI 추가하기

Prefabs폴더의 Canvas를 선택하고 인스펙트뷰의 Open을 클릭한다.

하이라키의 +를 클릭해서 UI>Image를 2개 추가하고 이름을 TimeBar, Score로 한다 

 Images폴더에서 TimeBar, ScoreBoard를 선택해 인스펙트뷰의 Image컴포넌트의 Source Image에 끌어다 놓는다.

Preserve Aspect를 체크하고 Set NativeSize를 클릭후 적당히 사이즈를 조정한다.

위치는 Transform 의 Anchor Presets를 누르고 ALT를 눌러 상중과 상우를 선택해 적당해 배치한다.

TimeBar, Score Image자식으로 UI>Legacy>Text를 추가한다. 이름은 TimeText, ScoreText로 바꿔준다. Text는 000 Font Size는 48~64로 설정한다.

 

실행시 Text가 안보이는건 Text박스가 작아서이다. 하이라키에서 두 Text를 순서대로 골라서 박스사이즈를 맞춰준다.

아이템과 점수 만들기

Images폴더에 Item_color 스프라이트가 있다. 하나를 끌어다 씬뷰에 놓는다.

인스펙트뷰에서 CircleCollider2D를 추가해준다. Trigger를 체크해준다. 플레이어와 접촉을 감지하기 위해서다

Tag에서 ScoreItem를 추가로 만들어주고 지정해준다. 플레이어에서 Trigger가 발생했을때 Item인지 체크하기 위해서다.

ItemData.cs 스크립트를 작성에 어태치해준다. 단순히 플레이어가 먹었을때 올라가는 점수값 변수만 있다.

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

public class ItemData : MonoBehaviour {
    public int value = 0;       // 정수값을 설정할 수 있다
}

이제 이걸 프로젝트뷰 프리팹폴더로 끌어다  놓는다.

Ctrl-D로 3개 더 만든후 이름을 Item_blue, Item_green, Item_red, Item_white로 바꾸고

itemData 컴포넌트의 value를 blue:30, green:10, red:50, white:100으로 설정한다.

스프라이트 렌더러컴포넌트의 Sprite 검색◉을 누르고 이름에 맞는 스프라이트를 선택해준다.

실험삼아 아이템들을 씬뷰에 배치한다.

 

아이템 획득 스크립트

방금만든 아이템을 획등하는 스크립트를 PlayerController에 추가합니다.

Collider에서 Trigger가 발생시 OnTriggerEnter2D()에서 처리하므로 Tag가 "ScoreItem"이라면 충돌 게임오브젝트의 ItemData컴포넌트(스크립트)의 변수인 value값을 score로 설정한다. 이값은 나중에 GameManager에서 불리워져 점수로 추가된다.

PlayerController.cs
0.01MB

// 접촉 시작
private void OnTriggerEnter2D(Collider2D collision)
{
   if(collision.gameObject.tag == "Goal")  {
        Goal();        // 골
    }
    else if (collision.gameObject.tag == "Dead") {
        GameOver();     // 게임 오버
    }
    else if (collision.gameObject.tag == "ScoreItem"){
        // 점수 아이템
        // ItemData 가져오기
        ItemData item = collision.gameObject.GetComponent<ItemData>(); // 점수 얻기
        score = item.value;
        // 아이템 제거
        Destroy(collision.gameObject);
    }
}

 

 

사이드뷰 게임답게  배경화면을 옆으로 스크롤되게 만들어 봅시다.

 

새로운 씬 만들기

Stage1과 마찬가지고 Prefab 폴더의 배경및 지면과 플레이어 캐릭터를 배치하고 가로방향으로 화면이 스크롤 될 수 있도록 화면의 두배 크기로 스테이지를 배치합니다. Stage1을 복사하거나 다른이름으로 변경하면 쉽게 만들 수 있습니다. 이름은 BaseScene으로 합니다.

설정시 UI가 있어 편집이 어려우면 하이라키의 숨김기능이나 인스펙터의 비활성화를 하면 됩니다.

먼저 블록과 구멍을 없애고 지면을 여섯 개 나열합니다. 목표지점은 화면 오른쪽 끝에 배치하고 Collider의 EditCollider를 재 설정해 Wall Object은 좌우끝에 DeadZone은 아래쪽에 길게 배치합니다. 

Ctrl-S로 저장합니다. 이름이 BaseScene이 아니면 이름도 바꿔줍니다. 게임에 직접사용되지는 않고 앞으로 새로운 씬을 만들때 템플릿으로 사용할겁니다.

 

카메라를 관리하는 스크립트 추가하기

게임을 하면 화면이 캐릭터에 따라가는 장면을 보셨을겁니다. 

가장 간단한 방법은 Main Camera를 player의 자식오브젝트로 만들면 됩니다. 

여기서는 스크립트를 짜서 구현해 보겠습니다.. CameraManager.cs를 만듭니다.

변수 : 카메라의 상하좌우 리미트를 정하는 변수 입니다.

실제 카메라의 위치를 캐릭터에 고정해주는 법은 캐릭터의 x,y좌표를 카메라에 복사해주면 됩니다.  여기에 조금더 처리를 추가해 줍니다. 

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

public class CameraManager : MonoBehaviour
{
    // Update is called once per frame
    void Update() {
        GameObject player = GameObject.FindGameObjectWithTag("Player"); // 플레이어 찾기
        if(player != null) {
            float x = player.transform.position.x;
            float y = player.transform.position.y;
            float z = transform.position.z;  // z값은 카메라 자신을 사용합니다.
            Vector3 v3 = new Vector3(x, y, z); // 카메라 위치의 Vector3 만들기
            transform.position = v3;
        }
}

Update() : 

FindGameObjectWithTag("Player")로 Player를 찾습니다. 매번 찾는 이유는 Player가 Null인지 체크해주기 위해서입니다.

하이라키의 player를 선택하고 인스펙터뷰에서 Tag를 Player로 선택해줍니다.

Player가 Null이 아니라면 Player위치를 위치제한검사및 스크롤 처리를 해줍니다. 처리를 마치면 (x,y,z)를 카메라의 위치로 갱신합니다.

Vector3 v3 = new Vector3(x, y, z); // 카메라 위치의 Vector3 만들기
            transform.position = v3;

CameraManager.cs
0.00MB

 

스크립트를 MainCamera에 끌어다 어태치합니다.

이제 스크립트의 RightLimit값을 알아보겠습니다. 플레이를 시키고 Scene뷰탭을 클릭해서 씬뷰모드로 변경합니다.

하이라키에서 MainCamera를 선택하고 이동툴을 이용해 X축으로 끌어 화면 오른쪽끝이 포함되게 이동시켜봅니다. 전 약 18정도가 나왔습니다. 이 값을 스크립트의 RightLimit값으로 설정합니다. 플레이를 멈추면 다시 원상태로 됩니다.

플레이후 카메라위치를 바꾼이유는 플레이를 정지하면 변경한값들을 다시 원래 상태로 돌려주기 때문입니다. 게임 개발시 상당히 유용합니다.

하이라키의 back(배경화면)을 끌어다 카메라에 붙여줍니다.

게임을 실행해보면 캐릭터가 중간쯤 오면 화면이 스크롤하기 시작해서  Goal이 보이는 오른쪽 끝이 보이면 스크롤을 멈춥니다.  이때 배경은 카메라를 따라 다니기 때문에 항상 멈춰 있는 것 처럼 보입니다.  잘 생각해보면 캐릭터>카메라>배경 3종류가 같이 움직이고 Ground만 스크롤 되는 것입니다. 좀더 생각해보면 실제 Ground가 스크롤 되는게 아니라 카메라가 오른쪽으로 움직일 뿐입니다. Ground가 스크롤 되는 시점의 씬뷰를 보면 카메라의 위치가 가운데로 이동해서 찍고 있죠.

그리고 신기한건 캐릭터가 움직이다가 화면 중간쯤 오면 스크롤이 시작됩니다. 이건 이동제한 로직 때문입니다.

// 양 끝에 이동 제한 적용
if (x < leftLimit) {
    x = leftLimit;
} else if (x > rightLimit)  {
    x = rightLimit;
}

게임 시작시 캐릭터의 위치는 저의 경우 -7.44입니다. leftLimit는0이므로 캐릭터의 위치가 0이 될때까지는 x는 0으로 고정되죠. 따라서 카메라의 위치도 0으로 고정되서 안움직이는 겁니다. RigitLimit는 아까 오른쪽 골이 보이는 위치로 지정해 주었으니 오른쪽 골이 보이면 RightLimit에 고정되는거죠. 

 

배경에 다중 스크롤 적용하기

현재 배경이 카메라에 고정되어 움직이지 않는것 처럼 보입니다. 그 위에 화면보다 조금 큰 배경 이미지를 표시하고 조금 느리게 스크롤 되도록 만들어 봅시다. 다중 스크롤 되면 게임 화면에 깊이감이 더해집니다.

하이라키에 빈게임오브젝트를 만들고 이름을 SubScreen으로 합니다. SubScreen의 Transform>Position의 X,Y,Z값은 0으로 리셋합니다. 프로젝트뷰에서 배경이미지 back2를 두번 끌어다 놓고 자식으로 만듭니다. back2중 하나를 옆으로 붙여 길게 만들어 줍니다. Back2 두개를 다 선택하고  Sprite Rendere의 Order in Layer를 1로 만들어 배경보다 위쪽으로 해줍니다.

CameraManager.cs 맨 밑에 subScreen 처리가 있습니다. x속도를 반으로 해서 효과를 줍니다. 또 하이라키의  SubScreen을 끌어다 MainCamera의 CameraManager스크립터 컴포넌트의 subScreen 참조에 연결해줍니다.

if (subScreen != null) {
    y = subScreen.transform.position.y;
    z = subScreen.transform.position.z;
    Vector3 v = new Vector3(x / 2.0f, y, z);
    subScreen.transform.position = v;
}

플레이 해보면 subScreen이 플레이어보다 느린 속도로 스크롤 되네요.

 

강제스크롤 게임오버 처리

게임의 긴박감을 주기 위해 자동으로 횡스크롤을 하게 만들겠습니다. 캐릭터의 위치를 참조하지 않습니다.

하이라키의 DeadZone과 Wall을 카메라의 자식으로 만듭니다. Wall의 오른쪽 충돌 위치를 화면의 오른쪽으로 바꿔줍니다. 화면 오른쪽으로 가면 캐릭터가 스크롤을 못하게 하기 위해서입니다. MainCamera의 CameraManager 스크립트 컴포넌트의 IsForceScrollX만 체크해 줍니다. 

그럼 스크립트의 다음 부분이 처리 되어 자동으로 횡스크롤 합니다.

// 가로 방향 동기
if (isForceScrollX)  {
    // 가로 강제 스크롤
    x = transform.position.x + (forceScrollSpeedX * Time.deltaTime);
}

오른쪽 화면 끝으로 이동하면 더이상 못 나가는건 스크립트가 아닌 아까 Wall의 Collider위치를 화면 오른쪽으로 배치해서 그럽니다.

동작을 확인하셨으면 File>SaveAs를 선 BaseForcedScrollStage라는 이름으로 씬을 저장합니다. 나중에 강제 스크롤 스테이지용 템플릿으로 사용합니다

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

public class CameraManager : MonoBehaviour
{
    public float leftLimit = 0.0f;      // 왼쪽 스크롤 제한
    public float rightLimit = 0.0f;     // 오른쪽 스크롤 제한 
    public float topLimit = 0.0f;       // 위 스크롤 제한 
    public float bottomLimit = 0.0f;    // 아래 스크롤 제한

    public GameObject subScreen;        // 서브 스크린

    public bool isForceScrollX = false;     // X 축 강제 스크롤 플래그
    public float forceScrollSpeedX = 0.5f;  // 1초간 움직일 X의 거리
    public bool isForceScrollY = false;     // Y 축 강제 스크롤 플래그
    public float forceScrollSpeedY = 0.5f;  // 1초간 움직일 Y의 거리

    // Start is called before the first frame update
    void Start() {
        
    }

    // Update is called once per frame
    void Update() {
        GameObject player = GameObject.FindGameObjectWithTag("Player"); // 플레이어 찾기
        if(player != null) {
            // 카메라의 좌표 갱신
            float x = player.transform.position.x;
            float y = player.transform.position.y;
            float z = transform.position.z;
            // 가로 방향 동기
            if (isForceScrollX)  {
                // 가로 강제 스크롤
                x = transform.position.x + (forceScrollSpeedX * Time.deltaTime);
            }
            // 양 끝에 이동 제한 적용
            if (x < leftLimit) {
                x = leftLimit;
            } else if (x > rightLimit)  {
                x = rightLimit;
            }
            // 세로 방향 동기
            if (isForceScrollY) {
                // 세로 강제 스크롤
                y = transform.position.y + (forceScrollSpeedY * Time.deltaTime);
            }
            // 위 아래에 이동 제한 적용
            if (y < bottomLimit) {
                y = bottomLimit;
            }
            else if (y > topLimit){
                y = topLimit;
            }
            Vector3 v3 = new Vector3(x, y, z); // 카메라 위치의 Vector3 만들기
            transform.position = v3;

            // 서브 스크린 스크롤
            if (subScreen != null) {
                y = subScreen.transform.position.y;
                z = subScreen.transform.position.z;
                Vector3 v = new Vector3(x / 2.0f, y, z);
                subScreen.transform.position = v;
            }
        }
    }
}

 

하나의 씬에서 시작이미지, UI등을 껏다 켰다 했지만 이제는 타이틀, 종료, 게임씬등을 분리해 보겠습니다. 스테이지도 여러게 만들어야 하고 본격적인 씬관리가 필요합니다.

 

타이틀 화면 씬 만들기

현재씬은 Stage1입니다. 그렇지 않은 분은 이름을 Stage1로 바꿉니다. 

현재상태에서 Ctrl-S로 저장합니다.

File>New Scene을 선택 2D씬을 선택 새로 만듭니다.

아직 저장되지 않았으므로 File>Save를 선택한후 팝업창에서 Title로 Scene폴더에 저장합니다. 

 

타이틀 씬을 빌드에 추가하기

File>Build Setting을 열고 Title씬을 드래그드랍해서 추가합니다. 주의할 점은 타이틀이 맨 위로 오개 합니다. 실행시 위에서 부터 로딩되기 때문입니다.

타이틀화면 UI만들기

하이라키뷰에서 +를 눌러 UI>Image를 추가합니다. 하이라키에서 Canvas를 선택하고 Render Mode를 Screen Space-Camera로 설정하고 Rendere Camera 를 Main Camera로 선택해 줍니다.

 

배경배치하기

Image를 선택하고 이름을 BackImage로 변경후, Images폴더의 title_back을 끌어다 Image컴포넌트의 SourceImage로 끌어다 줍니다. 화면에 꽉차지 않으면 transform Anchor Presets+ALT를 누르고 stretch를 선택해 줍니다. 

 UI>Image를 2개 더 추가해주고 CharaImage, LogoImage로 이름을 변경해줍니다. Image폴더에서 title_chra와 title_logo를 설정합니다. 위치는 그림처럼 합니다. 각이미지의 원본 비율을 사용하기 위해서는 Image컴포넌트의 Preserve Aspect를 사용할 필요도 있습니다.

 

스타트 버튼 배치하기

하이라키뷰 +를 눌러 UI>Legacy>Button을 추가후 이름을 StartButton으로 바꾸고 Image폴더에서 button이미지를 끌어다 Image컴포넌트에 놓습니다. Preserve Aspect를 체크, Set Native Size 클릭후, 하이라키에서 StartButton을 펼쳐  Text를 선택하고 인스펙터뷰에서 Text컴포넌트에서 START로 변경하고 사이즈를 64로 변경합니다.

 

타이틀 화면에서 게임 화면으로 이동하기

마지막으로 [START] 버튼을 눌렸을때 게임 씬인 Stage1으로 이동하는 처리를 구현하겠습니다. 5장에서 만든 ChangeScene스크립트를 StartButton에 어태치해줍니다.

StartButton을 선택하고 OnClick()컴포넌트에 StartButton()을 끌어다 연결해준후 No Function을 눌러 Load()를 연결해 줍니다.

스크립트에서 Stage1을 로드할 수 있도록 스크립트 변수에 Stage1을 써줍니다.

StartButton을 누르면 OnClick()이벤트실행되고 > 이벤트는 ChangeScene스크립트의 Load()를 불러주고 > Load가 SceneName을 불러줍니다.

 

플레이 해보면 Title씬에서 START버튼을 누르면 Stage1씬으로 바뀌고 게임이 시작됩니다.

 

이번 장에서 공부할 내용은 다음과 같습니다.

  • 게임 시작을 타이틀 화면에서 하기
  • 몇개의 스테이지 마련하기
  • 결과 화면에서 게임 성적 확인하기
  • 타이틀 화면으로 돌아가기
  • 플레이어를 추적해 게임 배경이 횡스크롤되게 하기

 

추가할 게임요소

  • 시간제한
  • 아이템과 점수
  • 적과 장애물
  • 이동하는 발판
  • 고정포대
  • 움직이는 적

 

게임 클리어되었을때 NEXT버튼이나 게임오버시 RESTART 버튼을 사용할 수 있도록 해보겠습니다.

씬 변경 스크립트 만들기

프로젝트뷰 +를 클릭해 ChangeScene이름의 스크립트를 만듭니다.

씬매니저를 사용하기 위해 using UnityEngine.SceneManagement; 를 선업합니다.

불러올 씬을 참조하기 위해  public string SceneName; 변수를 선언합니다.

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

public class ChangeScene : MonoBehaviour
{
    public string SceneName;  //불러올씬
    // Start is called before the first frame update

    void Start(){
        
    }

    // Update is called once per frame
    void Update(){
        
    }
    public void Load() {
        SceneManager.LoadScene(SceneName);
    }
}

저장합니다. 하이라키의 RestartButtong과 NextButtong에 ChangeScene 스크립트를 어태치 합니다.

이제 RestartButton을 누르면 ChangeScene스크립트의 Load()가 실행되게 만들겁니다.

우선 하이라키의 RestartButtong을 선택후 인스펙트뷰의 OnClick의 +를 눌러 리스트를 하나 생성합니다.

하이라키의 RestartButton을 끌어다 OnClick() None에 놓습니다.  NoFuction을 선택해 팝업이 나오면 ChagneScene>Load()를 선택합니다. 오른쪽같이 변경되어야 합니다.

 

스크립트의 Scene Name에 Stage1이라고 입력합니다.

하이라키의 Canvas를 프로젝터뷰로 드래그앤드랍해서 프리팹으로 만들어줍니다. UI가 동작하기 위해서는 EventSystem도 필요한데 프리팹에는 포함되어 있지 않기 때문에 나중에 +를 눌러 추가할 수 있습니다.

 

현재 Scene의 이름이 SampleScene입니다. 이름을 Scene1으로 변경하겠습니다. 리로드해줍니다.

'유니티2D게임 > 버튼과 UI' 카테고리의 다른 글

게임과 UI를 관리하는 스크립트 만들기  (0) 2023.05.14
게임UI 만들기  (3) 2023.05.14

게임 전반을 관리하는 GameManager를 만들겠습니다. 프로젝트뷰의 +를 클릭해 C# Script를 만들고 GameManager라 이름 변경합니다. 아이콘이 톱니바퀴모양으로 됩니다. 이걸 끌어다 하이라키의 Canvas에 끌어다 어태치합니다.

GameManager 스크립트를 다음과 같이 작성합니다.

UI를 컨트롤 하기 위해 다음과 같이 선언합니다.

GameManager.cs
0.00MB

using UnityEngine.UI; // UI 를 사용할 때 필요

GUI를 다루기 위해 변수를 선언합니다. 아까 만들었던 패널  버튼 이미지등입니다.

public GameObject mainImage;        // 이미지를 담아두는 GameObject
public Sprite gameOverSpr;          // GAME OVER 이미지
public Sprite gameClearSpr;         // GAME CLEAR 이미지
public GameObject panel;            // 패널
public GameObject restartButton;    // RESTART 버튼
public GameObject nextButton;       // NEXT 버튼
Image titleImage;                   // 이미지를 표시하고있는 Image 컴포넌트

보통은 시작화면을 게임씬과 분리하여 독립된 씬으로 사용하는데 여기서는 게임씬안에서 시작시 감추네요

Invoke()함수로 "GAME START이미지를 Inactive하는InactiveImage()"를 1초후 호출합니다. 

panel.SetActive()를 이용 감춥니다. 포함된 버튼들도 다 감추어 집니다.

void Start() {
    Invoke("InactiveImage", 1.0f); // 이미지 숨기기
    panel.SetActive(false); // 버튼(패널)을 숨기기
}

Invoke() 함수는 다음과 같이 SetActive()를 이용해 비활성화 합니다.

void InactiveImage() {
    mainImage.SetActive(false); // 이미지 숨기기
}

Update()에서는 PlayerController.gameState 변수를 참조해서 Image오브젝트들을 표시하거나 설정합니다.

원래 런타임시의 객체에 접근하려면 Public으로 참조하던지 Find해야하는데 PlayerController는 아무런 참조없이 그냥 사용하고 있습니다. 이건 gameState를 static으로 선언했기 때문입니다. PlayerController의 gameState외 다른것들은 꼭 연결후 사용하셔야 합니다. 일종의 싱글턴 기법입니다. 

GameOver일시 NEXT버튼의 interactable변수를 false로 해 비활성 상태로 만듭니다. 또한 PlayerController.gameState=gameend로 설정해 다음 프레임부터 아무런 처리도 안하게 만들어 줍니다.

if (PlayerController.gameState == "gameClear"){
    ~생략
}
else if (PlayerController.gameState == "gameOver"){
    ~생략
}
else if (PlayerController.gameState == "playing"){
    // 게임 중
}

이제 하이라키에서 Canvas를 선택하고 GameManager의 변수들을 연결해야합니다. 하이라키의  Cavans에서 Image, Panel, RestartButton, NextButtong을 다음과 같이 끌어서 연결합니다. 이후  Images폴더에서 GameOver, StageClear 스프라이트를 끌어다 놓와줍니다.

GameManager의 전체코드 입니다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI 를 사용할 때 필요

public class GameManager : MonoBehaviour
{
    public GameObject mainImage;        // 이미지를 담아두는 GameObject
    public Sprite gameOverSpr;          // GAME OVER 이미지
    public Sprite gameClearSpr;         // GAME CLEAR 이미지
    public GameObject panel;            // 패널
    public GameObject restartButton;    // RESTART 버튼
    public GameObject nextButton;       // NEXT 버튼
    Image titleImage;                   // 이미지를 표시하고있는 Image 컴포넌트

    // Start is called before the first frame update
    void Start() {
        Invoke("InactiveImage", 1.0f); // 이미지 숨기기
        panel.SetActive(false); // 버튼(패널)을 숨기기
    }

    // Update is called once per frame
    void Update()
    {
        if (PlayerController.gameState == "gameClear")
        {
            // 게임 클리어
            mainImage.SetActive(true); // 이미지 표시
            panel.SetActive(true); // 버튼(패널)을 표시
            // RESTART 버튼 무효화 
            Button bt = restartButton.GetComponent<Button>();
            bt.interactable = false;
            mainImage.GetComponent<Image>().sprite = gameClearSpr;
            PlayerController.gameState = "gameEnd";
        }
        else if (PlayerController.gameState == "gameOver")
        {
            // 게임 오버
            mainImage.SetActive(true);      // 이미지 표시
            panel.SetActive(true);          // 버튼(패널)을 표시
            // NEXT 버튼 비활성
            Button bt = nextButton.GetComponent<Button>();
            bt.interactable = false;
            mainImage.GetComponent<Image>().sprite = gameOverSpr;
            PlayerController.gameState = "gameEnd";
        }
        else if (PlayerController.gameState == "playing")
        {
            // 게임 중

        }
    }
    
    void InactiveImage() {
        mainImage.SetActive(false); // 이미지 숨기기
    }
}

'유니티2D게임 > 버튼과 UI' 카테고리의 다른 글

게임 재시작 기능 만들기  (0) 2023.05.14
게임UI 만들기  (3) 2023.05.14

이미지 UI 만들기

하이라키 + 를 누르고 UI>Image를 클릭한다. Canvas를 부모로 Image가 생기고 EventSystem까지 생겼다. ㅠㅠ

Canvas 표시 설정

하이라키의 Canvas를 더블클릭해서 포커스해보면 게임화면 보다 훨씬 큰 크기입니다. 

현재 Canvas의 Renderer Mode는 Screen Overlay라 결국 최종 출력위에 오버레이 되는 거라 출력은 게임화면 사이즈를 무시하고 현재 이 상태가 오버레이되지만 게임화면과 좀더 직관적인 편집을 위해 모드를 Screen Space - Camera로 변경합니다. 나중에 다시 변경해도 문제없습니다. Renderer Camera를 하이라키의 Main Camera를 끌어다 놓거나 ◉검색브라우저를 눌러 찾아도 됩니다.

이제 하이라키의 Canvas를 더블클릭해서 포커스를 주면 게임화면과 같이 보이게 되나 이미지가 보이지 않습니다. 문제는 Order in Layer입니다. 이걸 10으로 변경해서 가장 위로 위치 시킵니다. 

Overlay Mode에서는 무조건 게임씬 맨 위로 오버레이되기 때문에 이런 문제가 없습니다. 여기서 다루지 않은 World Space모드는 Cavas위치를 게임내 3D 월드공간 특정 위치에 배치됩니다. 따라서 자유도는 있지만 게임출력화면에 딱 붙어 있지 않으므로 잘 조절해야 합니다.

 

현재 이미지는 Cavas 좌하에 위치하고 있습니다. 중앙으로 옮겨보겠습니다. 하이라키의 Image를 클릭한후 손으로 옮겨도 되겠지만 좀더 편한 방법이 있습니다. 인스펙터뷰의 Transform아래 좌표그림을 눌러봅니다. Anchor 프리셋이 나타납니다. 잘 읽어보면 Alt를 누를 경우 set position을 바꿀수 있습니다. Alt를 누르면 화면이 바뀌고 가운데를 누르면 위치가 가운데로 변경됩니다.

Image가 선택되어져 있는 상태에서 프로젝트뷰 Images폴더에서 GameStart이미지를 Image컴포넌트의 Source Image에 끌어다 가져다 놓으면 표시가 바뀝니다.

그런데 사이즈가 좀 작고 글씨가 뭉쳐보이지요 같은 Image컴포넌트 내 PreserveApect를 체크하고 Set Native Size를 클릭하면 보기좋게 바뀝니다.

버튼 UI 추가하기

게임이 끝난 후 재시작하기 위한 RESTART버튼을 추가합니다. 버튼도 UI오브젝트이기에 Cavas안에 배치합니다.

계층 뷰의 Canvas를 선택하고 +>UI>Legacy>Button을 선택합니다. 이름을 RestartButton으로 변경합니다.

Images폴더내 Button스프라이트를 끌어다 Image컴포넌트의 Source Image로 끌어다 주고 Preserve Aspect 체크 Set Native Size를 클릭후 화면에 맞게 적당히 사이즈를 조절해줍니다.

하이라키의 RestartButton을 펼쳐보면 Text가 숨어 있습니다. RestartText로 이름을 변경해주고 인스펙터뷰의 Text컴포넌트에서 RESTART로 Text내용을 바꿔주고 사이즈도 적당히 바꿔줍니다.

하이라키뷰에서 RestartButton를 선택후 Ctrl-D를 눌러 복사한후 이름을 NextButton으로 변경한후 펼치후 Text를 인스펙터뷰에서 NEXT로 바꿉니다. RestartButton을 선택후 위치도 적당히 아래로 내려줍니다. (위치는 Text가 아닌 Button을 해야 함께 움직입니다.)

하이라키에서 Canvas선택후 +를 눌러 UI>패널을 만듭니다. 버튼2개를 감쌀 정도로 사이즈를 바꿔줍니다.

 

이후 버튼 2개를 패널의 자식으로 만듭니다. 패널을 선택하고 칼라의 알파를 0으로 변경합니다.

골인 지점에 도착했을때와 게임오버시의 충돌처리를 작성합니다.

gameState라는 게임 상태를 나타내는 정적변수를 선언해서

player가 죽었을때는 "gameover", Goal에 도착했을대는 "gameclear"상태가 되어

각 이벤트호출시 gameState가 "playing"상태가 아니면 처리하지 않게해서 플레이어를 멈추게 합니다.

보통의 게임오브젝트(인스턴스)는 씬이 바뀔경우 없어지게 되는데 gameState는 정적변수라 씬이 바뀌어도 게임 전체에 계속 남아 있을 수 있다.

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

public class PlayerController : MonoBehaviour{
	~생략~
    public static string gameState = "playing"; //게임중
		~생략~
    void Start(){
		~생략~
        gameState = "playing";
    }

    // Update is called once per frame
    void Update(){
        if(gameState != "playing") {
            return;
        }
		~생략~
    }
    private void FixedUpdate() {
        if(gameState != "playing") {
            return;
        }
       		~생략~
    }
    public void Jump() {
       		~생략~
    }
    private void OnTriggerEnter2D(Collider2D collision) {
       		~생략~
    }
    public void Goal() {
        		~생략~
        gameState = "gameClear";
        GameStop(); //게임중지;
    }
    public void GameOver() {
       		~생략~
        gameState = "gameOver";
        GameStop();   //게임중지
        GetComponent<CapsuleCollider2D>().enabled = false;  //플레이어 판정 비활성화
        rbody.AddForce(new Vector2(0, 5), ForceMode2D.Impulse);  //한번 튕겨줍니다.
    }
    void GameStop() {
        Rigidbody2D rbody = GetComponent<Rigidbody2D>();
        rbody.velocity = Vector2.zero;
    }
}

PlayerController.cs
0.00MB

+ Recent posts