3D 미니 프로젝트 2 - 11 ] 스코어 파일 저장 및 점프 맵 랭킹
지난 포스팅에서는 상점과 스탯 강화 등에 대해 구현하였다.
3D 미니 프로젝트 2 - 10 ] 상점, 스탯 강화
지난 포스팅에서는 던전 스테이지를 만들어 보았다. 3D 미니 프로젝트 2 - 9 ] 스테이지 설정 지난 포스팅에서는 UI 구현에 대해 포스팅하였다. 3D 미니 프로젝트 2 - 8 ] UI 지난 포스팅에서는 씬 이
mini-noriter.tistory.com
이번에는 점프 맵과, 캐릭터 정보에 대한 부분을 파일로 저장하고, 다시 불러오는 작업을 해 보도록 하자
점프맵 중도 포기 기능
점프맵은 완주를 해야만 보상을 얻고, 빠져나올 수 있었다.
중간에 그만두는 버튼을 만들어 잘못 들어가거나, 나가고 싶을 때 나올 수 있게 만들어 주도록 하자
(대신 중간에 나오면 스코어 정산 골드를 10분의 1밖에 얻지 못한다.)
스테이지 2,3에서는 1에서 만든 버튼과 Panel을 복사하여 붙여넣기 해 주었다.
(EventSystem도 같이 복사 해 준다.)
수정된 Managing.cs와 Player.cs이다.
Managing.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Managing : MonoBehaviour
{
GameObject player; // 플레이어 오브젝트
GameObject saveObject; // 정보 저장 오브젝트
public GameObject exitUI; // exit UI
public Text ScoreTxt; // scoreTxt;
SaveInfos savestats; // playerstat
SaveInformation saveInfo; // 정보 저장 코드
Vector3 startPos; // 시작 포지션(세이브 포인트를 먹기 전 리스폰 위치)
public Text noticeText;
public Text scoreText;
public GameObject fallPanel; // 떨어졌을 때 나오는 판넬
public GameObject panel; // 판넬
int score; // UI 점수를 갱신할 때, 잠시 현재 점수를 불러와 저장하는 변수
float onTime = 0f;
float delTime = 3.0f;
float onTime2 = 0f; // 떨어졌을 때의 알림 시간
float delTime2 = 2.0f; // 떨어졌을 때의 알림 최대 지속
bool isOn = false;
bool fallNoticeOn = false; // 떨어졌을 때의 알림이 떠 있는 상태?
// Start is called before the first frame update
void Start()
{
player = GameObject.FindGameObjectWithTag("Player");
saveObject = GameObject.FindGameObjectWithTag("information");
savestats = GameObject.FindGameObjectWithTag("saveInfo").GetComponent();
saveInfo = saveObject.GetComponent();
startPos = player.transform.position;
if(saveInfo.GetStage() == 1)
{
delTime = 3.0f;
}
else if(saveInfo.GetStage() == 2)
{
delTime = 5.0f;
ShowInfoStage2();
}
}
// Update is called once per frame
void Update()
{
if (isOn)
{
onTime += Time.deltaTime;
if(onTime > delTime)
{
panel.SetActive(false);
isOn = false;
onTime = 0f;
}
}
if (fallNoticeOn)
{
onTime2 += Time.deltaTime;
if (onTime2 > delTime2)
{
fallPanel.SetActive(false);
fallNoticeOn = false;
onTime2 = 0f;
}
}
}
public void MoveToTarget(Vector3 target)
{
// 플레이어를 타겟으로 이동시키는 함수
player.transform.position = target;
}
public void addScore(int num)
{
switch (num)
{
case 0: // silver
score = int.Parse(scoreText.text);
score++;
saveInfo.addCntScore(1); // 점수 관리 오브젝트에 1점 추가
saveInfo.info.totalScore += 1;
scoreText.text = score.ToString();
break;
case 1: // gold
score = int.Parse(scoreText.text);
score += 10;
saveInfo.addCntScore(10); // 점수 관리 오브젝트 갱신
saveInfo.info.totalScore += 10;
scoreText.text = score.ToString();
break;
}
}
public void ShowNotices(int num)
{
switch (num)
{
case 1:
panel.SetActive(true); // 첫 번째는 내용 변화가 X
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
break;
case 2:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "동전을 먹으면 점수가 올라갑니다!\n 동전을 최대한 많이 먹으면서 골인 지점까지 가면 돼요!";
break;
case 3:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "안 보이는 곳에도 동전이 있을 수 있어요!\n 만점을 받기 위해서는 눈썰미가 좋아야겠죠?";
break;
case 4:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "방금 먹은 노란색 꼬깔은 세이브 포인트에요!\n 바닥에 떨어지면 세이브 포인트로 복귀한답니다!";
break;
case 5:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "전방에 움직이는 파란 색 발판이 보이나요?\n 튕겨 나가지 않게 조심하세요!";
break;
case 6:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "앞에 보라색 발판이 보이나요?\n 통! 통! 튀기면서 저 멀리 하늘 위로 올라가 봐요!";
break;
case 7:
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "앞에 노란색 발판은 크기가 줄었다가 늘었다가 하네요!\n 크기가 커지기를 기다렸다가 가는 것을 추천해요!";
break;
}
}
public void ShowFallNotice()
{
fallPanel.SetActive(true);
if (!fallNoticeOn) // UI가 사라진 상태에서 UI 생성 시
{
fallNoticeOn = true;
}
else
{
onTime2 = 0f;
}
}
public void ShowInfoStage2()
{
isOn = true;
panel.SetActive(true);
noticeText.text = "오른쪽, 왼쪽 길 중에 한 곳을 선택하세요!\n 맵에 대한 자세한 설명을 보시려면 물음표에 가까이 가주세요!";
}
public void ShowInfoStage2Plus()
{
panel.SetActive(true);
if (!isOn) // UI가 사라진 상태에서 UI 생성 시
{
isOn = true;
}
else
{
onTime = 0f;
}
noticeText.text = "오른쪽 길 : 여러 가지 장애물들을 뚫고 가는 코스\n 왼쪽 길 : 세심한 컨트롤이 요구되는 코스\n 한 번 선택하면 바꾸지 못하니 주의!";
}
public void StageClearUI()
{
panel.SetActive(true);
noticeText.text = "스테이지 점수 : " + saveInfo.GetCntScore()+"\n 누적 점수 : "+saveInfo.GetTotalScore();
}
public void StageClearGainGoldUI()
{
panel.SetActive(true);
noticeText.text = "전 스테이지 클리어 완료!\n 완주 시, 획득 골드의 100배 만큼 골드를 획득합니다!\n 획득 골드 : "+saveInfo.info.totalScore * 100+"G";
}
public void onExitUI()
{
if (!exitUI.activeSelf)
{
ScoreTxt.text = saveInfo.info.totalScore.ToString() + " (획득 골드 " + saveInfo.info.totalScore * 10 + ")";
exitUI.SetActive(true);
}
else
{
exitUI.SetActive(false);
}
}
public void doExit()
{
saveInfo.SaveInfoToFile();
if (saveInfo.topScore[2] < saveInfo.info.totalScore) // 항상 정렬을 하니 마지막 원소와 비교 해 준다.
{
saveInfo.topScore[2] = saveInfo.info.totalScore;
Array.Sort(saveInfo.topScore);
Array.Reverse(saveInfo.topScore);
}
savestats.info.playerCntGold += saveInfo.info.totalScore * 10;
saveInfo.info.reset();
SceneManager.LoadScene("Boss1");
}
}
Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
GameObject manager;
GameObject saveObject;
public GameObject startPos; // 시작지점
SaveInformation saveInfo;
ChooseRoute choose;
Managing managing;
Rigidbody rigid;
SaveInfos saveStats;
bool isJumpState = false;
bool dontMove = false;
Vector3 ReturnPos; // 세이브 포인트를 먹지 않았을 때
float jumpForce = 60.0f;
int showNotice = 0;
// Start is called before the first frame update
void Start()
{
rigid = GetComponent();
manager = GameObject.FindGameObjectWithTag("Manager");
saveObject = GameObject.FindGameObjectWithTag("information");
ReturnPos = startPos.transform.position;
managing = manager.GetComponent();
saveInfo = saveObject.GetComponent();
saveStats = GameObject.FindGameObjectWithTag("saveInfo").GetComponent();
}
private void Update()
{
if (Input.GetButtonDown("Jump") && !isJumpState)
{
isJumpState = true;
rigid.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
// Update is called once per frame
void FixedUpdate()
{
float h = dontMove ? 0f : Input.GetAxisRaw("Horizontal");
float v = dontMove ? 0f : Input.GetAxisRaw("Vertical");
rigid.AddForce(new Vector3(h, 0, v), ForceMode.Impulse);
}
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.tag == "base")
{
isJumpState = false;
}
else if(collision.gameObject.tag == "under")
{
managing.ShowFallNotice();
rigid.velocity = Vector3.zero;
managing.MoveToTarget(ReturnPos);
}
}
void gotoNextMap()
{
saveInfo.clearCntScore(); // 현재 스테이지 점수 초기화!(새 스테이지로 가기 때문) - 점수를 내보낸 이후로 초기화를 시켜 주어야 한다.
dontMove = false;
switch (saveInfo.GetStage())
{
case 1:
saveInfo.stageUp(); // 이곳으로 옮겨 주어야 한 번에 두 스테이지를 건너 뛰는 것을 방지할 수 있음
SceneManager.LoadScene("Jump_2");
break;
case 2:
saveInfo.stageUp();
SceneManager.LoadScene("Jump_3");
break;
case 3:
saveInfo.stageUp();
saveInfo.SaveInfoToFile();
if (saveInfo.topScore[2] < saveInfo.info.totalScore)
{
saveInfo.topScore[2] = saveInfo.info.totalScore;
Array.Sort(saveInfo.topScore);
Array.Reverse(saveInfo.topScore);
}
saveStats.info.playerCntGold += saveInfo.info.totalScore * 100;
saveInfo.info.reset(); // 점수 리셋
SceneManager.LoadScene("Boss1");
break;
}
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag == "SavePoint")
{
ReturnPos = other.gameObject.transform.position;
other.gameObject.SetActive(false); // 세이브 포인트를 먹었으니 비활성화
}
if(other.gameObject.tag == "gold")
{
managing.addScore(1);
other.gameObject.SetActive(false);
}
if (other.gameObject.tag == "silver")
{
managing.addScore(0);
other.gameObject.SetActive(false);
}
if (other.gameObject.tag == "chooseRoute")
{
ReturnPos = other.gameObject.transform.position;
choose = other.gameObject.GetComponent();
choose.onWall();
other.gameObject.SetActive(false); // 세이브 포인트를 먹었으니 비활성화
}
if (other.gameObject.tag == "goal")
{
rigid.velocity = Vector3.zero;
dontMove = true;
// saveInfo.SumScore(); // 현재 스테이지 점수를 합산
if(saveInfo.GetStage() == 3)
{
managing.StageClearGainGoldUI();
}
else
{
managing.StageClearUI();
}
Invoke("gotoNextMap",2.0f);
}
if(other.gameObject.tag == "question")
{
switch (saveInfo.GetStage())
{
case 2:
managing.ShowInfoStage2Plus();
break;
}
}
if (other.gameObject.tag == "Notice")
{
if(other.gameObject.name == "1" && showNotice < 1)
{
managing.ShowNotices(1);
showNotice++;
}
else if(other.gameObject.name == "2" && showNotice < 2)
{
managing.ShowNotices(2);
showNotice++;
}
else if (other.gameObject.name == "3" && showNotice < 3)
{
managing.ShowNotices(3);
showNotice++;
}
else if (other.gameObject.name == "4" && showNotice < 4)
{
managing.ShowNotices(4);
showNotice++;
}
else if (other.gameObject.name == "5" && showNotice < 5)
{
managing.ShowNotices(5);
showNotice++;
}
else if (other.gameObject.name == "6" && showNotice < 6)
{
managing.ShowNotices(6);
showNotice++;
}
else if (other.gameObject.name == "7" && showNotice < 7)
{
managing.ShowNotices(7);
showNotice++;
}
}
}
}
Managing.cs에는 ExitUI를 on/off하는 함수와 ExitUI에서 Exit 버튼을 눌렀을 때, 점프맵 밖으로 완전히 나가게 하는 doExit()함수를 만들어 주었다.
Player.cs에서는 현재 점수를 실시간으로 더하기 위해 동전을 먹을 때, saveObject에 있는 cntScore를 더해주는 부분을 추가 해 주었다.
또한 topScore는 int 배열로 만들어 주어, top3의 점수까지 저장되어 랭킹에 표시할 수 있게 만들었다.
랭킹 UI를 아래와 같이 간단하게 만들어 주었다.
게임 종료
게임 종료 메뉴 UI를 아래 사진과 같이 만들어 주었다.
esc키를 입력 받고, esc키를 누르게 되면 위 사진과 같은 메뉴가 뜨면서 게임 내 시간이 멈추게 설정하였다.
시간을 일시정지 시키는 것은 여기를 참고하였다. (Time.timeScale 이용)
메뉴를 켤 때, Time.timeScale을 0으로 만들어 주어 시간이 멈추게 하였고, 메뉴를 끌 때는 Time.timeScale을 1.0f로 하여 다시 시간이 가게 하였다.
어둡게 설정한 것은 Panel을 하나 더 두어 설정하였다.
게임 내용 저장
점프 맵
점프 맵의 점수들을 저장 해 주도록 해 보자. 파일로 저장시킨 다음, 게임을 시작할 때, 파일의 내용을 Load 하는 방식을 사용한다.
public void SaveInfoToFile()
{
string fileName = "jumpScoreInfo";
string path = Application.dataPath + "/" + fileName + ".dat";
FileStream fs = new FileStream(path, FileMode.Create); // 파일 통로 생성
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, info); // 직렬화 하여 저장
Debug.Log("파일 저장 완료");
fs.Close();
}
public void LoadInfoFile()
{
string fileName = "jumpScoreInfo";
string path = Application.dataPath + "/" + fileName + ".dat";
if (File.Exists(path))
{
// 만약 파일이 존재하면
FileStream fs = new FileStream(path, FileMode.Open);
BinaryFormatter formatter = new BinaryFormatter();
Info infoImsi = formatter.Deserialize(fs) as Info; // 역 직렬화 후, 클래스 형태에 맞는 객체에 다시 저장
info = infoImsi;
Debug.Log("저장 된 현재 점수 : " + info.cntScore);
Debug.Log("저장 된 누적 점수 : " + info.totalScore);
fs.Close();
}
else
{
// 파일이 존재하지 않으면
Debug.Log("파일이 존재하지 않음");
}
}
파일 저장/로드 함수
private void Awake()
{
GameObject[] objs = GameObject.FindGameObjectsWithTag("information"); // information Tag를 가진 놈들을 배열에 불러오고
if (objs.Length > 1) // 만약 이미 전에 생성된 Obj가 있다면 배열의 길이는 2가 될 것이다.
Destroy(gameObject); // DontDestroy로 지정된 것은 Awake가 다시 실행되지 않으므로 새로 생성되는 것만 삭제한다.
DontDestroyOnLoad(gameObject); // 씬이 바뀌어도 사라지지 않게한다.
if (!isLoad)
{
LoadInfoFile();
isLoad = true;
}
Debug.Log("SaveBase");
}
SaveInformation.cs에서 Awake때 파일을 불러오게끔 해 준다.
그렇게 해 주면 시작하자마자 전에 했던 스코어가 바로 반영되어 있음을 볼 수 있다.
점프맵과 마찬가지로 Player에 대한 정보도 클래스 안에 넣어서 Serialize 하여 파일로 저장 해 주고, 불러와 준다.
Player 정보들
Player 정보들은 이미 SaveObject에 있는 클래스에서 한 번에 저장되어 있다.
왜냐하면 씬을 이동할 때, 정보를 유지하기 위해 만들어 놓았기 때문이다.
따라서 SaveObject에서 점프 맵 스코어를 저장하는 함수를 가져와서 파일에 저장 & 종료를 해 주는 기능을 넣고 버튼에 연결시켜 주면 된다.
public void SaveInfoToFile()
{
string fileName = "PlayerInfo";
string path = Application.dataPath + "/" + fileName + ".dat";
FileStream fs = new FileStream(path, FileMode.Create); // 파일 통로 생성
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, info); // 직렬화 하여 저장
Debug.Log("파일 저장 완료");
fs.Close();
Application.Quit();
}
public void LoadInfoFile()
{
string fileName = "PlayerInfo";
string path = Application.dataPath + "/" + fileName + ".dat";
if (File.Exists(path))
{
// 만약 파일이 존재하면
FileStream fs = new FileStream(path, FileMode.Open);
BinaryFormatter formatter = new BinaryFormatter();
playerInfo infoImsi = formatter.Deserialize(fs) as playerInfo; // 역 직렬화 후, 클래스 형태에 맞는 객체에 다시 저장
info = infoImsi;
fs.Close();
}
else
{
// 파일이 존재하지 않으면
Debug.Log("파일이 존재하지 않음");
}
}
저장 함수
파일에 데이터를 저장 한 다음, DataSet.cs 코드에서 데이터를 불러 와 다시 세팅을 해 주게 된다.
(처음에는 씬 이동 시 데이터 리셋용도였지만 저장 용으로도 사용 가능!)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DataSet : MonoBehaviour
{
SaveInfos saveData; // 저장된 코드
PlayerItem playerItem; // 플레이어 아이템 코드
PlayerCode playerCode; // 플레이어 스탯이 저장된 코드
UIManager uiManager; // UI 매니저
// Start is called before the first frame update
void Start()
{
saveData = GameObject.FindGameObjectWithTag("saveInfo").GetComponent<SaveInfos>();
playerItem = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerItem>();
playerCode = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerCode>();
if(GameObject.FindGameObjectWithTag("uimanager") != null)
{
uiManager = GameObject.FindGameObjectWithTag("uimanager").GetComponent();
}
resetData();
if (uiManager != null)
reSetUI();
}
public void resetData()
{
playerCode.playerMaxHealth = saveData.info.playerMaxHealth;
playerCode.playerHealth = saveData.info.playerCntHealth;
playerCode.playerMana = saveData.info.playerCntMP;
playerCode.playerMaxMana = saveData.info.playerMaxMP;
playerCode.playerStrength = saveData.info.playerStrength;
playerCode.playerAccuracy = saveData.info.playerAcc;
playerItem.playerCntGold = saveData.info.playerCntGold;
playerItem.enchantOrigin = saveData.info.enchantOrigin;
playerItem.cntHPPotion = saveData.info.HPPotion;
playerItem.cntMPPotion = saveData.info.MPPotion;
playerCode.strEnchantCnt = saveData.info.strCnt;
playerCode.accEnchantCnt = saveData.info.accCnt;
playerCode.HPEnchantCnt = saveData.info.HPCnt;
playerCode.MPEnchantCnt = saveData.info.MPCnt;
for (int i = 0; i < saveData.info.weapons.Length; i++)
{
if (saveData.info.weapons[i].baseAtk == 0)
return;
playerItem.weapons[i] = saveData.info.weapons[i];
playerCode.hasWeapons[saveData.info.weapons[i].weaponCode] = true; // 무기를 얻은 여부도 반영 해 준다.
playerItem.SetEnchantInfo(i); // playerItem -> weapon 반영(실제 데미지가 계산되는 곳으로)
}
}
public void reSetUI()
{
uiManager.goldTxt.text = saveData.info.playerCntGold.ToString();
}
}
플레이어가 가지고 있는 무기 오브젝트 정보도 저장된 정보가 바로 반영됨을 볼 수 있다.