
유저데이터 클래스 - 유저의 데이터를 저장
[System.Serializable]
public class UserData
{
public string id;
public string name;
public string ps;
public int balance;
public int cash;
public UserData()
{
} // 역직렬화를 위한 기본 생성자
public UserData(string id, string name, string ps) // 회원가입 시 생성자
{
this.id = id;
this.name = name;
this.ps = ps;
balance = 150000; // 통장 초기 금액
cash = 100000; // 현금 초기 금액
}
public UserData(string id, string name, string ps, int balance, int cash) // 기존 유저 생성자
{
this.id = id;
this.name = name;
this.ps = ps;
this.balance = balance;
this.cash = cash;
}
}
로그인 씬에서 사용하는 스크립트 - 로그인 씬의 UI를 담당
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PopupLogin : MonoBehaviour
{
[Header("로그인 오브젝트 할당")] [SerializeField]
private TMP_InputField loginID; // 로그인 아이디
[SerializeField] private TMP_InputField loginPS; // 로그인 비번
[SerializeField] private GameObject loginErrorPanel; // 로그인 에러 판넬
[Header("회원가입 InputText 할당")] [SerializeField]
private TMP_InputField signUpID; // 아이디 입력
[SerializeField] private TMP_InputField signUpName; // 이름 입력
[SerializeField] private TMP_InputField signUpPS; // 비번 입력
[SerializeField] private TMP_InputField signUpPSConfirm; // 비번 확인
[Header("회원가입 오브젝트 할당")] [SerializeField]
private GameObject signUpPanel; // 회원가입 판넬
[SerializeField] private GameObject signUpErrorPanel; // 에러 판넬
[SerializeField] private TextMeshProUGUI signUpErrorText; // 비번 잘못 입력 시 출력되는 문구
private bool _signUpConfirm; // 회원가입이 확인되었는지
// 비밀번호 TMP_InputField가 한글로 입력되면 *이 한번에 8개 출력되는 것을 방지
// 게임 플레이중에는 키보드 입력이 텍스트를 의도하는 것이 아니기 때문에 unity에서 자동으로 IME 기능을 비활성화
void Update()
{
// 한글 기준으로 한 문자(김 <- 이런식으로)를 완성해야 '*'하나로 인식하는 것 같음
// 비밀번호 입력 필드에 포커스가 있을 때만 IME를 끕
// 다른 입력 필드(ID, 이름 등)에는 IME 설정이 영향을 주지 않도록 합니다.
if (loginPS.isFocused || signUpPS.isFocused || signUpPSConfirm.isFocused)
{
Input.imeCompositionMode = IMECompositionMode.Off;
}
else
{
// 다른 입력 필드에 포커스가 있거나 아무것도 포커스되지 않은 경우
// Unity가 자동으로 IME를 관리하도록 둡니다.
Input.imeCompositionMode = IMECompositionMode.On;
}
}
public void OnSingUpButtonClick() // 회원가입 버튼
{
if (signUpPanel.activeSelf == false) // 회원가입 판넬이 비활성화라면
{
// 메인화면 Sign Up버튼 누르면 회원가입 정보 기입 초기화
signUpID.text = "";
signUpName.text = "";
signUpPS.text = "";
signUpPSConfirm.text = "";
signUpErrorText.text = "";
signUpPanel.SetActive(true); // 회원가입 판넬 활성화
}
else // 회원가입 판넬 활성화 상태라면
{
SignUpSaveData(); // 회원가입 데이터 저장
if (_signUpConfirm) // 회원가입 완료상태면
{
signUpPanel.SetActive(false); // 회원가입 판넬 비활성화
_signUpConfirm = false; // 회원가입 상태 미완으로 변경
}
}
}
public void OnOkButtonClick() // 에러 판넬 Ok버튼
{
if (loginErrorPanel.activeSelf) // 로그인 에러 판넬 활성화 상태라면
{
loginErrorPanel.SetActive(false); // 비활성화로 전환
}
else // 로그인 에러 판넬 비활성화 상태라면
{
signUpErrorPanel.SetActive(false); // 회원가입 에러 판넬 비활성화
}
}
public void OnCancelButtonClick() // 회원가입 Cancel버튼 클릭
{
signUpPanel.SetActive(false); // 회원가입 판넬 비활성화
}
private void SignUpSaveData() // 회원가입 데이터 저장
{
// 공백이 포함되어 있으면 공백 제거한 상태로 저장
string newID = signUpID.text.Trim();
string newName = signUpName.text.Trim();
string newPassWord = signUpPS.text.Trim();
string newPSConfirm = signUpPSConfirm.text.Trim();
if (newID.Contains(" ")) // 아이디에 공백이 있을 경우
{
signUpErrorPanel.SetActive(true); // 에러 판넬 활성화
signUpErrorText.text = "아이디를 확인해주세요."; // 에러문구
return;
}
if (newName.Contains(" ")) // 이름에 공백이 있을 경우
{
signUpErrorPanel.SetActive(true); // 에러 판넬 활성화
signUpErrorText.text = "이름을 확인해주세요."; // 에러문구
return;
}
if (newPassWord != newPSConfirm) // 비밀번호와 확인 비밀번호가 다를 경우
{
signUpErrorPanel.SetActive(true); // 에러 판넬 활성화
signUpErrorText.text = "비밀번호를 확인해주세요."; // 에러문구
return;
}
// 위의 아무 문제가 없으면 유저 데이터 저장
_signUpConfirm = true; // 회원가입 완료
signUpErrorText.text = ""; // 에러 문구 초기화
UserData newUser = new UserData(newID, newName, newPassWord); // 신규 유저 등록
GameManager.Instance.userDataList.Add(newUser); // 유저 목록에 추가
GameManager.Instance.SaveUserData(); // 신규 유저 데이터 저장
}
public void OnLoginButtonClick() // 로그인 버튼 (json)
{
// loginErrorPanel의 자식인 TextMeshProUGUI 오브젝트를 저장;
TextMeshProUGUI loginErrorText = loginErrorPanel.GetComponentInChildren<TextMeshProUGUI>();
string userID = loginID.text.Trim();
string userPS = loginPS.text.Trim();
// 첫 번째 요소(w.ID)를 반환하거나 없으면 기본값(null)을 반환함
UserData savedUserData = GameManager.Instance.userDataList.FirstOrDefault(w => w.id == userID);
if (userID == "")
{
loginErrorText.text = "아이디를 입력하세요.";
loginErrorPanel.SetActive(true);
return;
}
if (userPS == "")
{
loginErrorText.text = "비밀번호를 입력하세요.";
loginErrorPanel.SetActive(true);
return;
}
if (savedUserData != null && savedUserData.id == userID) // 저장된 아이디와 입력 아이디가 같으면
{
if (savedUserData.ps == userPS) // 저장된 비번과 입력 비번이 같으면
{
GameManager.Instance.CurrentUserInfo(savedUserData); // 게임매니저에 로그인한 유저를 현재 사용 유저로 저장
SceneManager.LoadScene("PopupBank"); // 다음 씬으로 이동
}
else
{
loginErrorText.text = "비밀번호를 잘못 입력하셨습니다.";
loginErrorPanel.SetActive(true);
}
}
else
{
loginErrorText.text = "해당 아이디가 없습니다.";
loginErrorPanel.SetActive(true);
}
}
}
팝업뱅크씬의 UI를 담당하는 스크립트
using System.Linq;
using TMPro;
using UnityEngine;
public class PopupBank : MonoBehaviour
{
[Header("PopupBank 씬 오브젝트 할당")] [SerializeField]
public GameObject deposit; // 입금 오브젝트
[SerializeField] public GameObject withdrawal; // 출금 오브젝트
[SerializeField] public GameObject remittance; // 송금 오브젝트
[SerializeField] private GameObject atm; // ATM 오브젝트
[SerializeField] private GameObject bgPanel; // 잔액부족 판넬
[SerializeField] private TMP_InputField depositInputField; // 입금 직접 입력 오브젝트
[SerializeField] private TMP_InputField withdrawalInputField; // 출금 직접 입력 오브젝트
[SerializeField] private TMP_InputField remittanceTargetInputField; // 송금 대상 오브젝트
[SerializeField] private TMP_InputField remittanceCashInputField; // 송금 금액 오브젝트
[SerializeField] private TextMeshProUGUI errorText; // 에러 문구 오브젝트
[Header("화면UI에 텍스트 연결")] [SerializeField]
private TextMeshProUGUI nameText; // 유저 이름 출력 텍스트
[SerializeField] private TextMeshProUGUI balanceText; // 통장 잔액 출력 텍스트
[SerializeField] private TextMeshProUGUI cashText; // 현금 잔액 출력 텍스트
void Start()
{
Refresh();
}
private void Refresh() // UI 업데이트
{
CurrentUserInfo(); // 현재 유저 정보 업데이트
GameManager.Instance.SaveUserData();
}
private void CurrentUserInfo() // 현재 유저 정보
{
// 게임매니저에서 저장된 현재 유저 정보를 화면 텍스트 UI에 연결
nameText.text = GameManager.Instance.CurrentUserData.name;
balanceText.text = $"Balance {GameManager.Instance.CurrentUserData.balance:N0}";
cashText.text = $"{GameManager.Instance.CurrentUserData.cash:N0}";
}
public void OnDepositButtonClick() // 입금 창으로 넘어가는 버튼
{
deposit.SetActive(true);
atm.SetActive(false);
}
public void OnWithdrawalButtonClick() // 출금 창으로 넘어가는 버튼
{
withdrawal.SetActive(true);
atm.SetActive(false);
}
public void OnRemittanceButtonClick() // 송금 창으로 넘어가는 버튼
{
remittance.SetActive(true);
atm.SetActive(false);
}
public void OnMoneyChangeButtonClick(int amount) // 입출금(10000, 30000, 50000) 버튼
{
if (deposit.activeSelf) // 입금 창일 때
{
if (GameManager.Instance.CurrentUserData.cash >= amount) // 현금 >= 입금 금액
{
GameManager.Instance.DepositCash(amount); // 입금 진행
}
else
{
bgPanel.SetActive(true);
}
}
else if (withdrawal.activeSelf) // 출금 창일 때
{
if (GameManager.Instance.CurrentUserData.balance >= amount) // 통장 금액 >= 출금 금액
{
GameManager.Instance.WithdrawalCash(amount); // 출금 진행
}
else
{
bgPanel.SetActive(true);
}
}
Refresh();
}
public void OnRemittanceCashButtonClick() // 송금하는 버튼
{
string remittanceTargetText = remittanceTargetInputField.text.Trim(); // 인풋필드에서 입력한 대상 텍스트를 저장
string remittanceCashText = remittanceCashInputField.text.Trim(); // 인풋필드에서 입력한 현금 텍스트를 저장
// 첫 번째 요소(w.id 또는 w.name)를 반환하거나 없으면 기본값(null)을 반환함
UserData targetUser =
GameManager.Instance.userDataList.FirstOrDefault(w =>
w.name == remittanceTargetText || w.id == remittanceTargetText);
if (targetUser == null) // 송금 대상이 없는 ID면 에러
{
bgPanel.SetActive(true);
errorText.text = "대상이 없습니다.";
return; // 강제 종료
}
// 송금 대상 / 금액을 입력 안하면 에러
if (string.IsNullOrEmpty(remittanceTargetText) || string.IsNullOrEmpty(remittanceCashText))
{
bgPanel.SetActive(true);
errorText.text = "입력 정보를 확인해주세요.";
return; // 강제 종료
}
if (int.TryParse(remittanceCashText, out int remittanceCash)) // 송금 금액 숫자형으로 변환
{
// 현재 유저 금액 >= 송금할 금액
if (GameManager.Instance.CurrentUserData.cash >= remittanceCash)
{
GameManager.Instance.RemittanceCash(targetUser, remittanceCash);
}
else // 잔액이 부족하면 에러
{
bgPanel.SetActive(true);
errorText.text = "잔액이 부족합니다.";
return; // 강제 종료
}
}
else
{
bgPanel.SetActive(true);
errorText.text = "금액을 다시 입력해주세요.";
return; // 강제 종료
}
if (GameManager.Instance.CurrentUserData == targetUser)
{
bgPanel.SetActive(true);
errorText.text = "자기 자신에게는 불가능합니다.";
return; // 강제 종료
}
Refresh();
}
public void InputDepositButtonClick() // 직접 입력 입금 버튼
{
string amountText = depositInputField.text; // 직접 입력 금액 텍스트를 저장
// 입력 받은 값을 문자열에서 정수형으로 변환시켜 amount변수에 저장
// 변환이 되었으면 true
if (int.TryParse(amountText, out int amount))
{
GameManager.Instance.DepositCash(amount);
depositInputField.text = ""; // 문구 초기화
Refresh();
}
}
public void InputWithdrawalButtonClick() // 직접 입력 출금 버튼
{
string amountText = withdrawalInputField.text; // 직접 입력 금액 텍스트를 저장
// 입력 받은 값을 문자열에서 정수형으로 변환시켜 amount변수에 저장
if (int.TryParse(amountText, out int amount))
{
GameManager.Instance.WithdrawalCash(amount);
withdrawalInputField.text = ""; // 문구 초기화
Refresh();
}
}
public void PanelOk() // 금액부족 판넬 Ok버튼
{
bgPanel.SetActive(false);
}
public void OnBackButtonClick() // 뒤로 가기
{
if (deposit.activeSelf) // 입금 오브젝트 활성화 되어있을 경우
{
deposit.SetActive(false);
}
else if (withdrawal.activeSelf) // 출금 오브젝트 활성화 되어있을 경우
{
withdrawal.SetActive(false);
}
else if (remittance.activeSelf) // 송금 오브젝트 활성화 되어있을 경우
{
remittance.SetActive(false);
}
atm.SetActive(true);
Refresh();
}
}
게임매니저 - 전체적인 게임 관리
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public UserData CurrentUserData { get; private set; } // 현재 로그인된 유저 데이터
public List<UserData> userDataList = new List<UserData>(); // 유저들 데이터 리스트
private string _path; // 유저 데이터 저장/로드할 파일 경로
void Awake() // 초기화
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
// Combine(저장경로, 생성할 파일 이름);
// Application.persistentDataPath - 모바일에서 경로 지정할 때 사용하는 것 같음 - 읽기 쓰기 가능한 저장 경로
// Directory.GetCurrentDirectory() - 프로젝트 최상단에 저장하는 경로
_path = Path.Combine(Directory.GetCurrentDirectory(), "UserData.json");
Debug.Log(_path); // 제이슨 저장 경로
LoadUserData(); // 저장된 데이터 불러옴
}
#region 기능구현
public void DepositCash(int amount) // 입금(정산) - 버튼에 연결
{
if (CurrentUserData.cash >= amount) // 현재 유저 현금 >= 해당 입금 금액
{
CurrentUserData.cash -= amount;
CurrentUserData.balance += amount;
}
}
public void WithdrawalCash(int amount) // 출금(정산) - 버튼에 연결
{
if (CurrentUserData.balance >= amount) // 현재 유저 통장 >= 해당 출금 금액
{
CurrentUserData.balance -= amount;
CurrentUserData.cash += amount;
}
}
public void RemittanceCash(UserData targetUser, int amount) // 송금(정산) - 버튼에 연결
{
if (targetUser == null) return; // 저장된 유저 데이터가 없다면 종료
if (CurrentUserData.balance >= amount) // 현재 유저 통장 금액 >= 송금 금액
{
CurrentUserData.balance -= amount;
targetUser.balance += amount; // 송금 대상 통장에 금액 송금
}
}
#endregion
#region json으로 저장
public void SaveUserData() // 유저 데이터 저장
{
// userData 인자를 Json형식의 문자열로 변환 -> 직렬화
// Formatting.Indented 가독성을 높이기 위해 들여쓰기를 해줌
string jsonSave = JsonConvert.SerializeObject(userDataList, Formatting.Indented);
// path 경로에 파일이 없으면 jsonSave내용으로 생성하고, 파일이 있으면 내용을 덮어쓴다.
File.WriteAllText(_path, jsonSave);
}
private void LoadUserData() // 유저 데이터 불러오기
{
if (File.Exists(_path)) // 경로에 파일이 존재하면
{
// 경로에 있는 파일의 모든 텍스트 내용을 읽어옴 -> 문자열 변수에 저장
string json = File.ReadAllText(_path);
// json 형식의 문자열을 C# 객체(UserData 타입)로 변환 -> 역직렬화
userDataList = JsonConvert.DeserializeObject<List<UserData>>(json);
}
if (userDataList == null) // 저장된 데이터가 없다면
{
userDataList = new List<UserData>(); // 기본값으로 할당
}
}
public void CurrentUserInfo(UserData userData) // 현재(로그인한) 유저 정보
{
CurrentUserData = userData; // 로그인한 유저 정보를 현재 유저정보에 저장
}
#endregion
}
_path = Path.Combine(Application.persistentDataPath, "UserData.json"); - 기존 코드
_path = Path.Combine(Directory.GetCurrentDirectory(), "UserData.json"); - 튜터님이 알려주신 코드
Application.persistentDataPath: Application이 아마 모바일에서
사용하는 코드일 것이라고 알려주셨다.
모바일은 제이슨 저장경로가 정해져있기 때문에 사용한다고 하셨는데,
나는 모바일용 경로로 저장하고 있었던 것이었다.
Directory.GetCurrentDirectory(): 현재 프로젝트의 최상단 폴더에 저장되는데, 위의 코드와 비교해보자면 저장경로를 찾기가 쉽다는 것이다.