서버 쪽에서 mapId를 알면 맵 파일을 읽어와서 collision 판단을 할 수 있도록 구현하고 어떤 좌표를 기준으로 플레이어의 존재 유무를 판단하여 스킬 hit 판정을 해보자.
private static void GenerateMap()
{
GenerateByPath("Assets/Resources/Map");
GenerateByPath("../Common/MapData");
}
private static void GenerateByPath(string pathPrefix)
{
GameObject[] gameObjects = Resources.LoadAll<GameObject>("Prefabs/Map");
...
}
유니티 에디터에서 Tools->GenerateMap을 클릭하면 생성되는 맵 파일 경로를 추가하였다. 서버 측에서 Common/MapData 폴더 내에 존재하는 맵 텍스트 파일을 읽어들일 것이다.
...
public class Map
{
...
public struct Vector2Int
{
public int x;
public int y;
public Vector2Int(int x, int y) { this.x = x; this.y = y; }
public static Vector2Int up { get { return new Vector2Int(0, 1); } }
public static Vector2Int down { get { return new Vector2Int(0, -1); } }
public static Vector2Int left { get { return new Vector2Int(-1, 0); } }
public static Vector2Int right { get { return new Vector2Int(1, 0); } }
public static Vector2Int operator+(Vector2Int a, Vector2Int b)
{
return new Vector2Int(a.x + b.x, a.y + b.y);
}
}
public void LoadMap(int mapId, string pathPrefix = "../../../../../Common/MapData")
{
string mapName = "Map_" + mapId.ToString("000");
// Collision 관련 파일
string text = File.ReadAllText($"{pathPrefix}/{mapName}.txt");
StringReader reader = new StringReader(text);
MinX = int.Parse(reader.ReadLine());
...
}
Client에서 MapManager에 있는 코드를 통째로 Server에서 새로 만든 Map.cs에 붙여넣어주었고, 유니티 관련 코드는 삭제하였다. 2차원 좌표가 필요하기 때문에 Vector2Int 구조체를 추가로 정의해주었다.
public GameRoom Add(int mapId)
{
GameRoom gameRoom = new GameRoom();
gameRoom.Init(mapId);
...
}
Map _map = new Map(); // 내가 소속 중인 맵
public void Init(int mapId)
{
_map.LoadMap(mapId);
}

Server::Program에서 RoomManager.Add(1)를 호출하여 게임 룸을 하나 만들고, 받아온 맵 id를 게임 룸 내부의 Init 함수의 인자로 전달하고, Map 객체의 LoadMap의 인자로 또 전달하여 "../../../../../Common/MapData"의 경로에 들어가서 맵 파일을 긁어오도록 구현했다.
Dictionary<int, Player> _players = new Dictionary<int,Player>();
플레이어를 List로 처음에 담아두도록 구현했는데 이 방법이 아닌 playerId를 키로 이용하여 빠르게 찾을 수 있도록 딕셔너리로 바꿔주었다.
bool[,] _collision; // 벽 여부 관리
Player[,] _players; // 플레이어 여부 관리. 추후 몬스터도 추가되면 Creature 부모 클래스로 바꿀 수 있음
...
public bool CanGo(Vector2Int cellPos, bool checkObjects = true)
{
if (cellPos.x < MinX || cellPos.x > MaxX)
return false;
if (cellPos.y < MinY || cellPos.y > MaxY)
return false;
int x = cellPos.x - MinX;
int y = MaxY - cellPos.y;
return !_collision[y, x] && (!checkObjects || _players[y,x] == null);
}
...
public void LoadMap(int mapId, string pathPrefix = "../../../../../Common/MapData")
{
...
_collision = new bool[yCount, xCount];
_players = new Player[yCount, xCount];
...
}
Map 클래스에서 기존에 벽 여부만 관리하고 갈 수 있는지 여부를 판단했으나 이번에는 플레이어 여부도 관리를 해주도록 해주었다. CanGo 매개변수에 checkObjects를 추가해서 false일 경우 플레이어 존재 여부를 체크하지 않도록 한다.
public void HandleMove(Player player, C_Move movePacket)
{
if (player == null)
return;
lock(_lock)
{
// TODO : 검증
PositionInfo movePosInfo = movePacket.PosInfo; // 플레이어가 이동할 위치
PlayerInfo info = player.Info; // 현재 플레이어 정보
// 다른 좌표로 이동할 경우 이동 할 수 있는지 체크
if(movePosInfo.PosX != info.PosInfo.PosX || movePosInfo.PosY != info.PosInfo.PosY)
{
if (_map.CanGo(new Vector2Int(movePosInfo.PosX, movePosInfo.PosY)) == false)
return;
}
info.PosInfo.State = movePosInfo.State;
info.PosInfo.MoveDir = movePosInfo.MoveDir;
_map.ApplyMove(player, new Vector2Int(movePosInfo.PosX, movePosInfo.PosY));
public bool ApplyMove(Player player, Vector2Int dest)
{
PositionInfo posInfo = player.Info.PosInfo;
if (CanGo(dest, true) == false)
return false;
return true;
}
플레이어의 이동 패킷 좌표와 현재 좌표를 비교하여 이동하려고 하는 경우 갈 수 있는지 여부를 체크해서 만약 갈 수 있는 경우 ApplyMove 함수를 호출하여 현재 플레이어 위치를 null로 갱신하고 새로운 좌표에 플레이어 객체를 넣어주도록 한다.