| @@ -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子弹 | |||
| /// <summary> | |||
| /// 装弹冷却 | |||
| /// </summary> | |||
| 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())); | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 使用物品栏中的道具 | |||
| /// </summary> | |||
| /// <returns>被使用的道具</returns> | |||
| public Prop? UseProp() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| var oldProp = PropInventory; | |||
| PropInventory = null; | |||
| return oldProp; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 是否正在更换道具(包括捡起与抛出) | |||
| /// </summary> | |||
| private bool isModifyingProp = false; | |||
| public bool IsModifyingProp | |||
| { | |||
| get => isModifyingProp; | |||
| set { | |||
| lock (gameObjLock) | |||
| { | |||
| isModifyingProp = value; | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 是否在隐身 | |||
| /// </summary> | |||
| private bool isInvisible = false; | |||
| public bool IsInvisible | |||
| { | |||
| get => isInvisible; | |||
| set { | |||
| lock (gameObjLock) | |||
| { | |||
| isInvisible = value; | |||
| } | |||
| } | |||
| } | |||
| /* | |||
| /// <summary> | |||
| /// 进行一次远程攻击 | |||
| /// </summary> | |||
| /// <param name="posOffset">子弹初始位置偏差值</param> | |||
| /// <returns>攻击操作发出的子弹</returns> | |||
| 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; | |||
| } | |||
| /// <summary> | |||
| /// 尝试将子弹数量减1 | |||
| /// </summary> | |||
| /// <returns>减操作是否成功</returns> | |||
| private bool TrySubBulletNum() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (bulletNum > 0) | |||
| { | |||
| --bulletNum; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 尝试将子弹数量加1 | |||
| /// </summary> | |||
| /// <returns>加操作是否成功</returns> | |||
| public bool TryAddBulletNum() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (bulletNum < maxBulletNum) | |||
| { | |||
| ++bulletNum; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| */ | |||
| /// <summary> | |||
| /// 尝试加血 | |||
| /// </summary> | |||
| /// <param name="add">欲加量</param> | |||
| /// <returns>加操作是否成功</returns> | |||
| 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; | |||
| } | |||
| /// <summary> | |||
| /// 尝试减血 | |||
| /// </summary> | |||
| /// <param name="sub">减血量</param> | |||
| /// <returns>减操作是否成功</returns> | |||
| 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; | |||
| } | |||
| /// <summary> | |||
| /// 增加死亡次数 | |||
| /// </summary> | |||
| /// <returns>当前死亡次数</returns> | |||
| private int AddDeathCount() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| ++deathCount; | |||
| return deathCount; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 加分 | |||
| /// </summary> | |||
| /// <param name="add">增加量</param> | |||
| public void AddScore(int add) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| score += add; | |||
| Debugger.Output(this, " 's score has been added to: " + score.ToString()); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 减分 | |||
| /// </summary> | |||
| /// <param name="sub">减少量</param> | |||
| public void SubScore(int sub) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| score -= sub; | |||
| Debugger.Output(this, " 's score has been subed to: " + score.ToString()); | |||
| } | |||
| } | |||
| /* | |||
| /// <summary> | |||
| /// 遭受攻击 | |||
| /// </summary> | |||
| /// <param name="subHP"></param> | |||
| /// <param name="hasSpear"></param> | |||
| /// <param name="attacker">伤害来源</param> | |||
| /// <returns>人物在受到攻击后死了吗</returns> | |||
| 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; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 攻击被反弹,反弹伤害不会再被反弹 | |||
| /// </summary> | |||
| /// <param name="subHP"></param> | |||
| /// <param name="hasSpear"></param> | |||
| /// <param name="bouncer">反弹伤害者</param> | |||
| /// <returns>是否因反弹伤害而死</returns> | |||
| 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; | |||
| } | |||
| } | |||
| */ | |||
| /// <summary> | |||
| /// 角色所属队伍ID | |||
| /// </summary> | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 角色携带的信息 | |||
| /// </summary> | |||
| 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<BuffType, bool> Buff | |||
| { | |||
| get { | |||
| Dictionary<BuffType, bool> buff = new Dictionary<BuffType, bool>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体 | |||
| /// </summary> | |||
| /// <param name="obj">移动的物体</param> | |||
| /// <param name="moveVec">移动的位移向量</param> | |||
| /// <returns>和它碰撞的物体</returns> | |||
| 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<IEnumerable<IGameObj>, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList = | |||
| (IEnumerable<IGameObj> 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; | |||
| } | |||
| /// <summary> | |||
| /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差 | |||
| /// </summary> | |||
| /// <param name="obj"></param> | |||
| /// <param name="square">矩形的中心坐标</param> | |||
| /// <returns></returns> | |||
| //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; | |||
| //} | |||
| /// <summary> | |||
| /// 寻找最大可能移动距离 | |||
| /// </summary> | |||
| /// <param name="obj">移动物体,默认obj.Rigid为true</param> | |||
| /// <param name="nextPos">下一步要到达的位置</param> | |||
| /// <param name="moveVec">移动的位移向量,默认与nextPos协调</param> | |||
| /// <returns>最大可能的移动距离</returns> | |||
| 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<IEnumerable<IGameObj>, ReaderWriterLockSlim>[] lists; | |||
| public CollisionChecker(IMap gameMap) | |||
| { | |||
| this.gameMap = gameMap; | |||
| lists = new Tuple<IEnumerable<IGameObj>, ReaderWriterLockSlim>[gameMap.GameObjDict.Count]; | |||
| int i = 0; | |||
| foreach(var keyValuePair in gameMap.GameObjDict) | |||
| { | |||
| lists[i++] = new Tuple<IEnumerable<IGameObj>, ReaderWriterLockSlim>(keyValuePair.Value as IList<IGameObj>, gameMap.GameObjLockDict[keyValuePair.Key]); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <OutputType>Library</OutputType> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="FrameRateTask" Version="1.1.2" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\Preparation\Preparation.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// 碰撞结束后要做的事情 | |||
| /// </summary> | |||
| public enum AfterCollision | |||
| { | |||
| ContinueCheck = 0, // 碰撞后继续检查其他碰撞 | |||
| MoveMax = 1, // 行走最远距离 | |||
| Destroyed = 2 // 物体已经毁坏 | |||
| } | |||
| private readonly ITimer gameTimer; | |||
| private readonly Action<IMoveable> EndMove; | |||
| private readonly CollisionChecker collisionChecker; | |||
| private readonly Func<IMoveable, IGameObj, XY, AfterCollision> OnCollision; | |||
| /// <summary> | |||
| /// Constrctor | |||
| /// </summary> | |||
| /// <param name="gameMap">游戏地图</param> | |||
| /// <param name="OnCollision">发生碰撞时要做的事情,第一个参数为移动的物体,第二个参数为撞到的物体,第三个参数为移动的位移向量,返回值见AfterCollision的定义</param> | |||
| /// <param name="EndMove">结束碰撞时要做的事情</param> | |||
| public MoveEngine | |||
| ( | |||
| IMap gameMap, | |||
| Func<IMoveable, IGameObj, XY, AfterCollision> OnCollision, | |||
| Action<IMoveable> EndMove | |||
| ) | |||
| { | |||
| this.gameTimer = gameMap.Timer; | |||
| this.EndMove = EndMove; | |||
| this.OnCollision = OnCollision; | |||
| this.collisionChecker = new CollisionChecker(gameMap); | |||
| } | |||
| /// <summary> | |||
| /// 在无碰撞的前提下行走最远的距离 | |||
| /// </summary> | |||
| /// <param name="obj">移动物体,默认obj.Rigid为true</param> | |||
| /// <param name="moveVec">移动的位移向量</param> | |||
| 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<int> | |||
| ( | |||
| () => 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(); | |||
| } | |||
| } | |||
| } | |||