이동 동기화 #1

Eunho Bae·2022년 6월 24일

이동 동기화를 구현하는 방법은 두 가지가 있다.

  1. 클라이언트에서 이동한다는 요청을 서버에 보내고 서버의 응답을 기다린 후 움직이는 방법. 일반적인 MMORPG에서 많이 사용된다. 하지만 서버의 응답을 기다려야 하기 때문에 렉이 걸릴 수 있다.
  2. 먼저 플레이어가 이동하고 서버에 통보하는 방식. 뒤늦게 통보받은 서버가 다른 클라들에게 이동 패킷을 뿌려주는 방식.

이 강의에서는 1번 방법으로 구현해본다.

Protocol.proto

Protocol.proto에서 아래와 같이 수정했다.
기존에서 플레이어 좌표 뿐만 아니라 플레이어가 향하고 있는 방향정보와 상태정보를 같이 이동 패킷에 포함하도록 변경시켰다.

enum CreatureState {
	IDLE = 0;
	MOVING = 1;
	SKILL = 2;
	DEAD = 3;
}

enum MoveDir {
	NONE = 0;
	UP = 1;
	DOWN = 2;
	LEFT = 3;
	RIGHT = 4;
}

message C_Move {
  PositionInfo posInfo = 1;
}

message S_Move {
  int32 playerId = 1;
  PositionInfo posInfo = 2;
}

message PlayerInfo {
  int32 playerId = 1;
  string name = 2;
  PositionInfo posInfo = 3;
}

message PositionInfo {
	CreatureState state = 1;
	MoveDir moveDir = 2;
	int32 posX = 3;
	int32 posY = 4;
}

Protocol.proto 수정 후 GenProto.bat 실행해서 갱신시켜주고 Server를 빌드하였다.

ClientSession 수정하기

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

			MyPlayer = PlayerManager.Instance.Add();
			{
				MyPlayer.Info.Name = $"Player_{MyPlayer.Info.PlayerId}";
				MyPlayer.Info.PosInfo.State = CreatureState.Idle;
				MyPlayer.Info.PosInfo.MoveDir = MoveDir.None;
				MyPlayer.Info.PosInfo.PosX = 0;
				MyPlayer.Info.PosInfo.PosY = 0;
				MyPlayer.Session = this;
			}

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

Protocol.proto에서 message PlayerInfo 부분을 수정하였기 때문에 그에 맞춰 MyPlayer 객체를 만들고 초기화 시켜준다.

PacketHandler 수정

	public static void C_MoveHandler(PacketSession session, IMessage packet)
	{
		C_Move movePacket = packet as C_Move;
		ClientSession clientSession = session as ClientSession;

        // 플레이어가 입장한 방에 있는 모든 유저들한테 브로드캐스팅. 이동 했다는 사실을 뿌리는 방식으로 작업
        Console.WriteLine($"C_Move ({movePacket.PosInfo.PosX}, {movePacket.PosInfo.PosY}");

		if (clientSession.MyPlayer == null)
			return;

		if (clientSession.MyPlayer.Room == null)
			return;

		// TODO 검증하기. 거짓된 정보가 올 수도 있기 때문


		// 일단 서버에서 좌표 이동
		PlayerInfo info = clientSession.MyPlayer.Info; // 현재 서버에 있는 플레이어의 정보
		info.PosInfo = movePacket.PosInfo; // 그 정보에 얼마만큼 이동했는지 패킷 이동 정보를 덮어씌어서 서버 상의 플레이어 좌표를 갱신
		
		// 그리고 그 갱신시킨 정보를 다른 플레이어한테도 알려줌
		S_Move resMovePacket = new S_Move();
		resMovePacket.PlayerId = clientSession.MyPlayer.Info.PlayerId; // 누가 움직이는가?
		resMovePacket.PosInfo = movePacket.PosInfo;

		clientSession.MyPlayer.Room.Broadcast(resMovePacket);
	}

클라이언트 쪽에서 Move 패킷이 오면 서버에서 그 패킷을 처리해주게 되는데 패킷이 잘 도착했는지 검증 후 같은 룸에 존재하는 모든 다른 플레이어들에게 누가 이동했는지 전부 Move 패킷을 뿌려준다.

GameRoom에서 브로드캐스팅 함수 작성

public void Broadcast(IMessage packet)
		{
			lock(_lock)
            {
				foreach(Player p in _players)
                {
					p.Session.Send(packet);
                }
            }
		}

Broadcast 함수는 C_MoveHandler에서 호출시켜주고 있는데 여러 클라이언트가 C_Move 패킷을 동시에 쏘면 C_MoveHandler가 다양한 쓰레드에서 동시다발적으로 실행될 수 있다. 그래서 반드시 C_MoveHandler에서 호출되는 함수는 정의할 때 lock을 해줘야 한다.
하지만 여기서 주의할 점은 lock을 남발해버리면 멀티쓰레딩의 의미가 사라져버리기 때문에 주의해야 한다.

그래서 lock을 거는 대신 jobQueue에 넣어준 후 단일 쓰레드에서 싹 다 처리하도록 하는게 좋다.

profile
개인 공부 정리

0개의 댓글