NetworkManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
using static UIManager;
using static VoteManager;
using UnityEngine.Experimental.Rendering.Universal;
public class NetworkManager : MonoBehaviourPunCallbacks
{
public static NetworkManager NM;
void Awake() => NM = this;
public GameObject DisconnectPanel, WaitingPanel, InfoPanel, GamePanel, ReportPanel,
EmergencyPanel, VotePanel, KickPanel, NoOneKickPanel, CrewWinPanel, ImposterWinPanel;
public List<PlayerScript> Players = new List<PlayerScript>();
public PlayerScript MyPlayer;
public GameObject CrewInfoText, ImposterInfoText, WaitingBackground, Background;
public bool isGameStart;
public Transform SpawnPoint;
public Light2D PointLight2D;
public GameObject[] Interactions;
public GameObject[] Doors;
public GameObject[] Lights;
PhotonView PV;
public bool isTest;
public enum ImpoType { OnlyMaster, Rand1, Rand2 }
public ImpoType impoType;
public int VoteTimer;
void Start()
{
if (isTest) return;
Screen.SetResolution(1600, 900, false);
PV = photonView;
ShowPanel(DisconnectPanel);
ShowBackground(WaitingBackground);
}
public void Connect(InputField NickInput)
{
if (string.IsNullOrWhiteSpace(NickInput.text)) return;
PhotonNetwork.LocalPlayer.NickName = NickInput.text;
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
PhotonNetwork.JoinLobby();
}
public override void OnJoinedLobby()
{
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 10 }, null);
}
public override void OnJoinedRoom()
{
ShowPanel(WaitingPanel);
MyPlayer = PhotonNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity)
.GetComponent<PlayerScript>();
SetRandColor();
}
public void ShowPanel(GameObject CurPanel)
{
DisconnectPanel.SetActive(false);
WaitingPanel.SetActive(false);
InfoPanel.SetActive(false);
GamePanel.SetActive(false);
ReportPanel.SetActive(false);
EmergencyPanel.SetActive(false);
VotePanel.SetActive(false);
KickPanel.SetActive(false);
NoOneKickPanel.SetActive(false);
CrewWinPanel.SetActive(false);
ImposterWinPanel.SetActive(false);
CurPanel.SetActive(true);
}
void ShowBackground(GameObject CurBackground)
{
WaitingBackground.SetActive(false);
Background.SetActive(false);
CurBackground.SetActive(true);
}
void SetRandColor()
{
List<int> PlayerColors = new List<int>();
for (int i = 0; i < Players.Count; i++)
PlayerColors.Add(Players[i].colorIndex);
while (true)
{
int rand = Random.Range(1, 13);
if (!PlayerColors.Contains(rand))
{
MyPlayer.GetComponent<PhotonView>().RPC("SetColor", RpcTarget.AllBuffered, rand);
break;
}
}
}
public void SortPlayers() => Players.Sort((p1, p2) => p1.actor.CompareTo(p2.actor));
public Color GetColor(int colorIndex)
{
return UM.colors[colorIndex];
}
public void GameStart()
{
// 방장이 게임시작
SetImpoCrew();
PhotonNetwork.CurrentRoom.IsOpen = false;
PhotonNetwork.CurrentRoom.IsVisible = false;
ChatManager.CM.photonView.RPC("ChatClearRPC", RpcTarget.AllViaServer, false);
PV.RPC("GameStartRPC", RpcTarget.AllViaServer);
}
void SetImpoCrew()
{
List<PlayerScript> GachaList = new List<PlayerScript>(Players);
if (impoType == ImpoType.OnlyMaster)
{
Players[0].GetComponent<PhotonView>().RPC("SetImpoCrew", RpcTarget.AllViaServer, true);// 테스트 : 방장만 임포스터
}
else if (impoType == ImpoType.Rand1)
{
for (int i = 0; i < 1; i++) // 임포스터 1명 (테스트)
{
int rand = Random.Range(0, GachaList.Count); // 랜덤
Players[rand].GetComponent<PhotonView>().RPC("SetImpoCrew", RpcTarget.AllViaServer, true);
GachaList.RemoveAt(rand);
}
}
else if (impoType == ImpoType.Rand2)
{
for (int i = 0; i < 2; i++) // 임포스터 2명 (기본)
{
int rand = Random.Range(0, GachaList.Count); // 랜덤
Players[rand].GetComponent<PhotonView>().RPC("SetImpoCrew", RpcTarget.AllViaServer, true);
GachaList.RemoveAt(rand);
}
}
}
[PunRPC]
void GameStartRPC()
{
StartCoroutine(GameStartCo());
}
IEnumerator GameStartCo()
{
ShowPanel(InfoPanel);
ShowBackground(Background);
if (MyPlayer.isImposter) ImposterInfoText.SetActive(true);
else CrewInfoText.SetActive(true);
yield return new WaitForSeconds(3);
isGameStart = true;
MyPlayer.SetPos(SpawnPoint.position);
MyPlayer.SetNickColor();
MyPlayer.SetMission();
UM.GetComponent<PhotonView>().RPC("SetMaxMissionGage", RpcTarget.AllViaServer);
yield return new WaitForSeconds(1);
ShowPanel(GamePanel);
ShowGameUI();
StartCoroutine(UM.KillCo());
StartCoroutine(UM.EnergencyCo());
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
UM.GetComponent<PhotonView>().RPC("SetMaxMissionGage", RpcTarget.AllViaServer);
VM.photonView.RPC("VoteUpdateRPC", RpcTarget.All);
}
public int GetCrewCount()
{
int crewCount = 0;
for (int i = 0; i < Players.Count; i++)
if (!Players[i].isImposter) ++crewCount;
return crewCount;
}
void ShowGameUI()
{
if (MyPlayer.isImposter)
{
UM.SetInteractionBtn0(5, false);
UM.SetInteractionBtn1(4, false);
UM.SetInteractionBtn2(6, true);
}
else
{
UM.SetInteractionBtn0(0, false);
UM.SetInteractionBtn1(4, false);
UM.SetInteractionBtn2(7, false);
}
}
[PunRPC]
void ReportRPC(int actor, int targetDeadColorIndex)
{
// actor가 리포트함
ShowPanel(ReportPanel);
UM.ReportDeadBodyImage.color = UM.colors[targetDeadColorIndex];
StartCoroutine(ShowVotePanelCo(actor));
}
[PunRPC]
void EmergencyRPC(int actor)
{
// actor가 긴급소집함
ShowPanel(EmergencyPanel);
StartCoroutine(ShowVotePanelCo(actor));
}
IEnumerator ShowVotePanelCo(int callActor)
{
yield return new WaitForSeconds(4);
ShowPanel(VotePanel);
VM.VoteInit(callActor);
foreach (GameObject DeadBody in GameObject.FindGameObjectsWithTag("DeadBody"))
PhotonNetwork.Destroy(DeadBody);
}
[PunRPC]
void ShowGhostRPC()
{
for (int i = 0; i < Players.Count; i++)
{
if (!MyPlayer.isDie) continue;
if (Players[i].isDie)
{
Players[i].transform.GetChild(1).gameObject.SetActive(true);
Players[i].transform.GetChild(2).gameObject.SetActive(true);
}
}
}
public void WinCheck()
{
int crewCount = 0;
int impoCount = 0;
for (int i = 0; i < Players.Count; i++)
{
var Player = Players[i];
if (Players[i].isDie) continue;
if (Player.isImposter)
++impoCount;
else
++crewCount;
}
if (impoCount == 0 && crewCount > 0) // 모든 임포가 죽음
Winner(true);
else if(impoCount != 0 && impoCount > crewCount) // 임포가 크루보다 많음
Winner(false);
}
public void Winner(bool isCrewWin)
{
if (!isGameStart) return;
if (isCrewWin)
{
print("크루원 승리");
ShowPanel(CrewWinPanel);
Invoke("WinnerDelay", 3);
}
else
{
print("임포스터 승리");
ShowPanel(ImposterWinPanel);
Invoke("WinnerDelay", 3);
}
}
void WinnerDelay()
{
Application.Quit();
}
}
PlayerScript.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
using static NetworkManager;
using static UIManager;
public class PlayerScript : MonoBehaviourPunCallbacks
{
public Rigidbody2D RB;
public GameObject[] Anims;
public SpriteRenderer[] CharacterSR;
public Transform Character, Canvas, Ghost;
public Text NickText;
public enum State { Idle, Walk };
public State state;
public bool isWalk, isMove, isImposter, isKillable, isDie;
public int actor, colorIndex;
public float speed; //기본 40
public PlayerScript KillTargetPlayer;
public int targetDeadColorIndex;
[SerializeField] int _voteColorIndex; // 투표한 사람 색
public int VoteColorIndex { get => _voteColorIndex; set => PV.RPC("VoteColorIndexRPC", RpcTarget.AllBuffered, value); }
[PunRPC] void VoteColorIndexRPC(int value) { _voteColorIndex = value; }
public List<int> VotedColors = new List<int>();
[HideInInspector] public PhotonView PV;
[HideInInspector] public string nick;
Vector2 input;
void Start()
{
PV = photonView;
actor = PV.Owner.ActorNumber;
nick = PV.Owner.NickName;
SetNick();
NM.Players.Add(this);
NM.SortPlayers();
isMove = true;
StartCoroutine(StateCo());
}
IEnumerator StateCo()
{
while (true) yield return StartCoroutine(state.ToString());
}
void OnDestroy()
{
NM.Players.Remove(this);
NM.SortPlayers();
}
void SetNick()
{
NickText.text = PV.IsMine ? PhotonNetwork.NickName : PV.Owner.NickName;
}
void Update()
{
if (!PV.IsMine) return;
if (isMove)
{
input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized;
RB.velocity = input * speed;
isWalk = RB.velocity != Vector2.zero;
PV.RPC("AnimSprites", RpcTarget.All, isWalk, input);
}
if (NM.isGameStart)
{
Camera.main.transform.position = transform.position + new Vector3(0,0,-10);
}
NM.PointLight2D.transform.position = transform.position + new Vector3(0,0,10);
}
public void SetPos(Vector3 target)
{
transform.position = target;
}
[PunRPC]
void AnimSprites(bool _isWalk, Vector2 _input)
{
if (_isWalk)
{
state = State.Walk;
if (_input.x == 0) return;
if (_input.x < 0)
{
Character.localScale = Vector3.one;
if (Ghost.gameObject.activeInHierarchy) Ghost.localScale = Vector3.one;
}
else
{
Character.localScale = new Vector3(-1, 1, 1);
if (Ghost.gameObject.activeInHierarchy) Ghost.localScale = new Vector3(-1, 1, 1);
}
}
else
{
state = State.Idle;
}
}
void ShowAnim(int index)
{
for (int i = 0; i < Anims.Length; i++)
Anims[i].SetActive(index == i);
}
IEnumerator Idle()
{
ShowAnim(0);
yield return new WaitForSeconds(0.1f);
}
IEnumerator Walk()
{
ShowAnim(0);
yield return new WaitForSeconds(0.15f);
ShowAnim(1);
yield return new WaitForSeconds(0.15f);
}
[PunRPC]
public void SetColor(int _colorIndex)
{
CharacterSR[0].color = UM.colors[_colorIndex];
CharacterSR[1].color = UM.colors[_colorIndex];
colorIndex = _colorIndex;
}
[PunRPC]
void SetImpoCrew(bool _isImposter)
{
isImposter = _isImposter;
}
public void SetNickColor()
{
if (!isImposter) return;
for (int i = 0; i < NM.Players.Count; i++)
{
if (NM.Players[i].isImposter) NM.Players[i].NickText.color = Color.red;
}
}
public void SetMission()
{
if (!PV.IsMine) return;
if (isImposter) return;
List<int> GachaList = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (int i = 0; i < 4; i++)
{
int rand = Random.Range(0, GachaList.Count);
NM.Interactions[GachaList[rand]].SetActive(true);
UM.MissionMaps[GachaList[rand]].SetActive(true);
GachaList.RemoveAt(rand);
}
}
void OnCollisionEnter2D(Collision2D col)
{
if (!col.gameObject.CompareTag("Player")) return;
Physics2D.IgnoreCollision(GetComponent<CapsuleCollider2D>(), col.gameObject.GetComponent<CapsuleCollider2D>());
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.CompareTag("DeadBody") && PV.IsMine)
{
if (isDie) return;
UM.SetInteractionBtn1(4, true);
targetDeadColorIndex = col.GetComponent<DeadBodyScript>().colorIndex;
}
if (!col.CompareTag("Player") || !NM.isGameStart) return;
if (!PV.IsMine || !isImposter || !isKillable || col.GetComponent<PlayerScript>().isDie) return;
if (!col.GetComponent<PlayerScript>().isImposter)
{
UM.SetInteractionBtn0(5, true);
KillTargetPlayer = col.GetComponent<PlayerScript>();
}
}
void OnTriggerExit2D(Collider2D col)
{
if (col.CompareTag("DeadBody") && PV.IsMine)
{
if (isDie) return;
UM.SetInteractionBtn1(4, false);
targetDeadColorIndex = -1;
}
if (!col.CompareTag("Player") || !NM.isGameStart) return;
if (!PV.IsMine || !isImposter || !isKillable || col.GetComponent<PlayerScript>().isDie) return;
if (!col.GetComponent<PlayerScript>().isImposter)
{
UM.SetInteractionBtn0(5, false);
KillTargetPlayer = null;
}
}
public void Kill()
{
// 죽이기 성공
StartCoroutine(UM.KillCo());
KillTargetPlayer.GetComponent<PhotonView>().RPC("SetDie", RpcTarget.AllViaServer, true, colorIndex, KillTargetPlayer.colorIndex);
Vector3 TargetPos = KillTargetPlayer.transform.position;
transform.position = TargetPos;
GameObject CurDeadBody = PhotonNetwork.Instantiate("DeadBody", TargetPos, Quaternion.identity);
CurDeadBody.GetComponent<PhotonView>().RPC("SpawnBody", RpcTarget.AllViaServer, KillTargetPlayer.colorIndex, Random.Range(0, 2));
}
[PunRPC]
void SetDie(bool b, int _killerColorIndex, int _deadBodyColorIndex)
{
isDie = b;
transform.GetChild(0).gameObject.SetActive(false);
transform.GetChild(1).gameObject.SetActive(false);
if (PV.IsMine)
{
StartCoroutine(UM.DieCo(_killerColorIndex, _deadBodyColorIndex));
transform.GetChild(1).gameObject.SetActive(true);
transform.GetChild(2).gameObject.SetActive(true);
Physics2D.IgnoreLayerCollision(8, 9);
PV.RPC("SetGhostColor", RpcTarget.AllViaServer, colorIndex);
NM.GetComponent<PhotonView>().RPC("ShowGhostRPC", RpcTarget.AllViaServer);
}
}
[PunRPC]
void SetVotedDie()
{
isDie = true;
transform.GetChild(0).gameObject.SetActive(false);
transform.GetChild(1).gameObject.SetActive(false);
if (PV.IsMine)
{
transform.GetChild(1).gameObject.SetActive(true);
transform.GetChild(2).gameObject.SetActive(true);
Physics2D.IgnoreLayerCollision(8, 9);
PV.RPC("SetGhostColor", RpcTarget.AllViaServer, colorIndex);
NM.GetComponent<PhotonView>().RPC("ShowGhostRPC", RpcTarget.AllViaServer);
}
}
[PunRPC]
void SetGhostColor(int colorIndex)
{
Color color = UM.colors[colorIndex];
Ghost.GetChild(0).GetComponent<SpriteRenderer>().color = new Color(color.r, color.g, color.b, 0.6f);
}
public void VotedColorsAdd(int votedColorIndex)
{
VotedColors.Add(votedColorIndex);
}
[PunRPC]
void VotedColorsClearRPC()
{
VotedColors.Clear();
}
}
VoteManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
using static NetworkManager;
using static UIManager;
public class VoteManager : MonoBehaviourPun
{
public static VoteManager VM;
void Awake() => VM = this;
int callActor, voteTimer;
bool isAllVoted;
public void VoteInit(int _callActor)
{
// 변수 초기화
callActor = _callActor;
isAllVoted = false;
foreach (PlayerScript player in NM.Players)
player.VoteColorIndex = -2;
voteTimer = NM.VoteTimer;
SortPlayer();
ChatManager.CM.ChatClear();
StopCoroutine(VoteTimerCo());
StartCoroutine(VoteTimerCo());
// 패널 초기화
for (int i = 0; i < UM.VotePanels.Length; i++)
{
bool isExist = i < NM.Players.Count;
Transform CurVotePanel = UM.VotePanels[i];
CurVotePanel.gameObject.SetActive(isExist);
if (isExist)
{
PlayerScript CurPlayer = NM.Players[i];
CurVotePanel.GetChild(0).GetComponent<Image>().color = NM.GetColor(CurPlayer.colorIndex);
CurVotePanel.GetChild(1).gameObject.SetActive(CurPlayer.actor == callActor);
Text NickText = CurVotePanel.GetChild(3).GetComponent<Text>();
NickText.text = CurPlayer.nick;
NickText.color = NM.MyPlayer.isImposter && CurPlayer.isImposter ? Color.red : Color.white;
// 죽은 사람은 비활성화
CurVotePanel.GetComponent<CanvasGroup>().alpha = CurPlayer.isDie ? 0.7f : 1;
CurVotePanel.GetChild(5).GetComponent<Toggle>().interactable = !CurPlayer.isDie;
CurVotePanel.GetChild(6).gameObject.SetActive(CurPlayer.isDie);
// 투표받은 리스트 초기화
CurPlayer.PV.RPC("VotedColorsClearRPC", RpcTarget.All);
}
else
CurVotePanel.GetChild(1).gameObject.SetActive(false);
CurVotePanel.GetChild(4).gameObject.SetActive(false);
CurVotePanel.GetChild(5).GetComponent<Toggle>().isOn = false;
for (int j = 0; j < CurVotePanel.GetChild(7).childCount; j++)
Destroy(CurVotePanel.GetChild(7).GetChild(j).gameObject);
}
for (int i = 0; i < UM.SkipVoteResultGrid.transform.childCount; i++)
Destroy(UM.SkipVoteResultGrid.transform.GetChild(i).gameObject);
UM.SkipVoteToggle.isOn = false;
UM.CancelVoteToggle.isOn = true;
ActiveToggles(true);
// 자기가 죽은 상태이면 토글 모두 비활성화
if (NM.MyPlayer.isDie)
{
for (int i = 0; i < UM.VotePanels.Length; i++)
UM.VotePanels[i].GetChild(5).GetComponent<Toggle>().interactable = false;
UM.CancelVoteToggle.interactable = false;
UM.SkipVoteToggle.interactable = false;
}
}
public void ToggleChanged(int toggle)
{
if (toggle < 0) NM.MyPlayer.VoteColorIndex = toggle; // -1 투표 건너뛰기, -2 모두취소
else NM.MyPlayer.VoteColorIndex = NM.Players[toggle].colorIndex;
photonView.RPC("VoteUpdateRPC", RpcTarget.All);
}
void SortPlayer()
{
NM.Players.Sort((p1, p2) => p1.colorIndex.CompareTo(p2.colorIndex));
NM.Players.Sort((p1, p2) => p1.isDie.CompareTo(p2.isDie));
}
void ActiveToggles(bool b)
{
for (int i = 0; i < UM.VotePanels.Length; i++)
UM.VotePanels[i].GetChild(5).gameObject.SetActive(b);
UM.CancelVoteToggle.gameObject.SetActive(b);
UM.SkipVoteToggle.gameObject.SetActive(b);
}
[PunRPC]
void VoteUpdateRPC()
{
SortPlayer();
// 패널 업데이트
for (int i = 0; i < UM.VotePanels.Length; i++)
{
bool isExist = i < NM.Players.Count;
Transform CurVotePanel = UM.VotePanels[i];
CurVotePanel.gameObject.SetActive(isExist);
if (isExist)
{
PlayerScript CurPlayer = NM.Players[i];
CurVotePanel.GetChild(0).GetComponent<Image>().color = NM.GetColor(CurPlayer.colorIndex);
CurVotePanel.GetChild(1).gameObject.SetActive(CurPlayer.actor == callActor);
Text NickText = CurVotePanel.GetChild(3).GetComponent<Text>();
NickText.text = CurPlayer.nick;
NickText.color = NM.MyPlayer.isImposter && CurPlayer.isImposter ? Color.red : Color.white;
CurVotePanel.GetChild(4).gameObject.SetActive(CurPlayer.VoteColorIndex != -2);
}
else
{
CurVotePanel.GetChild(1).gameObject.SetActive(false);
CurVotePanel.GetChild(4).gameObject.SetActive(false);
}
}
AllVotedCheck();
}
void AllVotedCheck()
{
int aliveCount = 0;
int votedCount = 0;
for (int i = 0; i < NM.Players.Count; i++)
{
var CurPlayer = NM.Players[i];
if (!CurPlayer.isDie)
{
++aliveCount;
if (CurPlayer.VoteColorIndex != -2) ++votedCount;
}
}
if (!isAllVoted && aliveCount == votedCount)
{
isAllVoted = true;
ActiveToggles(false);
Invoke("ShowVoteResultIcon", 0.5f);
}
}
void ShowVoteResultIcon()
{
// 모든 클라이언트가 결과 실행
List<int> SkipVotedColors = new List<int>();
for (int i = 0; i < NM.Players.Count; i++)
{
var CurPlayer = NM.Players[i];
if (CurPlayer.isDie) continue;
if (CurPlayer.VoteColorIndex != -1) // CurPlayer가 사람을 투표했다면
NM.Players.Find(x => x.colorIndex == CurPlayer.VoteColorIndex)?.VotedColorsAdd(CurPlayer.colorIndex);
else // CurPlayer가 스킵했다면
SkipVotedColors.Add(CurPlayer.colorIndex);
}
// 투표 작은 아이콘 표시
for (int i = 0; i < NM.Players.Count; i++)
{
var CurPlayer = NM.Players[i];
if (CurPlayer.isDie) continue;
for (int j = 0; j < CurPlayer.VotedColors.Count; j++)
{
var VoteResultImage = Instantiate(UM.VoteResultImage, UM.VotePanels[i].GetChild(7));
VoteResultImage.GetComponent<Image>().color = NM.GetColor(CurPlayer.VotedColors[j]);
}
}
for (int i = 0; i < SkipVotedColors.Count; i++)
{
var VoteResultImage = Instantiate(UM.VoteResultImage, UM.SkipVoteResultGrid.transform);
VoteResultImage.GetComponent<Image>().color = NM.GetColor(SkipVotedColors[i]);
}
Invoke("VoteResultLogic", 3f);
}
void VoteResultLogic()
{
if (!PhotonNetwork.IsMasterClient) return;
int maxCount = 0;
for (int i = 0; i < NM.Players.Count; i++)
{
var CurPlayer = NM.Players[i];
if (CurPlayer.isDie) continue;
if (maxCount < CurPlayer.VotedColors.Count) maxCount = CurPlayer.VotedColors.Count;
}
int overlapCount = 0;
int maxColorIndex = -10;
for (int i = 0; i < NM.Players.Count; i++)
{
var CurPlayer = NM.Players[i];
if (CurPlayer.isDie) continue;
if (maxCount == CurPlayer.VotedColors.Count)
{
++overlapCount;
maxColorIndex = CurPlayer.colorIndex;
}
}
bool isKick = maxCount > 0 && overlapCount == 1;
photonView.RPC("ReturnToGameRPC", RpcTarget.AllViaServer, isKick, maxColorIndex);
}
[PunRPC]
void ReturnToGameRPC(bool isKick, int maxColorIndex)
{
StartCoroutine(ReturnToGameCo(isKick, maxColorIndex));
}
IEnumerator ReturnToGameCo(bool isKick, int maxColorIndex)
{
if (!NM.VotePanel.activeInHierarchy) yield break;
if (isKick) // maxColorIndex가 퇴출당한다
{
NM.ShowPanel(NM.KickPanel);
var KickPanel = NM.KickPanel.transform;
KickPanel.GetChild(0).GetComponent<Image>().color = NM.GetColor(maxColorIndex);
var targetPlayer = NM.Players.Find(x => x.colorIndex == maxColorIndex);
string impoCrew = targetPlayer.isImposter ? "<color=red>임포스터</color>였" : "크루원이었";
KickPanel.GetChild(2).GetComponent<Text>().text = $"{targetPlayer.nick}은 {impoCrew}습니다";
targetPlayer.PV.RPC("SetVotedDie", RpcTarget.All);
}
else // 아무도 퇴출당하지 않음
NM.ShowPanel(NM.NoOneKickPanel);
NM.MyPlayer.SetPos(NM.SpawnPoint.position);
ChatManager.CM.photonView.RPC("ChatClearRPC", RpcTarget.AllViaServer, false);
yield return new WaitForSeconds(3);
NM.ShowPanel(NM.GamePanel);
NM.WinCheck();
StartCoroutine(UM.KillCo());
StartCoroutine(UM.EnergencyCo());
StopCoroutine(VoteTimerCo());
}
IEnumerator VoteTimerCo()
{
int _voteTimer = voteTimer;
for (int i = _voteTimer; i >= 0; i--)
{
voteTimer = i;
UM.VoteTimerText.text = $"{voteTimer}초후 투표 건너뛰기";
yield return new WaitForSeconds(1);
}
// 투표 안한 사람은 강제로 -1선택
if (NM.MyPlayer.VoteColorIndex == -2)
{
UM.SkipVoteToggle.isOn = true;
}
}
}
ChatManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using static UIManager;
using static NetworkManager;
using UnityEngine.UI;
public class ChatManager : MonoBehaviourPun
{
public static ChatManager CM;
void Awake() => CM = this;
public void ChatClear()
{
photonView.RPC("ChatClearRPC", RpcTarget.AllViaServer, true);
}
public void OnEndEdit()
{
if (Input.GetKeyDown(KeyCode.Return) && !string.IsNullOrWhiteSpace(UM.ChatInput.text))
{
string chat = $"{NM.MyPlayer.nick} : {UM.ChatInput.text}";
UM.ChatInput.text = "";
photonView.RPC("ChatRPC", RpcTarget.All, chat);
}
}
[PunRPC]
void ChatRPC(string chat)
{
UM.ChatText.text += (UM.ChatText.text == "" ? "" : "\n") + chat;
Fit(UM.ChatText.GetComponent<RectTransform>());
Fit(UM.ChatContent);
UM.ChatScroll.value = 0;
}
void Update()
{
if (!PhotonNetwork.InRoom) return;
ChatEnable();
}
void ChatEnable()
{
// 엔터시 채팅 활성화
if (Input.GetKeyDown(KeyCode.Return))
{
UM.ChatInput.ActivateInputField();
UM.ChatInput.Select();
}
}
[PunRPC]
void ChatClearRPC(bool active)
{
UM.ChatText.text = "";
UM.ChatPanels[0].SetActive(active);
UM.ChatPanels[1].SetActive(false);
}
void Fit(RectTransform rect) => LayoutRebuilder.ForceRebuildLayoutImmediate(rect);
}
InteractionScript.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using static UIManager;
using static NetworkManager;
public class InteractionScript : MonoBehaviourPun
{
public enum Type { Customize, Mission, Emergency };
public Type type;
GameObject Line;
public int curInteractionNum;
void Start()
{
Line = transform.GetChild(0).gameObject;
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.CompareTag("Player") && col.GetComponent<PhotonView>().IsMine)
{
if (type == Type.Customize)
{
Line.SetActive(true);
UM.SetInteractionBtn0(1, true);
}
else if (type == Type.Mission)
{
if (col.GetComponent<PlayerScript>().isImposter) return;
UM.curInteractionNum = curInteractionNum;
Line.SetActive(true);
UM.SetInteractionBtn0(0, true);
}
else if (type == Type.Emergency)
{
if (col.GetComponent<PlayerScript>().isDie) return;
Line.SetActive(true);
bool isEmergency = UM.emergencyCooltime == 0;
UM.SetInteractionBtn0(8, isEmergency);
}
}
}
void OnTriggerExit2D(Collider2D col)
{
if (col.CompareTag("Player") && col.GetComponent<PhotonView>().IsMine)
{
if (type == Type.Customize)
{
Line.SetActive(false);
UM.SetInteractionBtn0(0, false);
}
else if (type == Type.Mission)
{
if (col.GetComponent<PlayerScript>().isImposter) return;
Line.SetActive(false);
UM.SetInteractionBtn0(0, false);
}
else if (type == Type.Emergency)
{
Line.SetActive(false);
if(NM.MyPlayer.isImposter) UM.SetInteractionBtn0(5, false);
else UM.SetInteractionBtn0(0, false);
}
}
}
}
UIManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
using static NetworkManager;
public class UIManager : MonoBehaviourPun
{
public static UIManager UM;
void Awake() => UM = this;
// 0 : use, 1 customize, 2 cancel, 3 start, 4 report, 5 kill, 6 sabotage, 7 null, 8 emergency
public Sprite[] sprites;
int curBtn0, curBtn1, curBtn2;
bool active0, active1, active2;
public Image WaitingInteractionBtn0, InteractionBtn0, InteractionBtn1, InteractionBtn2;
public Text Interaction0Text;
public Image PreviewImage;
public Color[] colors;
public GameObject CustomizePanel, DiePanel;
public GameObject[] ColorCancel;
public Button[] ColorBtn;
public Button StartBtn;
public Transform LeftBottom, RightTop, LeftBottomMap, RightTopMap, PlayerMap;
public GameObject[] MissionMaps;
public Image KillerImage, DeadbodyImage;
public Text LogText;
public GameObject[] Minigames;
public GameObject MissionClearText;
public int curInteractionNum;
public Slider MissionGageSlider;
public Transform[] VotePanels;
public GameObject SabotagePanel;
public Button[] DoorMaps;
public Image ReportDeadBodyImage;
public Toggle[] VoteToggles;
public Toggle SkipVoteToggle, CancelVoteToggle;
public GameObject SkipVoteResultGrid;
public Image KickPanelImage;
public Text KickPanelText;
PhotonView PV;
public GameObject VoteResultImage;
public InputField ChatInput;
public Text ChatText;
public Scrollbar ChatScroll;
public RectTransform ChatContent;
public GameObject[] ChatPanels;
public int killCooltime, emergencyCooltime;
public Text VoteTimerText;
void Start()
{
PV = photonView;
}
public void SetInteractionBtn0(int index, bool _active)
{
curBtn0 = index;
active0 = _active;
// 대기실
if (!NM.isGameStart)
{
WaitingInteractionBtn0.sprite = sprites[index];
WaitingInteractionBtn0.GetComponent<Button>().interactable = active0;
}
else
{
InteractionBtn0.sprite = sprites[index];
InteractionBtn0.GetComponent<Button>().interactable = active0;
}
}
public void SetInteractionBtn1(int index, bool _active)
{
curBtn1 = index;
active1 = _active;
InteractionBtn1.sprite = sprites[index];
InteractionBtn1.GetComponent<Button>().interactable = active1;
}
public void SetInteractionBtn2(int index, bool _active)
{
curBtn2 = index;
active2 = _active;
InteractionBtn2.sprite = sprites[index];
InteractionBtn2.GetComponent<Button>().interactable = active2;
}
public void ColorChange(int _colorIndex)
{
PreviewImage.color = colors[_colorIndex];
NM.MyPlayer.GetComponent<PhotonView>().RPC("SetColor", RpcTarget.AllBuffered, _colorIndex);
}
public void ClickInteractionBtn0()
{
// 커스터마이즈
if (curBtn0 == 1)
{
CustomizePanel.SetActive(true);
SetIsCustomize(false);
PreviewImage.color = colors[NM.MyPlayer.colorIndex];
}
// 킬
else if (curBtn0 == 5)
{
if (NM.MyPlayer.isDie) return;
NM.MyPlayer.Kill();
}
// 사용
else if (curBtn0 == 0)
{
// 크루원 작업
GameObject CurMinigame = Minigames[Random.Range(0, Minigames.Length)];
CurMinigame.GetComponent<MinigameManager>().StartMission();
}
// 이머전시
else if (curBtn0 == 8)
{
if (NM.MyPlayer.isDie) return;
NM.GetComponent<PhotonView>().RPC("EmergencyRPC", RpcTarget.AllViaServer, NM.MyPlayer.actor);
}
}
public void ClickInteractionBtn1()
{
// 리포트
if (curBtn1 == 4)
{
if (NM.MyPlayer.isDie) return;
NM.GetComponent<PhotonView>().RPC("ReportRPC", RpcTarget.AllViaServer, NM.MyPlayer.actor, NM.MyPlayer.targetDeadColorIndex);
}
}
public void ClickInteractionBtn2()
{
// 사보타지
if (curBtn2 == 6)
{
SabotagePanel.SetActive(true);
}
}
public void SetIsCustomize(bool b)
{
NM.MyPlayer.isMove = b;
}
void Update()
{
if (!PhotonNetwork.InRoom) return;
SetActiveColors();
SetMap();
if (!PhotonNetwork.IsMasterClient) return;
ShowStartBtn();
}
void ShowStartBtn()
{
StartBtn.gameObject.SetActive(true);
//StartBtn.interactable = PhotonNetwork.CurrentRoom.PlayerCount >= 7; // 기본값
StartBtn.interactable = PhotonNetwork.CurrentRoom.PlayerCount >= 1; // 2
}
public void SetActiveColors()
{
List<int> colorList = new List<int>();
for (int i = 0; i < NM.Players.Count; i++)
colorList.Add(NM.Players[i].colorIndex);
for (int i = 0; i < ColorCancel.Length; i++)
{
bool contain = colorList.Contains(i + 1);
ColorCancel[i].SetActive(contain);
ColorBtn[i].interactable = !contain;
}
}
public void SetMap()
{
// 실제 맵
float width = RightTop.position.x - LeftBottom.position.x;
float height = RightTop.position.y - LeftBottom.position.y;
Vector3 MyPlayerPos = NM.MyPlayer.transform.position;
float playerWidth = MyPlayerPos.x - LeftBottom.position.x;
float playerHeight = MyPlayerPos.y - LeftBottom.position.y;
// 지도
float widthMap = RightTopMap.position.x - LeftBottomMap.position.x;
float heightMap = RightTopMap.position.y - LeftBottomMap.position.y;
float playerMapX = LeftBottomMap.position.x + (playerWidth / width) * widthMap;
float playerMapY = LeftBottomMap.position.y + (playerHeight / height) * heightMap;
PlayerMap.position = new Vector3(playerMapX, playerMapY, 0);
}
public IEnumerator KillCo()
{
if (!NM.MyPlayer.isImposter) yield break;
SetInteractionBtn0(5, false);
NM.MyPlayer.isKillable = false;
for (int i = 20; i > 0; i--) // 기본 15초 킬대기
//for (int i = 5; i > 0; i--)
{
killCooltime = i;
if (UM.curBtn0 == 5)
Interaction0Text.text = killCooltime.ToString();
else
Interaction0Text.text = "";
yield return new WaitForSeconds(1);
}
killCooltime = 0;
Interaction0Text.text = "";
NM.MyPlayer.isKillable = true;
}
public IEnumerator EnergencyCo()
{
for (int i = 20; i > 0; i--)
{
emergencyCooltime = i;
if (UM.curBtn0 == 8)
Interaction0Text.text = emergencyCooltime.ToString();
else
Interaction0Text.text = "";
yield return new WaitForSeconds(1);
}
emergencyCooltime = 0;
Interaction0Text.text = "";
}
public IEnumerator DieCo(int killerColorIndex, int deadBodyColorIndex)
{
DiePanel.SetActive(true);
KillerImage.color = UM.colors[killerColorIndex];
DeadbodyImage.color = UM.colors[deadBodyColorIndex];
yield return new WaitForSeconds(4);
DiePanel.SetActive(false);
}
public void ShowLog(string log)
{
LogText.text = log;
}
[PunRPC]
public void SetMaxMissionGage()
{
MissionGageSlider.maxValue = NM.GetCrewCount();
}
[PunRPC]
public void AddMissionGage()
{
MissionGageSlider.value += 0.25f;
if (MissionGageSlider.value == MissionGageSlider.maxValue)
{
// 크루원 승리
NM.Winner(true);
}
}
public IEnumerator MissionClearCo(GameObject MissionPanel)
{
MissionPanel.SetActive(false);
MissionClearText.SetActive(true);
yield return new WaitForSeconds(2);
MissionClearText.SetActive(false);
}
public void MissionClear(GameObject MissionPanel)
{
StartCoroutine(MissionClearCo(MissionPanel));
PV.RPC("AddMissionGage", RpcTarget.AllViaServer);
}
public void DoorMapClick(int doorIndex)
{
PV.RPC("DoorMapClickRPC", RpcTarget.AllViaServer, doorIndex);
}
[PunRPC]
void DoorMapClickRPC(int doorIndex)
{
StartCoroutine(DoorCo(doorIndex));
StartCoroutine(DoorCoolCo(doorIndex));
}
IEnumerator DoorCo(int doorIndex)
{
NM.Doors[doorIndex].SetActive(true);
yield return new WaitForSeconds(7);
NM.Doors[doorIndex].SetActive(false);
}
IEnumerator DoorCoolCo(int doorIndex)
{
if (!NM.MyPlayer.isImposter) yield break;
DoorMaps[doorIndex].interactable = false;
yield return new WaitForSeconds(18);
DoorMaps[doorIndex].interactable = true;
}
}
DeadBodyScript.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using static UIManager;
using static NetworkManager;
public class DeadBodyScript : MonoBehaviourPun
{
public Transform[] Bodys;
Transform CurBody;
public int colorIndex;
[PunRPC]
public void SpawnBody(int _colorIndex, int randBody)
{
CurBody = Bodys[randBody];
CurBody.gameObject.SetActive(true);
CurBody.GetChild(1).GetComponent<SpriteRenderer>().color = UM.colors[_colorIndex];
gameObject.tag = "DeadBody";
colorIndex = _colorIndex;
NM.WinCheck();
}
}
MinigameManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UIManager;
using static NetworkManager;
public class MinigameManager : MonoBehaviour, IMinigame
{
public int curRemainMission;
int remainMission;
public void StartMission()
{
remainMission = curRemainMission;
gameObject.SetActive(true);
}
public void CancelMission()
{
gameObject.SetActive(false);
}
public void CompleteMission()
{
if (--remainMission <= 0)
{
UM.MissionClear(gameObject);
NM.Interactions[UM.curInteractionNum].SetActive(false);
UM.MissionMaps[UM.curInteractionNum].SetActive(false);
}
}
}
IMinigame.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
interface IMinigame
{
void StartMission();
void CancelMission();
void CompleteMission();
}
Mini0Target.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class Mini0Target : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
RectTransform RT;
MinigameManager MM;
Vector2 offset;
bool isClear;
void Awake()
{
RT = GetComponent<RectTransform>();
MM = transform.GetComponentInParent<MinigameManager>();
}
void OnEnable()
{
RT.anchoredPosition = new Vector2(Random.Range(-300, 300), Random.Range(-300, 300));
isClear = false;
}
Vector2 GetMousePos()
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
return new Vector2(mousePos.x, mousePos.y);
}
public void OnPointerDown(PointerEventData data)
{
offset = GetMousePos() - new Vector2(RT.transform.position.x, RT.transform.position.y);
}
public void OnDrag(PointerEventData data)
{
RT.transform.position = GetMousePos() - offset;
}
public void OnPointerUp(PointerEventData data)
{
if (RT.anchoredPosition.x > -20 && RT.anchoredPosition.x < 20
&& RT.anchoredPosition.y > -20 && RT.anchoredPosition.y < 20)
{
if (!isClear) MissionClear();
}
}
void MissionClear()
{
isClear = true;
RT.anchoredPosition = Vector2.zero;
MM.CompleteMission();
}
}
Mini1Target.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Mini1Target : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
RectTransform RT;
MinigameManager MM;
Vector2 offset, firstPos;
bool isClear;
void Awake()
{
RT = GetComponent<RectTransform>();
MM = transform.GetComponentInParent<MinigameManager>();
}
void OnEnable()
{
isClear = false;
firstPos = new Vector2(-139, -272);
RT.anchoredPosition = firstPos;
}
Vector2 GetMousePos()
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
return new Vector2(mousePos.x, mousePos.y);
}
public void OnPointerDown(PointerEventData data)
{
offset = GetMousePos() - new Vector2(RT.transform.position.x, RT.transform.position.y);
}
public void OnDrag(PointerEventData data)
{
RT.transform.position = GetMousePos() - offset;
}
public void OnPointerUp(PointerEventData data)
{
if (RT.anchoredPosition.x > -90 && RT.anchoredPosition.x < 90
&& RT.anchoredPosition.y > 170 && RT.anchoredPosition.y < 260)
{
if (!isClear) MissionClear();
}
else RT.anchoredPosition = firstPos;
}
void MissionClear()
{
isClear = true;
RT.anchoredPosition = new Vector2(0, 215);
MM.CompleteMission();
}
}
Mini2Target.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Mini2Target : MonoBehaviour, IPointerUpHandler
{
MinigameManager MM;
bool isClear;
Slider slider;
void Awake()
{
MM = transform.GetComponentInParent<MinigameManager>();
slider = GetComponent<Slider>();
}
void OnEnable()
{
isClear = false;
slider.value = 0;
slider.interactable = true;
}
public void MissionClear(float f)
{
if (isClear) return;
if (f > 0.9f)
{
isClear = true;
slider.interactable = false;
slider.value = 1;
MM.CompleteMission();
}
}
public void OnPointerUp(PointerEventData eventData)
{
if (isClear) return;
slider.value = 0;
}
}