【Advanced Windows Phone Programming】用做游戏的思想优化应用(1)

在移动开发中,根据业务模式的不同主要分为两个阵营,即游戏阵营和应用阵营,在windows phone中也不例外,由于开发模式和技巧有比较明显的不同,所以做应用的人并不能很轻易的转到游戏开发上,同理做游戏的亲们也需要很多努力才能做出好的应用,如果不使用游戏引擎,诸如按钮或者列表等控件写起来会比较麻烦。

应用开发中,整个系统的运行机制可以说主要是基于事件的被动通知,而在游戏开发中,运行机制则是基于主动查询的,以手指按下按钮举例,在应用开发中,首先注册按下事件,那么当用户在注册事件的按钮上上按下就会收到按下事件,而回调函数则可以根据相关的EventArgs获取参数从而进行处理,而在游戏开发中,则是在绘制每一帧之前来查询手指的按下的位置是否在按钮的包围盒内,如果在这一帧里手指按下的点的坐标在包围盒内则进行处理,否则不处理。由此可见,应用开发中有固定且较为简单的开发模式可以遵循,开发难度较小,但是效率则相对较低下,灵活性不够好,当我们需要进行一些较为高效,需要更多控制的操作时传统的开发模式可能难以满足我们的要求,而如果使用游戏开发的模式则可以比较好的解决这类问题,那我们在这里主要来探讨一下如何将游戏开发的思想应用到应用开发中。以下为最简易的传统游戏运行流程:

即:初始化-->加载资源-->更新数据(如输入状态等)-->绘制。

可见,其实整个的流程很简洁,那么在什么场景下会用到它呢?请看如下例子:

假设我们在做语聊软件,需要用到长按时对讲功能,而产品有如下需求:

1.当长按录音时开始录音并出现如下图标

2.当手指滑动到改图标时图标更改如下,并做出相应的提醒

3.录音过程中需要出现进度条来表示当前录音的秒数和进度举例如下

4.录音时间不得超过60秒,当达到六十秒的时候自动发送,当录音时间达到55秒后进度条需要变红以示警告。场景如下:

 

整个场景比较清晰,需求也较为简单,而问题出在标识的进度条上,在基础组建库里并没有提供给我们类似的控件,而且整个标识过程也需要动画效果,而且半透明图标和图标与进度条之间的透明间隔也比较棘手。

比较传统的做法是通过旋转遮罩层来显示进度条,不过实现起来会比较麻烦,然而在游戏中这类东西则随处可见,比如技能的CD,装备打造,武器升级效果等等,所以,在这里我们采用游戏开发的方式来绘制它。

PS:如果各位亲有更好的方法一定留言,不胜感谢~

第一步是建立一个录音控件UserControl,我们暂且起名为recordControl。

由于应用开发的模型是基于事件通知的,所以并不会有系统计时器来通知你进行update和draw操作,所以我们首先要生成一个计时器来模拟它。

  DispatcherTimer timer ;

然后在构造函数里来初始化这个计时器,时间间隔为1s,这样我们每一秒来绘制一小段进度条,60次画完,可喜可贺~

  public RecordControl()
        {
            InitializeComponent();
            timer=new DispatcherTimer();
            timer.Interval = new TimeSpan(0, 0, 0, 0, 1000);
            timer.Tick += timer_Tick;
        }

接着我们在timer_Tick的时候来进行更新和绘制工作

void timer_Tick(object sender, EventArgs e)
        {
            Update();
            Draw();
        }    

这样我们在Update函数中来更新时间,在draw函数中画出进度条,就大功告成了。那么如何实现进度条的UI绘制呢,在M$提供的为数不多的几何图形里我们可以选用弧线:

 <Path HorizontalAlignment="Stretch" 
              VerticalAlignment="Stretch"
              Stroke="#FFF9BF0E"
              StrokeThickness="6" Name="CountingRing" >
                <Path.Data>
                    <PathGeometry>
                        <PathFigure StartPoint="128,0">
                            <ArcSegment IsLargeArc="False" SweepDirection="Clockwise" Point="128,0" Size="128,128" />
                        </PathFigure>
                    </PathGeometry>
                </Path.Data>
            </Path>

  

ArcSegment 的具体参数可以查询msdn,这里不多做赘述,比较主要的属性有IsLargArc,当其为true的时候表示绘制大角的弧形,反之则绘制小角的弧线,SweepDirection表示是顺时针方向绘制还是逆时针方向绘制,size标明所在园的大小,而point则表示终点坐标。
所以我们只需要在update()中更新point的坐标而在draw()中将其绘制到屏幕上即可。
做法举例如下:
首先我们定义一下dispacherTimer开始时间,因为我们每秒绘制一次,所以定义一个计数器i:
Int32 i=0;

  然后在update中先检测边界时间

Update()
{
    if (i==60)
    {
        timer.Stop();
        return;
    }

  double radian = 6 * 60* 3.14 / 180;//注意sin函数用的不是角度而是弧度
  double x = r+ r * Math.Sin(radian) - e;//r为半径,e为一个绝对小值,以防止在最后一秒绘制反转,可以用(1.0 / Math.PI)来代替
  double y = r- r* Math.Cos(radian);
  CheckState();//用来检测是否需要取消录音之类的
  i++;

}    

  

  计算出了x和y值之后再把弧线画到屏幕上即可

Draw()
{
     figure.Segments.Add(new ArcSegment() { SweepDirection =SweepDirection.Clockwise, IsLargeArc = i>= 30, Point = new Point(x, y), Size = new Size(r, r) });
     geometry.Figures.Add(figure);
     CountingRing.Data = geometry;
}

嗯,试着运行一下之后我们会发现,貌似有点不对劲呐~

这个弧线绘制慢的没有节操哇~

撸妹800上要花65秒才能画完,莫扎特要67秒哇......

why?

因为DispacherTimer是前台线程,而且每次tick的时间需要等待回调函数执行结束再算,所以再算上Draw到ui上时间当然要比60秒要多,那么如何解决呢?

我们来看看xna是如何做的,首先xna的绘制分30fps和60fps两种,而不是像我们的1fps~,当update超时之后系统会将IsRunningSlowly设置为true,这样你在下次update的时候就要想办法了~

于是我们也来着手改进我们的算法:

首先不能再使用1fps了~我们将初始化函数进行修改,因为我们并不需要绘制多个精灵,所以可以稍微降低一下帧率设置为10fps,这样效果就好多了。

  public RecordControl()
        {
            InitializeComponent();
            timer=new DispatcherTimer();
            timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            timer.Tick += timer_Tick;
        }

  接着我们取消掉用i来计数,而是使用时间差来计数,即我们每次算update时的时间与录音开始时的时间差,然后算出该时间差在60s中所占的比例,然后再按比例画出相应的弧,这样可以保证时间误差小于一次tick即100ms

定义一个DateTime来记录上次Update的时间:

DateTime Mls;

  然后我们来修改Update函数:

Update()
{
    if ((DateTime.Now.Subtract(Mls).TotalMilliseconds > 60000)
    {
        timer.Stop();
        return;
    }

  double minus = DateTime.Now.Subtract(Mls).TotalMilliseconds;//时间差
    double radian = 360 * (minus / 60000) * 3.14 / 180;//所占弧度
  double x = r+ r * Math.Sin(radian) - e;//r为半径,e为一个绝对小值,以防止在最后一秒绘制反转,可以用(1.0 / Math.PI)来代替
  double y = r- r* Math.Cos(radian);
  CheckState();//用来检测是否需要取消录音之类的

}    

  接着修改draw()函数如下

Draw()
{
     figure.Segments.Add(new ArcSegment() { SweepDirection =SweepDirection.Clockwise, IsLargeArc = minus>= 30000, Point = new Point(x, y), Size = new Size(r, r) });
     geometry.Figures.Add(figure);
     CountingRing.Data = geometry;
}

  至此为止,我们的控件主体就完成了,剩下的功能,只需要在update时进行记录,然后再draw时做相应处理即可。

在下一章节中,我们来继续试试在其他的场景下来应用这个方法做改进吧~

 

 



  PS:博主开了微博了~详情猛戳:http://weibo.com/SandCu

 

 

posted @ 2012-10-15 14:28  SandCu  阅读(1387)  评论(5编辑  收藏  举报