게임 입장

Eunho Bae·2022년 5월 25일

Protocol.proto 수정

syntax = "proto3";

package Protocol;
import "google/protobuf/timestamp.proto";
option csharp_namespace = "Google.Protobuf.Protocol";

enum MsgId {
  S_ENTER_GAME = 0;
  S_LEAVE_GAME = 1;
  S_SPAWN = 2; // 주변 플레이어가 S_SPAWN 알림을 받으면 그 플레이어의 클라에서 방금 스폰된 플레이어 만들어줌 (스폰은 플레이어 뿐만 아니라 모든 오브젝트 대상(화살, 몬스터 등))
  S_DESPAWN = 3;
  C_MOVE = 4; // 클라가 서버로 이동하고 싶다는 패킷 전송
  S_MOVE = 5; // 서버가 주변 플레이어들한테 누가 이동하고 있는지 알림
}

message S_EnterGame {
  PlayerInfo player = 1;
}

message S_LeaveGame { // 딱히 인자 필요 없음
}

message S_Spawn {
  repeated PlayerInfo players = 1; // 여러명이 등장할 수 있으니 list형태 즉 repeated
}

message S_Despawn {
  repeated int32 playerIds = 1; // 어떤 애가 없어질지만 알려주면 되니까 사라질 플레이어들의 id
}

message C_Move {
  int32 posX = 1;
  int32 posY = 2;
}

message S_Move { // 어떤 유저(id)가 어디로 이동하고 싶다는 정보를 인자로.
  int32 playerId =1;
  int32 posX = 2;
  int32 posY = 3;
}

message PlayerInfo {
  int32 playerId = 1; // DB에 들어가는 아이디가 아닌, 공격했을 때 구분하는 구분자
  string name = 2;
  int32 posX = 3;
  int32 posY = 4;
}

플레이어 입장

public class GameRoom
	{
		object _lock = new object();
		public int RoomId { get; set; }

		List<Player> _players = new List<Player>();

		public void EnterGame(Player newPlayer) { ... }
		public void LeaveGame(int playerId) { ... }
	}

EnterGame()

public void EnterGame(Player newPlayer)
		{
			if (newPlayer == null)
				return;

			lock (_lock)
			{
				_players.Add(newPlayer);
				newPlayer.Room = this;

				// 본인한테 정보 전송
				{
					S_EnterGame enterPacket = new S_EnterGame();
					enterPacket.Player = newPlayer.Info;
					newPlayer.Session.Send(enterPacket);

					S_Spawn spawnPacket = new S_Spawn();
					foreach (Player p in _players)
					{
						if (newPlayer != p) // 타인의 정보만
							spawnPacket.Players.Add(p.Info);
					}
					newPlayer.Session.Send(spawnPacket);
				}

				// 타인한테 정보 전송
				{
					S_Spawn spawnPacket = new S_Spawn();
					spawnPacket.Players.Add(newPlayer.Info);
					foreach (Player p in _players)
					{
						if (newPlayer != p)
							p.Session.Send(spawnPacket);
					}
				}
			}
		}

S_EnterGame 패킷을 만들고 새로 들어온 플레이어의 정보를 넣어준 후 새로 들어온 플레이어 입장에서 패킷을 서버로 보내준다.
그리고 S_Spawn 패킷을 만들고 현재 GameRoom에 참가 중인 자신을 제외한 모든 플레이어들을 뽑아와서 spawnPacket 내부에 Players 리스트에 정보들을 뽑아 넣어주고, 새로운 플레이어 입장에서 넣어준 S_Spawn 패킷을 쏘아 보내준다.
( 게임룸에 들어왔는데 제 주변에 누가누가 있으니 서버 측에서 제가 보내준 리스트에 따라 제 화면에 보이도록 해주세요!!)

그리고 S_Spawn 패킷을 다시 만들어서 이번에는 새로운 플레이어의 정보를 넣어준다. 그리고 룸에 참가중인 새로운 플레이어를 제외한 모든 플레이어의 입장에서 새로운 플레이어의 정보를 담은 S_Spawn 패킷을 서버로 보낸다. 즉 브로드캐스팅
(각 플레이어마다 새로 들어온 플레이어 정보를 담은 패킷을 쏘는 이유도 마찬가지로 서버한테 화면에 뿌려달라는 요청)

LeaveGame

public void LeaveGame(int playerId)
		{
			lock (_lock)
			{
				Player player = _players.Find(p => p.Info.PlayerId == playerId);
				if (player == null)
					return;

				_players.Remove(player);
				player.Room = null;

				// 본인한테 정보 전송
				{
					S_LeaveGame leavePacket = new S_LeaveGame();
					player.Session.Send(leavePacket);
				}

				// 타인한테 정보 전송
				{
					S_Despawn despawnPacket = new S_Despawn();
					despawnPacket.PlayerIds.Add(player.Info.PlayerId);
					foreach (Player p in _players)
					{
						if (player != p)
							p.Session.Send(despawnPacket);
					}
				}
			}
		}

플레이어가 나갈 때는 S_Despawn 패킷을 하나 만들고 그 패킷 안에 나갈 플레이어의 아이디를 add한다. (이 코드에서는 한명만 나감) 그리고 현재 나갈 플레이어를 제외한 게임룸에 참가중인 모든 플레이어들 입장에서 Send 함수를 호출하여 despawnPacket을 보내준다.

클라이언트 부분

ClientPacketManager

class PacketManager
{
	#region Singleton
	static PacketManager _instance = new PacketManager();
	public static PacketManager Instance { get { return _instance; } }
	#endregion

	PacketManager()
	{
		Register();
	}

	Dictionary<ushort, Action<PacketSession, ArraySegment<byte>, ushort>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>, ushort>>();
	Dictionary<ushort, Action<PacketSession, IMessage>> _handler = new Dictionary<ushort, Action<PacketSession, IMessage>>();
		
	public void Register()
	{		
		_onRecv.Add((ushort)MsgId.SEnterGame, MakePacket<S_EnterGame>);
		_handler.Add((ushort)MsgId.SEnterGame, PacketHandler.S_EnterGameHandler);		
		_onRecv.Add((ushort)MsgId.SLeaveGame, MakePacket<S_LeaveGame>);
		_handler.Add((ushort)MsgId.SLeaveGame, PacketHandler.S_LeaveGameHandler);		
		_onRecv.Add((ushort)MsgId.SSpawn, MakePacket<S_Spawn>);
		_handler.Add((ushort)MsgId.SSpawn, PacketHandler.S_SpawnHandler);		
		_onRecv.Add((ushort)MsgId.SDespawn, MakePacket<S_Despawn>);
		_handler.Add((ushort)MsgId.SDespawn, PacketHandler.S_DespawnHandler);		
		_onRecv.Add((ushort)MsgId.SMove, MakePacket<S_Move>);
		_handler.Add((ushort)MsgId.SMove, PacketHandler.S_MoveHandler);
	}
    
    ...
class PacketHandler
{
	public static void S_EnterGameHandler(PacketSession session, IMessage packet)
	{
		S_EnterGame enterGamePacket = packet as S_EnterGame;
		ServerSession serverSession = session as ServerSession;

		Debug.Log("S_EnterGameHandler");
		Debug.Log(enterGamePacket.Player);
	}

	public static void S_LeaveGameHandler(PacketSession session, IMessage packet)
	{
		S_LeaveGame leaveGameHandler = packet as S_LeaveGame;
		ServerSession serverSession = session as ServerSession;

		Debug.Log("S_LeaveGameHandler");
	}

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

		Debug.Log("S_SpawnHandler");
		Debug.Log(spawnPacket.Players);
	}

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

		Debug.Log("S_DespawnHandler");
	}

	public static void S_MoveHandler(PacketSession session, IMessage packet)
	{
		S_Move movePacket = packet as S_Move;
		ServerSession serverSession = session as ServerSession;

		Debug.Log("S_MoveHandler");
	}
}

받아온 패킷을 처리하는 함수들을 정의했다.

게임룸 관리

Player

	public class Player
	{
		public PlayerInfo Info { get; set; } = new PlayerInfo();
		public GameRoom Room { get; set; }
		public ClientSession Session { get; set; }
	}

PlayerManager

public class PlayerManager
	{
		public static PlayerManager Instance { get; } = new PlayerManager();

		object _lock = new object();
		Dictionary<int, Player> _players = new Dictionary<int, Player>();
		int _playerId = 1; // TODO
		
		public Player Add()
		{
			Player player = new Player();

			lock (_lock)
			{
				player.Info.PlayerId = _playerId;
				_players.Add(_playerId, player);
				_playerId++;
			}

			return player;
		}

		public bool Remove(int playerId)
		{
			lock (_lock)
			{
				return _players.Remove(playerId);
			}
		}

		public Player Find(int playerId)
		{
			lock (_lock)
			{
				Player player = null;
				if (_players.TryGetValue(playerId, out player))
					return player;

				return null;
			}
		}
	}

RoomManager

public class RoomManager
	{
		public static RoomManager Instance { get; } = new RoomManager();

		object _lock = new object();
		Dictionary<int, GameRoom> _rooms = new Dictionary<int, GameRoom>();
		int _roomId = 1;

		public GameRoom Add()
		{
			GameRoom gameRoom = new GameRoom();

			lock (_lock)
			{
				gameRoom.RoomId = _roomId;
				_rooms.Add(_roomId, gameRoom);
				_roomId++;
			}

			return gameRoom;
		}

		public bool Remove(int roomId)
		{
			lock (_lock)
			{
				return _rooms.Remove(roomId);
			}
		}

		public GameRoom Find(int roomId)
		{
			lock (_lock)
			{
				GameRoom room = null;
				if (_rooms.TryGetValue(roomId, out room))
					return room;

				return null;
			}
		}
	}

ClientSession

		public override void OnConnected(EndPoint endPoint)
		{
			Console.WriteLine($"OnConnected : {endPoint}");

			MyPlayer = PlayerManager.Instance.Add();
			{
				MyPlayer.Info.Name = $"Player_{MyPlayer.Info.PlayerId}";
				MyPlayer.Info.PosX = 0;
				MyPlayer.Info.PosY = 0;
				MyPlayer.Session = this;
			}

			RoomManager.Instance.Find(1).EnterGame(MyPlayer);
		}

		public override void OnRecvPacket(ArraySegment<byte> buffer)
		{
			PacketManager.Instance.OnRecvPacket(this, buffer);
		}

		public override void OnDisconnected(EndPoint endPoint)
		{
			RoomManager.Instance.Find(1).LeaveGame(MyPlayer.Info.PlayerId);

			SessionManager.Instance.Remove(this);

			Console.WriteLine($"OnDisconnected : {endPoint}");
		}

RoomManager는 GameRoom들을 관리하는 클래스이다. Client와 연결이 되었을 때 ClientSession에서 OnConnected() 함수를 통해 플레이어를 하나 생성하고 RoomManager의 Find(1) 즉 1번 룸(이 예제에선 게임룸 하나 밖에 없음. 각 게임룸은 1번부터 고유 아이디를 가짐)의 EnterGame에 인자로 넘겨준다.
ClientSession의 멤버변수로 MyPlayer가 선언되어 있어서 각 클라이언트 세션마다 하나의 플레이어가 할당되어 있다는 것을 알 수 있고, 룸을 나갈 때도 MyPlayer 변수를 이용하여 PlayerId를 뽑아 LeaveGame 함수의 인자로 전달해준다.

profile
개인 공부 정리

0개의 댓글