본문 바로가기
etc./🕹️unity

VR 개인 프로젝트

by yewoneeee 2022. 6. 11.

프로젝트 전체를 깃허브에 올리고 싶었는데 프로젝트 용량이 너무 커서 그냥 블로그에만 적어두려한다.


 

# 프로젝트 주제

어릴적 많이 했던 플래시 게임 '버거짱'을 모티브로 해서 버거 만들기 타이쿤 게임을 VR로 구현

 

# 개요

타이쿤 게임을 좋아하는 사람들에게 추천하는 VR 콘텐츠

VR 컨트롤러를 이용하여 조작

플레이어의 이동 없음

1인칭 시점

 

# 시나리오

버거 가게를 가상 공간으로 설정하여 구현

랜덤으로 손님과 주문이 생성되고, 주문에 맞는 버거를 제작해야함

버거의 난이도에 따라 손님이 지불하는 돈이 달라짐

틀리면 목숨이 하나씩 줄고, 목숨을 다 잃게 되면 게임 오버

 

# 구상도

 

# 시스템 설정

  • 손님 외형, 주문 모두 랜덤으로 변경
  • 컨트롤러에 레이저를 부착하여 컨트롤러가 어디를 가리키는지 보이게 설정
  • 선택된 오브젝트 아웃라인 색상 변경
  • 컨트롤러로 오브젝트 선택하고 핸드 트리거를 이용하여 오브젝트를 잡으면 플레이어 쪽으로
    오브젝트가 다가오게 설정
  • 도마 주변 영역에 재료 오브젝트가 들어오면 도마 가운데에 오브젝트가 위치하게 설정
  • 바닥에 떨어진 오브젝트는 자동으로 삭제
  • 각 재료가 모두 소진되면 자동으로 10개씩 생성됨
  • 돈과 목숨은 UI로 출력

# 버거 asset 구입

처음 구매한 버거 모양 에셋인데 재료가 하나하나 분리되는줄 알았는데

분리가 안 돼서 못쓰고 다른 에셋을 새로 구입했다.

이렇게 생겨서 당연히 분리되는 건줄 알았다..
위의 버거보다 내용물은 적지만 분리가 된다

버거 모양으로 된 에셋이 별로 없기도 했고 다 분리가 안 돼서 버거 에셋 찾기가 매우 힘들었다..


# 게임 시작 / 종료 화면

scene을 따로 생성해두고 버튼을 누르면 scene을 이동하도록 함

VR 게임에서 모든 면에서 같은 검정색 화면이 나오게 하려면 sphere 사용함

 

# ToGameStartScene.cs

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

public class ToGameStartScene : MonoBehaviour
{
    public void sceneChange()
    {
        SceneManager.LoadScene("BurgerMaker");
    }
}

 

게임 종료 화면도 동일함

 


#  배경 만들기

배경

무료 에셋이랑 cube 3d 오브젝트를 이용해서 배경을 만들었음

작업대와 접시, 도마와 재료에 콜라이더를 각각 설정해

재료들이 작업대, 접시, 도마를 통과해서 바닥으로 떨어지지 않게 설정함

재료들은 전부 prefab으로 만들어두고 사용함

버거 재료 prefab
버거 재료 설정

각 재료들에 collider 설정해주고 rigidbody는 중력 설정 위해 추가해줬음

 

#  손님 만들기

https://www.mixamo.com/

 

Mixamo

 

www.mixamo.com

여기서 대충 캐릭터 5개 정도를 가져와서 넣어줬음

애니메이션을 설정해서 프로젝트를 실행하면 차렷 자세로 들어가도록 설정함

애니메이션 설정

# RandomCharacter.cs

5개 캐릭터 prefab을 리스트에 넣어놓고 랜덤으로 한명씩 손님으로 오게 설정함

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

public class randomCharacter : MonoBehaviour
{
    public List<GameObject> character = new List<GameObject>(); //캐릭터 리스트
    int count = 0;
    int delete = 0;
    Vector3 point;
    GameObject madeCharacter;

    void Start()
    {
        point = GameObject.Find("CharacterSpot").GetComponent<Transform>().position;
    }

    void Update()
    {
        if (ARAVRInput.GetDown(ARAVRInput.Button.IndexTrigger, ARAVRInput.Controller.RTouch))
        {
            delete = 0;
            count = Random.Range(0, character.Count);
            madeCharacter=Instantiate(character[count], point, Quaternion.Euler(0, 180, 0));
            delete = 1;
        }
        if (ARAVRInput.GetDown(ARAVRInput.Button.IndexTrigger, ARAVRInput.Controller.LTouch))
        { 
            if (delete == 1)
            {
                Destroy(madeCharacter);
            }
        }
    }
}

 

# 재료 생성

게임 시작하면 접시 위에 재료가 10개씩 만들어지도록 설정

 

# MakeIngredient.cs

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

// 재료는 항상 10개로 맞춰놓음
// 0개가 되면 자동으로 10개 채워놓기

public class MakeIngredient : MonoBehaviour
{   
    public int N = 10; // 재료개수
    public Vector3 respwanSpot; // 재료를 생성할 위치
    public GameObject ingredient; // 생성할 재료 -> 유니티에서 연결
    string tagName; // 생성할 재료 태그명
    string spotName; // 생성할 위치
    public Vector3 move = new Vector3(0, 0.2f, 0);


    void Start()
    {
        tagName = ingredient.tag;
        spotName = ingredient.tag + "Plate";
        respwanSpot = GameObject.Find(spotName).transform.position; // 각 재료마다 생성할 위치 초기화
    }

    void Update()
    {
        makeIngredient(tagName);
    }

    void makeIngredient(string tagName) // 재료가 0개가 됐는지 확인 및 생성
    {
        GameObject[] temp = GameObject.FindGameObjectsWithTag(tagName);
        if (temp.Length == 0)
        {
            for (int i = 0; i < N; i++)
            {
                respwanSpot += move;
                Instantiate(ingredient, respwanSpot, transform.rotation);
            }
            respwanSpot = GameObject.Find(spotName).transform.position; // 각 재료마다 생성할 위치 초기화
        }
    }
   
}

 

# 주문 생성

화면에서 검은색 뷰 부분에 주문이 뜨도록 함

 

# BurgerOrder.cs

랜덤으로 주문 생성

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

// 랜덤으로 햄버거 주문 생성
// 주문 생성한것은 화면에 띄워주기

public class BurgerOrder : MonoBehaviour
{
    public List<GameObject> connect = new List<GameObject>(); // 프리팹 연결위한 리스트
    public List<ingredient> order = new List<ingredient>(); // 주문 리스트
    public List<GameObject> showOrder = new List<GameObject>(); // 실행도중 생성된 오브젝트 리스트
    public enum ingredient // 재료
    {
        bottomBread,
        lettuce,
        onion,
        patty,
        pickle,
        tomato,
        topBread
    }
    public GameObject orderWindow; // 주문창
    public int money = 0; // 돈
    CheckOrder checkOrder;
    public int isOrdering; // 게임 시작했는지
    Text getMoney; // text ui
    Text getLife;
    public int life = 1;

    private void Start()
    {
        checkOrder = GameObject.Find("Board").GetComponent<CheckOrder>();
        getMoney = GameObject.Find("Money").GetComponent<Text>();
        getLife = GameObject.Find("Life").GetComponent<Text>();
    }

    void Update()
    {

        if (ARAVRInput.GetDown(ARAVRInput.Button.IndexTrigger, ARAVRInput.Controller.RTouch)) // 오른쪽 인덱스트리거 누르면 주문 받아옴
        {
            listReset();
            randomOrder();
            isOrdering = 1;
        }
        if (life == 0)
        {
            sceneChange();
        }
        getMoney.text = money + " WON";
        getLife.text = "LIFE " + life;
    }

    public void sceneChange()
    {
        SceneManager.LoadScene("GameOverScene");
    }

    // 랜덤으로 주문 생성
    public void randomOrder() 
    {
        order.Add(ingredient.bottomBread); 
        int count = Random.Range(0, 4) + 1; // 재료개수
        for(int i = 0; i < count; i++)
        {
            int randomIngredient = Random.Range(0, 5) + 1; // 1-5
            order.Add((ingredient)randomIngredient);
        }
        order.Add(ingredient.topBread); 

        for(int i = 0; i < order.Count; i++) // 주문창에 띄워주기
        {
            for(int j = 0; j < connect.Count; j++)
            {
                if (connect[j].tag == order[i].ToString())
                {
                    Vector3 space = new Vector3(0, 0, -0.3f);
                    Vector3 view = GameObject.FindGameObjectsWithTag("view")[i].transform.position + space;
                    GameObject temp = Instantiate(connect[j], view, Quaternion.Euler(-90, 0, 0));
                    showOrder.Add(temp);
                    temp.GetComponent<Rigidbody>().isKinematic = true;
                    temp.layer = default;
                }
            }
        }
    }
    
    public void listReset() 
    {
        for (int i = 0; i < showOrder.Count; i++)
        {
            Destroy(showOrder[i]); // 주문창에 있는 오브젝트 destroy
        }
        showOrder.Clear(); // 주문화면 리스트 초기화
        order.Clear(); // 주문리스트 초기화
        checkOrder.orderRight = 0; // 주문확인 변수 초기화
        checkOrder.made.Clear();
    }
}

 

# 버거 제작

컨트롤러의 버튼을 사용해서 재료를 집고

도마보다 조금 크게 설정한 콜라이더에 닿으면 자동으로 재료가 도마위에 올라가도록 설정함

사진에서 큰 초록색 박스부분이 도마보다 조금 크게 설정한 콜라이더임

바닥과 다른 부분에 DestroyIngredient 스크립트를 넣어 재료가 바닥에 떨어지면 오브젝트를 destroy하도록 함

 

# DestroyIngredient.cs

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

public class destroyIngredient : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        if(other.tag=="bottomBread"||other.tag=="lettuce"|| other.tag =="onion"|| other.tag =="patty"|| other.tag =="pickle"||other.tag=="tomato"||other.tag=="topBread")
        {
            Destroy(other.gameObject);
        }
    }
}

 

# 주문 확인

도마에 설정해둔 콜라이더에 재료가 들어오면 list에 추가해주고

주문과 내가 만든 버거가 일치하면 버거 주문 난이도에 따라 돈을 받음

주문과 만든 버거가 일치하지 않으면 life를 하나 줄임

돈과 life는 ui로 띄워둠

돈과 목숨 ui

# CheckOrder

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

// 주문 확인
// 트리거에 들어오면 자동으로 재료가 하나씩쌓이게

public class CheckOrder : MonoBehaviour
{
    int check = 0; // 주문 확인용 변수
    public Vector3 space = new Vector3(0, 2f, 0); // 재료 사이 띄울 공간 
    public int orderRight = 0; // 주문 올바르면 1 틀렸으면 2
    public List<string> made = new List<string>(); // 도마 위에 만들어진 버거 리스트
    BurgerOrder burgerOrder; // 스크립트 받아올 변수
    void Start()
    {
        burgerOrder = GameObject.Find("Customer").GetComponent<BurgerOrder>();
    }

    void checkOrder()
    {
        if (burgerOrder.order.Count != made.Count) // 배열 인덱스 접근에러 막기위해
        {
            orderRight = 2; // 배열길이가 다르면 이미 틀렸으므로 2로 설정
        }
        else
        {
            for (int i = 0; i < burgerOrder.order.Count; i++) // 주문리스트 크기만큼 반복문
            {
                if (burgerOrder.order[i].ToString() == made[i]) // 주문이랑 올라온 재료랑 일치하는경우
                {
                    check++;
                }
            }
            if (check == burgerOrder.order.Count) // 모두 일치하면 orderRight 1로 만들어줌
            {
                orderRight = 1;
            }
            else // 일치하지 않으면 2, cf) 주문 확인 전일땐 0
            {
                orderRight = 2;
            }
        }
        if (burgerOrder.isOrdering == 1)
        {
            if (orderRight == 1) // 주문이 맞는 경우
            {
                burgerOrder.money += 800 * burgerOrder.order.Count;
            }
            else if (orderRight == 2) // 주문이 틀린 경우
            {
                burgerOrder.life--;
            }
        }
        else
        {
            burgerOrder.isOrdering = 0;
        }
    }


    void Update()
    {
        if (ARAVRInput.GetDown(ARAVRInput.Button.IndexTrigger, ARAVRInput.Controller.LTouch)) // 왼쪽 인덱스트리거 누르면 주문확인 함수 돌아감
        {
            checkOrder();
        }
    }
    GameObject prevObject;
    public void OnTriggerEnter(Collider other)
    {
        if (other.tag !="Untagged")
        {
            other.transform.rotation = Quaternion.Euler(0, 0, 0);
            other.transform.localPosition = transform.position+space;
            space += new Vector3(0, 0.05f, 0);
            other.GetComponent<Rigidbody>().isKinematic = true; // 재료 도마위에 고정
            if (prevObject != other.transform.gameObject)
            {
                prevObject = other.transform.gameObject;
                made.Add($"{other.tag}");
            }

        }
    }
    private void OnTriggerStay(Collider other)
    {
        if (other.tag != "Untagged")
        {
            if (orderRight != 0) // 버거를 다 만든 경우
            {
                if (other.tag == "bottomBread" || other.tag == "lettuce" || other.tag == "onion" || other.tag == "patty" || other.tag == "pickle" || other.tag == "tomato" || other.tag == "topBread")
                {
                    Destroy(other.gameObject); // 도마위에있는 재료오브젝트 destroy
                }
                for (int i = 0; i < burgerOrder.showOrder.Count; i++)
                {
                    Destroy(burgerOrder.showOrder[i]); // 주문창에 있는 오브젝트 destroy
                }
                burgerOrder.showOrder.Clear(); // 주문화면 리스트 초기화
                space = new Vector3(0, 0.15f, 0);
                burgerOrder.isOrdering = 0;
                check = 0;
            }
        }
    }
}

 

# 컨트롤러 관련

컨트롤러 관련해선 기본 제공 코드를 커스텀 하긴 했으나

강사님의 도움을 많이 받았다.

ARAVRInput.cs
0.01MB

 

 

  • 텔레포트 관련

CustomTeleportOrientationHandlerNode.cs
0.00MB
CustomTeleportTargetHandlerNode.cs
0.00MB
CustomTeleportTransitionBlink.cs
0.00MB
CustomLocomotionTeleport.cs
0.00MB

 

 

 

  • 레이저 관련

CustomLaserPointer.cs
0.01MB
CustomCanvasInput.cs
0.00MB

 

 

  • 그랩 관련

CustomGrabber.cs
0.01MB
CustomGrabbable.cs
0.00MB

 

 

 


# 실행 화면

 

 


# 프로젝트 후기

1년이 지난 후에 후기를 쓰지만 게임 개발에 한창 관심있을 때 이기도 했고 유니티도 배워보고 싶어서 신청했던 프로그램이었다.

이게 재학생들은 9시부터 6시까지 정규 수업을 못들으니까 요약본으로 올려줬는데 요약본의 시간이 그렇게 길진 않았는데 학교 수업 양이 많아지니까 진도 따라가기가 매우 힘들었다..

기말에 완전 밀려서 종강하고도 못놀고 강의를 들었었다..

 

근데 내용 자체는 엄청 알찼고 재밌었다.

방학때 하는 대면수업은 내가 유니티를 처음 해봐서 초급반을 들었었는데 고급반 들었으면 어려울 뻔 했다.

초급반도 따라가기 힘들었어..

대신 고급반은 AR 수업도 있었다.

나눠준 교재에 AR 실습도 있으니까 담에 해보면 될 것 같다!

 

어설프지만 나 혼자 만들었다 생각하니까 엄청 뿌듯했다ㅎㅎ

이 프로젝트 뒤에 취업 연계해서 프로젝트를 하나 더 하는게 있었는데 난 2학기에 학교를 다녀야해서 신청은 못했다.

 

강사님도 굉장히 친절하시고 재밌었다.

좋은 경험이었다!ㅎ

'etc. > 🕹️unity' 카테고리의 다른 글

VR UI 설정  (0) 2022.06.11
VR 실습  (0) 2022.06.11
2D Shooting  (0) 2022.06.11
3D FPS  (0) 2022.06.11
unity shader, effect, light & 지형 만들기  (0) 2022.06.11

댓글