先上類圖,略大,點擊此處放大:多線程
1.先說下方接口ide
1.1 場景物品接口 ISceneObject : OpLog.IOpItem, IStackPoolObject
oop
所有場景對象的基本接口,包含類型定義,通用渲染接口,所在場景,子對象樹,尺寸,座標等..post
1.2 遊戲場景接口 IScene : ISceneObject this
繼承於基本場景接口,擁有加入對象,對象列表,獲取相鄰對象,等其它邏輯.spa
1.3 Buff基類 IBuff .net
buff表現,擁有持續時間,加入/刪除/移動/開火/渲染/被擊中時觸發事件pwa
1.4 爆裂物接口 IHitSceneObject線程
假設需要形成傷害/或者破壞物體,都需要先擁有一個爆裂物,爆裂物會檢測爆裂範圍內的接口,而後逐個進行碰撞.code
1.5 可移動物品接口 IMoveSceneObject
擁有方向,速度,以及射程,繼承此接口對象,當你設置一個速度,在每幀渲染時,都會移動,直到收到中止或最大可移動距離
1.6 可加入Buff對象 ICanBuffSceneObject
繼承此接口物品可加入/移除buff,並受ibuff事件觸發.
1.7 可以使用裝置接口(主動/被動技能) IDevice
分爲2類通常,發射一個子彈,或者加入一個buff,或者二者都有
1.8 地圖上可獲取道具接口 IItem
僅參與碰撞,被碰撞後會對碰撞對象加入一個觸發buff.
2.而後是上方buff,所有的對象改動功能大部分都是buff觸發 無敵/武器升級/沒法移動/減速/回血等..
3武器 裝置和buff的關係
3.1 所有的buff有個持續時間,有一到兩個可選參數.
3.2 所有武器都會發射子彈,所有子彈都會建立一個爆裂物,爆裂物可以對碰撞對象產生傷害和加入buff
3.3 所有子彈/爆裂物都有碰撞列表,僅會對有效的目標進行傷害和加入buff
4 遊戲世界 GameWorld
4.1 每場遊戲都是一個遊戲世界
4.2 每個遊戲時間包括一個場景
4.3 每幀這個世界會進行活動.經過fpstimer
4.4 不一樣的遊戲世界有不一樣的遊戲模式,經過繼承GameWorld
5.渲染
5.1 基類實現,檢查是否有子對象,而後進行子對象渲染(調用一次 ,對象有渲染等級,也就是渲染優先級,有些每幀渲染,有些每5幀渲染以此類推)
/// <summary> /// 渲染 /// </summary> /// <param name="ms"></param> public virtual void Render(int ms, int lv) { if (SubObjects != null && SubObjects.Count > 0) { Assert.IsNull(SubObjects, "SubObjects"); foreach (var sceneObject in SubObjects) { sceneObject.Render(ms, lv); } } }5.2 場景渲染方法 (管理加入物體和移除物體)
/// <summary> /// 渲染所有物體 /// </summary> /// <param name="ms"></param> public override void Render(int ms, int lv) { base.Render(ms, lv); Assert.IsNull(SceneObjects, "SceneObjects"); #if TEST var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif //加入延遲對象 if (DelaySceneObjects.Count > 0) { lock (DelaySceneObjects) { var removeList = new List<DelaySceneObject>(); foreach (var sceneObject in DelaySceneObjects) { if (sceneObject != null && sceneObject.ActiveTime < CurrentTimeGetter.Now) { removeList.Add(sceneObject); if (OnAddSceneObject == null || OnAddSceneObject(sceneObject.SceneObject)) AddObject(sceneObject.SceneObject); } } foreach (var delaySceneObject in removeList) { DelaySceneObjects.Remove(delaySceneObject); } } #if TEST if (sw.ElapsedMilliseconds > 20) logger.Debug("DelaySceneObjects Add:" + sw.ElapsedMilliseconds); #endif } var list = RenderList; if (OthersLv.Count > 0) { list = RenderList.Where(r => !(r is OtherSceneObject) || (r as OtherSceneObject).Lv <= 0 || lv % (r as OtherSceneObject).Lv == 0).ToList(); } foreach (var sceneObject in list) { //Assert.IsNull(sceneObject, "sceneObject"); if (sceneObject != null && sceneObject.Hp > 0) { sceneObject.Render(ms, lv); } else { SceneObjects.Remove(sceneObject); } } }
5.3 遊戲場景渲染
/// <summary> /// 渲染事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer_Elapsed(object sender, FpsTimer.FpsElapsedEventArgs e) { if (ThreadNum == 0) { ThreadNum = System.Threading.Thread.CurrentThread.ManagedThreadId; } else { if (System.Threading.Thread.CurrentThread.ManagedThreadId != ThreadNum) { #if DEBUG System.Diagnostics.Debugger.Break(); #endif } } #if !TEST try { #endif if (!IsStart) { if ((CurrentTimeGetter.Now - StartTime).TotalMilliseconds > TankConfigs.AutoStartGameSleep) { IsStart = true; Statues = GameStatues.Start; CurrentScene.AddMessage(new Message() { Type = MessageType.RoundStart, Content = CurrentTimeGetter.Now + ":回合開始:" + OpId }); } } if (!IsStart) { return; } #if DEBUG long lastms = sw.ElapsedMilliseconds; #endif Ms += (int) e.ElapsedMilliseconds; if (CurrentTimeGetter.Now.Second != lastReaderTime.Second) { if (Math.Abs((Avg - TankConfigs.RenderFps)) > TankConfigs.RenderFps/5 || Math.Abs((Ms - 1000)) > 200) logger.Warn("[" + OpId + "]Fps:" + Avg + " MS:" + Ms); Ms = Avg = 0; } lastReaderTime = CurrentTimeGetter.Now; LeftMs += (int)e.ElapsedMilliseconds; this.BeforeRender(); for (int i = 0; i < (LeftMs / (TankConfigs.RenderTimer)); i++) { Avg++; if (CurrentScene != null) { CurrentScene.Render((int)(TankConfigs.RenderTimer), LoopSeed++ % 100); } LeftMs -= (int) (TankConfigs.RenderTimer); } if (LeftMs >= TankConfigs.RenderTimer) { Avg++; if (CurrentScene != null) { CurrentScene.Render((int)(LeftMs), LoopSeed++ % 100); } LeftMs = 0; } this.AfterRender(); if (Time <= 0) { Close(); } #if DEBUG if (sw.ElapsedMilliseconds - lastms > TankConfigs.RenderTimer*5) { logger.Warn("渲染時間過長:" + (sw.ElapsedMilliseconds - lastms)); } #endif #if !TEST } catch (Exception ex) { logger.ErrorException(ex, "渲染出錯!"); ErrorCount++; if (ErrorCount >= TankConfigs.MaxReaderErrorCount) { RoundEnd(0); } } #endif }
保證每秒能渲染到設定的幀數60fps,過快或者過慢,過慢進行渲染補償(連續渲染,過快進行跳過,等待下秒)
這裏並不採用每個世界一個線程的渲染方式,而是使用線程池,相應一個線程渲染幾個特定的遊戲世界,用於避開一些多線程鎖操做.
6.玩家操做流程
6.1 玩家命令會進入相應操做的坦克隊列
/// <summary> /// 開火 /// </summary> /// <param name="index">武器序號</param> /// <param name="arg">投擲參數1-100</param> public void Fire(int index = 0, int arg = 0) { if (CheckStatues()) { if (Tank.Devices.Count > index) { Tank.EnqueueCmd(new Tank.TankCmd(Tank.TankCmd.OpCmd.Fire, index, arg)); } } }
6.2 坦克會在渲染時運行隊列中的命令
public override void Render(int ms, int lv) { while (true) { TankCmd cmd = null; Cmds.TryDequeue(out cmd); if (cmd != null) { switch (cmd.Cmd) { case TankCmd.OpCmd.Fire: FireOnWeapon(Devices[(int) cmd.Args[0]], (int) cmd.Args[1]); break; case TankCmd.OpCmd.Move: MoveOn((Direction) cmd.Args[0]); break; case TankCmd.OpCmd.StopMove: Block(); break; } } else { break; } } base.Render(ms,lv); //檢查裝置CD foreach (var device in Devices) { device.Render(ms); } //Buff渲染觸發 foreach (var buff in Buffs) { buff.OnReader(this, ms); } //移除時間已經到了的buff foreach (var buff in Buffs) { if (CurrentTimeGetter.Now > buff.EndTime) { buff.OnBuffRemove(this); } } }