일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Photon Fusion
- CGV
- 앱 배포
- VR
- 모작
- 개발
- 오큘러스
- 가상현실
- 개발일지
- XR
- 포트폴리오
- 멀티플레이
- 유니티
- 길건너 친구들
- 연습
- 유니티 GUI
- 팀프로젝트
- HAPTIC
- 드래곤 플라이트
- ChatGPT
- Oculus
- meta
- meta xr
- 드래곤 플라이트 모작
- 유니티 Json 데이터 연동
- 유니티 UI
- 오브젝트 풀링
- input system
- OVR
- 팀 프로젝트
- Today
- Total
EasyCastleUNITY
바구니로 떨어지는 사과 받기 (여러가지 요소 추가) 본문
추가된 요소
1.Lobby에서 각각 효과가 다른 바구니를 선택하고 그 바구니를 통해 게임이 실행
2.최고 점수가 게임 중에 계속 보여짐, 게임 도중 최고점수가 갱신이 되면, 그 즉시 최고점수는 변화함
3.게임이 종료되면, 게임에서 사용한 바구니의 종류와 현재 점수, 그리고 전체적인 최고점수를 보여준다.
1. LobbyScene
1-1. 사용되는 스크립트: LobbyMain, (InfoManager: 선택된 바구니의 타입을 저장받음) InfoManager는 마지막에 작성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class LobbyMain : MonoBehaviour
{
[SerializeField]
List<GameObject> BasketGoList;
[SerializeField]
List<GameObject> InfoGoList;
[SerializeField]
private GameObject startButton;
// Start is called before the first frame update
void Start()
{
this.startButton.SetActive(false);
Debug.LogFormat("Lobby High Score:{0}", InfoManger.instance.highScore);
}
// Update is called once per frame
void Update()
{
//레이 생성
if (Input.GetMouseButtonDown(0))
{
Ray ray =Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 1000f, Color.red, 2f);
RaycastHit hit;
if(Physics.Raycast(ray.origin,ray.direction*1000f, out hit))
{
Debug.Log(hit.collider.gameObject.name);
GameObject foundBasketGo =
this.BasketGoList.Find(x => x == hit.collider.gameObject);
//찾은 basket의 index를 저장
int selectedBaskerType = this.BasketGoList.IndexOf(foundBasketGo);
InfoManger.instance.selectedBasketType = selectedBaskerType;
foreach(GameObject go in this.BasketGoList)
{
if(go != foundBasketGo)
{
//선택 되지 않은 것들
go.SetActive(false); //비활성화
startButton.SetActive(true); //버튼 활성화
}
}
//정보 없애기
for(int i=0; i<this.InfoGoList.Count; i++)
{
//선택된 타입이 아니면, 비활성화
if(i != selectedBaskerType)
{
InfoGoList[i].SetActive(false);
}
//선택이 되었으면
//해당 타입의 효과를 적어놓은 text를
//InfoManager의 effectText에 저장
else
{
string text = InfoGoList[i].GetComponent<Text>().text;
InfoManger.instance.effectText = text;
}
}
}
}
}
public void LoadGameScene()
{
SceneManager.LoadScene("GameScene");
}
}
2. GameScene
남은시간, 최고점수, 현재 점수, 바구니가 가지고 있는 효과를 보여준다.
사용한 스크립트
ItemController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemController : MonoBehaviour
{
[SerializeField]
private float moveSpeed = 3.0f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//리지드바디가 없는 오브젝트의 이동
//this.transform.Translate(방향 * 속도 * 시간)
this.transform.Translate(Vector3.down*this.moveSpeed*Time.deltaTime);
//바닥에 도달하면 제거
if (this.transform.position.y <= 0)
{
Destroy(this.gameObject);
}
}
}
ItemGenerator
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemGenerator : MonoBehaviour
{
private float elasedTime; //경과시간
private float spawnTime = 1f; //1초에 한번씩
public GameObject applePrefab;
public GameObject bombPrefab;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//시간 측정하기
//변수에 Time.deltaTime을 더해라
this.elasedTime += Time.deltaTime;
//1초가 지났다면
if(this.elasedTime >= this.spawnTime)
{
//아이템을 생성
this.CreateItem();
//시간 초기화
this.elasedTime = 0;
}
}
private void CreateItem()
{
//사과 또는 폭탄
int rand = Random.Range(1, 11); //1~10
GameObject itemGo = null;
if(rand > 2) // 3,4,5,6,7,8,9,10
{
//사과
itemGo= Instantiate(this.applePrefab);
}
else // 1,2 20%로 폭탄 생성
{
//폭탄
itemGo = Instantiate(this.bombPrefab);
}
//위치 설정
//x: -1, 1
//z: -1, 1
int x =Random.Range(-1, 2);
int z =Random.Range(-1, 2);
//위치를 설정
itemGo.transform.position = new Vector3(x, 5.0f, z);
}
}
BasketController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BasketController : MonoBehaviour
{
private AudioSource audioSource;
[SerializeField]
private AudioClip appleSfx;
[SerializeField]
private AudioClip bombSfx;
[SerializeField]
private GameDirector gameDirector;
private int selectedType = -1;
// Start is called before the first frame update
void Start()
{
this.audioSource = GetComponent<AudioSource>();
GameObject go = GameObject.Find("GameDirector");
this.gameDirector = go.GetComponent<GameDirector>();
this.selectedType = InfoManger.instance.selectedBasketType;
}
// Update is called once per frame
void Update()
{
//마우스 왼쪽 클릭 하면 (화면을 클릭하면) Ray를 만들자
if (Input.GetMouseButtonDown(0))
{
//화면상의 좌표-> Ray객체 생성
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float maxDistance = 100f;
//ray를 눈으로 확인
Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.red, 1f);
//ray와 collider의 충돌을 검사하는 메서드
//out 매개변수를 사용하려면 변수정의를 먼저 해야 한다.
RaycastHit hit;
//out 키워드를 사용해서 인자로 넣어라
//Raycast 메서드에서 연산된 결과를 hit 매개변수에 넣어줌
//Ray가 collider와 충돌하면 true, 아니면 false
if (Physics.Raycast(ray, out hit, maxDistance))
{
Debug.LogFormat("hit.point:{0}", hit.point);
//클릭한 지점으로 바구니 위치 변경
// this.transform.position = hit.point;
//x좌표와 z좌표를 반올림
float x = Mathf.RoundToInt(hit.point.x);
float z = Mathf.RoundToInt(hit.point.z);
//새로운 좌표 생성
this.transform.position = new Vector3(x, 0, z);
}
}
}
//충돌 연산
private void OnTriggerEnter(Collider other)
{
Debug.Log(other.tag);
CalculateScore(other);
this.gameDirector.UpdateScoreUI();
Destroy(other.gameObject); //사과 또는 폭탄을 제거
}
private void CalculateScore(Collider other)
{
if(this.selectedType == 0) //red
{
if (other.tag == "Apple")
{
Debug.Log("득점");
this.audioSource.PlayOneShot(this.appleSfx);
this.gameDirector.IncreaseScore(110);
}
else if (other.tag == "Bomb")
{
Debug.Log("감점");
this.audioSource.PlayOneShot(this.bombSfx);
this.gameDirector.DecreaseScore(50);
}
}
else if(this.selectedType == 1) //green
{
if (other.tag == "Apple")
{
Debug.Log("득점");
this.audioSource.PlayOneShot(this.appleSfx);
this.gameDirector.IncreaseScore(100);
}
else if (other.tag == "Bomb")
{
Debug.Log("감점");
this.audioSource.PlayOneShot(this.bombSfx);
this.gameDirector.DecreaseScore(45);
}
}
else if(this.selectedType == 2) //yellow
{
if (other.tag == "Apple")
{
Debug.Log("득점");
this.audioSource.PlayOneShot(this.appleSfx);
this.gameDirector.IncreaseScore(100);
}
else if (other.tag == "Bomb")
{
Debug.Log("감점");
this.audioSource.PlayOneShot(this.bombSfx);
this.gameDirector.DecreaseScore(50);
}
}
}
}
GameDirector
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.SocialPlatforms.Impl;
using UnityEngine.UI;
public class GameDirector : MonoBehaviour
{
[SerializeField]
private Text txtTime;
[SerializeField]
private Text txtScore;
[SerializeField]
private Text txtHighScore;
private float time = 60.0f;
private int score = 0;
private int selectedType = -1;
private int highScore;
// Start is called before the first frame update
void Start()
{
this.selectedType = InfoManger.instance.selectedBasketType;
this.highScore = InfoManger.instance.highScore;
this.UpdateScoreUI();
this.UpdateGame();
}
public void UpdateScoreUI()
{
if (this.score > this.highScore)
{
this.highScore = this.score;
}
this.txtScore.text = string.Format("{0} Point", score);
this.txtHighScore.text = string.Format("High Score: {0}", highScore);
}
private void UpdateGame()
{
if (this.selectedType == 0) //red
{
this.time += 0;
}
else if (this.selectedType == 1) //green
{
this.time += 0;
}
else if (this.selectedType == 2) //yellow
{
this.time += 10.0f;
}
}
public void IncreaseScore(int score)
{
this.score += score;
}
public void DecreaseScore(int score)
{
this.score -= score;
}
// Update is called once per frame
void Update()
{
this.time -= Time.deltaTime; //매프레임마다 감소된 시간을 표시
//https://sosobaba.tistory.com/244
//https://chragu.com/entry/C-double-to-string
this.txtTime.text = this.time.ToString("F1"); //소수점 1자리까지 표시
if (this.time <= 0)
{
SceneManager.LoadScene("GameOverScene");
InfoManger.instance.lastScore = this.score;
InfoManger.instance.highScore = this.highScore;
Debug.LogFormat("GameEnd HighScore:{0}", InfoManger.instance.highScore);
}
}
}
GameMain
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
[SerializeField]
private ItemGenerator itemGenerator;
[SerializeField]
private List<GameObject> basketPrefabs;
[SerializeField]
private Text effectText;
private BasketController basketController;
private int selectedBasketType = -1;
// Start is called before the first frame update
void Start()
{
this.selectedBasketType = InfoManger.instance.selectedBasketType;
Debug.LogFormat("<color=yellow>selectedBasketType:{0}</color>",
this.selectedBasketType);
Debug.LogFormat("GameStart HighScore:{0}", InfoManger.instance.highScore);
this.effectText.text = InfoManger.instance.effectText;
this.CreateBasket();
}
private void CreateBasket()
{
GameObject prefab = this.basketPrefabs[this.selectedBasketType];
GameObject basketGo = Instantiate(prefab);
basketGo.transform.position = Vector3.zero;
this.basketController = basketGo.GetComponent<BasketController>();
}
}
3. GameOverScene
로비로 버튼을 누르면, LobbyScene으로 가서 처음부터 다시
게임 다시하기를 누르면, 이번 게임에 사용한 바구니를 다시 가지고, 게임을 다시 실행한다.
사용한 스크립트
GameOverMain
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.SocialPlatforms.Impl;
using UnityEngine.UI;
public class GameOverMain : MonoBehaviour
{
[SerializeField]
private List<GameObject> basketPrefabs;
[SerializeField]
private Text txtScore;
[SerializeField]
private Text txtHighScore;
private int selectedBasketType = -1;
private int lastScore = 0;
private int highScore = 0;
// Start is called before the first frame update
void Start()
{
this.selectedBasketType = InfoManger.instance.selectedBasketType;
this.lastScore = InfoManger.instance.lastScore;
this.highScore = InfoManger.instance.highScore;
this.CreateBasket();
this.ScoreUI();
Debug.LogFormat("Over high score:{0}", this.highScore);
}
// Update is called once per frame
void Update()
{
}
private void CreateBasket()
{
GameObject prefab = this.basketPrefabs[this.selectedBasketType];
GameObject basketGo = Instantiate(prefab);
basketGo.transform.position = new Vector3(0, 0.3f, 0);
}
public void ScoreUI()
{
this.txtHighScore.text = string.Format("High Score: {0}", highScore);
this.txtScore.text = string.Format("{0} Point", lastScore);
}
public void LoadLobbyScene()
{
SceneManager.LoadScene("LobbyScene");
}
public void LoadGameScene()
{
SceneManager.LoadScene("GameScene");
}
}
정보를 저장하는 클래스 InfoManager (모든 씬에서 사용)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InfoManger
{
//싱글톤
public static readonly InfoManger instance = new InfoManger();
public int selectedBasketType = -1; //0:red, 1:green 2:yellow
public int lastScore = 0;
public string effectText;
public int highScore;
//getSet을 만드는 것이 좋다
//생성자
private InfoManger()
{
}
}
4. 결과 (시연 동영상)
1.Lobby에서 바구니 종류를 선택하여, 효과중 하나를 선택한다.
2.바구니가 선택되면, 버튼이 활성화되며 게임(GameScene) 으로 들어갈 수 있다.
3.게임으로 들어가면, 사과아 폭탄이 떨어지는데, 기본적으로 사과를 먹으면 +100점, 폭탄을 먹으면 -50점
3-1. 사과와 폭탄을 먹으면 나오는 소리가 다르다.
3-2. 남은 시간 및 최고 점수와 현재점수를 보여주고, 선택한 바구니가 가지고 있는 효과를 보여준다.
4. 시간이 다되면 게임이 종료되며, 게임오버 창(GameOverScene)으로 넘어간다.
4-1. 게임에서 선택한 바구니의 종류를 보여주고, 최고점수, 막 끝난 게임의 점수, 로비버튼과, 게임 재시작 버튼이 있다.
4-2. 로비버튼을 누르면 Lobby로 돌아가서 처음부터 다시 반복한다.
4-3. 게임 재시작 버튼을 누르면, 전에 게임에서 선택한 바구니를 가지고 다시 게임을 실행한다.
하면서 어려웠던 점
전에 했던 것을 토대로 만든 것이라, 크게 어려운 점은 없었다.
하지만 자잘한 부분에서 버벅거렸던 부분이 있었다.
바로 최고점수를 보여주는 부분이었다.
게임에 들어가면, 전에 나온 최고점수가 아니라, 0으로 초기화 되는 문제가 있었다.
이 문제를 해결하기 위해, 각 씬들을 불러올 때마다, Debug.Log를 이용하여, highScore
즉 그 시점에 최고점수를 출력해보았다.
이 방법을 통해, lobby에서만 해도 제대로 저장되고 있던 최고점수가
게임이 실행되면서 0으로 초기화 되고 있다는 것을 알게 되었다.
이 사실을 통해, 게임 씬과 관련된 스크립트가 문제라는 것을 알게 되었고,
다시 스크립트를 훑어보았다.
이런 식으로 highScore를 InfoManager에서 불러왔어야 하는데, 불러오지 않아서
GameDirector의 highScore가 계속 0으로 초기화되고 있어서 생기는 문제였다.
그래서 내가 아직 싱글톤을 통해서 정보를 받아오는 부분이 미숙하다고 생각했다.
이번 프로젝트는 싱글톤 패턴을 이용하여, 씬에서 넘어갈 때 필요한 정보를 저장하는 방식을 통해 구현했다.
위에 InfoManager를 보면 public을 사용하여 변수를 만들었지만
이런식으로 작성하는 것이 아닌, 각 변수에 맞는 get set 함수를 만들어서 활용하는 것이 OOP적으로 좋다.
또한 유니티에서 List를 활용하여, 인스턴스를 관리해본 것이 처음이라 잘 숙지해두어야 겠다고 생각했다.
'유니티 기초' 카테고리의 다른 글
간단 RPG (0) | 2023.08.08 |
---|---|
유니티 코루틴 (0) | 2023.08.08 |
바구니로 떨어지는 사과 받기 (0) | 2023.08.07 |
주말과제: 이동하고 몬스터 공격 (1) | 2023.08.05 |
유니티 Raycast hit 응용 (Warp && Translate) (0) | 2023.08.04 |