多人即时战斗游戏服务端系列[2]--90坦克Online游戏对象介绍以及渲染机制

先上类图,略大,点击此处放大:

1.先说下方接口

1.1 场景物品接口 ISceneObject : OpLog.IOpItem, IStackPoolObject

全部场景对象的基本接口,包含类型定义,通用渲染接口,所在场景,子对象树,尺寸,坐标等..

1.2  游戏场景接口 IScene : ISceneObject

继承于基本场景接口,拥有加入对象,对象列表,获取相邻对象,等其它逻辑.

1.3  Buff基类 IBuff

buff表现,拥有持续时间,加入/删除/移动/开火/渲染/被击中时触发事件

1.4  爆裂物接口 IHitSceneObject

假设须要造成伤害/或者破坏物体,都须要先拥有一个爆裂物,爆裂物会检測爆裂范围内的接口,然后逐个进行碰撞.

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);
                }
            }
        }
时间: 2024-10-11 11:28:07

多人即时战斗游戏服务端系列[2]--90坦克Online游戏对象介绍以及渲染机制的相关文章

多人即时战斗游戏服务端系列--90坦克Online网络版详解

前言: 一直想写一个关于这个项目的系列,当前此游戏目前处于下架过程中(运营不善,虽然本人还是挺喜欢这个游戏的). 距离开发已经一年多了.趁着还没忘光,就在此整理并共享出来. 能写多少算多少..弃坑勿怪... 先上一发服务器架构图: 考虑到当时项目的考量,以及后面用户群的表现.其实这个架构有点复杂了.以至于有点浪费. 首先,我们进行几个名词解释. 1.数据差异引擎:是使用一套数据协议和对象监听,保持多个不同位置(客户端/服务端或服务/服务端)之间的多个数据对象一致,一个主节点多个副节点同步的引擎,

多人即时战斗游戏服务端系列[3]--日志类及Config类以及Excel生成类

这里说一些辅助类的设计及介绍. 依然上类图先: 首先是日志类和Config,这个库以及Config库取自暗黑3的民间模拟器mooege, 当时看了写的简短小巧就直接拿过来用了. 后来虽然发现一些问题,进行简单修改和优化之后,在项目中进行使用了. 首先是日志类,看了下类图也就没几行,简单的就是一些输出层级,带有可外部加载的扩展方法,ExtensionLogTarget,通过反射加载,然后进行一些扩展目标的输出. 修改部分的话,比较简单,原先日志是同步输出,这样会导致卡掉一些时间比较敏感的运算,这边

游戏服务端架构发展史(转)

游戏服务端架构发展史(上) 游戏服务端架构发展史(中)

为什么多数游戏服务端是用 C++ 来写

早年开发游戏必须用C++,这没得说,2000-2004年,java还没有nio,其他动态语言不抗重负,只能C/C++能开发出完整可用的游戏服务端.直到2005年,韩国的游戏很多都还是纯C++写服务端,金山之前也开发过很多纯粹C++的游戏服务端,后来大家都切了. 现代选择有很多:java + javascript, c+python, c+lua, scala, go, erlang.我们面向性能的服务器用 java,面向逻辑服务器 python,面向高并发的会选择 scala,次一级高并发或者性

网页游戏服务端-人物移动广播优化

[本文转自网络http://janeky.iteye.com/blog/1614175] 这段时间在处理服务端人物移动广播遇到了问题,记录一下. 1.问题 现在的页游都朝着客户端的方向靠齐了,大地图,千人同屏.因此,也给页游的服务端开发带来了不少的挑战.假设一个场景地图是8000*8000大小,同时有1000人在.1秒钟内,每个玩家移动一次.按照最原始的做法,就是给同一个场景的用户广播消息.简单计算一下广播量:1000*1000=1000000的广播量,有点恐怖. 2.方案 优化的目标肯定是减少

开源unity3d、cocos2dx分布式游戏服务端引擎

一款开源的支持多人同时在线实时游戏的服务端引擎,使用简单的约定协议就能够使客户端与服务端进行交互,使用KBEngine插件能够快速与 (Unity3D.OGRE.Cocos2d.HTML5,等等)技术结合形成一个完整的客户端. 服务端底层框架使用c++编写,游戏逻辑层使用Python(支持热更新),开发者无需重复的实现一些游戏服务端通用的底层技术,将精力真正集中到游戏开 发层面上来,快速的打造各种网络游戏. Homepage http://www.kbengine.org Releases so

游戏服务端架构 介绍

游戏服务端架构 介绍 端游.手游服务端常用的架构是什么样的? http://www.zhihu.com/question/29779732 根据知乎问答文章整理而成. 作者:韦易笑 谢邀,手游页游和端游的服务端本质上没区别,区别的是游戏类型. 类型1:卡牌.跑酷等弱交互服务端 卡牌跑酷类因为交互弱,玩家和玩家之间不需要实时面对面PK,打一下对方的离线数据,计算下排行榜,买卖下道具即可,所以实现往往使用简单的 HTTP服务器: 登录时可以使用非对称加密(RSA, DH),服务器根据客户端uid,当

非常强的一款开源的分布式游戏服务端引擎(kbengine)

一款开源的游戏服务端引擎,使用简单的约定协议就能够使客户端与服务端进行交互,使用KBEngine插件能够快速与(Unity3D, OGRE, Cocos2d, HTML5, 等等)技术结合形成一个完整的客户端. 服务端底层框架使用c++编写,游戏逻辑层使用Python(支持热更新),开发者无需重复的实现一些游戏服务端通用的底层技术,将精力真正集中到游戏开发层面上来,快速的打造各种网络游戏. (经常被问到承载上限,kbengine底层架构被设计为多进程分布式动态负载均衡方案,理论上只需要不断扩展硬

游戏服务端pomelo完整安装配置过程

游戏服务端pomelo安装配置 一.安装环境 debian 7.0 amd64 二.安装需要的组件 1.安装nodejs 注:debian下nodejs没有相应的apt包,所以无法用apt-get安装,只能通过nodejs的源码包安装, 这里有比较全的其他系统环境下安装nodejs的方式https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager a.安装所需的组件,python g++ #apt-get i