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测试一下,呵呵,是不是同样也动起来了呢?

C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二)让物体动起来②

    可是大家会发现一个很大的问题:这方块移动得也太勉强了吧,抖来抖去的而且移动得也不平滑,是不是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
}

你可能感兴趣的:(silverlight)