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统计的工具类分享给大家。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
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;
}
}

 

转载于:https://www.cnblogs.com/GameCode/archive/2010/06/17/1759516.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值