상세 컨텐츠

본문 제목

0827 TIL - 고양이 밥주기 게임 제작

스파르타 코딩캠프/'24 Today I Learned

by lucar 2024. 8. 27. 17:32

본문

이번 프로젝트는 지난 번에 배운 내용은 스킵하고 빠르게 진행해보도록 하자.

 

이번에는 인트로 및 스타트 신도 같이 만들어보자

이번에는 메인 카메라의 사이즈와 백그라운드를 조절해서 백그라운드를 따로 만들지 않는다

 

강의에서 제공하는 UnityPackage파일을 import해서 다양한 이미지를 추가해주자

 

만들어진 이미지가 있다면 위치를 설정하고

간단하게 스프라이트에 이미지를 드래그 앤 드롭하는 것으로 화면을 구성할 수 있다.

 

시작 씬을 만들기 위해 씬을 하나 새로 만들어주자

이름은 StartScene으로 정하고 더블클릭 해보면

새로운 카메라 화면이 나온다.

마찬가지로 카메라 사이즈를 조절해주고 Square 추가 후 인트로 이미지를 스프라이트에 드래그 앤 드롭해주자

이런 식으로 사이즈도 미리 설정되어 있어 손 쉽게 만들 수 있다.

 

마찬가지로 미리 만들어진 스타트 버튼 이미지를 소스 이미지에 드래그 앤 드롭하면 간단하고 멋진 버튼이 완성된다.

 

이제 스타트 버튼을 누르면 메인씬으로 이동하기 위해 스크립트를 하나 제작해보자

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

public class StartBtn : MonoBehaviour
{ 
    public void StartGame() //스타트씬을 불러오는 함수
    {
        SceneManager.LoadScene("MainScene");
    }    
}

정말 많이 본 구문이다.

온 클릭 설정을 마치고 나서 스타트 버튼을 누르면?

 

잘 작동된다.

 

이제 게임적인 메커니즘을 만들러 다시 메인 씬으로 돌아가자

 

우선 발사될 탄환을 만들어보자

써클 스프라이트를 만들고 설정에서 스프라이트를 Knob으로 설정해준다.

 

스크립트를 만들고 수정해보자.

void Update()
{
    transform.position += Vector3.up; //Vector3(0.0f, 1.0f, 0.0f) 값을 계속 더해준다.
}

Unity에서는 Vector3.up을 입력하는 것으로 간단하게 위로 이동하는 스크립트를 작성할 수 있다.

실행해보면

공이 좀 많이 빠르다

Vector3.up * 0.3f를 이용해 속도를 줄여주자

 

녹화 프로그램의 프레임 문제로 빨라보인다

적당한 속도로 줄어들었다.

 

이제 총알이 강아지로부터 나갈 수 있도록 세팅해보자.

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

public class Dog : MonoBehaviour
{
    public GameObject food; //food를 참조할 수 있게 해줌

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("MakeFood", 0f, 0.5f); //0초부터 0.5초 마다 MakeFood 함수를 실행
    }

    void MakeFood()
    {
        float x = transform.position.x;
        float y = transform.position.y;
        Instantiate(food, new Vector2(x,y), Quaternion.identity); //food 객체를 x,y 좌표에서
        							//회전하지 않은 상태로 생성
    }
}

 

Food는 프리팹으로 넣어주고 독 스크립트에 연동해준다.

 

실행해보면?

강아지 위치에서 생성은 되나 머리 위가 아닌 몸에서 부터 발사된다.

void MakeFood()
{
    float x = transform.position.x;
    float y = transform.position.y + 2;
    Instantiate(food, new Vector2(x,y), Quaternion.identity);
}

y값에 2를 더해주자.

그리고 만들어진 Food의 더미데이터가 계속 쌓여가니 일정 Y고도를 넘어가면 사라지도록 바꿔주자

최대 높이는 26정도인 것 같다.

void Update()
{
    transform.position += Vector3.up * 0.3f;
    if (transform.position.y > 27.0f) // y값이 27이상이라면
    {
        Destroy(gameObject); // 이걸 파괴한다.
    }
}

 

이제 강아지를 이동시켜보자

Dog 스크립트를 열고 Update함수를 작성하자

 

void Update()
    {
        Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); //메인카메라 상의 마우스 좌표를 저장
        transform.position = new Vector2(mousePos.x, transform.position.y); //X값은 마우스 좌표 Y값은 그대로
    }

실행해보면?

잘 작동은 되지만 화면 밖으로 나가버릴 수도 있다.

화면 밖으로 마우스가 나가면 고정되게 만들어보자

 

void Update()
    {
        Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); //메인카메라 상의 마우스 좌표를 저장
        float x = mousePos.x;

        {
            if (x > 8.5f) x = 8.5f; //x 좌표가 8.5보다 크다면 8.5로 고정
            if (x < -8.5f) x = -8.5f; //x좌표가 -8.5보다 작다면 8.5로 고정
        }
        transform.position = new Vector2(x, transform.position.y); //X값은 마우스 좌표 Y값은 그대로
    }

실행해보자

이제 슈팅 게임이니까 강아지가 지키는 생선가게를 털러오는 고양이들을 만들어보자

이번에는 고양이의 배가 얼마나 찼는지 HP바 개념의 게이지도 같이 만들어보자

 

배고픈 고양이 Hierarchy안에 UI, 이미지를 추가해주고 렌더모드를 월드 스페이스로 변경한다.

 

크기를 설정한 이미지를 두개 만들고 pivot을 0,0.5로 만들어 좌측에서부터 얼마나 배가 불렀는지 점점 차오르는 기능을

X 스케일 값을 수정하는 것으로 구현할것이다.

 

이제 Cat 스크립트를 제작하고 수정하자

void Update()
{
    transform.position += Vector3.down * 0.05f; //0.05의 속도로 내려온다.
}

우선 간단하게 내려올 수 있게만 만들어두고

총알과 고양이들이 부딪힐 수 있게 둘다 Collider 컴포넌트를 추가해주자

 

그리고 중력의 영향을 받지 않게 Food의 바디 타입을 키네마틱으로 변경해주었다.

이 경우 위의 is Trigger를 체크해야한다.

 

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

public class Cat : MonoBehaviour
{
    public GameObject hungryCat; //고양이의 스프라이트 변경용
    public GameObject fullCat; // 고양이의 스프라이트 변경용
    public RectTransform front; //RectTransform을 수정할 개체를 불러옴
    
    float full = 5.0f; //최대 포만감
    float energy = 0.0f; //최소 포만감

    // Start is called before the first frame update
    void Start()
    {
        Application.targetFrameRate = 60; //60프레임으로 고정(추후 게임매니저로 이동예정)
        float x = Random.Range(-9.0f, 9.0f);//고양이가 생성될 랜덤한 X값 생성
        transform.position = new Vector2(x, 30.0f);//x,30값에 고양이 생성
    }

    // Update is called once per frame
    void Update()
    {
        if (energy < full) transform.position += Vector3.down * 0.05f; //포만도가 꽉 차지 않았다면
        else
        {
            if(transform.position.x < 0) transform.position += Vector3.left * 0.05f; //포만도가 꽉 찼다면
            else transform.position += Vector3.right * 0.05f;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Bullet")) //Bullet 태그를 가진 물체와 부딪힌다면
        {
            if(energy < full)
            {
                energy += 1.0f;
                front.localScale = new Vector3(energy / full, 1.0f, 1.0f);//스케일 x값을 변경한다.
                Destroy(collision.gameObject); //부딪힌 물체를 파괴한다.
                if (energy == 5.0f)
                {
                    hungryCat.SetActive(false); //배고픈 고양이 스프라이트를 비활성화
                    fullCat.SetActive(true); //배부른 고양이 스프라이트를 활성화
                }
            }
        }
    }
}

코드는 이렇게 된다.

실행해보면?

 

잘 작동한다.

이제 고양이를 자동으로 생성해보자... 라곤 해도 너무 반복한 기능이니 스킵하도록 하겠다. (전체 코드는 추가예정)

마찬가지로 엔드패널 만드는 부분도 생략한다!

 

전체코드

 

//GameManager

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.Antlr3.Runtime.Tree;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    public GameObject normalCat;
    public GameObject retryBtn;

    private void Awake()
    {
        if (instance == null) instance = this; //싱글톤 선언
        Application.targetFrameRate = 60;
        Time.timeScale = 1f;
    }

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("MakeCat", 0.0f, 1f);
    }

    void MakeCat()
    {
        Instantiate(normalCat);
    }

    public void GameOver()
    {
        retryBtn.SetActive(true);
        Time.timeScale = 0.0f;
    }
}

 

//Cat

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

public class Cat : MonoBehaviour
{
    public GameObject hungryCat;
    public GameObject fullCat;

    public RectTransform front; //RectTransform을 수정할 개체를 불러옴
    float full = 5.0f;
    float energy = 0.0f;

    // Start is called before the first frame update
    void Start()
    {
        float x = Random.Range(-9.0f, 9.0f);
        transform.position = new Vector2(x, 30.0f);
    }

    // Update is called once per frame
    void Update()
    {
        if (energy < full)//포만도가 꽉 차지 않았다면
        {
            transform.position += Vector3.down * 0.05f;
            if(transform.position.y < -21.0f) GameManager.instance.GameOver();
        }
        else
        {
            if (transform.position.x < 0) transform.position += Vector3.left * 0.05f; //포만도가 꽉 찼다면
            else transform.position += Vector3.right * 0.05f;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Bullet")) //Bullet 태그를 가진 물체와 부딪힌다면
        {
            if(energy < full)
            {
                energy += 1.0f;
                front.localScale = new Vector3(energy / full, 1.0f, 1.0f);//스케일 x값을 변경한다.
                Destroy(collision.gameObject); //부딪힌 물체를 파괴한다.
                if (energy == 5.0f)
                {
                    hungryCat.SetActive(false);
                    fullCat.SetActive(true);
                    Destroy(gameObject, 3.0f);
                }
            }
        }
    }
}

 

Dog

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

public class Dog : MonoBehaviour
{
    public GameObject food;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("MakeFood", 0f, 0.5f);
    }

void Update()
    {
        Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); //메인카메라 상의 마우스 좌표를 저장
        float x = mousePos.x;

        {
            if (x > 8.5f) x = 8.5f; //x 좌표가 8.5보다 크다면 8.5로 고정
            if (x < -8.5f) x = -8.5f; //x좌표가 -8.5보다 작다면 8.5로 고정
        }
        transform.position = new Vector2(x, transform.position.y); //X값은 마우스 좌표 Y값은 그대로
    }

    void MakeFood()
    {
        float x = transform.position.x;
        float y = transform.position.y + 2;
        Instantiate(food, new Vector2(x,y), Quaternion.identity);
    }
}

 

Food

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

public class Food : MonoBehaviour
{

    void Update()
    {
        transform.position += Vector3.up * 0.3f;
        if (transform.position.y > 26.0f)
        {
            Destroy(gameObject);
        }
    }
}

 

StartBtn

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

public class StartBtn : MonoBehaviour
{ 
    public void StartGame()
    {
        SceneManager.LoadScene("MainScene");
    }    
}

 

이제 진짜 마지막으로 레벨 디자인을 해보자

 

캔버스를 수정해서 빈 객체를 만들어주고 이름을 레벨로 설정했다.

이미지에 체력바를 만들 때와 같이 두 가지의 바 모양 이미지를 추가하고 이미지에 텍스트를 추가해 레벨을 표시

 

using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    public RectTransform levelFront; //레벨 게이지 참조
    public Text levelTxt;//레벨 표시 숫자 참조

    int score = 0; //점수 변수 선언
    int level = 1; //레벨 변수 선언

    public void AddScore() //득점 함수
    {
        score++; //실행될 때 마다 점수를 1 더한다.
        level = score / 5; // 5점을 얻으면 1레벨 추가
        levelTxt.text = level.ToString(); //레벨 int값을 문자열로 입력
        levelFront.localScale = new Vector3((score - level * 5) / 5.0f, 1f, 1f); //레벨 게이지 증가
    }
}

게임매니저 스크립트를 추가하고

 

 private void OnTriggerEnter2D(Collider2D collision)
 {
     if(collision.gameObject.CompareTag("Bullet")) //Bullet 태그를 가진 물체와 부딪힌다면
     {
         if(energy < full)
         {
             energy += 1.0f;
             front.localScale = new Vector3(energy / full, 1.0f, 1.0f);//스케일 x값을 변경한다.
             Destroy(collision.gameObject); //부딪힌 물체를 파괴한다.
             if (energy == 5.0f)
             {
                 if(!isFull) //루프 방지용
                 {
                     isFull = true; //루프 방지용
                     hungryCat.SetActive(false);
                     fullCat.SetActive(true);
                     Destroy(gameObject, 3.0f);
                     GameManager.instance.AddScore(); //득점 함수 실행
                 }
             }
         }
     }
 }

Cat 스크립트도 수정해준다.

실행해보면?

 

이제 진짜 마지막으로 레벨이 올라갔으니 더 강한 적이 나오게 해보자.

일단 MakeCat 함수의 InvokeRepeating이 너무 짧아서 적이 나오는 빈도가 너무 높다.

각 레벨마다 고양이의 등장 확률과 더 강한 적이 생성되는 로직을 만들어보자

 

void MakeCat()
{
    Instantiate(normalCat);

    //레벨이 1일 때는 20% 확률로 고양이 생성
    if (level == 1)
    {
        int p = Random.Range(0, 10);
        if (p < 2) Instantiate(normalCat);
    }
    //레벨이 2일 때는 50% 확률로 고양이 생성
    if(level == 2)
    {
        int p = Random.Range(0, 10);
        if (p < 5) Instantiate(normalCat);
    }
    //레벨이 3일 때는 뚱뚱한 고양이 생성
    else if(level == 3)
    {
        Instantiate(fatCat);
    }
}

레벨에 따라 추가적으로 발생하도록 로직을 구성해준다

 

fatcat이 없던 변수이기 때문에 위에 public GameObject fatCat; 을 추가하는 걸 잊지말자.

이번엔 캣 스크립트로 넘어와서 타입 별로 체력과 스피드를 재구성 해보자.

 

void Start()
{
    float x = Random.Range(-9.0f, 9.0f);
    transform.position = new Vector2(x, 30.0f);

    if (type == 1)
    {
        speed = 0.05f;
        full = 5f;
    }
    else if (type == 2)
    {
        speed = 0.02f;
        full = 10f;
    }
    else if (type == 3)
    {

    }
}

type3는 추가될 적을 위해 남겨놓았다.

public int type;를 위에 추가해서 type을 unity로 부터 참조받을 수 있도록 해보자.

 

fatcat과 cat에 type을 입력해준다.

실행해보면?

제법 게임 같다.

스폰 속도가 너무 빠르기 때문에 죽을 수 밖에 없지만 이거는 함수를 불러오는 지연시간을 손보면 괜찮을 것 같다.

오늘은 여기서 마무리!

관련글 더보기