zoukankan      html  css  js  c++  java
  • C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二)让物体动起来②

        第二种方法,CompositionTarget动画,官方描述为:CompositionTarget对象可以根据每个帧回调来创建自定义动画。其实直接点,CompositionTarget创建的动画是基于每次界面刷新后触发的,与窗体刷新率保持一致,所以频率是固定的,很难人工介入控制。

        那么如何使用它?xaml的界面代码还是和上一篇中描述的一样,这里不累述了。那么接下来就是创建对象并注册事件,全部代码如下:

    Rectangle rect; //创建一个方块作为演示对象

    double speed = 1; //设置移动速度

    Point moveTo; //设置移动目标

    public Window1() {

    InitializeComponent();

    rect = new Rectangle();

    rect.Fill = new SolidColorBrush(Colors.Red);

    rect.Width = 50;

    rect.Height = 50;

    rect.RadiusX = 5;

    rect.RadiusY = 5;

    Carrier.Children.Add(rect);

    Canvas.SetLeft(rect, 0);

    Canvas.SetTop(rect, 0);

    //注册界面刷新事件

    CompositionTarget.Rendering += new EventHandler(Timer_Tick);

    }

    private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

    moveTo = e.GetPosition(Carrier);

    }

            CompositionTarget的注册事件方法为:

            CompositionTarget.Rendering += new EventHandler(Timer_Tick);

        因为我们要实现的是鼠标点哪方块就移动到哪,所以我用一个变量moveTo保存鼠标点击点的Point。并在鼠标左键事件中赋值:moveTo = e.GetPosition(Carrier);同时设置方块X,Y方向的速度均为speed。

        接下来就是实现Timer_Tick了,它是基于窗体的时时刷新事件。我们这样写:

    private void Timer_Tick(object sender, EventArgs e) {

    double rect_X = Canvas.GetLeft(rect);

    double rect_Y = Canvas.GetTop(rect);

    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed : -speed));

    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed : -speed));

    }

        首先获取方块的X,Y位置,接下让方块的X,Y与moveTo的X,Y进行比较而判断是+speed还是-speed,这里的逻辑需要朋友们自行领会了。

        好了Ctrl+F5测试一下,呵呵,是不是同样也动起来了呢?

        可是大家会发现一个很大的问题:这方块移动得也太勉强了吧,抖来抖去的而且移动得也不平滑,是不是CompositionTarget有问题?其实不然,因为之前的Storyboard动画它不存在X,Y轴的速度,只需要设定起点和终点以及过程经历的时间就可以平滑的移动了,而CompositionTarget需要分别设定X,Y轴的速度,而我们这为了简单演示,X,Y轴的速度speed均设置成了5,这在现实使用中是绝对不合理的。因此,如果要模拟实际效果,必须计算终点和起点的正切值Tan,然后再根据直线速度speed通过Tan值计算出speed_X,speed_Y,最后改写成:

            Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));

            Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y));

        这样才能实现真实的移动(具体算法就不讨论了)。

        这一节讲解了如何使用CompositionTarget主界面刷新线程实现基于帧的动画,下一节我将讲解第三种动态创建动画的方法,并会对这三种方法进行一个归纳比较。

    WPF/Silverlight

    作者:深蓝色右手
    出处:http://alamiye010.cnblogs.com/
    教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
    本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。

    Tag标签: WPF/Silverlight动画游戏教程

    深蓝色右手
    关注 - 38
    粉丝 - 250

    荣誉:微软社区精英推荐博客

    关注博主

    1

    0

    0

    (请您对文章做出评价)

    « 上一篇:C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(一)让物体动起来①
    » 下一篇:C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(三)让物体动起来③

    posted on 2009-06-17 21:04 深蓝色右手 阅读(9703) 评论(41) 编辑 收藏

    Feedback

    1876220

    #1楼

    2009-06-18 09:32 | 花纯春

    支持博主
    回复 引用 查看

    #2楼

    2009-06-18 12:46 | sparks345

    第1节课就出师不利啊⊙﹏⊙b
    为什么我的这里报错哇 Storyboard下就没有SetTarget方法
    Storyboard.SetTarget
    还有这里,说缺参数
    storyboard.Begin();
    回复 引用 查看

    #3楼

    2009-06-18 13:14 | W.SiMin[未注册用户]

    个人感觉XNA比较缺乏GUI系统,要自己写,然后注入进去,还有其他的比如物理啊,什么的都要自己实现,比起C++那么多的开源组件,XNA显得比较蹩脚,就像当年的DIRECTX.NET
    回复 引用

    #4楼[楼主]

    2009-06-18 15:26 | 深蓝色右手

    @sparks345
    注意添加引用,需要引用Media类
    回复 引用 查看

    #5楼

    2009-06-18 21:06 | 凯锐

    @sparks345
    是你的.net framework沒有打sp1,這個Storyborad在sp1中有修改過的!
    博主在目錄中就指出了在vs2008 sp1 +以上開發的呀!
    呵呵.....
    回复 引用 查看

    #6楼

    2009-06-22 10:39 | 丝丝[未注册用户]

    能不能把示例代码放上来
    回复 引用

    #7楼

    2009-06-22 16:40 | yuhenyunchou[未注册用户]

    private void Timer_Tick(object sender, EventArgs e)
    {
    var rectX = Canvas.GetLeft(rectangle);
    var rectY = Canvas.GetTop(rectangle);
    if (moveTo.X == rectX && moveTo.Y == rectY) return;
    var width = Math.Abs(rectX - moveTo.X);
    var height = Math.Abs(rectY - moveTo.Y);
    var speedX = width > height ? speed : Width * speed / height;
    var speedY = width > height ? height * speed / width : speed;
    Canvas.SetLeft(rectangle, rectX + (rectX < moveTo.X ? speedX : -speedX));
    Canvas.SetTop(rectangle, rectY + (rectY < moveTo.Y ? speedY : -speedY));
    }
    回复 引用

    #8楼

    2009-06-22 16:42 | 雨恨云愁

    真实的移动实现补充如上
    回复 引用 查看

    #9楼

    2009-06-22 17:14 | 雨恨云愁

    疏忽了,上面的代码
    var speedX = width > height ? speed : Width * speed / height;
    应该改成:
    var speedX = width > height ? speed : width * speed / height;
    回复 引用 查看

    #10楼[楼主]

    2009-06-22 18:31 | 深蓝色右手

    @雨恨云愁
    ^()^,感谢您的支持!
    回复 引用 查看

    #11楼

    2009-06-27 18:10 | Fencer_HAHA[未注册用户]

    @雨恨云愁
    你在9楼写的代码不一样吗?
    而且加上后有时候还是不稳定
    回复 引用

    #12楼

    2009-07-04 17:44 | ianc

    画面抖动的原因应该不是博主讲的那样
    private void Timer_Tick(object sender, EventArgs e) {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if (Math.Abs(rect_X - moveTo.X) > 1)
    {
    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed : -speed));
    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed : -speed));
    }
    }
    这样就可以了。
    回复 引用 查看

    #13楼

    2009-07-05 17:34 | mienfong[未注册用户]

    樓上少了Y軸,是下面的寫法才對。
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if (Math.Abs(rect_X - moveTo.X) > 1 || Math.Abs(rect_Y - moveTo.Y) > 1)
    {
    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed : -speed));
    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed : -speed));
    }
    回复 引用

    #14楼[楼主]

    2009-07-05 18:03 | 深蓝色右手

    @mienfong
    @ianc
    呵呵,感谢大家的支持!
    回复 引用 查看

    #15楼

    2009-07-10 16:16 | 乡村里的.NET

    上面的代码也没解决抖动的问题
    回复 引用 查看

    #16楼

    2009-07-18 09:50 | 刑ˇ天

    怎么要转弯啊?不能直线吗?
    回复 引用 查看

    #17楼[楼主]

    2009-07-18 17:16 | 深蓝色右手

    @刑ˇ天
    不太清楚您说的是什么
    回复 引用 查看

    #18楼

    2009-07-23 17:03 | sdhj

    Rectangle rect;
    double speed = 1;
    double speed_X;
    double speed_Y;
    Point moveTo=new Point(0,0);
    public Window2()
    {
    InitializeComponent();
    rect = new Rectangle();
    rect.Fill = new SolidColorBrush(Colors.Red);
    rect.Width = 50;
    rect.Height = 50;
    rect.RadiusX = 5;
    rect.RadiusY = 5;
    Carrier.Children.Add(rect);
    Canvas.SetLeft(rect, 0);
    Canvas.SetTop(rect, 0);
    //注册界面刷新事件
    CompositionTarget.Rendering += new EventHandler(Timer_Tick);
    }
    private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
    moveTo = e.GetPosition(Carrier);
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    double x = Math.Abs(moveTo.X - rect_X);
    double y = Math.Abs(moveTo.Y - rect_Y);
    double len = Math.Sqrt(x * x + y * y);
    if (len != 0)
    {
    speed_X = (x / len) * speed;
    speed_Y = (y / len) * speed;
    }
    else
    {
    speed_X = speed;
    speed_Y = speed;
    }
    }
    private void Timer_Tick(object sender, EventArgs e)
    {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));
    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y));
    }
    回复 引用 查看

    #19楼

    2009-07-30 21:18 | 为理想在路上奔跑

    楼主,我把那个speed改大了点,抖的好厉害哦,这是什么原因,是应为刷新的问题么
    回复 引用 查看

    #20楼

    2009-08-05 17:15 | wangxj[未注册用户]

    DoubleAnimation doubleAnimation = new DoubleAnimation( Canvas.GetLeft(rect), p.X,new Duration(TimeSpan.FromMilliseconds(500)));
    我在wpf里,这句可以通过,在silverlight里,我句报错,wpf和sl不一样吗?
    回复 引用

    #21楼[楼主]

    2009-08-05 18:22 | 深蓝色右手

    @wangxj
    写法上会有区别,功能上也是有差距的,毕竟SL是子集
    回复 引用 查看

    #22楼

    2009-08-08 16:21 | 快乐的流浪汉

    刚开始学你的这个系列教程,很好!!!
    好象还没有人解决speed改大抖的厉害的问题哦!
    我改了一下,解决了这个问题,供参考一下,呵呵!!!!!
    其他部分代码一样,主要改动如下:
    private void Timer_Tick(object sender, EventArgs e)
    {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if (moveTo.X == rect_X && moveTo.Y == rect_Y)
    return;
    double width = Math.Abs(rect_X - moveTo.X);
    double height = Math.Abs(rect_Y - moveTo.Y);
    double judgeSpeed = width > height ? width : height;
    if (judgeSpeed > speed)
    {
    double speed_X = width > height ? speed : width * speed / height;
    double speed_Y = width > height ? height * speed / width : speed;
    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));
    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y));
    }
    else
    {
    Canvas.SetLeft(rect, moveTo.X);
    Canvas.SetTop(rect, moveTo.Y);
    }
    }
    回复 引用 查看

    #23楼

    2009-08-09 21:23 | sparks345

    double rectX = Canvas.GetLeft(rect);
    double rectY = Canvas.GetTop(rect);
    if (Double.IsNaN(rectY))
    rectY = 0;
    Canvas.SetLeft(rect, rectX +( rectX > moveTo.X ? -speed : speed));
    double lx = Math.Abs(moveTo.X - rectX);
    double ly = Math.Abs(moveTo.Y - rectY);
    double speedY = ly * speed / lx;
    Canvas.SetTop(rect, rectY + (rectY > moveTo.Y ? -speedY : speedY));
    ------------------
    不知道为什么 Canvas.GetTop(rect) 会返回 NaN~~
    好郁闷~~
    回复 引用 查看

    #24楼[楼主]

    2009-08-09 21:43 | 深蓝色右手

    不行你先设置一下Canvas.SetTop(rect)调试一下
    回复 引用 查看

    #25楼

    2009-08-18 14:39 | peerless

    呵呵呵 这是很不错的教程啊。。。。
    谢谢楼主
    我这样做了一使矩形不抖
    Rectangle rect; //创建一个方块作为演示对象
    double actionTime = 400; //设置动作时间(相对于屏幕刷新次数的时间,如400即为屏幕刷新400次需要的时间)
    double speedX = 0;
    double speedY = 0;
    Point moveTo; //设置移动目标
    public MainPage()
    {
    InitializeComponent();
    rect = new Rectangle();
    rect.Fill = new SolidColorBrush(Colors.Red);
    rect.Width = 50;
    rect.Height = 50;
    rect.RadiusX = 5;
    rect.RadiusY = 5;
    Carrier.Children.Add(rect);
    Canvas.SetLeft(rect, 0);
    Canvas.SetTop(rect, 0);
    //注册界面刷新事件
    CompositionTarget.Rendering += new EventHandler(Timer_Tick);
    }
    private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
    moveTo = e.GetPosition(Carrier);
    speedX = (moveTo.X - Canvas.GetLeft(rect))/actionTime;
    speedY = (moveTo.Y - Canvas.GetTop(rect))/actionTime;
    }
    private void Timer_Tick(object sender, EventArgs e)
    {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if ((moveTo.X - Canvas.GetLeft(rect) < 1) && (moveTo.X - Canvas.GetLeft(rect) > -1))
    speedX = 0;
    if ((moveTo.Y - Canvas.GetTop(rect) <1) && (moveTo.Y - Canvas.GetTop(rect) > -1))
    speedY = 0;
    Canvas.SetLeft(rect, rect_X + speedX);
    Canvas.SetTop(rect, rect_Y + speedY);
    }
    回复 引用 查看

    #26楼

    2009-08-20 09:49 | 卷毛[未注册用户]

    产生闪烁的主要原因如下:
    speed代表了方块每次移动的距离,当最后一次移动时,会超过moveTo所指定值,所以,要比较每次移动的预期位置与moveTo,当预期位置小于moveTo时,移动到预期的位置,否则移动到moveTo。
    具体代码如下,请各位指正:
    private void Timer_Tick(object sender, EventArgs e)
    {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if (rect_X == moveTo.X && rect_Y == moveTo.Y)
    return;
    double width = Math.Abs(rect_X - moveTo.X);
    double height = Math.Abs(rect_Y - moveTo.Y);
    double len = Math.Sqrt(width * width + height * height);
    double speed_X = speed * width / len;
    double speed_Y = speed * height / len;
    double offset_X = rect_X < moveTo.X ? speed_X : -speed_X;
    double offset_Y = rect_Y < moveTo.Y ? speed_Y : -speed_Y;
    if(Math.Abs(rect_X - moveTo.X) > offset_X)
    Canvas.SetLeft(rect, rect_X + offset_X);
    else
    Canvas.SetLeft(rect, moveTo.X);
    if (Math.Abs(rect_Y - moveTo.Y) > offset_Y)
    Canvas.SetTop(rect, rect_Y + offset_Y);
    else
    Canvas.SetTop(rect, moveTo.Y);
    }
    回复 引用

    #27楼

    2009-12-16 14:59 | 哈哈哈由于[未注册用户]

    引用卷毛:
    private void Timer_Tick(object sender, EventArgs e)
    {
    double rect_X = Canvas.GetLeft(rect);
    double rect_Y = Canvas.GetTop(rect);
    if (rect_X == moveTo.X && rect_Y == moveTo.Y)
    return;
    double width = Math.Abs(rect_X - moveTo.X);
    double height = Math.Abs(rect_Y - moveTo.Y);
    double len = Math.Sqrt(width * width + height * height);
    double speed_X = speed * width / len;
    double speed_Y = speed * height / len;
    double offset_X = rect_X < moveTo.X ? speed_X : -speed_X;
    double offset_Y = rect_Y < moveTo.Y ? speed_Y : -speed_Y;
    if(Math.Abs(rect_X - moveTo.X) > offset_X)
    Canvas.SetLeft(rect, rect_X + offset_X);
    else
    Canvas.SetLeft(rect, moveTo.X);
    if (Math.Abs(rect_Y - moveTo.Y) > offset_Y)
    Canvas.SetTop(rect, rect_Y + offset_Y);
    else
    Canvas.SetTop(rect, moveTo.Y);
    } quote]
    可以讲讲吗都是什么意思吗?
    回复 引用

    #28楼

    2010-01-12 00:36 | 采云摘月

    按照楼主提出的解决方案,果然能够防止抖动!谢谢,楼主提醒啊!!
    代码如下:

    view source

    print?

    01
    double rect_X = Canvas.GetLeft(rect);

    02
    double rect_Y = Canvas.GetTop(rect);

    03
    double x = Math.Abs(moveTo.X - rect_X);

    04
    double y = Math.Abs(moveTo.Y - rect_Y);

    05
    double tanR;

    06
    if (x == 0)

    07
    {

    08
    tanR = 0;

    09
    speed_Y = 0;//

    10
    speed_X = 0;

    11
    }

    12
    else

    13
    {

    14
    tanR = y / x;

    15
    speed_Y = speed * Math.Sin(Math.Atan(tanR));//

    16
    speed_X = speed * Math.Cos(Math.Atan(tanR));

    17
    }

    18
    Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));

    19
    Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y));

    回复 引用 查看

    #29楼

    2010-01-27 10:11 | 包建强

    CompositionTarget的Rending事件上挂的方法,不用的时候最好Remove掉,不然会降低性能。
    回复 引用 查看

    #30楼[楼主]

    2010-01-27 15:13 | 深蓝色右手

    @包建强
    是的,类似传统游戏中的游戏主循环。
    回复 引用 查看

    #31楼

    2010-03-23 22:39 | Coki

    角度只在左键点击时才会改变,应该把计算speed_x和speed_y的过程放在鼠标点击事件里,帧更新的方法里只需要加减这个固定的速度就好了,抖动是因为某帧结束后方块从目标点一边移动到了另一边,下一帧时他又会移回来,如此往复。所以防止抖动还要加上一个判断,假如某方向当前距离小于或等于该方向的速度,直接把方块移动到目标点上即可。

    view source

    print?

    01
    private void Timer_Tick(object sender, EventArgs e)

    02
    {

    03
    double rect_x = Canvas.GetLeft(rect);

    04
    double rect_y = Canvas.GetTop(rect);

    05
    if (Math.Abs(rect_x - moveTo.X) <= speed_x || Math.Abs(rect_y - moveTo.Y) <= speed_y)

    06
    {

    07
    Canvas.SetLeft(rect, moveTo.X);

    08
    Canvas.SetTop(rect, moveTo.Y);

    09
    }

    10
    else

    11
    {

    12
    Canvas.SetLeft(rect, rect_x + ((rect_x > moveTo.X) ? -speed_x : speed_x));

    13
    Canvas.SetTop(rect, rect_y + ((rect_y > moveTo.Y) ? -speed_y : speed_y));

    14
    }

    15
    }

    16

    17
    private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    18
    {

    19
    moveTo = e.GetPosition(carrier);

    20
    x = Math.Abs(Canvas.GetLeft(rect) - moveTo.X);

    21
    y = Math.Abs(Canvas.GetTop(rect) - moveTo.Y);

    22
    if (x != 0 && y != 0)

    23
    {

    24
    double tan = y / x;

    25
    speed_y = Math.Sin(Math.Atan(tan)) * speed;

    26
    speed_x = (1 / tan) * speed_y;

    27
    }

    28
    else if (x == 0 && y != 0)

    29
    {

    30
    speed_x = 0;

    31
    speed_y = speed;

    32
    }

    33
    else if (x != 0 && y == 0)

    34
    {

    35
    speed_x = speed;

    36
    speed_y = 0;

    37
    }

    38
    else if (x == 0 && y == 0)

    39
    {

    40
    speed_x = 0;

    41
    speed_y = 0;

    42
    }

    43
    }

  • 相关阅读:
    Authentication with SignalR and OAuth Bearer Token
    [Web API] 如何让 Web API 统一回传格式以及例外处理[转]
    EF6 Database First (DbContext)
    DbContext运行时动态附加上一个dbset
    命令模式
    责任链模式
    策略模式
    Sql Server isnull() 用法
    状态者模式
    dom元素改变监听
  • 原文地址:https://www.cnblogs.com/hsapphire/p/1829927.html
Copyright © 2011-2022 走看看