이번 포스트에서는 이전에 만든 구조에 유니티를 추가하여 서버 프로그래밍을 해보겠습니다.
우선, 이전에 존재하던 Player 오브젝트를 프리팹화하여 로직에서 생성하도록 하겠습니다.
새롭게 추가되는 스크립트는 다음과 같습니다.
MyPlayer의 경우, 내가 조종하는 플레이어를 위한 스크립트입니다.
컴포넌트로 해당 스크립트를 오브젝트에 붙여서 동작합니다.
Player의 경우, 내가 조종하는 플레이어가 아닌 다른 플레이어를 위한 스크립트입니다.
마찬가지로 컴포넌트로서 동작합니다.
마지막으로 PlayerManager의 경우,
패킷에 따라 로직을 수행하되 MyPlayer와 Player에 따라 다르게 로직을 수행합니다.
1) 유니티 프로젝트 - PacketHandler
class PacketHandler
{
public static void S_BroadcastEnterGameHandler(PacketSession session, IPacket packet)
{
S_BroadcastEnterGame enterPacket = packet as S_BroadcastEnterGame;
ServerSession serverSession = session as ServerSession;
PlayerManager.Instance.EnterGame(enterPacket);
}
public static void S_BroadcastLeaveGameHandler(PacketSession session, IPacket packet)
{
S_BroadcastLeaveGame leavePacket = packet as S_BroadcastLeaveGame;
ServerSession serverSession = session as ServerSession;
PlayerManager.Instance.LeaveGame(leavePacket);
}
public static void S_PlayerListHandler(PacketSession session, IPacket packet)
{
S_PlayerList listPacket = packet as S_PlayerList;
ServerSession serverSession = session as ServerSession;
PlayerManager.Instance.Add(listPacket);
}
public static void S_BroadcastMoveHandler(PacketSession session, IPacket packet)
{
S_BroadcastMove movePacket = packet as S_BroadcastMove;
ServerSession serverSession = session as ServerSession;
PlayerManager.Instance.Move(movePacket);
}
}
PacketHandler의 경우,
들어온 패킷에 따라 PlayerManager의 각기 다른 메서드를 수행하도록 설정하였습니다.
2) 유니티 프로젝트 - NetworkManager
public class NetworkManager : MonoBehaviour
{
ServerSession _session = new ServerSession();
public void Send(ArraySegment<byte> sendBuff)
{
_session.Send(sendBuff);
}
void Start()
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
Connector connector = new Connector();
connector.Connect(endPoint, () => { return _session; }, 1);
}
void Update()
{
List<IPacket> list = PacketQueue.Instance.PopAll();
foreach(IPacket packet in list)
PacketManager.Instance.HandlePacket(_session, packet);
}
}
NetworkManager의 로직 일부가 변경되었습니다.
우선, 코루틴을 통해 서버로 지정한 시간마다 패킷을 보내는 작업을 더이상 수행하지 않습니다.
대신 해당 작업은 나의 Player인 MyPlayer가 수행합니다.
또 Update 메서드에서는 매 프레임마다
하나의 패킷을 수행하던 코드에서 모든 패킷을 수행하도록 변경되었습니다.
3) 유니티 프로젝트 - PacketQueue
public class PacketQueue
{
.
.
.
public List<IPacket> PopAll()
{
List<IPacket> list = new List<IPacket>();
lock (_lock)
{
while (_packetQueue.Count > 0)
list.Add(_packetQueue.Dequeue());
}
return list;
}
}
PacketQueue에서 하나의 패킷만 Pop하지 않고
대신 지역 변수로 List를 하나 만들고 해당 List에 모든 패킷을 저장한 뒤
리턴하는 PopAll 메서드를 추가하였습니다.
추가된 PopAll 메서드를 사용하여 NetworkManager에서 작업을 수행합니다.
4) 유니티 프로젝트 - Player
public class Player : MonoBehaviour
{
public int PlayerId { get; set; }
void Start()
{
}
void Update()
{
}
}
내가 조종하지 않는 다른 Player들을 위한 컴포넌트인 Player의 경우
PlayerId를 프로퍼티로서 갖게 됩니다.
5) 유니티 프로젝트 - MyPlayer
public class MyPlayer : Player
{
NetworkManager _networkManager;
void Start()
{
StartCoroutine("SendPacketperTime");
_networkManager = GameObject.Find("NetworkManager").GetComponent<NetworkManager>();
}
void Update()
{
}
IEnumerator SendPacketperTime()
{
while (true)
{
yield return new WaitForSeconds(0.25f);
C_Move movePacket = new C_Move();
movePacket.posX = UnityEngine.Random.Range(-50, 50);
movePacket.posY = 0;
movePacket.posZ = UnityEngine.Random.Range(-50, 50);
_networkManager.Send(movePacket.Write());
}
}
}
내가 조종하는 Player인 MyPlayer의 경우,
이전에 NetworkManager가 수행하던 코루틴을 통한 패킷을 전송하던 작업을 대신 수행합니다.
또한 MyPlayer는 Player로부터 상속받아 동일하게 프로퍼티인 PlayerId를 갖고 있습니다.
6) 유니티 프로젝트 - PlayerManager
public class PlayerManager
{
MyPlayer _myPlayer;
Dictionary<int, Player> _players = new Dictionary<int, Player>();
public static PlayerManager Instance { get; } = new PlayerManager();
public void Add(S_PlayerList packet)
{
Object obj = Resources.Load("Player");
foreach(S_PlayerList.Player eachPlayer in packet._playerList)
{
GameObject go = Object.Instantiate(obj) as GameObject;
// 내가 조종하는 플레이어라면
if (eachPlayer.isSelf)
{
MyPlayer myPlayer = go.AddComponent<MyPlayer>();
myPlayer.PlayerId = eachPlayer.playerId;
myPlayer.transform.position = new Vector3(eachPlayer.posX, eachPlayer.posY, eachPlayer.posZ);
_myPlayer = myPlayer;
}
// 내가 조종하지 않는 다른 플레이어라면
else
{
Player player = go.AddComponent<Player>();
player.PlayerId = eachPlayer.playerId;
player.transform.position = new Vector3(eachPlayer.posX, eachPlayer.posY, eachPlayer.posZ);
_players.Add(eachPlayer.playerId, player);
}
}
}
public void Move(S_BroadcastMove packet)
{
// 내가 조종하는 플레이어가 이동
if (_myPlayer.PlayerId == packet.playerId)
{
_myPlayer.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
}
// 내가 조종하지 않는 다른 플레이어가 이동
else
{
Player player = null;
if (_players.TryGetValue(packet.playerId, out player))
{
player.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
}
}
}
public void EnterGame(S_BroadcastEnterGame packet)
{
if (packet.playerId == _myPlayer.PlayerId)
return;
Object obj = Resources.Load("Player");
GameObject go = Object.Instantiate(obj) as GameObject;
Player player = go.AddComponent<Player>();
player.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
_players.Add(packet.playerId, player);
}
public void LeaveGame(S_BroadcastLeaveGame packet)
{
// 내가 조종하는 플레이어가 GameRoom을 나감
if (_myPlayer.PlayerId == packet.playerId)
{
GameObject.Destroy(_myPlayer.gameObject);
_myPlayer = null;
}
// 내가 조종하지 않는 다른 플레이어가 GameRoom을 나감
else
{
Player player = null;
if (_players.TryGetValue(packet.playerId, out player))
{
GameObject.Destroy(player.gameObject);
_players.Remove(packet.playerId);
}
}
}
}
패킷에 따라 Player에 맞는 동작을 수행하는 PlayerManager입니다.
PlayerManager에서는 패킷의 종류인 플레이어 목록 / 입장 / 퇴장 / 이동을 각각 처리하며
이때 Player인지, MyPlayer인지에 따라 다르게 처리합니다.
최종 실행 결과는 다음과 같습니다.
이로써 최종적으로 C#을 통한 서버 프로그래밍 포스트를 마치겠습니다.
일련의 포스트는 인프런 Rookiss 님의
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
를 바탕으로 제작되었습니다.
구성이 굉장히 알차고 유용한 강의이므로 관심이 있으신 분들은 아래의 링크에서 확인하시면 됩니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 강의 | Rookiss - 인프런
Rookiss | 네트워크/멀티쓰레드/운영체제 등 핵심 전공 지식을 공부하고 게임 서버를 바닥부터 만들어보면서 MMORPG 기술을 학습하는 강의입니다., MMORPG 개발에 필요한 모든 기술, C# + Unity로 Step By St
www.inflearn.com