diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs new file mode 100644 index 0000000..fc16f6b --- /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/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs new file mode 100644 index 0000000..93e0074 --- /dev/null +++ b/logic/GameEngine/CollisionChecker.cs @@ -0,0 +1,224 @@ +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..0424397 --- /dev/null +++ b/logic/GameEngine/MoveEngine.cs @@ -0,0 +1,176 @@ +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(); + } + } +}