Object Manager
- GameObject 생성과 제거하는 함수를 제공한다.
- GameObject 생성 시 ID를 할당한다.
- 생성된 GameObject 중 Player만 관리한다.
📄 ObjectManager.cs
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class ObjectManager
{
public static ObjectManager Instance { get; } = new ObjectManager();
object _lock = new object();
Dictionary<int, Player> _players = new Dictionary<int, Player>();
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;
}
}
}
Game Object
Game Object
- 현재 Scene에 존재하는 Game Object의 ID, 이름, 위치, Stat 등의 데이터를 관리한다.
- GameOject에 발생하는 이벤트를 처리한다.
📄 GameObject.cs
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class GameObject
{
public GameObjectType ObjectType { get; protected set; } = GameObjectType.None;
public int Id
{
get { return Info.ObjectId; }
set { Info.ObjectId = value; }
}
public GameRoom Room { get; set; }
public ObjectInfo Info { get; set; } = new ObjectInfo();
public PositionInfo PosInfo { get; private set; } = new PositionInfo();
public StatInfo Stat { get; private set; } = new StatInfo();
public float Speed
{
get { return Stat.Speed; }
set { Stat.Speed = value; }
}
public int Hp
{
get { return Stat.Hp; }
set { Stat.Hp = Math.Clamp(value, 0, Stat.MaxHp); }
}
public MoveDir Dir
{
get { return PosInfo.MoveDir; }
set { PosInfo.MoveDir = value; }
}
public CreatureState State
{
get { return PosInfo.State; }
set { PosInfo.State = value; }
}
public GameObject()
{
Info.PosInfo = PosInfo;
Info.StatInfo = Stat;
}
public virtual void Update()
{
}
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;
}
public static MoveDir GetDirFromVec(Vector2Int dir)
{
if (dir.x > 0)
return MoveDir.Right;
else if (dir.x < 0)
return MoveDir.Left;
else if (dir.y > 0)
return MoveDir.Up;
else
return MoveDir.Down;
}
public virtual void OnDamaged(GameObject attacker, int damage)
{
if (Room == null)
{
return;
}
Stat.Hp = Math.Max(Stat.Hp - damage, 0);
S_ChangeHp changePacket = new S_ChangeHp();
changePacket.ObjectId = Id;
changePacket.Hp = Stat.Hp;
Room.Broadcast(changePacket);
if (Stat.Hp <= 0)
{
OnDead(attacker);
}
}
public virtual void OnDead(GameObject attacker)
{
if (Room == null)
{
return;
}
S_Die diePacket = new S_Die();
diePacket.ObjectId = Id;
diePacket.AttackerId = attacker.Id;
Room.Broadcast(diePacket);
GameRoom room = Room;
room.LeaveGame(Id);
Stat.Hp = Stat.MaxHp;
PosInfo.State = CreatureState.Idle;
PosInfo.MoveDir = MoveDir.Down;
PosInfo.PosX = 0;
PosInfo.PosY = 0;
room.EnterGame(this);
}
}
}
Player
- GameObject의 자식 클래스이다.
- Player에 발생하는 이벤트를 처리한다.
📄 Player.cs
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class Player : GameObject
{
public ClientSession Session { get; set; }
public Player()
{
ObjectType = GameObjectType.Player;
}
public override void OnDamaged(GameObject attacker, int damage)
{
base.OnDamaged(attacker, damage);
}
public override void OnDead(GameObject attacker)
{
base.OnDead(attacker);
}
}
}
Monster
- GameObject의 자식 클래스이다.
- Monster에 발생하는 이벤트를 처리한다.
- Monster의 행동을 처리한다.
📄 Monster.cs
using Google.Protobuf.Protocol;
using Server.Data;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
namespace Server.Game
{
public class Monster : GameObject
{
public Monster()
{
ObjectType = GameObjectType.Monster;
Stat.Level = 1;
Stat.Hp = 100;
Stat.MaxHp = 100;
Stat.Speed = 5.0f;
State = CreatureState.Idle;
}
public override void Update()
{
switch (State)
{
case CreatureState.Idle:
UpdateIdle();
break;
case CreatureState.Moving:
UpdateMoving();
break;
case CreatureState.Skill:
UpdateSkill();
break;
case CreatureState.Dead:
UpdateDead();
break;
}
}
Player _target;
int _searchCellDist = 10;
int _chaseCellDist = 20;
long _nextSearchTick = 0;
protected virtual void UpdateIdle()
{
if (_nextSearchTick > Environment.TickCount64)
return;
_nextSearchTick = Environment.TickCount64 + 1000;
Player target = Room.FindPlayer(p =>
{
Vector2Int dir = p.CellPos - CellPos;
return dir.cellDistFromZero <= _searchCellDist;
});
if (target == null)
return;
_target = target;
State = CreatureState.Moving;
}
int _skillRange = 1;
long _nextMoveTick = 0;
protected virtual void UpdateMoving()
{
if (_nextMoveTick > Environment.TickCount64)
return;
int moveTick = (int)(1000 / Speed);
_nextMoveTick = Environment.TickCount64 + moveTick;
if (_target == null || _target.Room != Room)
{
_target = null;
State = CreatureState.Idle;
BroadcastMove();
return;
}
Vector2Int dir = _target.CellPos - CellPos;
int dist = dir.cellDistFromZero;
if (dist == 0 || dist > _chaseCellDist)
{
_target = null;
State = CreatureState.Idle;
BroadcastMove();
return;
}
List<Vector2Int> path = Room.Map.FindPath(CellPos, _target.CellPos, checkObjects: false);
if (path.Count < 2 || path.Count > _chaseCellDist)
{
_target = null;
State = CreatureState.Idle;
BroadcastMove();
return;
}
if (dist <= _skillRange && (dir.x == 0 || dir.y == 0))
{
_coolTick = 0;
State = CreatureState.Skill;
return;
}
Dir = GetDirFromVec(path[1] - CellPos);
Room.Map.ApplyMove(this, path[1]);
BroadcastMove();
}
void BroadcastMove()
{
S_Move movePacket = new S_Move();
movePacket.ObjectId = Id;
movePacket.PosInfo = PosInfo;
Room.Broadcast(movePacket);
}
long _coolTick = 0;
protected virtual void UpdateSkill()
{
if (_coolTick == 0)
{
if (_target == null || _target.Room != Room || _target.Hp == 0)
{
_target = null;
State = CreatureState.Moving;
BroadcastMove();
return;
}
Vector2Int dir = (_target.CellPos - CellPos);
int dist = dir.cellDistFromZero;
bool canUseSkill = (dist <= _skillRange && (dir.x == 0 || dir.y == 0));
if (canUseSkill == false)
{
State = CreatureState.Moving;
BroadcastMove();
return;
}
MoveDir lookDir = GetDirFromVec(dir);
if (Dir != lookDir)
{
Dir = lookDir;
BroadcastMove();
}
Skill skillData = null;
DataManager.SkillDict.TryGetValue(1, out skillData);
_target.OnDamaged(this, skillData.damage + Stat.Attack);
S_Skill skill = new S_Skill() { Info = new SkillInfo() };
skill.ObjectId = Id;
skill.Info.SkillId = skillData.id;
Room.Broadcast(skill);
int coolTick = (int)(1000 * skillData.cooldown);
_coolTick = Environment.TickCount64 + coolTick;
}
if (_coolTick > Environment.TickCount64)
return;
_coolTick = 0;
}
protected virtual void UpdateDead()
{
}
}
}
Projectile
- GameObject의 자식 클래스이며 모든 투사체의 부모 클래스이다.
📄 Projectile.cs
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class Projectile : GameObject
{
public Data.Skill Data { get; set; }
public Projectile()
{
ObjectType = GameObjectType.Projectile;
}
public virtual void Update()
{
}
}
}
Arrow
- Projectile의 자식 클래스이다.
- Arrow의 움직임을 처리한다.
📄 Arrow.cs
using Google.Protobuf.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class Arrow : Projectile
{
public GameObject Owner { get; set; }
long _nextMoveTick = 0;
public override void Update()
{
if (Data == null || Data.projectile == null || Owner == null || Room == null)
return;
if (_nextMoveTick >= Environment.TickCount64)
return;
long tick = (long)(1000 / Data.projectile.speed);
_nextMoveTick = Environment.TickCount64 + tick;
Vector2Int destPos = GetFrontCellPos();
if (Room.Map.CanGo(destPos))
{
CellPos = destPos;
S_Move movePacket = new S_Move();
movePacket.ObjectId = Id;
movePacket.PosInfo = PosInfo;
Room.Broadcast(movePacket);
Console.WriteLine("Move Arrow");
}
else
{
GameObject target = Room.Map.Find(destPos);
if (target != null)
{
target.OnDamaged(this, Data.damage + Owner.Stat.Attack);
}
Room.Push(Room.LeaveGame, Id);
}
}
}
}