using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
public class FieldItem : MonoBehaviourPun, IInteractable
{
[Header("Visual")]
[SerializeField] private SpriteRenderer spriteRenderer;
public int itemID;
public ItemData itemData;
[Header("UI 연결")]
public TextMeshProUGUI interactText;
void Start()
{
if (interactText == null) gameObject.GetComponent<TextMeshProUGUI>();
interactText.gameObject.SetActive(false);
}
public void OnPhotonInstantiate (PhotonMessageInfo info)
{
object[] data = info.photonView.InstantiationData;
if (data != null && data.Length > 0)
{
//넘어온 ID 받기
this.itemID = (int)data[0];
//ID 기반으로 이미지 채우기 -> ItemDatabase 사용해서 매핑
ItemData dataObj = ItemManager.instance.GetItem(this.itemID);
if (dataObj != null)
{
this.itemData = dataObj;
this.spriteRenderer.sprite = dataObj.icon;
}
}
}
public void ShowUI(bool show)
{
interactText.gameObject.SetActive(show);
}
public void Interact(Player interactor)
{
photonView.RPC(nameof(RequestGetItem), RpcTarget.MasterClient, interactor);
}
[PunRPC]
public void RequestGetItem(Player interactor)
{
if (this.itemData == null) return;
this.itemData = null;
photonView.RPC(nameof(GetItem), interactor);
PhotonNetwork.Destroy(gameObject);
}
[PunRPC]
public void GetItem()
{
InventoryModel.instance.AddItem(this.itemData);
}
//사용X
public void ShowPanel(bool show){}
}
void Interact()에서 당사자면 바로 아이템을 파괴하는 문제 ⇒ 이전에 작성한 폭죽 스크립트 보면서 작성해서 그때의 문제 파트 그대로 작성해버림GetItem() 부분에서 인자로 아이템 ID를 받아서 처리하는 것이 중간에 ReqeustGetItem()에서 null 처리하거나 중간에 다른 원인으로 인해 null처리되어 버려도 실행하는데에 문제가 없기에 그렇게 수정해야함IPunInstantiateMagicCallBack 사용해서 OnPhotonInstantiate()가 자동으로 호출됨using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
public class FieldItem : MonoBehaviourPun, IInteractable, IPunInstantiateMagicCallback
{
[Header("Visual")]
[SerializeField] private SpriteRenderer spriteRenderer;
//내부데이터
private int itemID;
private ItemData itemData;
private bool isCollected;
[Header("UI 연결")]
public TextMeshProUGUI interactText;
void Start()
{
if (interactText == null) gameObject.GetComponent<TextMeshProUGUI>();
interactText.gameObject.SetActive(false);
}
//포톤 Instantiate로 생성될 때 데이터(ID)를 받는 함수
public void OnPhotonInstantiate (PhotonMessageInfo info)
{
//InstantiationData는 object[] 형태
object[] data = info.photonView.InstantiationData;
if (data != null && data.Length > 0)
{
//넘어온 ID 받기
this.itemID = (int)data[0];
//ID 기반으로 이미지 채우기 -> ItemDatabase 사용해서 매핑
ItemData dataObj = ItemManager.instance.GetItem(this.itemID);
if (dataObj != null)
{
this.itemData = dataObj;
this.spriteRenderer.sprite = dataObj.icon;
}
}
}
public void ShowUI(bool show)
{
interactText.gameObject.SetActive(show);
}
public void Interact(Player interactor)
{
photonView.RPC(nameof(RequestGetItem), RpcTarget.MasterClient, interactor);
}
[PunRPC]
public void RequestGetItem(Player interactor)
{
if (this.itemData == null || isCollected) return;
isCollected = true;
photonView.RPC(nameof(GetItem), interactor, this.itemID);
PhotonNetwork.Destroy(gameObject);
}
[PunRPC]
public void GetItem(int id)
{
ItemData item = ItemManager.instance.GetItem(id);
InventoryModel.instance.AddItem(item);
}
//사용X
public void ShowPanel(bool show){}
}
OnPhotonInstantiate()를 통해서 아이템을 생성하기 위해서 InstantiationData에 아이템의 아이디를 넣어주어야함InstantiateRoomObject를 사용해야 다른 사람들도 볼 수 있고 + 후자를 사용해야 방장이 나가도 아이템이 유지되기에 후자를 선택해서 사용할 예정[PunRPC]
private void RPC_TryCraftItem()
{
if (!PhotonNetwork.IsMasterClient) return;
//재료 확인
foreach (bool state in slotStates)
{
if (!state) return;
}
int fireworkID = 3;
object[] data = new object[] {fireworkID};
Vector3 dropPos = spawnPoint.position;
dropPos += (Vector3)(Random.insideUnitCircle * 0.2f);
PhotonNetwork.InstantiateRoomObject(craftResultPrefabName, dropPos, Quaternion.identity, 0, data);
for (int i=0; i<slotStates.Length; i++)
{
photonView.RPC(nameof(RPC_UpdateSlot), RpcTarget.All, i, false);
}
}InstantiateRoomObject 호출하면서 마지막에 인자로 object[] data를 전달해주면 되는 형식이었다 ⇒ 생각보다 간단하고 원래랑 별 차이없어서 삽질한 기분…죽은 후 아이템 드랍하는 부분
OnDie() 메소드에서 그 안에 있는 스크립트를 호출할지, 아니면 아예 그 메소드 안에서 InventoryModel을 호출해서 그 안에서 처리하게 할 지 였는데… 아무래도 인벤토리 관련이니까 단일책임이나 관련성으로 미루어보아 후자로 처리하는게 나을 것 같다는 결론이 나왔다… ⇒ 결론적으로는 InventoryModel을 호출해서 처리하고 거기서 드랍할 아이템을 받아와서 PlayerController에서 드랍하도록하기로 결정.public ItemData DropItem()
{
ItemData dropItem = this.item;
this.item = null;
OnInventoryChanged?.Invoke();
return dropItem;
}OnDie() 메소드에서 위의 메소드를 호출해 드랍할 아이템을 가져와 드랍하도록 메소드 수정 작성void Die()
{
Debug.Log($"{photonView.Owner.NickName} 사망!");
if (photonView.IsMine)
{
Vector3 randomPos = transform.position;
ItemData dropItem = InventoryModel.instance.DropItem();
if (dropItem != null) photonView.RPC(nameof(RPC_DropItems), RpcTarget.MasterClient, dropItem.itemID, randomPos);
}
photonView.RPC(nameof(RPC_OnDieVisual), RpcTarget.All);
}
[PunRPC]
void RPC_DropItems(int itemID, Vector3 randomPos)
{
//FieldItem 생성 로직 -> object 사용해서 ID 전달
object[] data = new object[] {itemID};
//플레이어가 나가도 아이템이 남아야하기 때문에 RoomObject로 생성하기
PhotonNetwork.InstantiateRoomObject("FieldItemPrefab", randomPos, Quaternion.identity, 0, data);
}
[PunRPC]
void RPC_OnDieVisual()
{
if (spriteRenderer != null)
{
playerNameText.color = Color.red;
Color color = spriteRenderer.color;
color.a = 0.5f; //반투명
spriteRenderer.color = color;
}
GetComponent<Collider2D>().enabled = false;
}OnDie() 메소드 내에서 InventoryModel의 인스턴스를 사용해 그 안에 있는 드랍해야할 아이템을 (우선은 1개의 아이템만 가져오도록 해두었다) 받아온다RPC_OnDieVisual()이라는 함수를 만들어 모든 플레이어가 볼 수 있도록 처리하였다[죽기전: 화약 아이템 들고 있음]
[죽은 후: 바닥에 화약 아이템 떨어져있음 + 다른 사람 눈에도 반투명하게 보이도록 우선]
⇒ 이후에 생존자에게는 죽은 사람 안보이도록 처리해야함…

using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
public class Fireplace : MonoBehaviourPun, IInteractable
{
[Header("UI 연결")]
public TextMeshProUGUI interactText;
void Start()
{
if (interactText != null) interactText.gameObject.SetActive(false);
}
public void ShowUI(bool show)
{
interactText.gameObject.SetActive(show);
}
public void Interact(Player interacter)
{
Debug.Log("interact");
GameStateManager.instance.MeetingButtonPressed(interacter);
}
//사용X
public void ShowPanel(bool show) {}
}
#region [투표 관련 로직]
...
public int maxMeetingCount = 1; //플레이어당 최대 회의 소집 횟수
private const string MEETING = "MeetingCount"; //상수 키값 (CustomProperties)
...
public void MeetingButtonPressed(Player interacter)
{
if (currentState != GameState.Playing_OnLight && currentState != GameState.Playing_OffLight) return;
int currentCount = GetPlayerMeetingCount(interacter);
if (currentCount >= maxMeetingCount) {
Debug.Log("회의 소집 횟수를 모두 소진했습니다");
return;
}
photonView.RPC("RPC_RequestMeeting", RpcTarget.MasterClient, interacter);
}
[PunRPC]
public void RPC_RequestMeeting(Player interacter)
{
if (!PhotonNetwork.IsMasterClient) return;
if (currentState == GameState.Voting) return;
//2차 검사
int currentCount = GetPlayerMeetingCount(interacter);
if (currentCount >= maxMeetingCount) return;
//회의 소집 가능 횟수 관련 CustomProperties 업데이트
Hashtable props = new Hashtable {{MEETING, currentCount + 1}};
interacter.SetCustomProperties(props);
StartMeeting();
}
//플레이어의 현재 투표 소집 사용횟수 가져오는 함수
private int GetPlayerMeetingCount (Player player)
{
if (player.CustomProperties.ContainsKey(MEETING))
return (int) player.CustomProperties[MEETING];
return 0; //키가 없으면 0번 사용한 것.
}
//투표 횟수 초기화용 함수
public void ResetMeetingCounts()
{
foreach (Player p in PhotonNetwork.PlayerList)
{
Hashtable props = new HashTable {{MEETING, 0}};
p.SetCustomProperties(props);
}
}
#endregion
NotifyUI)를 하나 만들어주고, GSM에서는 이 메시지 클래스를 호출해서 업데이트 하는 식으로 수정함. 코루틴은 UI 클래스 안에 작성해줘, GSM은 호출만하면 알아서 시간 지났을 때, 사라지게해주니 간편함 + 다른 공지/메시지 작성도 훨씬 간편함.회고
4주차에는 여행 일정이 있어서 회의 전까지 작업한 날이 하루(굳이굳이 따지면 1.5일?인데 2월 7일에 작업로그를 그냥 한 번에 작성했다.)이다. 당시에 여행 일정이 있어서 맡은 분량도 확연히 적어서 하루에 집중해서 끝낼 수 있던 간단간단한 부분이었다..