| @@ -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,185 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR | |||
| /// </summary> | |||
| public abstract class GameObj : IMoveable | |||
| { | |||
| protected readonly object gameObjLock = new(); | |||
| /// <summary> | |||
| /// 可移动物体专用锁 | |||
| /// </summary> | |||
| 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; | |||
| /// <summary> | |||
| /// 移动速度 | |||
| /// </summary> | |||
| public int MoveSpeed | |||
| { | |||
| get => moveSpeed; | |||
| set { | |||
| lock (gameObjLock) | |||
| { | |||
| moveSpeed = value; | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 原初移动速度 | |||
| /// </summary> | |||
| 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); | |||
| } | |||
| /// <summary> | |||
| /// 设置位置 | |||
| /// </summary> | |||
| /// <param name="newpos">新位置</param> | |||
| public void SetPosition(XY newpos) | |||
| { | |||
| Position = newpos; | |||
| } | |||
| /// <summary> | |||
| /// 设置移动速度 | |||
| /// </summary> | |||
| /// <param name="newMoveSpeed">新速度</param> | |||
| public void SetMoveSpeed(int newMoveSpeed) | |||
| { | |||
| MoveSpeed = newMoveSpeed; | |||
| } | |||
| /// <summary> | |||
| /// 复活时数据重置 | |||
| /// </summary> | |||
| public virtual void Reset() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| facingDirection = new XY(1, 0); | |||
| isMoving = false; | |||
| canMove = false; | |||
| isResetting = true; | |||
| this.position = birthPos; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable | |||
| /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 | |||
| /// </summary> | |||
| /// <returns> 依具体类及该方法参数而定,默认为false </returns> | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using Preparation.GameData; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 出生点 | |||
| /// </summary> | |||
| 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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 所有物,具有主人(Parent)(特定玩家)属性的对象 | |||
| /// </summary> | |||
| 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) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <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,175 @@ | |||
| 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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; } | |||
| @@ -13,6 +13,6 @@ namespace Preparation.Interface | |||
| Dictionary<GameObjIdx, ReaderWriterLockSlim> GameObjLockDict { get; } | |||
| public bool IsOutOfBound(IGameObj obj); | |||
| public IOutOfBound GetOutOfBound(XYPosition pos); // 返回新建的一个OutOfBound对象 | |||
| public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象 | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| @@ -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, | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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))); | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||