• Silverlight游戏开发心得(2)——调度器的其他话题


      上篇文章的访问量竟然超过了1000,哈哈,还从来没有这么多人看过我的文章呢。虽然在公司内部,我十分看重交流,互相汲取经验,避免重复造轮子的事情发生,但在这之外,还没有过如此多的人来看自己的东西。

      我仍然要强调一个事情,虽然已经强调多遍:朋友们,如果你打算腾出你宝贵的时间来看我写的文章,那么请你一定腾出更多的时间去实践一下,用代码来实践这一切。这要比阅读困难的多,我只花了个把小时就读完了调度器这个章节,但我却花了整整一个月来实现它。但也只有在这样的过程中,才能够学习到属于自己的东西——“实践出真知”。

      I : 调度器的扩展

      在上篇文章里,我向大家展示了一个简单调度器的结构。在这个基础上,能够做的事情还很多。其中性能的均衡是很重要的一点。建议大家看看这篇文章《游戏里的时钟》。里面那张消去波峰的图很有意思,当然那是一种理想情况,但并非没有可能做到。首先我们要得到系统的负载情况;在Silverlight 中这一点很容易做到;如果您对Silverlight 的API很熟悉,就能够想起来这个 Analytics 类,其实即便不熟悉也没有关系,随时可以查阅Silverlight文档----“Manual is your friend .” 更好的办法就是写成一个工具类,放在那里随时,可以调用。一旦得到CPU的负载,接下来的事情就好办多了,这时候就体现了调度器的强大,在调度器的结构里,需要执行的是一个个在队列里的时间任务和帧任务,还有每帧都执行一次,永不停息的渲染任务。(在实际的开发中,如果有必要,你也可以定义更多的任务类型。)或许某一段特殊的时间内,CPU的负载极高,达到你设置的警戒线了,比如60%,游戏里的人物变得很卡,这是非常糟糕的事情,这个时候就有必要进行一下时钟调节。

      在简单调度器里,任务队列的任务是无条件得到执行的,那么现在,可能只有在CPU负载不超过警戒线的情况下得以执行,否则就会进行调节:调节的手段是多样的;调节的策略根据具体的情况而来。你可以简单的在高于警戒线的时候就略过一些任务;也可以把任务的帧间隔/时间间隔拉的更长,比如2帧执行一次的任务,可以是3帧执行一次,甚至你可以把那个永不停息的渲染任务停止,这都取决于你的具体情况。应该说,这里不仅仅是调度器的问题了,还需要很多其他的模块相配合,但最终是在调度器这里得到解决的。或许现在我要赞美一下调度器了,在开发过程中有这样一个单独的出口是多么美妙的事情呀。

      据一个例子说明在游戏里调度器的性能调节:在游戏里新登录的时候,在出生地那里总是聚集着很多的人,这时候游戏会变得很卡,虽然你会很快离开这里到其他地方去冒险,但很卡,运动的很慢,离开这个地方都变得困难。这时候我们可以执行一个“视觉重点”的调节策略,假设你的注意力都集中在以你为中心的一小块区域上,对于其他区域你是不那么关心的,虽然随着你四处走动,这个区域在变化,就像你在舞台上被一盏灯照着一样。那么我们就可以调节调度器,把这个区域内的任务都照常执行,但在这个区域外的任务进行调节,注意,这个时候的调节也是要非常小心的,并不是越多越好,其实应该说是越少越好,比如你可以把其他人物的动画渲染帧频放慢,可以取消一些场景特效,但你无论如何都要保证你的网络数据包如期发送。我们需要找到负载最大的任务,但并不是说就要调节它,如果这个任务是非常的重要,那么可怕就要在其他地方想办法了。这都取决于你的游戏具体情况。应该说,设计一套良好的调节策略是不容易的。

      在进行你的调节策略时,还需要防止一点,就是调节震荡的产生;比如你现在的CPU负载超过了警戒线,决定把A任务忽略执行,A任务被忽略后,CPU负载很快下降,此时系统觉得一切正常了,于是把一切都恢复了,A任务同样也得以恢复;结果CPU又马上超过负载,于是系统就在警戒线的两侧开始震荡,这不是我们希望的,要避免这些,要把调整量限制的更为合适,或者改进效果统计分析。

      II:多线程

      摩尔定律不再起作用后,并行编程的重要性怎么提都为过。

      我要说一个令人沮丧的事情,在目前的结构和经验中,整体都是基于单线程的。都在探讨高效的算法,精巧的结构。但对于并行,提的不多,经验也少。我也尝试在游戏里引入并行,我必须要承认,这很难。当然了,对于我困难的事情,对于各位来说,或许很容易,毕竟我不是一个“Smart Guy”。调度器基于任务系统的结构,还是给我们使用多线程提供了方便的。首先,需要拆分代码中过于庞杂的函数。 比如在这个方法里 public void ExecuteFrame(),执行力帧任务队列,时间任务队列,还有渲染任务。如果把帧任务队列和时间任务队列拆分出来,就可以建立两个线程来分别执行他们。我要提到的一点就是,避免在不同的线程里执行和UI相关联的事情,即便使用异步委托取得了跨线程的操作权限,得到的结果仍然是阻塞的,串行的,这并没有多少意义。在设计的过程中,应该小心的辨别,分拆得到任务的执行顺序和关联关系,尽量避免竞争资源,逻辑处理放在前面(只要不涉及UI的时候,Silverlight还是可以真正多线程并行处理的),UI的渲染可以统一执行。

       这里涉及的种种问题,恐怕只有在实践中慢慢体会了,我希望自己这块砖抛出来,能吸引片片美玉飞来,大家共同探讨探讨。

    在这里我把自己写的一个CPU统计的工具类分享给大家。

    代码
    public class CPUCounter
    {
    private static TextBlock txtCpuLoader;
    private static Canvas _canvas;
    private static System.Windows.Threading.DispatcherTimer dt;
    private static Analytics analytics;

    //此方法使IE的状态栏上显示FPS
    //大部分教程提到要在Html里面添加 <param name="EnableFrameRateCounter" value="true"/>,
    //但我的实验结果是添加与否都会显示FPS,但IE的安全设置要修改
    public static void ShowFrameFrequency(bool _showFrequency, int _maxFrameRate)
    {
    System.Windows.Interop.SilverlightHost host
    = Application.Current.Host;
    System.Windows.Interop.Settings settings
    = host.Settings;
    settings.EnableFrameRateCounter
    = _showFrequency;
    settings.MaxFrameRate
    = _maxFrameRate;
    }

    //添加一个CPU统计到指定的Canvas上面,可以指定颜色大小和位置
    //这个统计器是自启动的
    public static void AddCPULoader(Canvas _rootCanvas, int _fontSize, Color _color, Point _pos)
    {
    _canvas
    = _rootCanvas;
    txtCpuLoader
    = new TextBlock()
    {
    FontSize
    = _fontSize,
    Foreground
    = new SolidColorBrush(_color),
    FontWeight
    = FontWeights.Bold
    };

    _canvas.Children.Add(txtCpuLoader);
    Canvas.SetLeft(txtCpuLoader, _pos.X);
    Canvas.SetTop(txtCpuLoader, _pos.Y);
    dt
    = new System.Windows.Threading.DispatcherTimer();
    dt.Interval
    = new TimeSpan(0, 0, 0, 0, 1);
    analytics
    = new Analytics();
    dt.Tick
    += new EventHandler(PrintTxt);
    dt.Start();
    }

    static void PrintTxt(object sender, EventArgs e)
    {
    txtCpuLoader.Text
    = "CourseCPU:" + GetSuitableDouble(analytics.AverageProcessLoad, 2) +
    "% AllCPU:" + GetSuitableDouble(analytics.AverageProcessorLoad, 2) + "%";
    }

    //卸载CPU统计器
    public static void RemoveCPULoader()
    {
    if (_canvas != null)
    {
    _canvas.Children.Remove(txtCpuLoader);
    if (dt != null)
    {
    dt.Tick
    -= new EventHandler(PrintTxt);
    dt.Stop();
    }
    }
    }

    public static string GetSuitableDouble(double _d, int _digit)
    {
    string result = string.Empty;
    int d = (int)(_d * Math.Pow(10, _digit));
    double dd = d / Math.Pow(10, _digit);
    if (dd == 0)
    {
    for (int i = 0; i < _digit; i++)
    {
    result
    += "0";
    }
    return ("0." + result);
    }
    if (dd.ToString().Length != d.ToString().Length + 1)
    {
    result
    = dd.ToString().PadRight(d.ToString().Length + 1, '0');
    return result;
    }
    result
    = dd.ToString();
    return result;
    }
    }
  • 相关阅读:
    C#连接手机安装软件和发送信息
    asp.net 简单分页打印
    asp.net 下载的几种方式
    js 刷新后不提示并保留控件状态
    JAVA 基础编程练习题2 【程序 2 输出素数】
    JAVA 基础编程练习题1 【程序 1 不死神兔】
    setMaxActive和setMaxWait方法
    java.lang.UnsupportedClassVersionError: com/mysql/jdbc/Driver : Unsupported major.minor version 52.0
    java.lang.RuntimeException: org.dom4j.DocumentException: 1 字节的 UTF-8 序列的字节 1 无效。
    HTML DOM firstChild lastChild nextSibling previousSibling 属性_获取属性值的undefined问题
  • 原文地址:https://www.cnblogs.com/GameCode/p/1759516.html
Copyright © 2020-2023  润新知