캐릭터 이동 동기화
rpg 게임에서 꽃이라고 할 수 있는 캐릭터 이동 동기화를 맞춰보겠다. 지난 포스팅에 작성한 맵이 쉐이더 문제로 다른 맵으로 교체하고 새로 bake 해줬다.
이동 동기화의 순서는 다음과 같다.
- 클라이언트 A가 이동을 원하는 위치 vector를 서버에게 C_Move 라는 패킷으로 보낸다.
- C_Move 패킷을 받은 서버는 그 위치가 올바른 위치인지 판단하고 올바르지 않다면 S_Banish 패킷을 보낸다.
- 만약 올바른 위치라면 이동하는 좌표와 ObjectId를 포함한 S_Move 패킷을 모든 플레이어에게 Broadcast 한다.
- S_Move 패킷을 받은 클라이언트 A, B 등 모든 플레이어는 움직임을 원하는 object를 Id를 통해 찾아낸다.
- 찾아낸 객체를 _agent.SetDestination(target); 으로 이동을 명령한다.
- 원하는 위치에 도착하면 C_StopMove 패킷을 보낸다.
- 위 과정을 비슷하게 반복한다.
이제 이걸 코드로 작성해보자.
MyPlayerController의 일부이다.
public void OnClickMouseInputEvent()
{
if (Input.GetMouseButtonDown(1))
{
Ray ray = cm.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
// TODO : 이동 패킷
//MoveTarget(hit.point);
C_Move movePacket = new C_Move() { PosInfo = new PositionInfo() { Pos = new Positions() } };
movePacket.PosInfo.State = CreatureState.Moving;
Positions pos = new Positions() { PosX = hit.point.x, PosY = hit.point.y, PosZ = hit.point.z };
movePacket.PosInfo.Pos = pos;
Managers.Network.Send(movePacket);
}
}
}
패킷을 내가 지정한 위치를 포함해 보낸다.
받은 서버는 다음과 같다. 여기서 바로 처리해주지 않고 room을 담당하는 쓰레드에게 일감을 떠 넘겨 lock을 사용하지 않도록 한다.
public static void C_MoveHandler(PacketSession session, IMessage packet)
{
C_Move movePacket = packet as C_Move;
ClientSession clientSession = session as ClientSession;
Player player = clientSession.MyPlayer;
if (player == null)
return;
GameRoom room = player.Room;
if (room == null)
return;
room.Push(room.HandleMove, player, movePacket);
}
public void HandleMove(Player player, C_Move movePacket)
{
if (player == null)
return;
// TODO : 검증
PositionInfo movePosInfo = movePacket.PosInfo;
ObjectInfo info = player.Info;
if (movePacket.PosInfo.Pos.PosX <= 0 || movePacket.PosInfo.Pos.PosX >= 1000 || movePacket.PosInfo.Pos.PosZ <= 0 || movePacket.PosInfo.Pos.PosZ >= 1000)
return;
info.PosInfo.State = movePosInfo.State;
player.DestPos = movePacket.PosInfo.Pos;
S_Move resMovePacket = new S_Move();
resMovePacket.ObjectId = player.Info.ObjectId;
resMovePacket.DestPosInfo = movePacket.PosInfo;
Broadcast(resMovePacket);
}
S_Move를 클라에게 보내면 그 값을 바로 처리해주어야한다.
public static void S_MoveHandler(PacketSession session, IMessage packet)
{
S_Move movePacket = (S_Move)packet;
GameObject go = Managers.Object.FindById(movePacket.ObjectId);
if (go == null)
return;
BaseController bc = go.GetComponent<BaseController>();
if (bc == null)
return;
Vector3 target = new Vector3(movePacket.DestPosInfo.Pos.PosX, movePacket.DestPosInfo.Pos.PosY, movePacket.DestPosInfo.Pos.PosZ);
bc.MoveTarget(target);
}
public override void MoveTarget(Vector3 target)
{
if (_agent == null) return;
StopCoroutine(OnMove(target));
StartCoroutine(OnMove(target));
}
IEnumerator OnMove(Vector3 target)
{
_agent.ResetPath();
_agent.SetDestination(target);
state = PlayerState.Moveing;
while (true)
{
if (Vector3.Distance(_agent.destination, transform.position) < 0.3f)
{
if(Managers.Object.MyPlayer.gameObject == gameObject)
{
C_StopMove moveStopPacket = new C_StopMove() { PosInfo = new PositionInfo() };
moveStopPacket.PosInfo.Pos = new Positions() { PosX = transform.position.x, PosY = transform.position.y, PosZ = transform.position.z };
Vector3 rotationEuler = transform.rotation.eulerAngles;
moveStopPacket.PosInfo.Rotate = new RotateInfo() { RotateX = rotationEuler.x, RotateY = rotationEuler.y, RotateZ = rotationEuler.z };
Managers.Network.Send(moveStopPacket);
}
break;
}
yield return null;
}
}
이제 목적지에 도착했다면 C_StopMove를 보내게되는데 이때 자신의 플레이어라면 C_StopMove를 보내준다.
public void HandleStopMove(Player player, C_StopMove stopMovePacket)
{
if (stopMovePacket.PosInfo.Pos.PosX <= 0 || stopMovePacket.PosInfo.Pos.PosX >= 1000 || stopMovePacket.PosInfo.Pos.PosZ <= 0 || stopMovePacket.PosInfo.Pos.PosZ >= 1000)
{
S_Banish banPacket = new S_Banish();
player.Session.Send(banPacket);
return;
}
player.Info.PosInfo.State = stopMovePacket.PosInfo.State;
player.Pos = stopMovePacket.PosInfo.Pos;
player.Ratate = stopMovePacket.PosInfo.Rotate;
player.DestPos = null;
S_StopMove resStopMovePacket = new S_StopMove();
resStopMovePacket.ObjectId = player.Info.ObjectId;
resStopMovePacket.PosOk = true;
resStopMovePacket.Rotate = stopMovePacket.PosInfo.Rotate;
resStopMovePacket.Pos = player.Pos;
Broadcast(resStopMovePacket);
}
이 역시 클라에게 다시 에코서버 처럼 보내주고 클라에서 좌표 값을 서버랑 맞춰줘야한다.
public override void StopMove(Vector3 receivedEuler, Vector3 receivePos)
{
_agent.ResetPath();
_agent.velocity = Vector3.zero;
state = PlayerState.Idle;
if (transform.position != receivePos)
transform.DOMove(receivePos, 0.2f);
Quaternion targetRotation = Quaternion.Euler(receivedEuler);
if (transform.rotation != targetRotation)
transform.DORotate(receivedEuler, 0.1f);
}
이렇게 해서 플레이어의 이동 동기화를 맞출 수 있다.
영상은 생략한다. 다음 포스팅은 플레이어의 공격과 모션에 대해 쓸 예정이다.
'Unity > 온라인 RPG' 카테고리의 다른 글
[Unity 3D] 몬스터 움직임 동기화 with Dedicated Server (0) | 2024.05.02 |
---|---|
[Unity 3D] 캐릭터 공격 애니메이션 동기화 (0) | 2024.04.24 |
[Unity 3D] Navigation을 이용한 캐릭터 이동 (0) | 2024.04.17 |
[Unity 3D] 캐릭터 선택창 및 캐릭터 생성 (0) | 2024.04.13 |
[Unity 3D] Title과 Login 구현 (UI 작업, 서버 연동) (0) | 2024.04.09 |