MyPlayer 분리

Eunho Bae·2022년 5월 27일

내가 조종하는 플레이어 분리

PlayerController 하나만 만들고 Player 프리팹에 컴포넌트를 추가하는 식으로 구현을 했었다. 싱글 플레이에서는 상관없지만 그 안에 키보드 입력을 받는 코드가 들어있기 때문에 멀티 플레이에서 문제가 될 수 있다. 즉 내가 위로 가는 버튼을 눌렀는데 그 맵에 존재하는 플레이어 전부가 영향을 받아버리는 것이다.

따라서 MyPlayerController를 하나 만들어서 내가 진짜 조종하는 플레이어를 따로 프리팹으로 만들고 컴포넌트로 소스파일을 추가하였다.

ObjectManager

public class ObjectManager
{
	public MyPlayerController MyPlayer { get; set; }
	Dictionary<int, GameObject> _objects = new Dictionary<int, GameObject>();

	public void Add(PlayerInfo info, bool myPlayer = false)
    {
		if(myPlayer)
        {
			GameObject go = Managers.Resource.Instantiate("Creature/MyPlayer");
			go.name = info.Name;
			_objects.Add(info.PlayerId, go);

			MyPlayer = go.GetComponent<MyPlayerController>();
			MyPlayer.Id = info.PlayerId;
			MyPlayer.CellPos = new Vector3Int(info.PosX, info.PosY, 0);
        }
		else
        {
			GameObject go = Managers.Resource.Instantiate("Creature/MyPlayer");
			go.name = info.Name;
			_objects.Add(info.PlayerId, go);

			PlayerController pc = go.GetComponent<PlayerController>();
			pc.Id = info.PlayerId;
			pc.CellPos = new Vector3Int(info.PosX, info.PosY, 0);
		}
    }
    
    public void RemoveMyPlayer()
    {
		if (MyPlayer == null)
			return;

		Remove(MyPlayer.Id);
		MyPlayer = null;
    }
    
    ...
  }

PacketHandler

class PacketHandler
{
	public static void S_EnterGameHandler(PacketSession session, IMessage packet)
	{
		S_EnterGame enterGamePacket = packet as S_EnterGame;

		Managers.Object.Add(enterGamePacket.Player, myPlayer: true);

	}

	public static void S_LeaveGameHandler(PacketSession session, IMessage packet)
	{
		S_LeaveGame leaveGameHandler = packet as S_LeaveGame;
		Managers.Object.RemoveMyPlayer();
	}

	public static void S_SpawnHandler(PacketSession session, IMessage packet)
	{
		S_Spawn spawnPacket = packet as S_Spawn;

		foreach(PlayerInfo player in spawnPacket.Players)
        {
			Managers.Object.Add(player, myPlayer: false);
        }
	}

	public static void S_DespawnHandler(PacketSession session, IMessage packet)
	{
		S_Despawn despawnPacket = packet as S_Despawn;

		foreach (int id in despawnPacket.PlayerIds)
		{
			Managers.Object.Remove(id);
		}
	}

컨텐츠 코드에 접근하는 문제

Session.cs 내부의 PacketSession에서 OnRecv함수 안 OnRecvPacket을 호출하고 있다. 이 함수는 PacketSession을 상속받는 ServerSession의 OnRecvPacket() 함수를 호출하는데 이 함수 내에서 다시 PacketManager의 OnRecvPacket() 함수를 호출하고 있다. 그러면 ClientPacketManager(PacketManager)의 OnRecvPacket() 함수 내에서 action.Invoke() 함수를 호출하는데 이때 action에 등록된 PacketHandler::S_EnterGameHandler() 함수를 호출하고 이 안에 컨텐츠 코드에 접근하는 코드(Managers.Resource.Instantiate("Creature/MyPlayer");)가 있어서 '오직 메인 쓰레드에서만' 이라는 에러가 났던 것이다.

다른 쓰레드에서는 컨텐츠 코드에 바로 접근하면 크래시 발생한다. 따라서 패킷 큐에다가 넣어두고 NetworkManager의 Update에서 패킷 큐를 계속 확인하는데 뭔가 있으면 꺼내서 패킷을 처리하게 된다. (NetworkManager는 Managers 내부에서 생성되어 메인쓰레드에서 동작하기 때문이다.)

ServerSession

public class ServerSession : PacketSession
{
	public override void OnConnected(EndPoint endPoint)
	{
		Debug.Log($"OnConnected : {endPoint}");

		PacketManager.Instance.CustomHandler = (s, m, i) =>
		{
			PacketQueue.Instance.Push(i, m);
		};
	}

ClientPacketManager

class PacketManager{

	public Action<PacketSession, IMessage, ushort> CustomHandler { get; set; }
    
    ...
    
	void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer, ushort id) where T : IMessage, new()
	{
		T pkt = new T();
		pkt.MergeFrom(buffer.Array, buffer.Offset + 4, buffer.Count - 4);

		if (CustomHandler != null) 
		{
			CustomHandler.Invoke(session, pkt, id);
		}
		else
		{
			Action<PacketSession, IMessage> action = null;
			if (_handler.TryGetValue(id, out action))
				action.Invoke(session, pkt);
		}
	}
    
    ...
profile
개인 공부 정리

0개의 댓글