지금 시작하기 앞서 화살을 쏠 때 애니메이션과 연속적인 화살이 나가지 않는 버그가 있으니 그 부분만 수정하고 진행하겠다.
public void UseSkill(int skillId)
{
if (skillId == 1)
{
_coSkill = StartCoroutine("CoStartPunch");
}
else if(skillId == 2)
{
_coSkill = StartCoroutine("CoStartShootArrow");
}
}
요 부분을 빼먹었기 때문이다.
Data & Config
이번에 해볼거는 화살이 소멸될 때 플레이어나 몬스터에게 맞추면 데미지를 들어가게 할 것이다. 그런데 지금 서버나 클라에서도 하드 코딩 되어 있는 부분이 많다. 예를 들어 스킬id나 플레이어 처리 등 데이터를 하드 코딩 처리했는데 이 부분을 쉽게 관리하게 수정하자.
클라이언트에서 DataManager를 통해 관리하던 것을 서버쪽에서도 진행해보자.
그런데 문제는 Unity 자체에서 제공하는 Json 파일들에 대한 유틸들이 서버쪽에는 동작하지 않기 때문에 Nuget 패키지에서 Json처리에 대한 패키지를 설치해주어야 한다.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Server.Data
{
public interface ILoader<Key, Value>
{
Dictionary<Key, Value> MakeDict();
}
public class DataManager
{
public Dictionary<int, Data.Stat> StatDict { get; private set; } = new Dictionary<int, Data.Stat>();
public void Init()
{
StatDict = LoadJson<Data.StatData, int, Data.Stat>("StatData").MakeDict();
}
Loader LoadJson<Loader, Key, Value>(string path) where Loader : ILoader<Key, Value>
{
string text = File.ReadAllText($"");
return Newtonsoft.Json.JsonConvert.DeserializeObject<Loader>(text);
}
}
}
따라서 서버에서 데이터를 로드하는 부분을 만들어줬다. 그런데 우리가 나중에 생각해보면 데이터 파일들이 여러 경로나 서버 파일이 다른 경로에 있을 가능성이 매우 높다.(배포 시) 그렇기에 ConfigManager를 두어 서버에 관한 설정들을 조절해 파일들을 불러오게 하자.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Server.Data
{
[Serializable]
public class ServerConfig
{
public string dataPath;
// 서버 동접자 등등
// 서버에 관한 설정
}
public class ConfigManager
{
public static ServerConfig Config { get; private set; }
public static void LoadConfig()
{
string text = File.ReadAllText("config.json");
Config = Newtonsoft.Json.JsonConvert.DeserializeObject<ServerConfig>(text);
}
}
}
{
"dataPath": "../../../../../Client/Assets/Resources/Data"
}
Json 파일은 이렇게 설정하면 된다.
이제 Skill에 대해 작업을 한번 해보자.
#region Skill
[Serializable]
public class Skill
{
public int id;
public string name;
public float cooldown;
public int damage;
public SkillType skillType;
public ProjectileInfo projectile;
}
public class ProjectileInfo
{
public string name;
public float speed;
public int range;
public string prefab;
}
[Serializable]
public class SkillData : ILoader<int, Skill>
{
public List<Skill> skills = new List<Skill>();
public Dictionary<int, Skill> MakeDict()
{
Dictionary<int, Skill> dict = new Dictionary<int, Skill>();
foreach (Skill skill in skills)
dict.Add(skill.id, skill);
return dict;
}
}
#endregion
이렇게 스킬에 대한 정보를 만들어주고 DataManager에서 Skill을 로드할 수 있게 처리해주면 된다.
{
"skills": [
{
"id": "1",
"name": "평타",
"cooldown": "0.2",
"damage": "10",
"skillType": "SkillAuto"
},
{
"id": "2",
"name": "화살 공격",
"cooldown": "0.2",
"damage": "5",
"skillType": "SkillProjectile",
"projectile": {
"name": "화살",
"speed": "10.0",
"range": "10",
"prefab": "Creature/Arrow"
}
}
]
}
스킬 정보에 대한 Json 파일이다.
스탯
이제 우리가 json파일을 통해 서버에서 데미지 등을 끌어올 수 있게 됐다. 그래서 우리가 원하는 피격을 할 수 있게 됐는데 문제는 체력 등 스탯이 존재하지 않기 때문에 먼저 만들어보자.
message StatInfo {
int32 hp = 1;
int32 maxHp = 2;
float speed = 3;
}
using Google.Protobuf.Protocol;
using Server.Game.Room;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Object
{
public class Player : GameObject
{
public ClientSession Session { get; set; }
public Player()
{
ObjectType = GameObjectType.Player;
Speed = 10.0f;
}
public override void OnDamaged(GameObject attacker, int damage)
{
Console.WriteLine($"TODO : damage : {damage}");
}
}
}
using Google.Protobuf.Protocol;
using Server.Game.Room;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Object
{
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() { PosInfo = new PositionInfo() };
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 GameObject()
{
Info.PosInfo = PosInfo;
Info.StatInfo = Stat;
}
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 virtual void OnDamaged(GameObject attacker, int damage)
{
}
}
}
using Google.Protobuf.Protocol;
using Server.Game.Room;
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Object
{
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);
Console.WriteLine($"damage : {Data.damage}");
}
Room.LeaveGame(Id);
}
}
}
}
이제 클라이언트 쪽에서 확인해보자. CreatureController에 추가해준다.
StatInfo _stat = new StatInfo();
public StatInfo Stat
{
get { return _stat; }
set
{
if (_stat.Equals(value))
return;
_stat.Hp = value.Hp;
_stat.MaxHp = value.MaxHp;
_stat.Speed = value.Speed;
}
}
public float Speed
{
get { return Stat.Speed; }
set { Stat.Speed = value; }
}
이렇게 해주면 이제 체력이 정상적으로 동기화 된다.
데미지 판정
드디어 OnDamaged함수를 작성해보자.
public virtual void OnDamaged(GameObject attacker, int damage)
{
Stat.Hp -= damage;
if (Stat.Hp <= 0)
{
Stat.Hp = 0;
OnDead(attacker);
}
S_ChangeHp changePacket = new S_ChangeHp();
changePacket.ObjectId = Id;
changePacket.Hp = Stat.Hp;
Room.Broadcast(changePacket);
}
public virtual void OnDead(GameObject attacker)
{
}
'Unity > 온라인 RPG' 카테고리의 다른 글
[Unity 2D] 서버 구조 변경 - Command 패턴과 Job (0) | 2024.03.14 |
---|---|
[Unity 2D] 서버 연동 - Hp bar와 DieEffect 그리고 몬스터 Ai (0) | 2024.03.13 |
[Unity 2D] 서버 연동 - 스킬 동기화와 히트 판정 (0) | 2024.03.11 |
[Unity 2D] 서버 연동 - MyPlayer 분리 및 이동 동기화 (0) | 2024.03.08 |
[Unity 2D] 서버 연동 - 멀티플레이 환경 및 게임 입장 (1) | 2024.03.07 |