C#服务端的微信小游戏——多人在线角色扮演(五)
每一次心跳,都会带来改变。从浩瀚的宇宙到渺小的尘埃,都将在时间的维度上不断的改变自己。
——茂叔
谁在心跳?
上一篇讲到了心跳,这是我们游戏世界的一切发生变化的核心,因此,无论GameWorld
、GameMap
还是GameObject
都应该有心跳HeartBeat
,也都应该有向监控窗体报告信息的LOG
。HeartBeat
是自我存在的内在基础,而LOG
就是自我存在对外界的反射。
于是,很自然的,我们可以用一个抽象类来将三者统一起来,我们可以把这个类就叫做“存在”Existence
。
为了判别每一个实例,我们在实例化的时候还要为他们生成一个唯一的EID
,以及为他们预留一个Name
属性,方便必要时为其命名。为了实现EID
,我们需要在G
类里面添加两个静态方法以及一个静态成员:
……
private static readonly Random r = new Random(DateTime.Now.Millisecond);
public static string MakeMD5(string input)
{
MD5 md5 = MD5.Create();
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
}
public static int RND(int min, int max)
{
return r.Next(min, max + 1);
}
……
修改GameWorld
的HeartBeat
让其遍历所有地图,并调用GameMap
的HeartBeat
。
同样道理,修改GameMap
的HeartBeat
,让其遍历所有内容,并调用GameObject
的HeartBeat
。
经过调整之后的代码如下:
Existence.cs
abstract class Existence
{
private string eID;
private string name;
public Action<string> LOG;
public string EID { get => eID; }
public string Name { get => name; set => name = value; }
protected Existence(Action<string> LOGFUN = null)
{
eID = G.MakeMD5(DateTime.Now.ToString("yyyyMMddHHmmssffff") + G.RND(10000, 99999).ToString() + G.RND(10000, 99999).ToString())
+ G.MakeMD5(G.RND(10000, 99999).ToString() + G.RND(10000, 99999).ToString());
LOG = LOGFUN;
if (LOG == null)
{
LOG = DefaultLOG;
}
}
private void DefaultLOG(string msg)
{
//如果没有指定日志方法,那就啥都不做……
}
public abstract void HeartBeat();
}
GameWorld.cs
class GameWorld : Existence
{
private readonly List<GameMap> gMaps;
public GameWorld(Action<string> LOGFUN = null) : base(LOGFUN)
{
gMaps = new List<GameMap>();
LOG("GameWorld(" + EID + ")创建成功");
}
public override void HeartBeat()
{
LOG("GameWorld(" + EID + ")正在心跳 " + G.GlobeTime);
GameMap[] MapList;
lock (gMaps)
{
MapList = gMaps.ToArray();
}
foreach (GameMap Map in MapList)
{
Map.HeartBeat();
}
}
}
出于线程安全考虑,我们把所有地图的引用先复制出来,在进行遍历和心跳。
GameMap.cs
class GameMap : Existence
{
private readonly List<GameObject> gObjects;
public GameMap(Action<string> LOGFUN = null) : base(LOGFUN)
{
gObjects = new List<GameObject>();
LOG("GameMap(" + EID + ")创建成功");
}
public override void HeartBeat()
{
LOG("GameMap(" + EID + ")正在心跳 " + G.GlobeTime);
GameObject[] ObjectList;
lock (gObjects)
{
ObjectList = gObjects.ToArray();
}
foreach (GameObject OBJ in ObjectList)
{
OBJ.HeartBeat();
}
}
}
也是出于线程安全考虑,我们把所有内容的引用先复制出来,在进行遍历和心跳。
GameObject.cs
class GameObject : Existence
{
public GameObject(Action<string> LOGFUN = null) : base(LOGFUN)
{
LOG("GameObject(" + EID + ")创建成功");
}
public override void HeartBeat()
{
LOG("GameObject(" + EID + ")正在心跳 " + G.GlobeTime);
}
}
这样,游戏世界的基本运转就可以进行了。
GameMap的更多需求
让我们来梳理一下GameMap
的需求。
首先,游戏应该不止一个地图才对,当我们达到一个地图的边界的时候,应该要可以找到相邻地图才行。
所以,我们每个地图都应该有其四周地图的信息。
为了便于我们处理地图刷新,我们给地图添加一个最后刷新时间的成员。
当然,说到刷新,我们还应该应该给地图设置一个刷新时的内容清单,以便按照清单生成内容。
于是,我们先定义一个清单结构。
struct MapItem {
public ushort ClassID;
public sbyte X;
public sbyte Y;
}
其中,ObjectClassId
代表生成什么内容,X
,Y
表示这个内容在地图上的位置。
然后给GameMap
添加以下成员:
public string NorthMap = "";
public string SouthMap = "";
public string WestMap = "";
public string EastMap = "";
public DateTime LastRefreshTime;
public List<MapItem> MapItems = new List<MapItem>();
当然了,地图实例化之后还需要初始化,也就是首次使用前刷新一下,按清单生成内容,不然一个空地图有什么用?
所以我们还要添加刷新的方法:
public void Refresh()//刷新
{
LOG("重新生成地图("+EID+")内容");
lock (this)
{
gObjects.Clear();
foreach (MapItem item in MapItems)
{
//生成内容
}
}
LastRefreshTime = DateTime.Now;
}
刷新可能涉及对很多数据的操作,为保证安全,先锁定。每次刷新后记录下刷新完毕的时间。
GameWorld载入第一张地图
好了,现在我们可以为GameWorld
载入第一张地图了。
修改GameWorld
的构造函数如下:
public GameWorld(Action<string> LOGFUN) : base(LOGFUN)
{
gMaps = new List<GameMap>();
LOG("GameWorld(" + EID + ")创建成功");
GameMap Map = new GameMap(LOG);
Map.Refresh();
gMaps.Add(Map);
}
然后调试一下:
一切都很顺利……只是地图没有内容,只有悠长的回音,略显寂寥……
下一篇,我们将弥补这个遗憾,讨论内容的细节,以及内容生成的具体策略。
上一篇:C#服务端的微信小游戏——多人在线角色扮演(四)
下一篇:C#服务端的微信小游戏——多人在线角色扮演(六)
请用微信扫描查看游戏效果演示