화살 스킬 같은 경우 클라이언트에서 화살을 생성해서 날린다고 해도 서버에서 굳이 화살 좌표를 맞춰줘야 할지 선택을 해야하는데 와우 같은 게임의 경우 A플레이어가 몬스터를 대상으로 스킬을 쓴 후 바로 다른 지역으로 이동해서 사라지더라도 B플레이어가 뒤늦게 들어왔을 때 스킬만 덩그러니 발동되는 것을 볼 수 있다. 따라서 스킬도 서버에서 어느정도 관리를 해주는게 좋다.
message ObjectInfo {
int32 objectId = 1;
string name = 2;
PositionInfo posInfo = 3;
}
message S_Spawn {
repeated ObjectInfo objects = 1;
}
message S_EnterGame {
ObjectInfo player = 1;
}
먼저 PlayerInfo를 ObjectInfo로 바꿔주고, playerId가 아닌 objectId로 바꿔주었다.
public class GameObject
{
public GameObjectType ObjectType { get; protected set; } = GameObjectType.None;
public int Id
{
get { return Info.ObjectId; }
set { Info.ObjectId = value; }
}
public ObjectInfo Info { get; set; } = new ObjectInfo() { PosInfo = new PositionInfo() };
public PositionInfo PosInfo { get; private set; } = new PositionInfo();
public GameObject()
{
Info.PosInfo = PosInfo; // 따로 관리를 하다가 나중에 패킷을 보낼때 PositionInfo를 ObjectInfo의 PosInfo에 넣어주고 보냄.
}
public GameRoom Room { get; set; }
public Vector2Int CellPos
{
get
{
return new Vector2Int(PosInfo.PosX, PosInfo.PosY);
}
set
{
PosInfo.PosX = value.x;
PosInfo.PosY = value.y;
}
}
public Vector2Int GetFrontCellPos()
{
return GetFrontCellPos(PosInfo.MoveDir);
}
public Vector2Int GetFrontCellPos(MoveDir dir)
{
Vector2Int cellPos = CellPos;
switch (dir)
{
case MoveDir.Up:
cellPos += Vector2Int.up;
break;
case MoveDir.Down:
cellPos += Vector2Int.down;
break;
case MoveDir.Left:
cellPos += Vector2Int.left;
break;
case MoveDir.Right:
cellPos += Vector2Int.right;
break;
}
return cellPos;
}
}
먼저 최상위 클래스인 GameObject를 만들고, 이를 상속하는 모든 오브젝트가 공유해서 사용할 수 있는 GetFrontCellPos, CellPos, Room, 그리고 패킷으로 보낼때 사용할 ObjectInfo, PositionInfo, 그리고 GameObjectType와 각 오브젝트마다 고유 id를 관리하는 Id를 정의해준다.
그리고 PositionInfo 위치정보는 따로 관리를 해줘서 GameObject 생성자에서 PosInfo에 넣어주도록 한다.
public class Player : GameObject
{
public ClientSession Session { get; set; }
public Player()
{
ObjectType = GameObjectType.Player;
}
}
플레이어는 플레이어만 각자 세션을 가지고 있기 때문에 GameObject로 올려주지 않았다.
public class Monster : GameObject
{
public Monster()
{
ObjectType = GameObjectType.Monster;
}
}
public class Projectile : GameObject
{
public Projectile()
{
ObjectType = GameObjectType.Projectile;
}
}
public class Arrow : Projectile
{
public GameObject Owner { get; set; }
public void Update()
{
}
}
투사체는 여러 종류가 있을 수 있기 때문에 Projectile 상위 클래스를 하나 정의한 다음 Arrow가 상속하도록 정의했다.
Arrow는 자신이 어느 오브젝트 소속인지 알 수 있게 하여 추후 데미지 판정이나 아군이 쐈는지 등을 판별할때 Owner를 사용할 수 있다.
public class ObjectManager
{
public static ObjectManager Instance { get; } = new ObjectManager();
...
Dictionary<int, Player> _players = new Dictionary<int, Player>();
// 맨 처음 비트는 부호비트
// 그다음 7비트는 ObjectType
// 나머지는 playerId
int _counter = 0; // 게임오브젝트 개수
public T Add<T>() where T : GameObject, new()
{
T gameObject = new T();
lock(_lock)
{
gameObject.Id = GenerateId(gameObject.ObjectType);
if(gameObject.ObjectType == GameObjectType.Player)
{
_players.Add(gameObject.Id, gameObject as Player);
}
}
return gameObject;
}
int GenerateId(GameObjectType type)
{
lock(_lock)
{
return ((int)type << 24) | (_counter++);
}
}
public static GameObjectType GetObjectTypeById(int id)
{
int type = (id >> 24) & 0x7F;
return (GameObjectType)type;
}
public bool Remove(int objectId)
{
GameObjectType objectType = GetObjectTypeById(objectId);
lock (_lock)
{
if(objectType == GameObjectType.Player)
return _players.Remove(objectId);
}
return false;
}
public Player Find(int objectId)
{
GameObjectType objectType = GetObjectTypeById(objectId);
lock (_lock)
{
if (objectType == GameObjectType.Player)
{
Player player = null;
if (_players.TryGetValue(objectId, out player))
return player;
}
}
return null;
}
}