Browse Source

Merge pull request #20 from shangfengh/new

build: add character
tags/0.1.0
TCL GitHub 3 years ago
parent
commit
b839bea349
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1177 additions and 146 deletions
  1. +0
    -0
      dependency/proto/.clang-format
  2. +0
    -0
      dependency/proto/Message2Clients.proto
  3. +0
    -0
      dependency/proto/Message2Server.proto
  4. +0
    -0
      dependency/proto/MessageType.proto
  5. +0
    -0
      dependency/proto/README.md
  6. +0
    -0
      dependency/proto/cpp_output.sh
  7. +0
    -0
      dependency/proto/format.sh
  8. +457
    -0
      logic/GameClass/GameObj/Character.cs
  9. +185
    -0
      logic/GameClass/GameObj/GameObj.cs
  10. +29
    -0
      logic/GameClass/GameObj/Map/BirthPoint.cs
  11. +28
    -0
      logic/GameClass/GameObj/ObjOfCharacter.cs
  12. +229
    -0
      logic/GameEngine/CollisionChecker.cs
  13. +17
    -0
      logic/GameEngine/GameEngine.csproj
  14. +175
    -0
      logic/GameEngine/MoveEngine.cs
  15. +5
    -5
      logic/Preparation/GameData/GameData.cs
  16. +3
    -3
      logic/Preparation/Interface/IGameObj.cs
  17. +1
    -1
      logic/Preparation/Interface/IMap.cs
  18. +4
    -4
      logic/Preparation/Interface/IMoveable.cs
  19. +8
    -7
      logic/Preparation/Utility/EnumType.cs
  20. +0
    -20
      logic/Preparation/Utility/Tools.cs
  21. +0
    -57
      logic/Preparation/Utility/Vector.cs
  22. +36
    -0
      logic/Preparation/Utility/XY.cs
  23. +0
    -49
      logic/Preparation/Utility/XYPosition.cs

+ 0
- 0
dependency/proto/.clang-format View File


+ 0
- 0
dependency/proto/Message2Clients.proto View File


+ 0
- 0
dependency/proto/Message2Server.proto View File


+ 0
- 0
dependency/proto/MessageType.proto View File


+ 0
- 0
dependency/proto/README.md View File


+ 0
- 0
dependency/proto/cpp_output.sh View File


+ 0
- 0
dependency/proto/format.sh View File


+ 457
- 0
logic/GameClass/GameObj/Character.cs View File

@@ -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;
}
}
}

+ 185
- 0
logic/GameClass/GameObj/GameObj.cs View File

@@ -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);
}
}
}

+ 29
- 0
logic/GameClass/GameObj/Map/BirthPoint.cs View File

@@ -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;
}
}

+ 28
- 0
logic/GameClass/GameObj/ObjOfCharacter.cs View File

@@ -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)
{
}
}
}

+ 229
- 0
logic/GameEngine/CollisionChecker.cs View File

@@ -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]);
}
}
}
}

+ 17
- 0
logic/GameEngine/GameEngine.csproj View File

@@ -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>

+ 175
- 0
logic/GameEngine/MoveEngine.cs View File

@@ -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();
}
}
}

+ 5
- 5
logic/Preparation/GameData/GameData.cs View File

@@ -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);
}


+ 3
- 3
logic/Preparation/Interface/IGameObj.cs View File

@@ -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; }


+ 1
- 1
logic/Preparation/Interface/IMap.cs View File

@@ -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对象
}
}

+ 4
- 4
logic/Preparation/Interface/IMoveable.cs View File

@@ -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
{


+ 8
- 7
logic/Preparation/Utility/EnumType.cs View File

@@ -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,


+ 0
- 20
logic/Preparation/Utility/Tools.cs View File

@@ -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;
}
}
}

+ 0
- 57
logic/Preparation/Utility/Vector.cs View File

@@ -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);
}
}
}

+ 36
- 0
logic/Preparation/Utility/XY.cs View File

@@ -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)));
}
}
}

+ 0
- 49
logic/Preparation/Utility/XYPosition.cs View File

@@ -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);
}
}
}

Loading…
Cancel
Save