어몽어스 만들기 [켠김에 완성까지]

 




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;
}
}