유니티 연동
지난 시간에는 지금까지 구축해왔던 클라이언트-서버간 연결 프로젝트를 유니티에 연동하여, 유니티 내에 3D Obejct 실린더로 만든 임시 객체 Player를 3초마다 찾는 실습을 진행해보았다. 이 때 서브 쓰레드의 작동으로 인해 Player를 정상적으로 찾지 못하였다. 따라서 메인 쓰레드에서 함수가 실행되어 플레이어를 찾을 수 있도록 설정해주었다. 또한 과거 Command 패턴 학습방법을 이용해 큐를 생성하여 일감을 던져주는 형태로 마찬가지로 변경하여 정상적으로 Player를 메인 쓰레드에서 찾을 수 있도록 설정하였다.
🦀 채팅을 넘어, 움직임까지
이전 유니티 연동 시간에서는 단순히 게임을 실행하면, Player를 찾았다는 로그만 덜렁나타나서 이것만 가지고서는 MMO의 개념을 명확하게 보여줄 수 없다. 따라서 이번 시간에는 실질적으로 플레이어가 생성이 되고, 움직이는 것 까지 진행해 볼 예정이다.
🧩 패킷 재설계
PDL에 돌아가 패킷을 설계하는 것 부터 다시 시작해보도록 하자. 게임에 입장, 퇴장과 현재 플레이어 목록, 이동과 같은 패킷을 다음과 같이 구현한다.
1. 입장
<packet name="S_BroadcastEnterGame">
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
2. 퇴장
<packet name ="C_LeaveGame">
</packet>
<packet name="S_BroadcastLeaveGame">
<int name="playerId"/>
</packet>
3. 현재 플레이어 목록
<packet name="S_PlayerList">
<list name="player">
<bool name="isSelf"/> <!-- 자기 자신인지 판단한다. -->
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</list>
</packet>
4. 이동
<packet name="C_Move">
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
<packet name="S_BroadcastMove">
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
각각의 패킷은 서버 → 클라에서 보내는 형태와 클라 → 서버에서 보내는 형태로 나뉘어 진다. 아래는 PDL.xml 파일의 전문이다.
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
<packet name="S_BroadcastEnterGame">
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
<packet name ="C_LeaveGame">
</packet>
<packet name="S_BroadcastLeaveGame">
<int name="playerId"/>
</packet>
<packet name="S_PlayerList">
<list name="player">
<bool name="isSelf"/>
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</list>
</packet>
<packet name="C_Move">
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
<packet name="S_BroadcastMove">
<int name="playerId"/>
<float name="posX"/>
<float name="posY"/>
<float name="posZ"/>
</packet>
</PDL>
위와 같이 작성해주고 난 이후에, PacketGenerator를 빌드한 후, GenPacket 배치파일을 실행하여 스크립트를 최신화 해 주도록 한다.
🧩 서버 로직 수정
Server - Session - ClientSession 스크립트로 돌아가 ClientSession 클래스에 이동 좌표의 값을 저장할 수 있도록 posX, Y, Z를 새롭게 추가하여 준다.
class ClientSession : PacketSession
{
public int SessionId { get; set; }
public GameRoom Room { get; set; }
public float posX { get; set; }
public float posY { get; set; }
public float posZ { get; set; }
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Program.Room.Push(() => Program.Room.Enter(this));
}
...
}
Server - GameRoom 스크립트로 돌아가 Enter 메서드를 수정해주도록 한다. 해당 함수에서 해야 할 일은 다음과 같다.
1. 플레이어를 추가하고,
2. 신입생한테 모든 플레이어의 목록을 전송한다.
3. 이 후, 모든 플레이어에게 신입생의 입장을 알려준다.
public void Enter(ClientSession session)
{
// 플레이어 추가하고
_sessions.Add(session);
session.Room = this;
// 신입생한테 모든 플레이어 목록 전송
S_PlayerList players = new S_PlayerList();
foreach (ClientSession s in _sessions)
{
players.players.Add(new S_PlayerList.Player()
{
isSelf = (s == session),
playerId = s.SessionId,
posX = s.posX,
posY = s.posY,
posZ = s.posZ
});
}
session.Send(players.Write());
// 신입생 입장을 모든 플레이어에게 알린다.
S_BroadcastEnterGame enter = new S_BroadcastEnterGame();
enter.playerId = session.SessionId;
enter.posX = 0;
enter.posY = 0;
enter.posZ = 0;
Broadcast(enter.Write());
}
이 때 Broadcast 메서드에서 에러가 발생하는데, 기존의 Broadcast 메서드는 오로지 채팅 패킷만을 보내라고 하드 코딩이 되어 있는데, Broadcast 메서드는 굉장히 다양하게 사용될 것이기에 이 부분을 모든 세션에서 다 적용해도 문제 없이 작동할 수 있도록 수정해준다.
public void Broadcast(ArraySegment<byte> segment)
{
_pendingList.Add(segment);
}
이제 플레이어가 들어온 엔터를 만들었으니, 나갔을 때를 관리하기 위해 Leave 메서드를 새롭게 만들어주자. 마찬가지로 해당 메서드에서 해야할 일는 다음과 같다.
1. 플레이어를 제거하고
2. 모두에게 이를 알려준다.
public void Leave(ClientSession session)
{
// 플레이어를 제거하고
_sessions.Remove(session);
// 모두에게 알린다.
S_BroadcastLeaveGame leave = new S_BroadcastLeaveGame();
leave.playerId = session.SessionId;
Broadcast(leave.Write());
}
마지막으로 Move 메서드를 만들어주어야 하는데, 해당 함수는 인터페이스를 어떻게 처리해주어야 할 지 의문이다. 기존 Enter와 Leave 같은 경우는 클라이언트 세션이 접속되거나, 끊겼을 때 메서드가 실행되도록 처리했기 때문에 문제가 없었기 때문이다.
그러나 자세히 살펴보면, 우리는 Server - Packet - PacketHandler에서 이를 처리하고 있었기 때문에 해당 스크립트를 열어 수정하면 된다. 🤣 그러므로 PacketHandler에서 Leave와 Move의 동작을 처리하도록 하자.
using Server;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
class PacketHandler
{
public static void C_LeaveGameHandler(PacketSession session, IPacket packet)
{
ClientSession clientSession = session as ClientSession;
if (clientSession.Room == null)
{
return;
}
GameRoom room = clientSession.Room;
room.Push(() => room.Leave(clientSession));
}
public static void C_MoveHandler(PacketSession session, IPacket packet)
{
C_Move movePacket = packet as C_Move;
ClientSession clientSession = session as ClientSession;
if (clientSession.Room == null)
{
return;
}
Console.WriteLine($"C_Move ({movePacket.posX}, {movePacket.posY}, {movePacket.posZ})");
GameRoom room = clientSession.Room;
room.Push(() => room.Move(clientSession, movePacket));
}
}
PacketHandler를 처리했다면, 다시 GameRoom 스크립트로 돌아가 Move 메서드 내부를 채워주도록 하자. Move 메서드에서 해야할 일은 다음과 같다.
1. 좌표를 변경한다
2. 이를 모두에게 알린다.
public void Move(ClientSession session, C_Move packet)
{
// 좌표를 변경하고
session.posX = packet.posX;
session.posY = packet.posY;
session.posZ = packet.posZ;
// 모두에게 알린다.
S_BroadcastMove move = new S_BroadcastMove();
move.playerId = session.SessionId;
move.posX = session.posX;
move.posY = session.posY;
move.posZ = session.posZ;
Broadcast(move.Write());
}
서버 로직 수정 끝! 😎
🧩 클라이언트 로직 수정
클라이언트 로직은 서버 로직과 같이 맞춰주면서 작업해주면 된다. 먼저 DummyClient - Packet - PacketHandler 스크립트를 열어준다. 해당 Handler 에서는 빌드가 통과할 수 있도록 함수만 만들어줄 뿐, 실질적인 작업은 유니티 내부에서 처리할 예정이다.
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
class PacketHandler
{
// 빌드가 통과할 수 있도록 함수만 만들어주고, 별다른 작업은 하지 않는다.
// 실질적으로는 유니티에서 처리할 예정
public static void S_BroadcastEnterGameHandler(PacketSession session, IPacket packet)
{
S_BroadcastEnterGame pkt = packet as S_BroadcastEnterGame;
ServerSession serverSession = session as ServerSession;
}
public static void S_BroadcastLeaveGameHandler(PacketSession session, IPacket packet)
{
S_BroadcastLeaveGame pkt = packet as S_BroadcastLeaveGame;
ServerSession serverSession = session as ServerSession;
}
public static void S_PlayerListHandler(PacketSession session, IPacket packet)
{
S_PlayerList pkt = packet as S_PlayerList;
ServerSession serverSession = session as ServerSession;
}
public static void S_BroadcastMoveHandler(PacketSession session, IPacket packet)
{
S_BroadcastMove pkt = packet as S_BroadcastMove;
ServerSession serverSession = session as ServerSession;
}
}
위와 같이 스크립트를 수정하면, 비주얼 스튜디오에 나타나는 에러들은 거의 다 사라질 것이다. 그러나 DummyClient - SessionManager에서 에러가 날 것인데 SendForEach 부분에서 채팅 패킷을 보내고 있었기 때문이다. 따라서 해당 메서드에서도 채팅 패킷을 보내는 것이 아니라, 이동 패킷을 보내도록 수정한다.
class SessionManager
{
static SessionManager _session = new SessionManager();
public static SessionManager Instance { get { return _session; } }
List<ServerSession> _sessions = new List<ServerSession>();
object _lock = new object();
Random _rand = new Random(); ⭕
public void SendForEach()
{
lock (_lock)
{
foreach (ServerSession session in _sessions)
{
C_Move movePacket = new C_Move();
movePacket.posX = _rand.Next(-50, 50);
movePacket.posY = 0;
movePacket.posZ = _rand.Next(-50, 50);
session.Send(movePacket.Write());
}
}
}
...
}
Random 함수를 이용하여 movePacket의 값을 -50 ~ 50 사이의 값으로 설정해주도록 한다. 이 후, session.Send에 movePacket을 넣어주어 DummyClient - Program 스크립트에서 0.25초씩 자기 좌표를 갱신해 랜덤으로 값을 보내도록 설정한다.
실행 결과 😎
정상적으로 콘솔창에서 로그가 뜨는 것을 볼 수 있다. 😍
다음 시간에는 콘솔에서 로그를 보는 것이 아닌, 실질적으로 유니티에서 임시 객체(실린더 형태)로 실습하도록 하자.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 5. 데이터베이스 (0) | 2024.01.11 |
---|---|
Part 4-6-4. 유니티 연동 : 유니티 연동 #4 (0) | 2024.01.10 |
Part 4-6-2. 유니티 연동 : 유니티 연동 #2 (1) | 2024.01.03 |
Part 4-6-1. 유니티 연동 : 유니티 연동 #1 (1) | 2024.01.02 |
Part 4-5-7. Job Queue : JobTimer (1) | 2023.12.28 |