你的位置:首页 > ASP.net教程

[ASP.net教程]一步一步开发Game服务器(四)地图线程


时隔这么久 才再一次的回归正题继续讲解游戏服务器开发。

开始讲解前有一个问题需要修正。之前讲的线程和定时器线程的时候是分开的。

但是真正地图线程与之前的线程模型是有区别的。

 

为什么会有区别呢?一个地图肯定有执行线程,但是每一个地图都有不同的时间任务。
比如检测玩家身上的buffer,检测玩家的状态值。这种情况下如何处理呢?很明显就需要定时器线程。

 我的处理方式是创建一个线程的时候根据需求创建对应的 timerthread

直接上代码其他不BB

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7  8 namespace Sz.ThreadPool 9 { 10   /// <summary> 11   /// 线程模型 12   /// </summary>   13   public class ThreadModel 14   { 15     /// <summary> 16     ///  17     /// </summary> 18     public bool IsStop = false; 19     /// <summary> 20     /// ID 21     /// </summary> 22     public int ID { get; private set; } 23     /// <summary> 24     /// 已分配的自定义线程静态ID 25     /// </summary> 26     public static int StaticID { get; private set; } 27  28     string Name; 29  30     /// <summary> 31     /// 初始化线程模型, 32     /// </summary> 33     /// <param name="name"></param> 34     public ThreadModel(String name) 35       : this(name, 1) 36     { 37  38     } 39  40     /// <summary> 41     /// 初始化线程模型 42     /// </summary> 43     /// <param name="name">线程名称</param> 44     /// <param name="count">线程数量</param> 45     public ThreadModel(String name, Int32 count) 46     { 47       lock (typeof(ThreadModel)) 48       { 49         StaticID++; 50         ID = StaticID; 51       } 52       this.Name = name; 53       if (count == 1) 54       { 55         System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); 56         thread.Name = "< " + name + "线程 >";         57         thread.Start(); 58         Logger.Info("初始化 " + thread.Name); 59       } 60       else 61       { 62         for (int i = 0; i < count; i++) 63         { 64           System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); 65           thread.Name = "< " + name + "_" + (i + 1) + "线程 >";           66           thread.Start(); 67           Logger.Info("初始化 " + thread.Name); 68         } 69       } 70     } 71  72     System.Threading.Thread threadTimer = null; 73  74     /// <summary> 75     /// 任务队列 76     /// </summary> 77     protected List<TaskModel> taskQueue = new List<TaskModel>(); 78     /// <summary> 79     /// 任务队列 80     /// </summary> 81     private List<TimerTask> timerTaskQueue = new List<TimerTask>(); 82  83     /// <summary> 84     /// 加入任务 85     /// </summary> 86     /// <param name="t"></param> 87     public virtual void AddTask(TaskModel t) 88     { 89       lock (taskQueue) 90       { 91         taskQueue.Add(t); 92       } 93       //防止线程正在阻塞时添加进入了新任务 94       are.Set(); 95     } 96  97     /// <summary> 98     /// 加入任务 99     /// </summary>100     /// <param name="t"></param>101     public void AddTimerTask(TimerTask t)102     {103       t.RunAttribute["lastactiontime"] = SzExtensions.CurrentTimeMillis();104       if (t.IsStartAction)105       {106         AddTask(t);107       }108       lock (timerTaskQueue)109       {110         if (threadTimer == null)111         {112           threadTimer = new System.Threading.Thread(new System.Threading.ThreadStart(TimerRun));113           threadTimer.Name = "< " + this.Name + " - Timer线程 >";          114           threadTimer.Start();115           Logger.Info("初始化 " + threadTimer.Name);116         }117         timerTaskQueue.Add(t);118       }119       timerAre.Set();120     }121 122     /// <summary>123     /// 通知一个或多个正在等待的线程已发生事件124     /// </summary>125     protected ManualResetEvent are = new ManualResetEvent(false);126 127     /// <summary>128     /// 通知一个或多个正在等待的线程已发生事件129     /// </summary>130     protected ManualResetEvent timerAre = new ManualResetEvent(true);131 132     /// <summary>133     /// 线程处理器134     /// </summary>135     protected virtual void Run()136     {137       while (!this.IsStop)138       {139         while ((taskQueue.Count > 0))140         {141           TaskModel task = null;142           lock (taskQueue)143           {144             if (taskQueue.Count > 0)145             {146               task = taskQueue[0];147               taskQueue.RemoveAt(0);148             }149             else { break; }150           }151 152           /* 执行任务 */153           //r.setSubmitTimeL();154           long submitTime = SzExtensions.CurrentTimeMillis();155           try156           {157             task.Run();158           }159           catch (Exception e)160           {161             Logger.Error(Thread.CurrentThread.Name + " 执行任务:" + task.ToString() + " 遇到错误", e);162             continue;163           }164           long timeL1 = SzExtensions.CurrentTimeMillis() - submitTime;165           long timeL2 = SzExtensions.CurrentTimeMillis() - task.GetSubmitTime();166           if (timeL1 < 100) { }167           else if (timeL1 <= 200L) { Logger.Debug(Thread.CurrentThread.Name + " 完成了任务:" + task.ToString() + " 执行耗时:" + timeL1 + " 提交耗时:" + timeL2); }168           else if (timeL1 <= 1000L) { Logger.Info(Thread.CurrentThread.Name + " 长时间执行 完成任务:" + task.ToString() + " “考虑”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); }169           else if (timeL1 <= 4000L) { Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “检查”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); }170           else171           {172             Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “考虑是否应该删除”任务脚本 耗时:" + timeL1 + " 提交耗时:" + timeL2);173           }174           task = null;175         }176         are.Reset();177         //队列为空等待200毫秒继续178         are.WaitOne(200);179       }180       Console.WriteLine(DateTime.Now.NowString() + " " + Thread.CurrentThread.Name + " Destroying");181     }182 183     /// <summary>184     /// 定时器线程处理器185     /// </summary>186     protected virtual void TimerRun()187     {188       ///无限循环执行函数器189       while (!this.IsStop)190       {191         if (timerTaskQueue.Count > 0)192         {193           IEnumerable<TimerTask> collections = null;194           lock (timerTaskQueue)195           {196             collections = new List<TimerTask>(timerTaskQueue);197           }198           foreach (TimerTask timerEvent in collections)199           {200             int execCount = timerEvent.RunAttribute.GetintValue("Execcount");201             long lastTime = timerEvent.RunAttribute.GetlongValue("LastExecTime");202             long nowTime = SzExtensions.CurrentTimeMillis();203             if (nowTime > timerEvent.StartTime //是否满足开始时间204                 && (nowTime - timerEvent.GetSubmitTime() > timerEvent.IntervalTime)//提交以后是否满足了间隔时间205                 && (timerEvent.EndTime <= 0 || nowTime < timerEvent.EndTime) //判断结束时间206                 && (nowTime - lastTime >= timerEvent.IntervalTime))//判断上次执行到目前是否满足间隔时间207             {208               //提交执行209               this.AddTask(timerEvent);210               //记录211               execCount++;212               timerEvent.RunAttribute["Execcount"] = execCount;213               timerEvent.RunAttribute["LastExecTime"] = nowTime;214             }215             nowTime = SzExtensions.CurrentTimeMillis();216             //判断删除条件217             if ((timerEvent.EndTime > 0 && nowTime < timerEvent.EndTime)218                 || (timerEvent.ActionCount > 0 && timerEvent.ActionCount <= execCount))219             {220               timerTaskQueue.Remove(timerEvent);221             }222           }223           timerAre.Reset();224           timerAre.WaitOne(5);225         }226         else227         {228           timerAre.Reset();229           //队列为空等待200毫秒继续230           timerAre.WaitOne(200);231         }232       }233       Console.WriteLine(DateTime.Now.NowString() + "Thread:<" + Thread.CurrentThread.Name + "> Destroying");234     }235   }236 }

View Code

当我线程里面第一次添加定时器任务的时候加触发定时器线程的初始化。

先看看效果

 

地图运作方式怎么样的呢?

来一张图片看看

在正常情况下一个地图需要这些事情。然后大部分事情是需要定时器任务处理的,只有客户端交互通信是不需要定时器任务处理。

封装地图信息类

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using Sz.MMO.GameServer.IMapScripts; 7 using Sz.MMO.GameServer.TimerMap; 8 using Sz.MMO.GameServer.TimerMonster; 9  10  11 /** 12  *  13  * @author 失足程序员 14  * @Blog http://www.cnblogs.com/ty408/ 15  * @mail 492794628@qq.com 16  * @phone 13882122019 17  *  18 */ 19 namespace Sz.MMO.GameServer.Structs.Map 20 { 21   /// <summary> 22   ///  23   /// </summary> 24   public class MapInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript 25   { 26     /// <summary> 27     /// 为跨服设计的服务器id 28     /// </summary> 29     public int ServerID { get; set; } 30     /// <summary> 31     /// 地图模板id 32     /// </summary> 33     public int MapModelID { get; set; } 34     /// <summary> 35     /// 地图id 36     /// </summary> 37     public long MapID { get; set; } 38  39     /// <summary> 40     /// 地图分线处理 41     /// </summary> 42     Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>> mapLineInfos = new Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>>(); 43  44     public MapInfo(string name, int mapModelId, int lineCount = 1) 45     { 46  47       this.MapID = SzExtensions.GetId(); 48       this.MapModelID = mapModelId; 49       Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID); 50  51       for (int i = 1; i <= lineCount; i++) 52       { 53         MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线"); 54  55         mapLineInfos[i] = lineInfo; 56       } 57       Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束"); 58     } 59  60   } 61  62   #region 地图分线 class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript 63   /// <summary> 64   /// 地图分线 65   /// </summary> 66   /// <typeparam name="TPlayer"></typeparam> 67   /// <typeparam name="TNpc"></typeparam> 68   /// <typeparam name="TMonster"></typeparam> 69   /// <typeparam name="TDropGoods"></typeparam> 70   class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript 71   { 72     public MapThread MapServer { get; set; } 73  74     public int ServerID { get; set; } 75  76     public int LineID { get; set; } 77  78     public int MapModelID { get; set; } 79  80     public long MapID { get; set; } 81  82     public MapLineInfo(string name) 83     { 84       Players = new List<TPlayer>(); 85       Monsters = new List<TMonster>(); 86       Npcs = new List<TNpc>(); 87       DropGoodss = new List<TDropGoods>(); 88       MapServer = new Structs.Map.MapThread(name); 89     } 90  91     /// <summary> 92     /// 地图玩家 93     /// </summary> 94     public List<TPlayer> Players { get; set; } 95  96     /// <summary> 97     /// 地图npc 98     /// </summary> 99     public List<TNpc> Npcs { get; set; }100 101     /// <summary>102     /// 地图怪物103     /// </summary>104     public List<TMonster> Monsters { get; set; }105 106     /// <summary>107     /// 地图掉落物108     /// </summary>109     public List<TDropGoods> DropGoodss { get; set; }110   }111   #endregion112 }

View Code
  Structs.Map.MapInfo<Player, Npc, Monster, Drop> map = new Structs.Map.MapInfo<Player, Npc, Monster, Drop>("新手村", 101, 2);

 



这样就创建了一张地图。我们创建的新手村有两条线。也就是两个线程

这样只是创建地图容器和地图线程而已。

如何添加各个定时器呢?

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using Sz.MMO.GameServer.IMapScripts; 7  8  9 /** 10  *  11  * @author 失足程序员 12  * @Blog http://www.cnblogs.com/ty408/ 13  * @mail 492794628@qq.com 14  * @phone 13882122019 15  *  16 */ 17 namespace Sz.MMO.GameServer.TimerMap 18 { 19   /// <summary> 20   ///  21   /// </summary> 22   public class MapHeartTimer : ThreadPool.TimerTask 23   { 24  25     int serverID, lineID, mapModelID; 26     long mapID; 27  28     /// <summary> 29     /// 指定1秒执行一次 30     /// </summary> 31     public MapHeartTimer(int serverID, int lineID, long mapID, int mapModelID) 32       : base(1000 * 10) 33     { 34       this.serverID = serverID; 35       this.lineID = lineID; 36       this.mapID = mapID; 37       this.mapModelID = mapModelID; 38     } 39  40     /// <summary> 41     ///  42     /// </summary> 43     public override void Run() 44     { 45        46       Logger.Debug("我是地图心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name); 47       Logger.Debug("我是地图心跳检查器 检查玩家是否需要复活,回血,状态"); 48       //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMapHeartTimerScript>(); 49       //foreach (var item in scripts) 50       //{ 51       //  item.Run(serverID, lineID, mapID, mapModelID); 52       //} 53     } 54  55   } 56 } 57  58  59  60  61  62 using System; 63 using System.Collections.Generic; 64 using System.Linq; 65 using System.Text; 66 using System.Threading.Tasks; 67 using Sz.MMO.GameServer.IMonsterScripts; 68  69  70 /** 71  *  72  * @author 失足程序员 73  * @Blog http://www.cnblogs.com/ty408/ 74  * @mail 492794628@qq.com 75  * @phone 13882122019 76  *  77 */ 78 namespace Sz.MMO.GameServer.TimerMonster 79 { 80   /// <summary> 81   ///  82   /// </summary> 83   public class MonsterHeartTimer: ThreadPool.TimerTask 84   { 85  86     int serverID, lineID, mapModelID; 87     long mapID; 88  89     /// <summary> 90     /// 指定1秒执行一次 91     /// </summary> 92     public MonsterHeartTimer(int serverID, int lineID, long mapID, int mapModelID) 93       : base(1000 * 10) 94     { 95       this.serverID = serverID; 96       this.lineID = lineID; 97       this.mapID = mapID; 98       this.mapModelID = mapModelID; 99     }100 101     /// <summary>102     /// 103     /// </summary>104     public override void Run()105     {      106       Logger.Debug("怪物心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name);107       Logger.Debug("怪物心跳检查器 检查怪物是否需要复活,需要回血,是否回跑");108       //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();109       //foreach (var item in scripts)110       //{111       //  item.Run(serverID, lineID, mapID, mapModelID);112       //}113     }114   }115 }116 117 118 119 using System;120 using System.Collections.Generic;121 using System.Linq;122 using System.Text;123 using System.Threading.Tasks;124 125 126 /**127  * 128  * @author 失足程序员129  * @Blog http://www.cnblogs.com/ty408/130  * @mail 492794628@qq.com131  * @phone 13882122019132  * 133 */134 namespace Sz.MMO.GameServer.TimerMonster135 {136   /// <summary>137   /// 138   /// </summary>139   public class MonsterRunTimer: ThreadPool.TimerTask140   {141 142     int serverID, lineID, mapModelID;143     long mapID;144 145     /// <summary>146     /// 指定1秒执行一次147     /// </summary>148     public MonsterRunTimer(int serverID, int lineID, long mapID, int mapModelID)149       : base(1000 * 5)150     {151       this.serverID = serverID;152       this.lineID = lineID;153       this.mapID = mapID;154       this.mapModelID = mapModelID;155     }156 157     /// <summary>158     /// 159     /// </summary>160     public override void Run()161     {      162       Logger.Debug("怪物移动定时器任务 执行线程:" + System.Threading.Thread.CurrentThread.Name);163       Logger.Debug("怪物移动定时器任务 怪物随机移动和回跑");164       //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();165       //foreach (var item in scripts)166       //{167       //  item.Run(serverID, lineID, mapID, mapModelID);168       //}169     }170   }171 }

View Code

 

就在初始化地图线程的时候加入定时器任务

 

 

 1     public MapInfo(string name, int mapModelId, int lineCount = 1) 2     { 3  4       this.MapID = SzExtensions.GetId(); 5       this.MapModelID = mapModelId; 6       Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID); 7  8       for (int i = 1; i <= lineCount; i++) 9       {10         MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线");11         //添加地图心跳检测器12         lineInfo.MapServer.AddTimerTask(new MapHeartTimer(ServerID, i, MapID, MapModelID));13         //添加怪物移动定时器14         lineInfo.MapServer.AddTimerTask(new MonsterRunTimer(ServerID, i, MapID, MapModelID));15         //添加怪物心跳检测器16         lineInfo.MapServer.AddTimerTask(new MonsterHeartTimer(ServerID, i, MapID, MapModelID));17 18         mapLineInfos[i] = lineInfo;19       }20       Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束");21     }

其实所有的任务定时器处理都是交给了timer线程,timer线程只负责查看该定时当前是否需要执行。
而具体的任务执行移交到线程执行器。线程执行器是按照队列方式执行。保证了timer线程只是一个简单的循环处理而不至于卡死
同样也保证了在同一张地图里面各个单元参数的线程安全性。

来看看效果。

为了方便我们看清楚一点,我把地图线程改为以一条线。

这样就完成了各个定时器在规定时间内处理自己的事情。

需要注意的是这里只是简单的模拟的一个地图处理各种事情,最终都是由一个线程处理的。那么肯定有人要问了。
你一个线程处理这些事情能忙得过来嘛?
有两点需要注意
1,你的每一个任务处理处理耗时是多久,换句话说你可以理解为你一秒钟能处理多少个任务。
2,你的地图能容纳多少怪物,多少玩家,多少掉落物?换句话说也就是你设计的复杂度间接限制了你的地图有多少场景对象。

那么还有什么需要注意的呢?

其实地图最大的消耗在于寻路。高性能的寻路算法和人性化寻路算法一直是大神研究的对象,我也只能是借鉴他们的了。

这一章我只是简单的阐述了地图运行和任务等划分和构成已经任务处理流程。

接下来我会继续讲解游戏服务器编程,一步一步的剖析。

文路不是很清晰。希望大家不要见怪。