멀티플레이 제작 끝
This commit is contained in:
@@ -1,29 +1,42 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class NewMonoBehaviourScript : MonoBehaviour
|
||||
public class Alignment : MonoBehaviour
|
||||
{
|
||||
|
||||
private GridLayoutGroup _gridLayoutGroup;
|
||||
private RectTransform _rectTransform;
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
|
||||
void Start()
|
||||
{
|
||||
_gridLayoutGroup = GetComponent<GridLayoutGroup>();
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
var max =
|
||||
(_rectTransform.rect.width / (_gridLayoutGroup.cellSize.x+_gridLayoutGroup.spacing.x)) *
|
||||
(_rectTransform.rect.height / _gridLayoutGroup.cellSize.y+_gridLayoutGroup.spacing.y);
|
||||
var current = transform.childCount;
|
||||
// 아바타가 하나도 없으면 계산할 필요 없음
|
||||
if (transform.childCount == 0) return;
|
||||
|
||||
// 1. 가로, 세로에 각각 '온전히' 몇 개가 들어갈 수 있는지 계산 (내림 처리)
|
||||
// (괄호 위치를 정확히 맞추었습니다!)
|
||||
float cols = Mathf.Floor(_rectTransform.rect.width / (_gridLayoutGroup.cellSize.x + _gridLayoutGroup.spacing.x));
|
||||
float rows = Mathf.Floor(_rectTransform.rect.height / (_gridLayoutGroup.cellSize.y + _gridLayoutGroup.spacing.y));
|
||||
|
||||
// 최소 1줄은 보장되도록 안전장치
|
||||
if (cols < 1) cols = 1;
|
||||
if (rows < 1) rows = 1;
|
||||
|
||||
// 2. 현재 화면에 들어갈 수 있는 최대 아바타 개수
|
||||
float max = cols * rows;
|
||||
float current = transform.childCount;
|
||||
|
||||
// 3. 꽉 차기 직전(80% 이상)이면 셀 사이즈를 10%씩 축소
|
||||
if (current / max >= 0.8f)
|
||||
{
|
||||
_gridLayoutGroup.cellSize = new Vector2(_gridLayoutGroup.cellSize.x * 0.9f, _gridLayoutGroup.cellSize.y * 0.9f);
|
||||
_gridLayoutGroup.cellSize = new Vector2(
|
||||
_gridLayoutGroup.cellSize.x * 0.9f,
|
||||
_gridLayoutGroup.cellSize.y * 0.9f
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,22 @@ using TMPro;
|
||||
using UnityEngine;
|
||||
using Photon.Pun;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
public class GameManager : MonoBehaviourPunCallbacks
|
||||
{
|
||||
public TextMeshProUGUI text;
|
||||
public GameObject readyPanel;
|
||||
public GameObject restartButton;
|
||||
public GameObject destroyRoomButton;
|
||||
|
||||
public static Dictionary<int, Sprite> AvatarCache = new Dictionary<int, Sprite>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (restartButton != null) restartButton.SetActive(false);
|
||||
if (destroyRoomButton != null) destroyRoomButton.SetActive(false); // [추가]
|
||||
// 씬 시작 시, 모두의 아바타를 다운로드하는 코루틴 시작
|
||||
StartCoroutine(DownloadAllAvatarsAndReady());
|
||||
}
|
||||
@@ -191,19 +196,68 @@ public class GameManager : MonoBehaviourPunCallbacks
|
||||
if (winnerActorNumber == -1)
|
||||
{
|
||||
text.text = "아무도 참여하지 않았습니다.";
|
||||
ShowRestartButtonIfMaster(); // 아무도 안 했을 때도 다시하기 버튼 띄우기
|
||||
return;
|
||||
}
|
||||
|
||||
// 우승자 닉네임 찾기
|
||||
string winnerName = "알 수 없음";
|
||||
foreach (var p in PhotonNetwork.PlayerList)
|
||||
foreach (var player in PhotonNetwork.PlayerList)
|
||||
{
|
||||
if (p.ActorNumber == winnerActorNumber) winnerName = p.NickName;
|
||||
if (player.ActorNumber == winnerActorNumber)
|
||||
{
|
||||
winnerName = player.NickName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
text.text = $"우승: {winnerName}!";
|
||||
// 텍스트를 바로 바꾸지 않고 "추첨 중"으로 변경! (스포일러 방지)
|
||||
text.text = "추첨 중...";
|
||||
|
||||
// 화면에서 우승자의 아바타만 빼고 전부 어둡게/투명하게 만드는 연출 실행!
|
||||
FindObjectOfType<Gazuaa>().HighlightWinner(winnerActorNumber);
|
||||
// Gazuaa 스크립트를 찾아서 룰렛 연출 시작 (이름도 같이 넘겨줍니다)
|
||||
FindObjectOfType<Gazuaa>().HighlightWinner(winnerActorNumber, winnerName);
|
||||
}
|
||||
|
||||
// Gazuaa.cs의 코루틴이 끝난 후(룰렛이 멈춘 후) 호출할 함수
|
||||
public void ShowRestartButtonIfMaster()
|
||||
{
|
||||
// 내가 방장일 때만 버튼을 활성화합니다.
|
||||
if (PhotonNetwork.IsMasterClient )
|
||||
{
|
||||
if (restartButton != null) restartButton.SetActive(true);
|
||||
if (destroyRoomButton != null) destroyRoomButton.SetActive(true); // [추가]
|
||||
}
|
||||
|
||||
|
||||
} // '다시 하기' 버튼의 OnClick() 이벤트에 연결할 함수
|
||||
|
||||
public void ClickRestart()
|
||||
{
|
||||
// 방장이 씬을 다시 로드합니다. (모두가 자동으로 따라옵니다!)
|
||||
// "GameScene" 부분은 실제 사용 중인 게임 씬의 이름으로 적어주세요.
|
||||
PhotonNetwork.LoadLevel("GameScene");
|
||||
}
|
||||
|
||||
// [추가] '방 삭제' 버튼의 OnClick() 이벤트에 연결할 함수
|
||||
public void ClickDestroyRoom()
|
||||
{
|
||||
// 방장이 모든 클라이언트에게 "방에서 나가!" 라고 명령을 내립니다.
|
||||
photonView.RPC("RpcLeaveRoom", RpcTarget.All);
|
||||
}
|
||||
|
||||
// [추가] 모두의 컴퓨터에서 실행될 방 나가기 함수
|
||||
[PunRPC]
|
||||
private void RpcLeaveRoom()
|
||||
{
|
||||
// 포톤 서버에 이 방에서 나가겠다고 요청합니다.
|
||||
PhotonNetwork.LeaveRoom();
|
||||
}
|
||||
|
||||
// [추가] 방에서 완전히 빠져나왔을 때 포톤이 자동으로 호출해 주는 함수
|
||||
public override void OnLeftRoom()
|
||||
{
|
||||
// 방에서 성공적으로 나갔다면, 최초 로비 화면으로 씬을 이동시킵니다.
|
||||
// "LobbyScene" 부분을 실제 로비 씬의 이름으로 꼭 바꿔주세요!!!
|
||||
SceneManager.LoadScene("LobbyScene");
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,20 @@ public class Gazuaa : MonoBehaviourPunCallbacks
|
||||
{
|
||||
_gazuaInput = new GazuaInput();
|
||||
_gazuaInput.Enable();
|
||||
|
||||
_gazuaInput.Player.Gacha.performed += Gacha;
|
||||
|
||||
if (logText != null) logText.text = ""; // 시작할 때 텍스트 비우기
|
||||
if (logText != null) logText.text = "";
|
||||
|
||||
// ---- [추가된 부분: 재시작 시 데이터 초기화] ----
|
||||
myAvatarCount = 0;
|
||||
knownAvatarCounts.Clear(); // 딕셔너리 비우기
|
||||
isGameActive = false; // 게임 상태 초기화
|
||||
|
||||
// 서버에 기록된 내 아바타 개수도 0으로 덮어씌우기
|
||||
Hashtable hash = new Hashtable();
|
||||
hash["AvatarCount"] = 0;
|
||||
PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
|
||||
// ---------------------------------------------
|
||||
}
|
||||
|
||||
private void Gacha(InputAction.CallbackContext obj)
|
||||
@@ -48,10 +58,11 @@ public class Gazuaa : MonoBehaviourPunCallbacks
|
||||
// 서버가 "누군가의 정보가 바뀌었어!" 라고 알려줄 때 실행됨 (나의 변경 사항도 포함됨)
|
||||
public override void OnPlayerPropertiesUpdate(Photon.Realtime.Player targetPlayer, Hashtable changedProps)
|
||||
{
|
||||
if (!this || !map) return;
|
||||
if (changedProps.ContainsKey("AvatarCount"))
|
||||
{
|
||||
int newTotalCount = (int)changedProps["AvatarCount"];
|
||||
int actorNumber = targetPlayer.ActorNumber;
|
||||
int actorNumber = targetPlayer.ActorNumber;
|
||||
string playerName = targetPlayer.NickName; // [핵심] 아까 로비에서 적은 닉네임 가져오기!
|
||||
|
||||
int oldCount = 0;
|
||||
@@ -71,9 +82,9 @@ public class Gazuaa : MonoBehaviourPunCallbacks
|
||||
for (int i = 0; i < avatarsToSpawn; i++)
|
||||
{
|
||||
GameObject newAvatar = Instantiate(avatarPrefab, map.transform);
|
||||
newAvatar.name = actorNumber.ToString();
|
||||
newAvatar.name = actorNumber.ToString();
|
||||
|
||||
if (GameManager.AvatarCache.ContainsKey(actorNumber))
|
||||
if (GameManager.AvatarCache.ContainsKey(actorNumber))
|
||||
{
|
||||
Image avatarImage = newAvatar.GetComponent<Image>();
|
||||
if (avatarImage != null)
|
||||
@@ -104,33 +115,83 @@ public class Gazuaa : MonoBehaviourPunCallbacks
|
||||
logText.text = string.Join("\n", logMessages);
|
||||
}
|
||||
|
||||
public void HighlightWinner(int winnerActorNumber)
|
||||
// GameManager에서 호출할 함수 (이름도 같이 받도록 매개변수 추가)
|
||||
public void HighlightWinner(int winnerActorNumber, string winnerName)
|
||||
{
|
||||
StartCoroutine(RouletteRoutine(winnerActorNumber, winnerName));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator RouletteRoutine(int winnerActorNumber, string winnerName)
|
||||
{
|
||||
string winnerNameStr = winnerActorNumber.ToString();
|
||||
|
||||
// Map 밑에 생성된 모든 아바타를 순회합니다.
|
||||
foreach (Transform child in map.transform)
|
||||
{
|
||||
Image img = child.GetComponent<Image>();
|
||||
if (img == null) continue;
|
||||
List<Transform> allAvatars = new List<Transform>();
|
||||
List<Transform> winnerAvatars = new List<Transform>(); // [수정] 당첨자의 '모든' 아바타를 담을 리스트
|
||||
|
||||
for (int i = 0; i < map.transform.childCount; i++)
|
||||
{
|
||||
Transform child = map.transform.GetChild(i);
|
||||
allAvatars.Add(child);
|
||||
|
||||
// 모든 아바타를 어둡게 만듭니다.
|
||||
child.GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 1f);
|
||||
|
||||
// 당첨자의 아바타라면 리스트에 싹 다 모아둡니다.
|
||||
if (child.name == winnerNameStr)
|
||||
{
|
||||
// 당첨자의 아바타: 원래 색상 유지 및 살짝 크게 만들기
|
||||
child.localScale = new Vector3(1.2f, 1.2f, 1.2f);
|
||||
|
||||
// (응용: 당첨자 아바타들 중 하나만 랜덤으로 골라 파티클을 터뜨려도 멋집니다!)
|
||||
}
|
||||
else
|
||||
{
|
||||
// 패배자의 아바타: 까맣게 만들고 반투명하게(Alpha 값 조절)
|
||||
img.color = new Color(0.3f, 0.3f, 0.3f, 0.5f);
|
||||
winnerAvatars.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
// 아무도 없거나 에러 방지용
|
||||
if (allAvatars.Count == 0 || winnerAvatars.Count == 0) yield break;
|
||||
|
||||
// 2. 시간 기반 룰렛 회전 설정
|
||||
float delay = 0.04f; // 초기 속도
|
||||
int currentIndex = 0;
|
||||
float elapsedTime = 0f; // 흐른 시간
|
||||
float spinDuration = 3.5f; // ★ [핵심] 정확히 3.5초 동안만 회전합니다!
|
||||
|
||||
while (elapsedTime < spinDuration)
|
||||
{
|
||||
// 이전 아바타 원상복구
|
||||
int prevIndex = (currentIndex == 0) ? allAvatars.Count - 1 : currentIndex - 1;
|
||||
allAvatars[prevIndex].localScale = Vector3.one;
|
||||
allAvatars[prevIndex].GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 1f);
|
||||
|
||||
// 현재 아바타 하이라이트
|
||||
allAvatars[currentIndex].localScale = new Vector3(1.2f, 1.2f, 1.2f);
|
||||
allAvatars[currentIndex].GetComponent<Image>().color = Color.white;
|
||||
|
||||
yield return new WaitForSeconds(delay);
|
||||
elapsedTime += delay; // 흐른 시간 누적
|
||||
|
||||
// 시간이 지날수록 속도를 살짝 늦춥니다.
|
||||
if (elapsedTime > spinDuration * 0.5f) delay = 0.08f; // 절반 지났을 때
|
||||
if (elapsedTime > spinDuration * 0.8f) delay = 0.15f; // 거의 끝날 때쯤
|
||||
|
||||
// 다음 아바타로 이동
|
||||
currentIndex = (currentIndex + 1) % allAvatars.Count;
|
||||
}
|
||||
|
||||
// 회전이 끝났으므로 마지막으로 켜져 있던 아바타 끄기
|
||||
allAvatars[currentIndex].localScale = Vector3.one;
|
||||
allAvatars[currentIndex].GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 1f);
|
||||
|
||||
// 3. ★ 정해진 시간이 끝나면, 당첨자의 '모든' 아바타를 동시에 하이라이트!
|
||||
foreach (Transform winner in winnerAvatars)
|
||||
{
|
||||
winner.localScale = new Vector3(1.5f, 1.5f, 1.5f);
|
||||
winner.GetComponent<Image>().color = Color.white;
|
||||
}
|
||||
|
||||
FindObjectOfType<GameManager>().text.text = $"{winnerName} 당첨!!!";
|
||||
FindObjectOfType<GameManager>().ShowRestartButtonIfMaster();
|
||||
}
|
||||
|
||||
private new void OnDisable()
|
||||
public override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
_gazuaInput.Disable();
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,22 @@ public class LobbyManager : MonoBehaviourPunCallbacks
|
||||
public Button joinButton; // 접속 버튼
|
||||
public Button startButton; // 게임 시작 버튼 (방장 전용)
|
||||
public TextMeshProUGUI statusText; // 현재 상태 텍스트
|
||||
public TMP_InputField nicknameInput; // [추가] 닉네임 입력칸
|
||||
public TMP_InputField urlInput;
|
||||
public TMP_InputField nicknameInput; // 닉네임 입력칸
|
||||
public TMP_InputField urlInput; // 이미지 URL 입력칸
|
||||
public GameObject loading;
|
||||
public Button afterLoadButton;
|
||||
public GameObject roomPanel;
|
||||
|
||||
// [1번 기능] 현재 접속자 수를 띄울 텍스트 (기존에 선언해두신 변수 활용)
|
||||
public TextMeshProUGUI currentPlayerText;
|
||||
|
||||
// [2번 기능] 기본 아바타 URL 리스트 (유니티 인스펙터에서도 개수/내용 수정 가능!)
|
||||
public string[] defaultAvatarUrls = new string[]
|
||||
{
|
||||
"https://scontent-icn2-1.cdninstagram.com/v/t51.82787-19/657402696_17932715739227818_1102219035706293785_n.jpg?stp=dst-jpg_s150x150_tt6&efg=eyJ2ZW5jb2RlX3RhZyI6InByb2ZpbGVfcGljLmRqYW5nby4yMDguYzEifQ&_nc_ht=scontent-icn2-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2gEiAmiFEpKqA0_AvOwYhu_V9dtAieTJ6EHLqVEzLZmvyXvRk0RZ7G3n6BO9gVZEKpE&_nc_ohc=X-ML0v44oVcQ7kNvwGmKPj8&_nc_gid=i-J7dc5bYegcPnPIkL9scQ&edm=AP4sbd4BAAAA&ccb=7-5&oh=00_Af7RgrBAKQfl7RpZSpdbPpPbKvVYuz5RYk93sONhI1Yrrw&oe=69F9A119&_nc_sid=7a9f4b", // 여기에 미리 쓸 URL들을 넣어주세요.
|
||||
"https://i.namu.wiki/i/0uubPPIF2f9mKDb2fD19gMa77g2rUpoDnQ5Ekb9iqSNea2sfq9u9eWmRUGZFMIOy77XxnSI0HrIfTBj-U-wt4Q.webp",
|
||||
"https://i.namu.wiki/i/Ob0--Jok1pkNtNu46VjPmTZWbmaql5Xf0pexZ5RBz3B5Nlj8z_xqYSyMqw70Ad5mEYa_i1GHcHda5pbilvBNOA.webp"
|
||||
};
|
||||
|
||||
void Start()
|
||||
{
|
||||
@@ -26,6 +37,17 @@ public class LobbyManager : MonoBehaviourPunCallbacks
|
||||
PhotonNetwork.ConnectUsingSettings();
|
||||
}
|
||||
|
||||
// [1번 기능] 매 프레임마다 현재 접속자 수를 확인해서 텍스트 업데이트
|
||||
void Update()
|
||||
{
|
||||
// 서버에 연결되어 있을 때, 그리고 텍스트 UI가 연결되어 있을 때만 실행
|
||||
if (PhotonNetwork.IsConnected && currentPlayerText != null)
|
||||
{
|
||||
// PhotonNetwork.CountOfPlayers = 현재 포톤 서버에 접속한 총 플레이어 수
|
||||
currentPlayerText.text = $"Current Playing: {PhotonNetwork.CountOfPlayers}";
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnConnectedToMaster()
|
||||
{
|
||||
loading.SetActive(false);
|
||||
@@ -39,11 +61,20 @@ public class LobbyManager : MonoBehaviourPunCallbacks
|
||||
// 방 코드나 닉네임이 비어있으면 안 넘어감
|
||||
if (string.IsNullOrEmpty(roomCodeInput.text) || string.IsNullOrEmpty(nicknameInput.text)) return;
|
||||
|
||||
// 1. [핵심] 포톤 서버에 내 닉네임을 공식적으로 등록합니다!
|
||||
// 1. 포톤 서버에 내 닉네임을 공식적으로 등록합니다!
|
||||
PhotonNetwork.NickName = nicknameInput.text;
|
||||
|
||||
// 2. 아바타 URL 저장 (기존과 동일)
|
||||
// 2. 아바타 URL 저장 및 [2번 기능] 예외 처리
|
||||
string myUrl = urlInput.text;
|
||||
|
||||
// 만약 유저가 URL을 입력하지 않았다면?
|
||||
if (string.IsNullOrEmpty(myUrl))
|
||||
{
|
||||
// 준비해둔 기본 URL 목록 중에서 랜덤으로 하나를 뽑아서 적용!
|
||||
int randomIndex = Random.Range(0, defaultAvatarUrls.Length);
|
||||
myUrl = defaultAvatarUrls[randomIndex];
|
||||
}
|
||||
|
||||
Hashtable myProps = new Hashtable() { { "AvatarUrl", myUrl } };
|
||||
PhotonNetwork.LocalPlayer.SetCustomProperties(myProps);
|
||||
|
||||
@@ -64,6 +95,7 @@ public class LobbyManager : MonoBehaviourPunCallbacks
|
||||
yield return new WaitForSecondsRealtime(2f);
|
||||
roomPanel.SetActive(true);
|
||||
startButton.gameObject.SetActive(false); // 일단 게임 시작 버튼은 숨겨놓기
|
||||
|
||||
// 내가 방장(처음 방을 만든 사람)이라면 게임 시작 버튼 활성화
|
||||
if (PhotonNetwork.IsMasterClient)
|
||||
{
|
||||
@@ -74,23 +106,18 @@ public class LobbyManager : MonoBehaviourPunCallbacks
|
||||
// 방장만 누를 수 있는 '게임 시작' 버튼에 연결할 함수
|
||||
public void ClickStartGame()
|
||||
{
|
||||
// "GameScene"이라는 이름의 씬을 불러옴 (모두가 다 같이 넘어갑니다!)
|
||||
PhotonNetwork.LoadLevel("GameScene");
|
||||
}
|
||||
|
||||
public void ClickLeaveRoom()
|
||||
{
|
||||
// 포톤 서버에 "나 이 방에서 나갈래!" 라고 요청합니다.
|
||||
PhotonNetwork.LeaveRoom();
|
||||
statusText.text = "방에서 나가는 중...";
|
||||
}
|
||||
|
||||
// [추가 2] 방에서 완전히 빠져나왔을 때 포톤이 자동으로 실행해 주는 콜백 함수
|
||||
public override void OnLeftRoom()
|
||||
{
|
||||
// 방에서 나왔으니, UI를 다시 처음 상태(로그인 화면)로 되돌립니다.
|
||||
roomPanel.SetActive(false);
|
||||
|
||||
statusText.text = "무사히 도망쳤다!";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user