Unity/프로젝트
팀프로젝트 - 판타지 마블 (Fantasy marble) 02 - 기능 리뷰 (주사위)
mynote6590
2025. 4. 8. 15:53
설계
주사위를 생성할 때 네트워크 동기화 부분에서 처음에 고민을 많이 했다. 처음 생각한 방식에는 2가지가 있었다.
- 방식 1 : 첫번째 방식은 방장이 주사위를 생성하고 방장이 아닌 플레이어에게 전달해주는 방식
- 방식 2 : 플레이어 개개인이 주사위를 생성하는 방식
두 방식중 첫번 째 방식을 사용해 만들기로 결정했는데 그 이유는 한명만 주사위를 만들고 그걸 공유하는 방식이 효율적이라고 생각했기 때문이다. 그리고 또 고려해야 할 점이 있었는데 랜덤으로 주사위의 결과값을 받는것을 RPC로 동기화를 한다면 각 로컬에서 다른 결과 값이 나오기 때문에 주사위를 굴린 사람만 랜덤 함수를 실행시키고 그에 대한 결과값을 RPC 동기화 시켜주어야 했다.
코드 설명
1. 주사위 생성 ( TurnBasedManager )
방장만 생성후 RPC를 이용해 나머지 플레이어에게 전달, 주사위 생성할 때 고유 번호 지정
private void Start()
{
// 방장이 생성, 보내기
if (PhotonNetwork.IsMasterClient == true)
{
_redDice = PhotonNetwork.Instantiate("DiceRed",new Vector3(7,0,-7), Quaternion.identity,0, new object[] { 0 }).GetComponent<DiceManager>();
_blueDice = PhotonNetwork.Instantiate("DiceBlue", new Vector3(8, 0, -8), Quaternion.identity, 0, new object[] { 1 }).GetComponent<DiceManager>();
_redDiceViewID = _redDice.photonView.ViewID;
_blueDiceViewID = _blueDice.photonView.ViewID;
_redDice._dicePlayerMove += PlayerMove;
_blueDice._dicePlayerMove += PlayerMove;
photonView.RPC("DiceSatting", RpcTarget.Others, _redDiceViewID, _blueDiceViewID);
}
}
[PunRPC]
public void DiceSatting(int _redDiceView, int _blueView)
{
_redDice = PhotonView.Find(_redDiceView).GetComponent<DiceManager>();
_blueDice = PhotonView.Find(_blueView).GetComponent<DiceManager>();
_redDice._dicePlayerMove += PlayerMove;
_blueDice._dicePlayerMove += PlayerMove;
}
2. 주사위 굴리기 ( TurnBasedManager )
Dice함수를 호출한 사람만 Random 돌리고 그에 대한 결과만 동기화
public void Dice()
{
int _redDiceNum = Random.Range(1,7);
int _blueDiceNum = Random.Range(1,7);
_redDice._photonView.RPC("DiceStart", RpcTarget.All, _redDiceNum); // 빨간 주사위 애니메이션 실행
_blueDice._photonView.RPC("DiceStart", RpcTarget.All, _blueDiceNum); // 파란 주사위 애니메이션 실행
}
DiceManager
주사위 굴리는 애니메이션 - 랜덤으로 rotation 돌리기
[PunRPC]
public void DiceStart(int _diceNum)
{
this._diceNum = _diceNum;
_isRolling = true;
_rb.AddForce(0, 5, 0, ForceMode.Impulse);
_diceCor = StartCoroutine(RollingDice());
}
IEnumerator RollingDice()
{
while (_isRolling == true)
{
transform.rotation = Quaternion.Euler(UnityEngine.Random.Range(0, 360), UnityEngine.Random.Range(0, 360), UnityEngine.Random.Range(0, 360));
yield return new WaitForSeconds(0.1f);
}
}
DiceManager
주사위 결과 보여주기 - 땅에 닿았을 때 미리 정해진 결과값에 맞게 rotation변경
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "DiceGround" && _isRolling == true)
{
_isRolling = false;
StopCoroutine(RollingDice());
_rb.velocity = Vector3.zero;
ChangeRotation(_diceNum);
_dicePlayerMove?.Invoke(diceID, _diceNum);
}
}
public void ChangeRotation(int num)
{
if (num == 1) { transform.rotation = one; }
else if (num == 2) { transform.rotation = two; }
else if (num == 3) { transform.rotation = three; }
else if (num == 4) { transform.rotation = four; }
else if (num == 5) { transform.rotation = five; }
else if (num == 6) { transform.rotation = six; }
}
3. 플레이어 이동 ( TurnBasedManager )
2개의 주사위 애니메이션이 끝나고 결과를 통해 플레이어를 이동시킵니다.
아래의 코드는 땅에 닿으면 발생되는 이벤트, 이벤트가 실행될 때 배열을 이용해 2개의 주사위 애니메이션이 끝났는지 체크 하는 함수 입니다
public void PlayerMove(int diceKey, int diceNum)
{
diceResults[diceKey] = diceNum;
if ((diceResults[0] != null && diceResults[1] != null))
{
int total = diceResults[0].Value + diceResults[1].Value;
ServerIngamePlayer._players[TurnMgr.currentTurn].RpcMovePlayer(total);
diceResults[0] = null;
diceResults[1] = null;
}
}
아쉬운 점
랜덤으로 돌리는 rotation을 이용해 주사위 애니메이션을 만들엇는데 조금 부자연스러운 점이 있다.
전체 코드
TurnBasedManager
using System.Collections;
using System.Collections.Generic;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
public class TurnBasedManager : MonoBehaviourPun
{
private int?[] diceResults = new int?[2]; // 주사위 2개라고 가정
DiceManager _redDice;
DiceManager _blueDice;
int _redDiceViewID;
int _blueDiceViewID;
private void Start()
{
// 방장이 생성, 보내기
if (PhotonNetwork.IsMasterClient == true)
{
_redDice = PhotonNetwork.Instantiate("DiceRed",new Vector3(7,0,-7), Quaternion.identity,0, new object[] { 0 }).GetComponent<DiceManager>();
_blueDice = PhotonNetwork.Instantiate("DiceBlue", new Vector3(8, 0, -8), Quaternion.identity, 0, new object[] { 1 }).GetComponent<DiceManager>();
_redDiceViewID = _redDice.photonView.ViewID;
_blueDiceViewID = _blueDice.photonView.ViewID;
_redDice._dicePlayerMove += PlayerMove;
_blueDice._dicePlayerMove += PlayerMove;
photonView.RPC("DiceSatting", RpcTarget.Others, _redDiceViewID, _blueDiceViewID);
}
}
[PunRPC]
public void DiceSatting(int _redDiceView, int _blueView)
{
_redDice = PhotonView.Find(_redDiceView).GetComponent<DiceManager>();
_blueDice = PhotonView.Find(_blueView).GetComponent<DiceManager>();
_redDice._dicePlayerMove += PlayerMove;
_blueDice._dicePlayerMove += PlayerMove;
}
public void Dice()
{
int _redDiceNum = Random.Range(1,7);
int _blueDiceNum = Random.Range(1,7);
_redDice._photonView.RPC("DiceStart", RpcTarget.All, _redDiceNum);
_blueDice._photonView.RPC("DiceStart", RpcTarget.All, _blueDiceNum);
}
public void PlayerMove(int diceKey, int diceNum)
{
diceResults[diceKey] = diceNum;
if ((diceResults[0] != null && diceResults[1] != null))
{
int total = diceResults[0].Value + diceResults[1].Value;
ServerIngamePlayer._players[TurnMgr.currentTurn].RpcMovePlayer(total);
diceResults[0] = null;
diceResults[1] = null;
}
}
}
DiceManager
using System;
using System.Collections;
using Photon.Pun;
using UnityEngine;
public class DiceManager : MonoBehaviourPun, IPunInstantiateMagicCallback
{
bool _isRolling;
public event Action<int, int> _dicePlayerMove;
public int diceID;
Rigidbody _rb;
Coroutine _diceCor;
public PhotonView _photonView;
public int _diceNum;
Quaternion one = Quaternion.Euler(-90, 0, 0);
Quaternion two = Quaternion.Euler(0, 0, 0);
Quaternion three = Quaternion.Euler(0, 90, -90);
Quaternion four = Quaternion.Euler(180, 0, -90);
Quaternion five = Quaternion.Euler(180, 0, 0);
Quaternion six = Quaternion.Euler(90, 0, 0);
private void Start()
{
_isRolling = false;
_photonView = GetComponent<PhotonView>();
_rb = GetComponent<Rigidbody>();
}
[PunRPC]
public void DiceStart(int _diceNum)
{
this._diceNum = 0;
this._diceNum = _diceNum;
_isRolling = true;
_rb.AddForce(0, 5, 0, ForceMode.Impulse);
_diceCor = StartCoroutine(RollingDice());
}
IEnumerator RollingDice()
{
while (_isRolling == true)
{
transform.rotation = Quaternion.Euler(UnityEngine.Random.Range(0, 360), UnityEngine.Random.Range(0, 360), UnityEngine.Random.Range(0, 360));
yield return new WaitForSeconds(0.1f);
}
}
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "DiceGround" && _isRolling == true)
{
_isRolling = false;
StopCoroutine(RollingDice());
_rb.velocity = Vector3.zero;
ChangeRotation(_diceNum);
_dicePlayerMove?.Invoke(diceID, _diceNum);
}
}
public void ChangeRotation(int num)
{
if (num == 1) { transform.rotation = one; }
else if (num == 2) { transform.rotation = two; }
else if (num == 3) { transform.rotation = three; }
else if (num == 4) { transform.rotation = four; }
else if (num == 5) { transform.rotation = five; }
else if (num == 6) { transform.rotation = six; }
}
public void OnPhotonInstantiate(PhotonMessageInfo info)
{
object[] data = info.photonView.InstantiationData;
if (data != null && data.Length > 0)
{
diceID = (int)data[0];
}
}
}