스위치 켜기는 어몽어스 게임 중 임포스터가 크루를 방해하기 위해 암전시켰을 때 수행하는 임무이다. 개인 미션이 아닌 크루 중 누구라도 완수하면 모두가 오나수한 것처럼 인정되는 공용 임무이기 때문에 Photon을 이용해 다른 사람에게도 미션 상태가 공유되도록 해야만 했다.
Light Switch On Off game
자료
직접 누끼따서 만들었다... 포토샵 없는 나같은 사람들은 Pixlr를 애용합시다.
멀티로 만들기 전 코드
처음부터 멀티로 생각하면 만들기도 어렵기 때문에 일단 멀티가 아닌 버전부터 생각해서 만들었다.
모든 불빛은 꺼져있다고 생각하고, 랜덤으로 일부 불빛만 켜고 스위치가 위로 가 있는지 아래로 가 있는지도 랜덤으로 설정했다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LightSwitchGame : MonoBehaviour
{
[Header("Game")]
[SerializeField] Button[] lightButtons;
[SerializeField] Image[] lightHandleImages;
[SerializeField] Image[] lightOnImages;
[SerializeField] Image progressBar;
AudioSource audioSource;
[Header("Game End")]
[SerializeField] GameObject endText;
bool gameEnd;
Color onColor = new Color(1f, 1f, 1f, 1f);
Color offColor = new Color(1f, 1f, 1f, 0f);
// Start is called before the first frame update
void Start()
{
progressBar.fillAmount = 0;
PickRandomUpDown();
PickRandomOnOff();
for(int i = 0; i < lightButtons.Length; i++)
{
int index = i;
lightButtons[index].onClick.AddListener(delegate { SwitchUpDown(lightHandleImages[index], lightOnImages[index]); });
}
endText.SetActive(false);
ShowProgress();
audioSource = GetComponent<AudioSource>();
}
private void PickRandomUpDown()
{
for (int i = 0; i < lightHandleImages.Length; i++)
{
int upDown = Random.Range(0, 2);
// Set whether the handle is up or down when the light is on.
// upDown == 0 : handle down / upDown == 1 : handle up
lightHandleImages[i].color = new Color(1f, 1f, 1f, upDown); // random up down
}
}
public void PickRandomOnOff()
{
int onNum = Random.Range(0, 3); // at least two must be off
for (int i = 0; i < onNum; i++)
{
int randomNum = Random.Range(0, 5);
lightOnImages[randomNum].color = onColor;
}
}
public void SwitchUpDown(Image btnImg, Image lightImg)
{
audioSource.Play();
// Change alpha value of img.
// If down handle -> up handle // up handle -> down handle
// If light on -> off // off -> on
btnImg.color = btnImg.color == offColor ? onColor : offColor;
lightImg.color = lightImg.color == offColor ? onColor : offColor;
ShowProgress();
CheckComplete();
}
public void ShowProgress()
{
var lightsOn = System.Array.FindAll(lightOnImages, x => x.color == onColor);
progressBar.fillAmount = (float)lightsOn.Length / lightOnImages.Length;
}
public void CheckComplete()
{
if (System.Array.TrueForAll(lightOnImages, value => { return value.color == onColor ? true : false; }))
{
endText.SetActive(true);
gameEnd = true;
foreach (var btn in lightButtons)
{
btn.interactable = false;
}
}
}
}
Multi 버전
결론부터 말하면 아래의 코드는 제대로 작동하지 않았다. 뭐가 문제였을까? (정답은 10초 뒤 공개)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
public class LightSwitchMulti : MonoBehaviour, IPunObservable
{
[Header("Network")]
PhotonView pv;
[Header("Game")]
public bool gameStart = false;
[SerializeField] Button[] lightButtons;
[SerializeField] Image[] lightHandleImages;
[SerializeField] Image[] lightOnImages;
[SerializeField] Image progressBar;
AudioSource audioSource;
[Header("Game End")]
[SerializeField] GameObject endText;
bool gameEnd;
Color onColor = new Color(1f, 1f, 1f, 1f);
Color offColor = new Color(1f, 1f, 1f, 0f);
// Start is called before the first frame update
void Start()
{
pv = GetComponent<PhotonView>();
progressBar.fillAmount = 0;
endText.SetActive(false);
audioSource = GetComponent<AudioSource>();
}
private void Update()
{
if (!gameStart && transform.GetChild(0).gameObject.activeInHierarchy)
{
// if someone opens this game window
gameStart = true;
StartGame();
}
}
private void StartGame()
{
if (gameStart)
{
pv.RPC(nameof(PickRandomUpDown), RpcTarget.All);
pv.RPC(nameof(PickRandomOnOff), RpcTarget.All);
for (int i = 0; i < lightButtons.Length; i++)
{
int index = i;
lightButtons[index].onClick.AddListener(delegate {
pv.RPC(nameof(SwitchUpDown), RpcTarget.All, index, index);
});
}
pv.RPC(nameof(ShowProgress), RpcTarget.All);
}
}
[PunRPC]
private void PickRandomUpDown()
{
Debug.Log("PickRandomUpDown");
for (int i = 0; i < lightHandleImages.Length; i++)
{
int upDown = Random.Range(0, 2);
// Set whether the handle is up or down when the light is on.
// upDown == 0 : handle down / upDown == 1 : handle up
lightHandleImages[i].color = new Color(1f, 1f, 1f, upDown); // random up down
}
}
[PunRPC]
public void PickRandomOnOff()
{
int onNum = Random.Range(0, 3); // at least two must be off
Debug.Log("onNum "+onNum);
for (int i = 0; i < onNum; i++)
{
int randomNum = Random.Range(0, 5);
Debug.Log("randomNum" + randomNum);
lightOnImages[randomNum].color = onColor;
}
}
[PunRPC]
public void SwitchUpDown(int btnIdx, int lightIdx)
{
Debug.Log("SwitchUpDown");
Image btnImg = lightHandleImages[btnIdx];
Image lightImg = lightOnImages[lightIdx];
audioSource.Play();
// Change alpha value of img.
// If down handle -> up handle // up handle -> down handle
// If light on -> off // off -> on
btnImg.color = btnImg.color == offColor ? onColor : offColor;
lightImg.color = lightImg.color == offColor ? onColor : offColor;
ShowProgress();
CheckComplete();
}
[PunRPC]
public void ShowProgress()
{
Debug.Log("ShowProgress");
var lightsOn = System.Array.FindAll(lightOnImages, x => x.color == onColor);
progressBar.fillAmount = (float)lightsOn.Length / lightOnImages.Length;
}
[PunRPC]
public void CheckComplete()
{
Debug.Log("CheckComplete");
if (System.Array.TrueForAll(lightOnImages, value => { return value.color == onColor ? true : false; }))
{
endText.SetActive(true);
gameEnd = true;
foreach (var btn in lightButtons)
{
btn.interactable = false;
}
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
stream.SendNext(gameStart);
}
else
{
gameStart = (bool)stream.ReceiveNext();
}
}
}
문제 해결
Random으로 숫자를 뽑는 부분까지 동기화하는 바람에, 각 client가 서로 다른 랜덤 숫자를 뽑았고, 그에 따라 서로 다른 UI가 보이게 되었다.
Random으로 숫자를 뽑는 함수를 따로 분리해서 첫번째로 퀘스트를 시작한 사람만 Random 숫자를 뽑고 그 결과에 따라 UI를 띄우는 부분만 동기화하면 된다.
또한 버튼에 Event를 추가하는 부분은 모든 클라이언트에게 필요한 부분이므로 Start로 옮겨주었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
public class LightSwitchMulti : MonoBehaviour, IPunObservable
{
[Header("Network")]
PhotonView pv;
[SerializeField] int[] pickUpDown;
[SerializeField] int[] colorOnOff;
[Header("Game")]
public bool gameStart = false;
[SerializeField] Button[] lightButtons;
[SerializeField] Image[] lightHandleImages;
[SerializeField] Image[] lightOnImages;
[SerializeField] Image progressBar;
AudioSource audioSource;
[Header("Game End")]
[SerializeField] GameObject endText;
bool gameEnd;
Color onColor = new Color(1f, 1f, 1f, 1f);
Color offColor = new Color(1f, 1f, 1f, 0f);
// Start is called before the first frame update
void Start()
{
pv = GetComponent<PhotonView>();
progressBar.fillAmount = 0;
pickUpDown = new int[lightHandleImages.Length];
colorOnOff = new int[lightOnImages.Length];
endText.SetActive(false);
audioSource = GetComponent<AudioSource>();
// Add Button Events
for (int i = 0; i < lightButtons.Length; i++)
{
int index = i;
lightButtons[index].onClick.AddListener(delegate {
pv.RPC(nameof(SwitchUpDown), RpcTarget.All, index, index);
});
}
}
private void Update()
{
if (!gameStart && transform.GetChild(0).gameObject.activeInHierarchy)
{
// if someone opens this game window
gameStart = true;
StartGame();
}
}
private void StartGame()
{
if (gameStart)
{
// Random picking will be done by first game starter
PickRandomUpDown();
PickRandomOnOff();
// Result of random pick will be shown to everyone
pv.RPC(nameof(PickUpDown), RpcTarget.All, pickUpDown);
pv.RPC(nameof(ColorOnOff), RpcTarget.All, colorOnOff);
pv.RPC(nameof(ShowProgress), RpcTarget.All);
}
}
private void PickRandomUpDown()
{
for (int i = 0; i < lightHandleImages.Length; i++)
{
int upDown = Random.Range(0, 2);
// Set whether the handle is up or down when the light is on.
// upDown == 0 : handle down / upDown == 1 : handle up
pickUpDown[i] = upDown;
}
}
[PunRPC]
public void PickUpDown(int[] arrUpDown)
{
for(int i = 0; i < lightHandleImages.Length; i++)
{
Debug.Log("Handle UpDown: "+arrUpDown[i]);
lightHandleImages[i].color = new Color(1f, 1f, 1f, arrUpDown[i]); // random up down
}
}
public void PickRandomOnOff()
{
int onNum = Random.Range(0, 3); // at least two must be off
Debug.Log("onNum "+onNum);
for (int i = 0; i < onNum; i++)
{
int randomNum = Random.Range(0, 5);
Debug.Log("randomNum" + randomNum);
colorOnOff[randomNum] = (int) onColor.a; // zero or one, so save it as int
}
}
[PunRPC]
public void ColorOnOff(int[] arrOnOff)
{
for (int i = 0; i < lightOnImages.Length; i++)
{
Debug.Log("Light On Off: "+arrOnOff[i]);
lightOnImages[i].color = new Color(1f, 1f, 1f, arrOnOff[i]);
}
}
[PunRPC]
public void SwitchUpDown(int btnIdx, int lightIdx)
{
Debug.Log("SwitchUpDown");
Image btnImg = lightHandleImages[btnIdx];
Image lightImg = lightOnImages[lightIdx];
if(transform.GetChild(0).gameObject.activeInHierarchy) audioSource.Play();
// Change alpha value of img.
// If down handle -> up handle // up handle -> down handle
// If light on -> off // off -> on
btnImg.color = btnImg.color == offColor ? onColor : offColor;
lightImg.color = lightImg.color == offColor ? onColor : offColor;
ShowProgress();
CheckComplete();
}
[PunRPC]
public void ShowProgress()
{
Debug.Log("ShowProgress");
var lightsOn = System.Array.FindAll(lightOnImages, x => x.color == onColor);
progressBar.fillAmount = (float)lightsOn.Length / lightOnImages.Length;
}
public void CheckComplete()
{
Debug.Log("CheckComplete");
if (System.Array.TrueForAll(lightOnImages, value => { return value.color == onColor ? true : false; }))
{
endText.SetActive(true);
gameEnd = true;
foreach (var btn in lightButtons)
{
btn.interactable = false;
}
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
stream.SendNext(gameStart);
}
else
{
gameStart = (bool)stream.ReceiveNext();
}
}
}
동기화할 때 넘겨주는 데이터 타입은 제한이 있으므로 아래 글을 참고 바란다. Image 같은 것을 바로 동기화하려고 하면 안 되고, 바꾸려는 부분을 int, float, Vector3 같이 더 자세한 데이터 타입으로 바꾸어 동기화해야 한다.
Photon RPC 동기화 가능한 데이터 타입 정리
[Unity] Photon Serialization - RPC 동기화 가능한 데이터 타입
Photon에서 RPC를 통해 동기화할 때나 PhotonSerializeView를 통해 동기화할 때나 동기화할 수 있는 데이터 타입에 제한이 있다. 특히 Photon을 사용할 때 게임 오브젝트 자체를 동기화하려고 하면 안 되는
psych-dobby.tistory.com
'Unity 공부' 카테고리의 다른 글
[Unity] Photon RPC 에러 Exception: Write Failed. Custom type not found (0) | 2022.11.20 |
---|---|
Azure PlayFab 회원가입, 로그인 (0) | 2022.11.18 |
[Unity] Photon Serialization - RPC 동기화 가능한 데이터 타입 (0) | 2022.11.08 |
[Unity] 어몽어스 미니게임 - 방패 임무 (Among Us Shield Task) (2) | 2022.11.04 |
[Unity] 베지어 곡선 활용해 오브젝트 곡선 이동 시키기 (0) | 2022.11.03 |
댓글