C#服务端的微信小游戏——多人在线角色扮演(五)

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

C#服务端的微信小游戏——多人在线角色扮演(五)

每一次心跳,都会带来改变。从浩瀚的宇宙到渺小的尘埃,都将在时间的维度上不断的改变自己。
——茂叔

谁在心跳?

上一篇讲到了心跳,这是我们游戏世界的一切发生变化的核心,因此,无论GameWorldGameMap还是GameObject都应该有心跳HeartBeat,也都应该有向监控窗体报告信息的LOGHeartBeat是自我存在的内在基础,而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);
        }
……

修改GameWorldHeartBeat让其遍历所有地图,并调用GameMapHeartBeat
同样道理,修改GameMapHeartBeat,让其遍历所有内容,并调用GameObjectHeartBeat
经过调整之后的代码如下:

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#服务端的微信小游戏——多人在线角色扮演(六)

请用微信扫描查看游戏效果演示

演示入口

猜你喜欢

转载自blog.csdn.net/foomow/article/details/92081155