Server programming

05_01_유니티 연동_유니티 연동#1

devRiripong 2023. 5. 17.
반응형

개요: 유니티와 서버를 연동 시켜 볼 것이다.

이 내용은 유니티와 서버를 연동하는 방법에 대한 상세한 가이드입니다. 코드 재사용성에 대한 좋은 소식과 일부 재사용 불가능성에 대한 나쁜 소식을 제공하며, Unity 프로젝트 설정, 서버 코드 이동, 에러 수정, 그리고 유니티에서의 테스트 방법에 대한 지침을 제공합니다.

  1. 유니티 설정:
    • Unity 프로젝트를 생성하고, Assets에 "Scripts"라는 새 폴더를 만듭니다.
    • 서버의 기존 코드를 가져와 필요한 부분을 선택합니다.
    • ServerCore의 다양한 클래스 파일들을 유니티의 Scripts 폴더에 복사 붙여넣기 합니다.
    • DummyClient의 ServerSession 및 Packet 폴더를 Scripts에 복사 붙여넣기 합니다.
    • 사용하지 않는 클래스를 삭제하고, 나머지 클래스를 새로운 폴더인 "Network"로 이동시킵니다.
  2. 에러 수정:
    • GenPackets 클래스의 TryWriteByte, Span, ReadOnlySpan 부분을 Unity가 인식할 수 있도록 코드를 수정합니다.
    • PacketFormat을 수정하여 변경된 부분이 적용되게 합니다.
    • PacketGenerator를 실행하고, GenPackets의 내용이 바뀌었는지 확인합니다.
    • 유니티의 폴더에도 수정된 코드가 생성되게 해줍니다.
    • SendBuffer의 Open에서 null을 반환하는 에러를 수정합니다.
  3. 유니티에서 테스트:
    • Scripts 폴더에 NetworkManager라는 새 스크립트를 생성합니다.
    • ServerSession을 생성하고, 이를 _session에 저장한 뒤에 연결하면 _session을 반환하도록 합니다.
    • PacketHandler에 메시지 출력을 추가합니다.
    • 유니티에 새로운 객체를 생성하고 이를 NetworkManager로 이름을 지정합니다. NetworkManager 스크립트를 이 객체에 연결합니다.
    • Server와 DummyClient를 실행하고, 유니티를 시작하여 플레이를 누릅니다. "Hello Server! I am 1" 메시지가 유니티 콘솔 창에 표시되어야 합니다.

이 가이드를 따르면, 유니티와 서버를 연동하는 방법을 배울 수 있습니다. 이를 통해 네트워크 기능을 가진 유니티 게임 개발에 한 걸음 더 나아갈 수 있습니다.

 

 

 

좋은 소식: 코드 재사용 가능. dll 쓰면 서버 기능 쓸 수 있지만 디버깅이 힘듦.

나쁜 소식: 전부 재사용하진 못함. 유니티 정책에 맞게 수정 필요.

GenPackets의 TryWriteByte, Span 을 이전 버전 유니티에서 사용 못했어. 그래서 수정함.

유니티에서 관리 하는 객체 메인 스레드 아닌 백그라운드 스레드에서 접근하면 크래시 발생. 메인 스레드에서만 하게 조절 필요

 

1.유니티 세팅

유니티 프로젝트 생성, 프로젝트 이름은 Client

Assets에 Scripts라는 폴더 생성

Server의 기존 코드들을 가져와서 걸러 낸다.

ServerCore의 Connector, JobQueue, Listener, PriorityQueue, RecvBuffer, SendBuffer, Session을 복사해서, 유니티의 Scripts 폴더에 탐색기에서 붙여 넣는다.

DummyClient의 ServerSesion이랑 Packet폴더(ClientPacketManager, GenPackets, PacketHandler)도 Scripts에 복붙한다.

서버에서 사용하던 JobQueue, Listener, PriorityQueue를 삭제한다.

남은 Session, ServerSession, RecvBuffer, SendBuffer, Connector를 NetWork라는 폴더를 만들어 옮긴다.

 

2.에러 수정

유니티가 2021 이전 버전이면 에러가 발생한다

2_1. GenPackets 수정

DummyClient의 Packet의 GenPackets로 가서 TryWriteByte, Span, ReadOnlySpan 부분을 유니티가 인식할 수 있는 코드로 바꿔준다.

public void Read(ArraySegment<byte> segment)
    {
        ushort count = 0;

        ReadOnlySpan<byte> s = new Span<byte>(segment.Array, segment.Offset, segment.Count);

        count += sizeof(ushort);
        count += sizeof(ushort);

        ushort chatLen = BitConverter.ToUInt16(s.Slice(count, s.Length - count));
				count += sizeof(ushort);
				this.chat = Encoding.Unicode.GetString(s.Slice(count, chatLen));
				count += chatLen;
    }

에서

public void Read(ArraySegment<byte> segment)
	{
		ushort count = 0;

		count += sizeof(ushort);
		count += sizeof(ushort);

		ushort chatLen = BitConverter.ToUInt16(segment.Array, segment.Offset + count);
		count += sizeof(ushort);
		this.chat = Encoding.Unicode.GetString(segment.Array, segment.Offset + count, chatLen);
		count += chatLen;
	}

이렇게 수정해 준다.

Write의 경우

public ArraySegment<byte> Write()
{
    ArraySegment<byte> segment = SendBufferHelper.Open(4096);
    ushort count = 0; 
    bool success = true;

    Span<byte> s = new Span<byte>(segment.Array, segment.Offset, segment.Count);

    count += sizeof(ushort);

    success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), (ushort)PacketID.C_Chat);
    count += sizeof(ushort);
    ushort chatLen = (ushort)Encoding.Unicode.GetBytes(this.chat, 0, this.chat.Length, segment.Array, segment.Offset + count + sizeof(ushort));
		success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), chatLen);
		count += sizeof(ushort);
		count += chatLen;
    success &= BitConverter.TryWriteBytes(s, count);
    if (success == false)
        return null;
    return SendBufferHelper.Close(count);
}

에서

public ArraySegment<byte> Write()
	{
		ArraySegment<byte> segment = SendBufferHelper.Open(4096);
		ushort count = 0;

		count += sizeof(ushort);
		Array.Copy(BitConverter.GetBytes((ushort)PacketID.C_Chat), 0, segment.Array, segment.Offset + count, sizeof(ushort));
		count += sizeof(ushort);
		ushort chatLen = (ushort)Encoding.Unicode.GetBytes(this.chat, 0, this.chat.Length, segment.Array, segment.Offset + count + sizeof(ushort));
		Array.Copy(BitConverter.GetBytes(chatLen), 0, segment.Array, segment.Offset + count, sizeof(ushort));
		count += sizeof(ushort);
		count += chatLen;

		Array.Copy(BitConverter.GetBytes(count), 0, segment.Array, segment.Offset, sizeof(ushort));

		return SendBufferHelper.Close(count);
	}

이렇게 바꿔준다.

2_2. PacketFormat 수정

변경된 부분이 적용되게 수정한다.

2_3. PacketGenerator를 빌드 후, GenPackets.bat를 실행하고, GetPackets의 내용이 바뀌었는지 확인한다.

2_4. 유니티의 폴더에도 수정된 코드가 생성되게 해준다.

GenPackets.bat를 편집기로 드래그 앤 드롭으로 열고

START ../../PacketGenerator/bin/PacketGenerator.exe ../../PacketGenerator/PDL.xml
XCOPY /Y GenPackets.cs "../../DummyClient/Packet"
XCOPY /Y GenPackets.cs "../../Client/Assets/Scripts/Packet"
XCOPY /Y GenPackets.cs "../../Server/Packet"
XCOPY /Y ClientPacketManager.cs "../../DummyClient/Packet"
XCOPY /Y ClientPacketManager.cs "../../Client/Assets/Scripts/Packet"
XCOPY /Y ServerPacketManager.cs "../../Server/Packet"

GenPackets.cs와 ClientPacketManager.cs가 유니티의 폴더에 생성되게 한다.

2_5. SendBuffer의 Open에서 null을 뱉어서 발생하는 에러를 수정한다.

public ArraySegment<byte> Open(int reserveSize) // 할당할 최대 크기를 넣어줄거야.
{
    //if (reserveSize > FreeSize)
    //    return null;

    return new ArraySegment<byte>(_buffer, _usedSize, reserveSize); 
}

3.유니티에서 테스트

3_1. 유니티에서 Scripts 폴더에 NetworkManager라는 이름의 script파일을 생성한다.

DummyClient의 Program에서 DNS을 세팅하는 부분을 복붙하고, ServerSession을 생성해 _session에 넣은 뒤 Connect하면 _session을 리턴하게 한다. 그리고 갯수를 1개로 한다.

public class NetworkManager : MonoBehaviour
{
    ServerSession _session = new ServerSession(); 

    void Start()
    {
        // DNS ( Domain Name System )
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
        // ipAddre는 식당 주소, 7777은 식당 정문인지 후문인지 문의 번호        }
        // 식당 주소 찾는 부분은 똑같을 거야. 

        Connector connector = new Connector();

        connector.Connect(endPoint,
            () => { return _session; },
            1);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

3_2. 패킷 핸들러에 메시지 출력 여기 코드가 실행될 것이기 때문에 유니티 엔진 메시지 코들를 넣는다.

class 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); 
        //if(chatPacket.playerId == 1)
        //    Console.WriteLine(chatPacket.chat);
    }
}

3_3. DummyClient의 갯수를 10개로 한다.

connector.Connect(endPoint, 
      () => { return SessionManager.Instance.Generate(); }, 
      10);

3_4. 유니티에 새로운 오브젝트를 만들고 이름을 NetworkManager로 한다. NerworkManager 스크립트를 드래그 앤 드롭한다.

3_5. Server를 DummyClient와 함께 실행하고, 유니티를 켜서 플레이를 누른다.

유니티의 Console 창에 Hello Server! I am 1 메시지가 뜬다.

 

 

출처: https://inf.run/ofxh

반응형

댓글