diff --git a/dependency/proto/.clang-format b/dependency/proto/.clang-format old mode 100755 new mode 100644 diff --git a/dependency/proto/Message2Clients.proto b/dependency/proto/Message2Clients.proto old mode 100755 new mode 100644 diff --git a/dependency/proto/Message2Server.proto b/dependency/proto/Message2Server.proto old mode 100755 new mode 100644 diff --git a/dependency/proto/MessageType.proto b/dependency/proto/MessageType.proto old mode 100755 new mode 100644 diff --git a/dependency/proto/README.md b/dependency/proto/README.md old mode 100755 new mode 100644 diff --git a/dependency/proto/cpp_output.sh b/dependency/proto/cpp_output.sh old mode 100755 new mode 100644 diff --git a/dependency/proto/format.sh b/dependency/proto/format.sh old mode 100755 new mode 100644 diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs new file mode 100644 index 0000000..9631793 --- /dev/null +++ b/logic/GameClass/GameObj/Character.cs @@ -0,0 +1,457 @@ +using Preparation.GameData; +using Preparation.Interface; +using Preparation.Utility; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace GameClass.GameObj +{ + public partial class Character : GameObj, ICharacter // 负责人LHR摆烂终了 + { + private readonly object beAttackedLock = new(); + +#region 角色的基本属性及方法,包括与道具的交互方法 + /* + //THUAI5子弹 + /// + /// 装弹冷却 + /// + protected int cd; + public int CD + { + get => cd; + private + set { + lock (gameObjLock) + { + cd = value; + Debugger.Output(this, string.Format("'s CD has been set to: {0}.", value)); + } + } + } + public int OrgCD { get; protected set; } + protected int maxBulletNum; + public int MaxBulletNum => maxBulletNum; // 人物最大子弹数 + protected int bulletNum; + public int BulletNum => bulletNum; // 目前持有的子弹数 + */ + public int MaxHp { get; protected set; } // 最大血量 + protected int hp; + public int HP + { + get => hp; + set { + lock (gameObjLock) + hp = value <= MaxHp ? value : MaxHp; + } + } + private int deathCount = 0; + public int DeathCount => deathCount; // 玩家的死亡次数 + + private int score = 0; + public int Score + { + get => score; + } + + // public double AttackRange => BulletFactory.BulletAttackRange(this.BulletOfPlayer); + + private double vampire = 0; // 回血率:0-1之间 + public double Vampire + { + get => vampire; + set { + if (value > 1) + lock (gameObjLock) + vampire = 1; + else if (value < 0) + lock (gameObjLock) + vampire = 0; + else + lock (gameObjLock) + vampire = value; + } + } + private double OriVampire { get; } + + /* + public readonly BulletType OriBulletOfPlayer; + private BulletType bulletOfPlayer; + public BulletType BulletOfPlayer + { + get => bulletOfPlayer; + set { + lock (gameObjLock) + bulletOfPlayer = value; + } + } + */ + + private Prop? propInventory; + public Prop? PropInventory // 持有的道具 + { + get => propInventory; + set { + lock (gameObjLock) + { + propInventory = value; + Debugger.Output(this, " prop becomes " + (PropInventory == null ? "null" : PropInventory.ToString())); + } + } + } + + /// + /// 使用物品栏中的道具 + /// + /// 被使用的道具 + public Prop? UseProp() + { + lock (gameObjLock) + { + var oldProp = PropInventory; + PropInventory = null; + return oldProp; + } + } + + /// + /// 是否正在更换道具(包括捡起与抛出) + /// + private bool isModifyingProp = false; + public bool IsModifyingProp + { + get => isModifyingProp; + set { + lock (gameObjLock) + { + isModifyingProp = value; + } + } + } + + /// + /// 是否在隐身 + /// + private bool isInvisible = false; + public bool IsInvisible + { + get => isInvisible; + set { + lock (gameObjLock) + { + isInvisible = value; + } + } + } + /* + /// + /// 进行一次远程攻击 + /// + /// 子弹初始位置偏差值 + /// 攻击操作发出的子弹 + public Bullet? RemoteAttack(XY posOffset) + { + if (TrySubBulletNum()) + return ProduceOneBullet(this.Position + posOffset); + else + return null; + } + protected Bullet? ProduceOneBullet(XY initPos) + { + var newBullet = BulletFactory.GetBullet(this); + newBullet?.SetPosition(initPos); + return newBullet; + } + + /// + /// 尝试将子弹数量减1 + /// + /// 减操作是否成功 + private bool TrySubBulletNum() + { + lock (gameObjLock) + { + if (bulletNum > 0) + { + --bulletNum; + return true; + } + return false; + } + } + /// + /// 尝试将子弹数量加1 + /// + /// 加操作是否成功 + public bool TryAddBulletNum() + { + lock (gameObjLock) + { + if (bulletNum < maxBulletNum) + { + ++bulletNum; + return true; + } + return false; + } + } + */ + /// + /// 尝试加血 + /// + /// 欲加量 + /// 加操作是否成功 + public bool TryAddHp(int add) + { + if (hp < MaxHp) + { + lock (gameObjLock) + hp = MaxHp > hp + add ? hp + add : MaxHp; + Debugger.Output(this, " hp has added to: " + hp.ToString()); + return true; + } + return false; + } + /// + /// 尝试减血 + /// + /// 减血量 + /// 减操作是否成功 + public bool TrySubHp(int sub) + { + if (hp > 0) + { + lock (gameObjLock) + hp = 0 >= hp - sub ? 0 : hp - sub; + Debugger.Output(this, " hp has subed to: " + hp.ToString()); + return true; + } + return false; + } + /// + /// 增加死亡次数 + /// + /// 当前死亡次数 + private int AddDeathCount() + { + lock (gameObjLock) + { + ++deathCount; + return deathCount; + } + } + /// + /// 加分 + /// + /// 增加量 + public void AddScore(int add) + { + lock (gameObjLock) + { + score += add; + Debugger.Output(this, " 's score has been added to: " + score.ToString()); + } + } + /// + /// 减分 + /// + /// 减少量 + public void SubScore(int sub) + { + lock (gameObjLock) + { + score -= sub; + Debugger.Output(this, " 's score has been subed to: " + score.ToString()); + } + } + /* + /// + /// 遭受攻击 + /// + /// + /// + /// 伤害来源 + /// 人物在受到攻击后死了吗 + public bool BeAttack(Bullet bullet) + { + + lock (beAttackedLock) + { + if (hp <= 0) + return false; // 原来已经死了 + if (bullet.Parent.TeamID != this.TeamID) + { + + if (HasShield) + { + if (bullet.HasSpear) + _ = TrySubHp(bullet.AP); + else + return false; + } + else + { + TrySubHp(bullet.AP); + } +#if DEBUG + Console.WriteLine($"PlayerID:{ID} is being shot! Now his hp is {hp}."); +#endif + if (hp <= 0) + TryActivatingLIFE(); // 如果有复活甲 + } + return hp <= 0; + } + } + /// + /// 攻击被反弹,反弹伤害不会再被反弹 + /// + /// + /// + /// 反弹伤害者 + /// 是否因反弹伤害而死 + private bool BeBounced(int subHP, bool hasSpear, Character? bouncer) + { + lock (beAttackedLock) + { + if (hp <= 0) + return false; + if (!(bouncer?.TeamID == this.TeamID)) + { + if (hasSpear || !HasShield) + _ = TrySubHp(subHP); + if (hp <= 0) + TryActivatingLIFE(); + } + return hp <= 0; + } + } + */ + /// + /// 角色所属队伍ID + /// + private long teamID = long.MaxValue; + public long TeamID + { + get => teamID; + set { + lock (gameObjLock) + { + teamID = value; + Debugger.Output(this, " joins in the team: " + value.ToString()); + } + } + } + private long playerID = long.MaxValue; + public long PlayerID + { + get => playerID; + set { + lock (gameObjLock) + { + playerID = value; + } + } + } + /// + /// 角色携带的信息 + /// + private string message = "THUAI5"; + public string Message + { + get => message; + set { + lock (gameObjLock) + { + message = value; + } + } + } +#endregion + +#region 角色拥有的buff相关属性、方法 + public void AddMoveSpeed(int buffTime, double add = 2.0) => buffManeger.AddMoveSpeed(add, buffTime, newVal => + { MoveSpeed = newVal < GameData.characterMaxSpeed ? newVal : GameData.characterMaxSpeed; }, + OrgMoveSpeed); + public bool HasFasterSpeed => buffManeger.HasFasterSpeed; + + public void AddShield(int shieldTime) => buffManeger.AddShield(shieldTime); + public bool HasShield => buffManeger.HasShield; + + public void AddLIFE(int LIFETime) => buffManeger.AddLIFE(LIFETime); + public bool HasLIFE => buffManeger.HasLIFE; + + public void AddSpear(int spearTime) => buffManeger.AddSpear(spearTime); + public bool HasSpear => buffManeger.HasSpear; + + private Array buffTypeArray = Enum.GetValues(typeof(BuffType)); + public Dictionary Buff + { + get { + Dictionary buff = new Dictionary(); + foreach (BuffType type in buffTypeArray) + { + if (type != BuffType.Null) + buff.Add(type, GetBuffStatus(type)); + } + return buff; + } + } + private bool GetBuffStatus(BuffType type) + { + switch (type) + { + case BuffType.Spear: + return this.HasSpear; + case BuffType.AddSpeed: + return this.HasFasterSpeed; + case BuffType.Shield: + return this.HasShield; + case BuffType.AddLIFE: + return this.HasLIFE; + default: + return false; + } + } + private void TryActivatingLIFE() + { + if (buffManeger.TryActivatingLIFE()) + { + hp = MaxHp; + } + } +#endregion + public override void Reset() // 要加锁吗? + { + _ = AddDeathCount(); + base.Reset(); + this.MoveSpeed = OrgMoveSpeed; + HP = MaxHp; + PropInventory = null; + // BulletOfPlayer = OriBulletOfPlayer; + // lock (gameObjLock) + // bulletNum = maxBulletNum; + + buffManeger.ClearAll(); + IsInvisible = false; + this.Vampire = this.OriVampire; + } + public override bool IsRigid => true; + public override ShapeType Shape => ShapeType.Circle; + protected override bool IgnoreCollideExecutor(IGameObj targetObj) + { + if (targetObj.Type == GameObjType.BirthPoint) + { + if (object.ReferenceEquals(((BirthPoint)targetObj).Parent, this)) // 自己的出生点可以忽略碰撞 + { + return true; + } + } + else if (targetObj.Type == GameObjType.Prop) // 自己队的地雷忽略碰撞 + { + return true; + } + return false; + } + } +} diff --git a/logic/GameClass/GameObj/GameObj.cs b/logic/GameClass/GameObj/GameObj.cs new file mode 100644 index 0000000..27f2abd --- /dev/null +++ b/logic/GameClass/GameObj/GameObj.cs @@ -0,0 +1,185 @@ +using Preparation.Interface; +using Preparation.Utility; +using System.Threading; + +namespace GameClass.GameObj +{ + /// + /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR + /// + public abstract class GameObj : IMoveable + { + protected readonly object gameObjLock = new(); + /// + /// 可移动物体专用锁 + /// + public object MoveLock => gameObjLock; + + protected readonly XY birthPos; + + private GameObjType type; + public GameObjType Type => type; + + private static long currentMaxID = 0; // 目前游戏对象的最大ID + public const long invalidID = long.MaxValue; // 无效的ID + public const long noneID = long.MinValue; + public long ID { get; } + + private XY position; + public XY Position + { + get => position; + protected + set { + lock (gameObjLock) + { + position = value; + } + } + } + public abstract bool IsRigid { get; } + + private XY facingDirection = new(1, 0); + public XY FacingDirection + { + get => facingDirection; + set { + lock (gameObjLock) + facingDirection = value; + } + } + public abstract ShapeType Shape { get; } + + private bool canMove; + public bool CanMove + { + get => canMove; + set { + lock (gameObjLock) + { + canMove = value; + } + } + } + + private bool isMoving; + public bool IsMoving + { + get => isMoving; + set { + lock (gameObjLock) + { + isMoving = value; + } + } + } + + private bool isResetting; + public bool IsResetting + { + get => isResetting; + set { + lock (gameObjLock) + { + isResetting = value; + } + } + } + public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 + public int Radius { get; } + + private PlaceType place; + public PlaceType Place + { + get => place; + set { + lock (gameObjLock) + { + place = value; + } + } + } + protected int moveSpeed; + /// + /// 移动速度 + /// + public int MoveSpeed + { + get => moveSpeed; + set { + lock (gameObjLock) + { + moveSpeed = value; + } + } + } + /// + /// 原初移动速度 + /// + private int orgMoveSpeed; + public int OrgMoveSpeed + { + get => orgMoveSpeed; + protected + set { + orgMoveSpeed = value; + } + } + + // 移动,改变坐标 + public long Move(XY moveVec) + { + lock (gameObjLock) + { + FacingDirection = moveVec; + this.Position += moveVec; + } + return (long)(moveVec * moveVec); + } + /// + /// 设置位置 + /// + /// 新位置 + public void SetPosition(XY newpos) + { + Position = newpos; + } + /// + /// 设置移动速度 + /// + /// 新速度 + public void SetMoveSpeed(int newMoveSpeed) + { + MoveSpeed = newMoveSpeed; + } + /// + /// 复活时数据重置 + /// + public virtual void Reset() + { + lock (gameObjLock) + { + facingDirection = new XY(1, 0); + isMoving = false; + canMove = false; + isResetting = true; + this.position = birthPos; + } + } + /// + /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable + /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 + /// + /// 依具体类及该方法参数而定,默认为false + protected virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false; + bool IMoveable.IgnoreCollide(IGameObj targetObj) => IgnoreCollideExecutor(targetObj); + public GameObj(XY initPos, int initRadius, PlaceType initPlace, GameObjType initType) + { + this.Position = this.birthPos = initPos; + this.Radius = initRadius; + this.place = initPlace; + this.type = initType; + ID = Interlocked.Increment(ref currentMaxID); + } + } +} diff --git a/logic/GameClass/GameObj/Map/BirthPoint.cs b/logic/GameClass/GameObj/Map/BirthPoint.cs new file mode 100644 index 0000000..3d4ab37 --- /dev/null +++ b/logic/GameClass/GameObj/Map/BirthPoint.cs @@ -0,0 +1,29 @@ +using Preparation.Interface; +using Preparation.Utility; +using Preparation.GameData; + +namespace GameClass.GameObj +{ + /// + /// 出生点 + /// + public class BirthPoint : ObjOfCharacter + { + public BirthPoint(XY initPos) : + base(initPos, GameData.numOfPosGridPerCell / 2, PlaceType.Land, GameObjType.BirthPoint) + { + this.CanMove = false; + } + // 修改建议:需要避免非自己的玩家进入出生点,否则会重叠 + public override bool IsRigid => true; + protected override bool IgnoreCollideExecutor(IGameObj targetObj) + { + if (targetObj.Type != GameObjType.Character) + return true; // 非玩家不碰撞 + else if (targetObj.Type == GameObjType.Character && targetObj.ID == this.Parent.ID) + return true; // 出生点所属的玩家不碰撞 + return false; + } + public override ShapeType Shape => ShapeType.Square; + } +} \ No newline at end of file diff --git a/logic/GameClass/GameObj/ObjOfCharacter.cs b/logic/GameClass/GameObj/ObjOfCharacter.cs new file mode 100644 index 0000000..a09c6e5 --- /dev/null +++ b/logic/GameClass/GameObj/ObjOfCharacter.cs @@ -0,0 +1,28 @@ +using Preparation.Interface; +using Preparation.Utility; + +namespace GameClass.GameObj +{ + /// + /// 所有物,具有主人(Parent)(特定玩家)属性的对象 + /// + public abstract class ObjOfCharacter : GameObj, IObjOfCharacter + { + private ICharacter? parent = null; // 主人 + public ICharacter? Parent + { + get => parent; + set { + lock (gameObjLock) + { + parent = value; + } + } + } + // LHR注:本来考虑在构造函数里设置parent属性,见THUAI4在游戏引擎中才设置该属性,作罢。——2021/9/24 + public ObjOfCharacter(XY initPos, int initRadius, PlaceType initPlace, GameObjType initType) : + base(initPos, initRadius, initPlace, initType) + { + } + } +} diff --git a/logic/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs new file mode 100644 index 0000000..08ca0f7 --- /dev/null +++ b/logic/GameEngine/CollisionChecker.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Preparation.Interface; +using Preparation.Utility; +using Preparation.GameData; + +namespace GameEngine +{ + internal class CollisionChecker + { + /// + /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体 + /// + /// 移动的物体 + /// 移动的位移向量 + /// 和它碰撞的物体 + public IGameObj? CheckCollision(IMoveable obj, XY moveVec) + { + XY nextPos = obj.Position + XY.VectorToXY(moveVec); + if (!obj.IsRigid) + { + if (gameMap.IsOutOfBound(obj)) + return gameMap.GetOutOfBound(nextPos); + return null; + } + // 在列表中检查碰撞 + Func, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList = + (IEnumerable lst, ReaderWriterLockSlim listLock) => + { + IGameObj? collisionObj = null; + listLock.EnterReadLock(); + try + { + foreach (var listObj in lst) + { + if (obj.WillCollideWith(listObj, nextPos)) + { + collisionObj = listObj; + break; + } + } + } + finally + { + listLock.ExitReadLock(); + } + return collisionObj; + }; + + IGameObj? collisionObj = null; + foreach (var list in lists) + { + if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null) + { + return collisionObj; + } + } + + return null; + } + /// + /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差 + /// + /// + /// 矩形的中心坐标 + /// + // private double MaxMoveToSquare(IMoveable obj, IGameObj square) + //{ + // double tmpMax; + // double angle = Math.Atan2(square.Position.y - obj.Position.y, square.Position.x - obj.Position.x); + // if (obj.WillCollideWith(square, obj.Position)) + // tmpMax = 0; + // else tmpMax = + // Math.Abs(XYPosition.Distance(obj.Position, square.Position) - obj.Radius - + // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle))))); + // return tmpMax; + // } + + // private double FindMaxOnlyConsiderWall(IMoveable obj, Vector moveVec) + //{ + // var desination = moveVec; + // double maxOnlyConsiderWall = moveVec.length; + // if (desination.length > 0) //如果length足够长,还是有可能穿墙的 + // { + // XYPosition nextXY = Vector.Vector2XY(desination) + obj.Position + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle)), (int)(obj.Radius * Math.Sin(moveVec.angle))); + // if (gameMap.IsWall(nextXY)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 + // { + // maxOnlyConsiderWall = MaxMoveToSquare(obj, gameMap.GetCell(nextXY)); + // } + // else //考虑物体宽度 + // { + // double dist = 0; + // XYPosition nextXYConsiderWidth; + // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle + Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle + Math.PI / 4))); + // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 + // { + // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth)); + // if (dist < maxOnlyConsiderWall) + // maxOnlyConsiderWall = dist; + // } + // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle - Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle - Math.PI / 4))); + // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 + // { + // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth)); + // if (dist < maxOnlyConsiderWall) + // maxOnlyConsiderWall = dist; + // } + // } + // } + // return maxOnlyConsiderWall; + // } + + /// + /// 寻找最大可能移动距离 + /// + /// 移动物体,默认obj.Rigid为true + /// 下一步要到达的位置 + /// 移动的位移向量,默认与nextPos协调 + /// 最大可能的移动距离 + public double FindMax(IMoveable obj, XY nextPos, XY moveVec) + { + double maxLen = (double)uint.MaxValue; + double tmpMax = maxLen; // 暂存最大值 + + // 先找只考虑墙的最大距离 + // double maxOnlyConsiderWall = FindMaxOnlyConsiderWall(obj, moveVec); + double maxDistance = maxLen; + foreach (var listWithLock in lists) + { + var lst = listWithLock.Item1; + var listLock = listWithLock.Item2; + listLock.EnterReadLock(); + try + { + foreach (IGameObj listObj in lst) + { + // 如果再走一步发生碰撞 + if (obj.WillCollideWith(listObj, nextPos)) + { + { + switch (listObj.Shape) // 默认obj为圆形 + { + case ShapeType.Circle: + { + // 计算两者之间的距离 + double mod = XY.Distance(listObj.Position, obj.Position); + int orgDeltaX = listObj.Position.x - obj.Position.x; + int orgDeltaY = listObj.Position.y - obj.Position.y; + + if (mod < listObj.Radius + obj.Radius) // 如果两者已经重叠 + { + tmpMax = 0; + } + else + { + double tmp = mod - obj.Radius - listObj.Radius; + // 计算能走的最长距离,好像这么算有一点误差? + tmp = tmp / Math.Cos(Math.Atan2(orgDeltaY, orgDeltaX) - moveVec.angle); + if (tmp < 0 || tmp > uint.MaxValue || tmp == double.NaN) + { + tmpMax = uint.MaxValue; + } + else + tmpMax = tmp; + } + break; + } + case ShapeType.Square: + { + // if (obj.WillCollideWith(listObj, obj.Position)) + // tmpMax = 0; + // else tmpMax = MaxMoveToSquare(obj, listObj); + // break; + if (obj.WillCollideWith(listObj, obj.Position)) + tmpMax = 0; + else + { + // 二分查找最大可能移动距离 + int left = 0, right = (int)moveVec.length; + while (left < right - 1) + { + int mid = (right - left) / 2 + left; + if (obj.WillCollideWith(listObj, obj.Position + new XY((int)(mid * Math.Cos(moveVec.angle)), (int)(mid * Math.Sin(moveVec.angle))))) + { + right = mid; + } + else + left = mid; + } + tmpMax = (uint)left; + } + break; + } + default: + tmpMax = uint.MaxValue; + break; + } + if (tmpMax < maxDistance) + maxDistance = tmpMax; + } + } + } + } + finally + { + // maxLen = Math.Min(maxOnlyConsiderWall, maxDistance); //最大可能距离的最小值 + listLock.ExitReadLock(); + } + } + // return maxLen; + return maxDistance; + } + + readonly IMap gameMap; + private readonly Tuple, ReaderWriterLockSlim>[] lists; + + public CollisionChecker(IMap gameMap) + { + this.gameMap = gameMap; + lists = new Tuple, ReaderWriterLockSlim>[gameMap.GameObjDict.Count]; + int i = 0; + foreach (var keyValuePair in gameMap.GameObjDict) + { + lists[i++] = new Tuple, ReaderWriterLockSlim>(keyValuePair.Value as IList, gameMap.GameObjLockDict[keyValuePair.Key]); + } + } + } +} diff --git a/logic/GameEngine/GameEngine.csproj b/logic/GameEngine/GameEngine.csproj new file mode 100644 index 0000000..2adc507 --- /dev/null +++ b/logic/GameEngine/GameEngine.csproj @@ -0,0 +1,17 @@ + + + + Library + net6.0 + enable + + + + + + + + + + + diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs new file mode 100644 index 0000000..867dae3 --- /dev/null +++ b/logic/GameEngine/MoveEngine.cs @@ -0,0 +1,175 @@ +using System; +using System.Threading; +using Preparation.Interface; +using Preparation.Utility; +using Timothy.FrameRateTask; +using Preparation.GameData; + +namespace GameEngine +{ + public class MoveEngine + { + /// + /// 碰撞结束后要做的事情 + /// + public enum AfterCollision + { + ContinueCheck = 0, // 碰撞后继续检查其他碰撞 + MoveMax = 1, // 行走最远距离 + Destroyed = 2 // 物体已经毁坏 + } + + private readonly ITimer gameTimer; + private readonly Action EndMove; + private readonly CollisionChecker collisionChecker; + private readonly Func OnCollision; + /// + /// Constrctor + /// + /// 游戏地图 + /// 发生碰撞时要做的事情,第一个参数为移动的物体,第二个参数为撞到的物体,第三个参数为移动的位移向量,返回值见AfterCollision的定义 + /// 结束碰撞时要做的事情 + public MoveEngine( + IMap gameMap, + Func OnCollision, + Action EndMove + ) + { + this.gameTimer = gameMap.Timer; + this.EndMove = EndMove; + this.OnCollision = OnCollision; + this.collisionChecker = new CollisionChecker(gameMap); + } + + /// + /// 在无碰撞的前提下行走最远的距离 + /// + /// 移动物体,默认obj.Rigid为true + /// 移动的位移向量 + private void MoveMax(IMoveable obj, XY moveVec) + { + + /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ + XY nextPos = obj.Position + XY.VectorToXY(moveVec); + double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec); + maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); + _ = obj.Move(new Vector(moveVec.angle, maxLen)); + } + + public void MoveObj(IMoveable obj, int moveTime, double direction) + { + if (obj.IsMoving) // 已经移动的物体不能再移动 + return; + new Thread + ( + ()=> + { + if (!obj.IsAvailable&&gameTimer.IsGaming) //不能动就直接return,后面都是能动的情况 + return; + lock (obj.MoveLock) + obj.IsMoving = true; + + XY moveVec = new(direction, 0.0); + double deltaLen = moveVec.length - Math.Sqrt(obj.Move(moveVec)); // 转向,并用deltaLen存储行走的误差 + IGameObj? collisionObj = null; + bool isDestroyed = false; + new FrameRateTaskExecutor( + () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting, + () => + { + moveVec.length = obj.MoveSpeed / GameData.numOfStepPerSecond; + + // 越界情况处理:如果越界,则与越界方块碰撞 + bool flag; // 循环标志 + do + { + flag = false; + collisionObj = collisionChecker.CheckCollision(obj, moveVec); + if (collisionObj == null) + break; + + switch (OnCollision(obj, collisionObj, moveVec)) + { + case AfterCollision.ContinueCheck: + flag = true; + break; + case AfterCollision.Destroyed: + Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); + isDestroyed = true; + return false; + case AfterCollision.MoveMax: + MoveMax(obj, moveVec); + moveVec.length = 0; + break; + } + } while (flag); + + deltaLen += moveVec.length - Math.Sqrt(obj.Move(moveVec)); + + return true; + }, + GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, + () => + { + int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); + bool flag; + do + { + flag = false; + if (!isDestroyed) + { + moveVec.length = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; + if ((collisionObj = collisionChecker.CheckCollision(obj, moveVec)) == null) + { + obj.Move(moveVec); + } + else + { + switch (OnCollision(obj, collisionObj, moveVec)) + { + case AfterCollision.ContinueCheck: + flag = true; + break; + case AfterCollision.Destroyed: + Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); + isDestroyed = true; + break; + case AfterCollision.MoveMax: + MoveMax(obj, moveVec); + moveVec.length = 0; + break; + } + } + } + } while (flag); + if (leftTime > 0) + { + Thread.Sleep(leftTime); // 多移动的在这里补回来 + } + lock (obj.MoveLock) + obj.IsMoving = false; // 结束移动 + EndMove(obj); + return 0; + }, + maxTotalDuration: moveTime + ) { + AllowTimeExceed = true, + MaxTolerantTimeExceedCount = ulong.MaxValue, + TimeExceedAction = b => + { + if (b) + Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); + +#if DEBUG + else + { + Console.WriteLine("Debug info: Object moving time exceed for once."); + } +#endif + } + }.Start(); + } + ).Start(); + } +} +} diff --git a/logic/Preparation/GameData/GameData.cs b/logic/Preparation/GameData/GameData.cs index 2d13d58..0903b6d 100644 --- a/logic/Preparation/GameData/GameData.cs +++ b/logic/Preparation/GameData/GameData.cs @@ -16,20 +16,20 @@ namespace Preparation.GameData public const int MinSpeed = 1; // 最小速度 public const int MaxSpeed = int.MaxValue; // 最大速度 - public static XYPosition GetCellCenterPos(int x, int y) // 求格子的中心坐标 + public static XY GetCellCenterPos(int x, int y) // 求格子的中心坐标 { - XYPosition ret = new((x * numOfPosGridPerCell) + (numOfPosGridPerCell / 2), (y * numOfPosGridPerCell) + (numOfPosGridPerCell / 2)); + XY ret = new((x * numOfPosGridPerCell) + (numOfPosGridPerCell / 2), (y * numOfPosGridPerCell) + (numOfPosGridPerCell / 2)); return ret; } - public static int PosGridToCellX(XYPosition pos) // 求坐标所在的格子的x坐标 + public static int PosGridToCellX(XY pos) // 求坐标所在的格子的x坐标 { return pos.x / numOfPosGridPerCell; } - public static int PosGridToCellY(XYPosition pos) // 求坐标所在的格子的y坐标 + public static int PosGridToCellY(XY pos) // 求坐标所在的格子的y坐标 { return pos.y / numOfPosGridPerCell; } - public static bool IsInTheSameCell(XYPosition pos1, XYPosition pos2) + public static bool IsInTheSameCell(XY pos1, XY pos2) { return PosGridToCellX(pos1) == PosGridToCellX(pos2) && PosGridToCellY(pos1) == PosGridToCellY(pos2); } diff --git a/logic/Preparation/Interface/IGameObj.cs b/logic/Preparation/Interface/IGameObj.cs index a32a1c5..9be3aab 100644 --- a/logic/Preparation/Interface/IGameObj.cs +++ b/logic/Preparation/Interface/IGameObj.cs @@ -4,10 +4,10 @@ namespace Preparation.Interface { public interface IGameObj { - public GameObjType Type { get; set; } + public GameObjType Type { get; } public long ID { get; } - public XYPosition Position { get; } // if Square, Pos equals the center - public double FacingDirection { get; } + public XY Position { get; } // if Square, Pos equals the center + public XY FacingDirection { get; } public bool IsRigid { get; } public ShapeType Shape { get; } public bool CanMove { get; set; } diff --git a/logic/Preparation/Interface/IMap.cs b/logic/Preparation/Interface/IMap.cs index 0d781b3..4ad6e18 100644 --- a/logic/Preparation/Interface/IMap.cs +++ b/logic/Preparation/Interface/IMap.cs @@ -13,6 +13,6 @@ namespace Preparation.Interface Dictionary GameObjLockDict { get; } public bool IsOutOfBound(IGameObj obj); - public IOutOfBound GetOutOfBound(XYPosition pos); // 返回新建的一个OutOfBound对象 + public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象 } } diff --git a/logic/Preparation/Interface/IMoveable.cs b/logic/Preparation/Interface/IMoveable.cs index bc16f5b..ff23718 100644 --- a/logic/Preparation/Interface/IMoveable.cs +++ b/logic/Preparation/Interface/IMoveable.cs @@ -7,9 +7,9 @@ namespace Preparation.Interface { object MoveLock { get; } public int MoveSpeed { get; } - public long Move(Vector moveVec); - protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现 - public bool WillCollideWith(IGameObj? targetObj, XYPosition nextPos) // 检查下一位置是否会和目标物碰撞 + public long Move(XY moveVec); + protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现 + public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 { if (targetObj == null) return false; @@ -20,7 +20,7 @@ namespace Preparation.Interface return false; if (targetObj.Shape == ShapeType.Circle) { - return XYPosition.Distance(nextPos, targetObj.Position) < targetObj.Radius + Radius; + return XY.Distance(nextPos, targetObj.Position) < targetObj.Radius + Radius; } else // Square { diff --git a/logic/Preparation/Utility/EnumType.cs b/logic/Preparation/Utility/EnumType.cs index 5dfb8cc..1d20b79 100644 --- a/logic/Preparation/Utility/EnumType.cs +++ b/logic/Preparation/Utility/EnumType.cs @@ -27,15 +27,16 @@ namespace Preparation.Utility Circle = 1, // 子弹和人物为圆形,格子为方形 Square = 2 } - public enum PlaceType // 位置标志,包括陆地(一般默认为陆地,如墙体等),草丛。游戏中每一帧都要刷新各个物体的该属性 + public enum PlaceType // 位置标志,包括陆地,建筑,草丛。游戏中每一帧都要刷新各个物体的该属性 { Null = 0, Land = 1, - Grass1 = 2, - Grass2 = 3, - Grass3 = 4, - Grass4 = 5, - Grass5 = 6, + Building = 2, + Grass1 = 3, + Grass2 = 4, + Grass3 = 5, + Grass4 = 6, + Grass5 = 7, } public enum BulletType // 子弹类型 { @@ -54,7 +55,7 @@ namespace Preparation.Utility Spear = 4, Gem = 5, // 新增:宝石 } - public enum PassiveSkillType // 被动技能 + public enum CharacterType // 职业 { Null = 0, RecoverAfterBattle = 1, diff --git a/logic/Preparation/Utility/Tools.cs b/logic/Preparation/Utility/Tools.cs deleted file mode 100644 index 78ee847..0000000 --- a/logic/Preparation/Utility/Tools.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Preparation.Utility -{ - public static class Tools - { - public static double CorrectAngle(double angle) // 将幅角转化为主值0~2pi - { - if (double.IsNaN(angle) || double.IsInfinity(angle)) - { - return 0.0; - } - while (angle < 0) - angle += 2 * Math.PI; - while (angle >= 2 * Math.PI) - angle -= 2 * Math.PI; - return angle; - } - } -} diff --git a/logic/Preparation/Utility/Vector.cs b/logic/Preparation/Utility/Vector.cs deleted file mode 100644 index 50614f0..0000000 --- a/logic/Preparation/Utility/Vector.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; - -namespace Preparation.Utility -{ - public struct Vector - { - public double angle; - public double length; - - public static XYPosition VectorToXY(Vector v) - { - return new XYPosition((int)(v.length * Math.Cos(v.angle)), (int)(v.length * Math.Sin(v.angle))); - } - public Vector2 ToVector2() - { - return new Vector2((int)(this.length * Math.Cos(this.angle)), (int)(this.length * Math.Sin(this.angle))); - } - public static Vector XYToVector(double x, double y) - { - return new Vector(Math.Atan2(y, x), Math.Sqrt((x * x) + (y * y))); - } - public Vector(double angle, double length) - { - if (length < 0) - { - angle += Math.PI; - length = -length; - } - this.angle = Tools.CorrectAngle(angle); - this.length = length; - } - } - - public struct Vector2 - { - public double x; - public double y; - public Vector2(double x, double y) - { - this.x = x; - this.y = y; - } - - public static double operator*(Vector2 v1, Vector2 v2) - { - return (v1.x * v2.x) + (v1.y * v2.y); - } - public static Vector2 operator +(Vector2 v1, Vector2 v2) - { - return new Vector2(v1.x + v2.x, v1.y + v2.y); - } - public static Vector2 operator -(Vector2 v1, Vector2 v2) - { - return new Vector2(v1.x - v2.x, v1.y - v2.y); - } - } -} diff --git a/logic/Preparation/Utility/XY.cs b/logic/Preparation/Utility/XY.cs new file mode 100644 index 0000000..ff28482 --- /dev/null +++ b/logic/Preparation/Utility/XY.cs @@ -0,0 +1,36 @@ +using System; + +namespace Preparation.Utility +{ + + public struct XY + { + public int x; + public int y; + public XY(int x, int y) + { + this.x = x; + this.y = y; + } + public override string ToString() + { + return "(" + x.ToString() + "," + y.ToString() + ")"; + } + public static int operator*(XY v1, XY v2) + { + return (v1.x * v2.x) + (v1.y * v2.y); + } + public static XY operator +(XY v1, XY v2) + { + return new XY(v1.x + v2.x, v1.y + v2.y); + } + public static XY operator -(XY v1, XY v2) + { + return new XY(v1.x - v2.x, v1.y - v2.y); + } + public static double Distance(XY p1, XY p2) + { + return Math.Sqrt(((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))); + } + } +} diff --git a/logic/Preparation/Utility/XYPosition.cs b/logic/Preparation/Utility/XYPosition.cs deleted file mode 100644 index 55647ec..0000000 --- a/logic/Preparation/Utility/XYPosition.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; - -namespace Preparation.Utility -{ - public struct XYPosition - { - public int x; - public int y; - public XYPosition(int x, int y) - { - this.x = x; - this.y = y; - } - public override string ToString() - { - return "(" + x.ToString() + "," + y.ToString() + ")"; - } - public static XYPosition operator +(XYPosition p1, XYPosition p2) - { - return new XYPosition(p1.x + p2.x, p1.y + p2.y); - } - public static XYPosition operator -(XYPosition p1, XYPosition p2) - { - return new XYPosition(p1.x - p2.x, p1.y - p2.y); - } - public static double Distance(XYPosition p1, XYPosition p2) - { - return Math.Sqrt(((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))); - } - /*public static XYPosition[] GetSquareRange(uint edgeLen) // 从THUAI4的BULLET.CS移植而来,不知还有用否 - { - XYPosition[] range = new XYPosition[edgeLen * edgeLen]; - int offset = (int)(edgeLen >> 1); - for (int i = 0; i < (int)edgeLen; ++i) - { - for (int j = 0; j < (int)edgeLen; ++j) - { - range[i * edgeLen + j].x = i - offset; - range[i * edgeLen + j].y = j - offset; - } - } - return range; - }*/ - public Vector2 ToVector2() - { - return new Vector2(this.x, this.y); - } - } -}