경작 15일차 유니티 (멀티 게임에서 PlayerPrefs 저장하기!)

한정화·2023년 2월 12일
0

#230212 일

큰일났다.. 오늘 벨로그 쓸 게 없다.. 알고리즘 스터디 가기 전까진 유니티만 했고 스터디 가선 한 문제도 못 풀었다..
..아니?!!!! 너 유니티 무시해? C#도 코딩이야 코딩! C#이 우스워? 우습냐고?

알겠으니까 진정하시고
유니티 정리 들어갑니다


오늘 내가 한 건

"포톤 엔진을 사용한 2인 멀티게임에서 각 플레이어의 승패를 저장해, 로그인 시 자신의 승률 통계를 볼 수 있도록 한다"

였다. PlayerPrefs를 이용하면 간단할 거라고 생각해서 별 걱정은 안 하고 있었는데, PhotonManager나 GameManager는 각 플레이어에게 귀속된 것이 아니라 게임 전체를 관리하는 Script이다보니 이들만으로는 이긴 사람과 진 사람의 승률 통계를 업데이트 하는 것은 불가능하다는 걸 깨달았다. GamaManager는 플레이어 tag로 승자와 패자가 누군지는 판단할 수 있지지만, 승자와 패자의 PlayerPrefs를 수정하는 것은 할 수 없었다. 해당 플레이어에게만 귀속되는 (다른 플레이어에는 귀속되지 않는) 스크립트는 PlayerCtrl뿐이므로 그걸 활용해야했다. 한참 고민하다가 내가 선택한 것은

  1. MonsterCtrl에서 몬스터와 플레이어가 충돌했을 때 PlayerCtrl의 점수 변수를 가져와 수정한다.
  2. PlayerCtrl에서는 점수 변수가 수정되면 수정된 변수로 PlayerPrefs를 업데이트한다.

이를 위해서는 다음과 같은 코드들이 구현되어야 한다.

  • 몬스터가 생성되면 MonsterCtrl 스크립트에서 각 플레이어의 PlayerScript를 불러온다.
  • 몬스터와 플레이어가 충돌하면(게임이 끝나면) MonsterCtrl에서 각 플레이어의 PlayerCtrl의 승패 횟수 변수값을 변경해준다.
  • 승패 횟수 변수값이 변경되면 PlayerCtrl에서 이 값을 PlayerPrefs에 저장한다.
  • (+승패 UI를 띄운다.)

1) 몬스터가 생성되면 MonsterCtrl 스크립트에서 각 플레이어의 PlayerScript를 불러온다.

public class MonsterCtrl : MonoBehaviour
{
	PlayerCtrl playerctrl1;  // 변수 선언 
    PlayerCtrl playerctrl2;

	void Start()  // 몬스터가 씬에 생성됐을 때 
    {
        playerctrl1 = GameObject.FindWithTag("Player1").GetComponent<PlayerCtrl>(); 
        playerctrl2 = GameObject.FindWithTag("Player2").GetComponent<PlayerCtrl>();  
        //Player 각각의 스크립트를 불러와 playerctrl 변수에 저장
    }

	...생략...

이렇게 하려면 몬스터가 생성될 때 씬에 이미 두 명의 플레이어가 모두 존재해야한다. 그렇지 않으면 GameObject.FindWithTag("Player")를 했을 때 씬에 Player 태그를 가진 게임 오브젝트를 찾을 수 없어 오류가 나기 때문이다. 하지만 내가 이 작업을 시작하기도 전에 이미 다른 팀원분들께서 그렇게 설정해두셨다. (감사합니다 팀원들 알러뷰 당신들의 천재성에 치얼스~ / 팀원들 : 누구세요? )


2) 몬스터와 플레이어가 충돌하면 각 플레이어의 PlayerCtrl 스크립트의 승패 횟수 변수값을 변경해준다.

public class MonsterCtrl : MonoBehaviour
{
...생략...


void OnCollisionEnter(Collision coll)
  {
      if(coll.collider.tag == "Player1")  //몬스터와 충돌한 플레이어의 태그가 player1이면 
      {
          losePlayer = 1;   //진 플레이어는 player1
          playerctrl1.newLose++;   //player1이 가지고 있는 playerctrl 스크립트의 lose 변수값을 늘려준다. 
          playerctrl2.newWin++;   //player2가 가지고 있는 playerctrl 스크립트의 win 변수값을 늘려준다.           
      }

      if(coll.collider.tag == "Player2")
      {
          losePlayer = 2;
          playerctrl1.newWin++;
          playerctrl2.newLose++;
      }
  }

하여튼 1), 2)의 작업을 하면서 처음 알게 된 것이 static 변수를 사용하지 않아도 다른 클래스의 변수나 함수를 가져올 수 있다는 것이다. 내가 썼던 static 변수는

public class PlayerCtrl : MonoBehaviourPunCallbacks
{
    public static int newWin;
    public static int newLose; 
}
public class MonsterCtrl : MonoBehaviour
{
  	k = PlayerCtrl.newWin 
}

과 같은 방식으로 사용되는데,

class A 에서 static으로 선언된 변수나 메소드는 다른 스크립트에서 A.변수명(A.메소드명)으로 접근할 수 있다. (PlayerCtrl 클래스에서 선언된 static 변수 newWin을 MonsterCtrl 클래스에서 PlayerCtrl.newWin으로 접근) 하지만 static의 단점은 정적인 변수이기 때문에 외부 클래스에서 유동적으로 값을 바꾸면서 사용할 수 없다는 것이다. 만약 MonsterCtrl에서

public class MonsterCtrl : MonoBehaviour
{
  	PlayerCtrl.newWin++;
}

를 시도하면 에러가 날 것이다. 만약 사용하고 싶다면

public class MonsterCtrl : MonoBehaviour
{
  	private int k;
  	k = PlayerCtrl.newWin;
 	k++;
}

의 방식을 사용해야 한다. 하지만 이렇게 하면 당연히 k값만 변경되고 PlayerCtrl 클래스의 newWin 변수의 값은 변경되지 않는다.

반대로 외부 스크립트를 전체를 호출해서 가져오면 외부 스크립트의 변수도 자유롭게 접근하고 값을 변경할 수 있다. 호출 방법은 (스크립트 이름) (스크립트를 저장할 변수명) 이다.

public class MonsterCtrl : MonoBehaviour
{
	PlayerCtrl p_ctrl;  //PlayerCtrl 스크립트를 가져와 p_ctrl 변수에 저장 
  
  	p_ctrl.newWin++;
}

오마이갓 so easy~ static 뭔지도 모르고 막 썼다가 오류나서 빙글빙글 돌아간 사람 나야 나~ㅎㅎ


3) 승패 횟수(newWin, newLose) 변수값이 변경되면 PlayerCtrl에서는 이를 PlayerPrefs에 저장한다.

public class PlayerCtrl : MonoBehaviourPunCallbacks
{
  	public int curWin;   //게임 시작 전 win 횟수(playerpref)
    public int curLose;  //게임 시작 전 lose 횟수(playerpref)

    public int newWin; //이기면 증가
    public int newLose; //지면 증가
 
..생략..
  
    void Start()
    {
        curWin = PlayerPrefs.GetInt("winNum");  //curWin에 이번 게임 이전까지의 win 횟수를 불러옴
        curLose = PlayerPrefs.GetInt("loseNum");
        newWin = 0;   //게임 시작 전이므로 0으로 초기화 
        newLose = 0;
 		check = 0;  //증가 횟수 측정 
    }
  
  
  	void Update()
    {
      if(newWin == 1 && check == 0) //MonsterCtrl에서 PlayerCtrl의 newWin을 증가시켰을 때 
		{ 
			curWin++;  //이긴 횟수에 1을 더함 
            check++;
            PlayerPrefs.SetInt("winNum", curWin);  //1을 더한 이긴 횟수를 PlayerPref에 저장     
      	}
  	}
 }

참고로 check는 별 게 아니고, 한 게임에서 점수가 여러 번 올라가는 것을 방지하기 위함이다. 원래 MonsterCtrl에서

if(coll.collider.tag == "Player1" && CollNumCheck == 0)
{
	losePlayer = 1; 
    playerctrl1.newLose++;
    playerctrl2.newWin++;    
}

각 Player의 점수를 올려준 후 Destroy를 이용해 두 플레이어를 없애줘야 하는데, 그러면 PlayerCtrl에서 PlayerPrefs를 저장하기도 전에 플레이어가 비활성화되어버리는 문제가 발생했다(아직 점수 저장을 못했는데 PlayerCtrl이 종료되어 점수가 저장이 안 됨). 그래서 Destory를 하지 않았더니 괴물가 플레이어의 충돌이 여러 번 감지되어 한 게임에 점수가 몇 백 번씩 올라가는 문제를 발견하였다. 그래서 PlayerCtrl에서 점수가 한 번 올라가면 check도 함께 올려, check가 0일 때에만 점수가 올라가도록 설정해주었다.


4) 승패 UI 띄우기

승패 UI도 PlayerPref 점수처럼 각 플레이어별로 다르게 띄워줘야하기 때문에 역시 PlayerCtrl 스크립트에서 작업했다. 사실 정리할 이유가 없는 굉장히 쉬운 작업인데 TextMeshProUGUI 는 처음 써봐서 정리해봤다. (그동안은 TMP_Text 로만 작업함)

public class PlayerCtrl : MonoBehaviourPunCallbacks
{
	public TextMeshProUGUI WinOrLoseUI; //TextMeshPro 타입의 WinOrLoseUI 변수 설정 
	
  
  	void Start() // 플레이어 생성 
  	{
  		//'WinOrLose' 태그를 가진 UI Text 요소 가져와 WinOrLoseUI 변수에 저장 
  		WinOrLoseUI = GameObject.FindGameObjectWithTag("WinOrLose")?.GetComponent<TextMeshProUGUI>();
		//승패를 보여주는 panel은 비활성화 (게임이 끝났을 때 활성화할 것임)
  		GameObject.Find("Canvas").transform.Find("Panel_WinOrLose").gameObject.SetActive(false);
  	}
  
  
  	if(newWin == 1 && check == 0)  //내가 이겼을 때 
	{
    	..생략.. 
 
  		//게임이 끝나고 승패가 결정됐으니 승패를 보여주는 Panel 활성화
  		GameObject.Find("Canvas").transform.Find("Panel_WinOrLose").gameObject.SetActive(true);
  		//UI text 내용을 'You Win!'으로 설정
		WinOrLoseUI.text = "You Win!";
	}
}

그 전까지는 UI 텍스트를 가져올 떄

public TMP_Text WinOrLose; 

WinOrLose.text = "You Win!"

를 주로 사용했었다. 이렇게 작성할 경우 스크립트에서는 텍스트 변수 WinOrLose가 가리키는 텍스트 게임 오브젝트를 알 수 없다. 따라서 유니티 인스펙터 창에서 스크립트의 WinOrLose 텍스트 변수에 텍스트 게임 오브젝트를 직접 드래그해서 연결해줘야 한다.

한편 오늘 내가 써본 것은 그러한 작업 없이 스크립트에서도 텍스트 변수와 텍스트 게임 오브젝트를 연결할 수 있다.

public TextMeshProUGUI WinOrLose;
  
WinOrLose = GameObject.FindGameObjectWithTag("WinOrLose")?.GetComponent<TextMeshProUGUI>();

WinOrLose = "You Win!"

사실 둘 사이에 스크립트 작성법 외에도 여러 차이가 있을 것인데(효율성이나 기능의 측면에서) 나는 아직 알아야 할 단계까지는 아닌 것 같다. 패스~

0개의 댓글