유니티 연동 #2

Eunho Bae·2022년 5월 16일
0

강의 주제

로그만 찍고 땡이 아니라 추가적인 액션(플레이어를 찾아 움직이게 하거나 스킬을 쓰도록)을 하도록 해보자

내용

문제1

PacketHandler에서 로그가 찍히지 않는 이유

 public static void S_ChatHandler(PacketSession session, IPacket packet)
    {
        S_Chat chatPacket = packet as S_Chat;
        ServerSession serverSession = session as ServerSession;

        if (chatPacket.playerId == 1)
        {
            Debug.Log(chatPacket.chat);

            GameObject go = GameObject.Find("Player");
            if (go == null)
                Debug.Log("플레이어 찾지 못함");
            else
                Debug.Log("플레이어 찾음");

        }
    }

유니티는 자신이 지정한 쓰레드가 아니라 다른 쓰레드에서 게임과 관련된 부분에 접근하는 것을 원천 차단한다.
따라서 패킷을 만들면 바로 핸들러를 통해 바로 처리하는게 아니라 큐에 잠시 저장 후 나중에 유니티 쓰레드에서 하나씩 꺼내 쓰도록 바꿔보자

메인쓰레드와 백그라운드 쓰레드(네트워크 처리하는)가 동시 접근해야 하니까 PacketQueue에 락을 걸어준다.

public class PacketQueue 
{
    public static PacketQueue Inst { get; }  = new PacketQueue();

    Queue<IPacket> _packetQueue = new Queue<IPacket> ();
    object _lock = new object ();   
    
    public void Push(IPacket packet)
    {
        lock(_lock)
        {
            _packetQueue.Enqueue (packet);
        }
    }

    public IPacket Pop()
    {
        lock(_lock)
        {
            if (_packetQueue.Count == 0)
                return null;

            return _packetQueue.Dequeue ();
        }
    }
}

그러면 패킷을 큐에 넣는 부분은 어디서 처리해야 좋을까?

백그라운드 쓰레드에서 receive 이벤트를 처리하면 MakePacket에서 IPacket을 구현하는 클래스 타입의 패킷을 만들고, 그 안에 메시지를 넣어줄 것이다.

이전과는 달리 MakePacket을 쪼개서 만들고 바로 처리하는 것이 아닌, MakePacket과 HandlePacket 둘로 나눌 것이다.

그리고 MakePacket을 호출하고 HandlePacket을 바로 해줄지, 아니면 OnRecvPacket 함수를 호출할때 전달하는 버퍼를 어떻게 처리해줄 것인지 정해줄 수 있도록 Action 매개변수를 추가로 정의한다.

public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer, Action<PacketSession, IPacket> onRecvCallback = null) // 옵션으로 어떻게 처리할 것인지 정해줄 수 있다.
	{
		ushort count = 0;

		ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
		count += 2;
		ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
		count += 2;

		Func<PacketSession, ArraySegment<byte>, IPacket> func = null;
		if (_makeFunc.TryGetValue(id, out func))
		{
			IPacket packet = func.Invoke(session, buffer);

			if (onRecvCallback != null)
				onRecvCallback.Invoke(session, packet);
			else
				HandlePacket(session, packet);
		}
	}

DummyClient::ServerSession 클래스에서는 다음과 같이 호출할 것이다.

		public override void OnRecvPacket(ArraySegment<byte> buffer)
		{
			PacketManager.Instance.OnRecvPacket(this, buffer, (s, p) => PacketQueue.Inst.Push(p)); // 유니티에서는 ServerSession 하나만 존재하기 때문에 굳이 s를 전달할 필요는 없다
		}

NetworkManager는 MonoBehaviour를 상속받고 유니티 게임오브젝트에 붙어있는 놈이므로 유니티 메인쓰레드에서 동작한다. 따라서 다음과 같이 Update함수 내에서 큐에 삽입하도록 한다.

 void Update()
    {
        IPacket packet = PacketQueue.Inst.Pop();
        if(packet != null)
        {
            PacketManager.Instance.HandlePacket(_session, packet);
        }
    }

이렇게 하면 PacketHandler에서 실행되는 함수는 유니티 메인쓰레드에서 실행하는 모양이 된다.

구현

다음으로 패킷을 받는것이 아닌 보내는 것도 구현해보자
클라이언트에서 서버측으로 보내기 때문에 패킷 종류는 C_Chat이 된다.

NetworkManager.cs

IEnumerator CoSendPacket()
   {
       while(true)
       {
           yield return new WaitForSeconds(3.0f); // 3초에 한번씩
           C_Chat chatPacket = new C_Chat();

           chatPacket.chat = "안녕 유니티!!";
           ArraySegment<byte> segment = chatPacket.Write();
           _session.Send(segment);
       }
   }

패킷의 ChunkSize를 너무 크게 할 필요는 없으니 65535로 줄이고 코루틴을 사용하여 3초에 한번씩 서버로 메시지를 담은 패킷을 보낼 것이다.

그리고 DummyClient는 빼고 Server 프로젝트만 시작 프로젝트로 설정하여 실행한 후 유니티를 실행시키면 다음과 같이 3초마다 한번씩 서버로 패킷을 보내게 된다.


보내기 전


유니티 실행


보낸 후 서버 콘솔 모습

profile
개인 공부 정리

0개의 댓글

관련 채용 정보