Browse Source

build: Copy GameEngine and add Character.cs

tags/0.1.0
shangfengh 3 years ago
parent
commit
593169ac15
4 changed files with 874 additions and 0 deletions
  1. +457
    -0
      logic/GameClass/GameObj/Character.cs
  2. +224
    -0
      logic/GameEngine/CollisionChecker.cs
  3. +17
    -0
      logic/GameEngine/GameEngine.csproj
  4. +176
    -0
      logic/GameEngine/MoveEngine.cs

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

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

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

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

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

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

Loading…
Cancel
Save