Silverlight动画教程

原文地址:(深蓝色右手博客)http://www.cnblogs.com/alamiye010/archive/2010/07/25/1784506.html。

前言:WPF/Silverlight矢量动画的描述我就不多说了,关于WPF/Silverlight与Flash的比较网上也是一堆一堆的,这里只想客观的告诉读者下面两点:

一、WPF开发的是桌面应用程序,自包括Vista在内以后的Windows系列操作系统均大量以之为主流图形工具,即将全面取代Winform,并且Windows 7集成了.NET3.5+框架,在当今Windows系列操作系统占据90%同类市场的现状下,这意味着什么呢?

二、Silverlight基于一个约4M左右的MINI型.NET框架,从发展趋势看是绝对有与Flash抗衡并且在未来超越它的可能性。Silverlight的优势更表现在它可以用一切.NET语言例如C#,VB.NET,F#等开发,拓展度与可以参与开发的人群远远高于只能用AS开发的FLASH。

转入正题,网上已经有很多关于如何创建WPF/Silverlight动画的教程,但是均为使用Blend工具制作,或直接写在xaml代码内的动画,这样往往造成很多朋友误以为其实WPF/Silverlight不就是MS的Flash?诚然,如果您真的像那些教程里说的去开发WPF/Silverlight程序,我个人觉得一点意义都没有。这样开发出来的东西根本就超越不了Flash,那何苦还要投入如此多的精力来学习它?

所以本系列教程将全方位的以纯C#程序语言进行动态创建一切可视化对象,这才是我本系列教程希望达到的目的。本教程基于Visual Studio 2010和SilverLight4.0。

Storyboard动画

好了,那么我首先介绍第一种动态创建动画的方法,这也是官方推荐的Storyboard动画。该类型动画您可以在网络上查阅相关资料进行了解,这里不赘述了,那么我们直接进入主题。首先我们新建一个Silverlight应用程序,接下来打开MainPage.xaml进入视图代码编辑器,这里我们这样写:

<UserControl x:Class="SilverlightAnimation3.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Canvas x:Name="LayoutRoot" Background="Silver" MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
    </Canvas>
</UserControl>

这段代码我创建了一个默认名称为LayoutRoot的Canvas(画布)容器布局控件,背景银色(这里请不要将背景色去掉,否则在Canvas中无内容时,无背景色将影响Fill填充宽高从而无法实现MouseLeftButtonDown点击事件),最后注册一个鼠标在它上面点击的事件。那么为什么要选择Canvas作为容器呢?因为Canvas可以实现它内部的控件任意的绝对定位,可以很方便的处理物体的移动。界面容器元素布局好了,那么接下来就动态创建物体对象了,后台MainPage.xaml.cs代码为:

namespace SilverlightAnimation1
{
    public partial class MainPage : UserControl
    {
        //创建一个方块作为演示对象
        Rectangle rect; 

        public MainPage()
        {
            InitializeComponent();
            
            rect = new Rectangle();
            rect.Fill = new SolidColorBrush(Colors.Red);
            rect.Width = 50;
            rect.Height = 50;
            rect.RadiusX = 5;
            rect.RadiusY = 5;
            LayoutRoot.Children.Add(rect);
        }

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            
        }
    }
}

这里我创建了一个50*50象素,圆角5*5红色的方块对象,并且将它作为子控件添加进LayoutRoot中。对象准备好了,那么接下来就是实现动画了。我们要实现的是鼠标点哪它就移动到哪:

private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    //获取点击处相对于容器的坐标位置
    Point p = e.GetPosition(LayoutRoot);
    //创建移动用的动画故事板
    Storyboard storyboard = new Storyboard();
    //创建X轴方向动画
    DoubleAnimation xAnimation = new DoubleAnimation()
    {
        //起点
        From = Canvas.GetLeft(rect),
        //终点
        To = p.X,
        //花费时间
        Duration = new Duration(TimeSpan.FromMilliseconds(500)),
    };
    //将X轴方向动画赋予rectangle
    Storyboard.SetTarget(xAnimation, rect);
    //设置X轴方向动画所影响的对象属性为Canvas.Left
    Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Canvas.Left)"));
    //将X轴方向动画添加进动画故事板中
    storyboard.Children.Add(xAnimation);

    //创建Y轴方向动画
    DoubleAnimation yAnimation = new DoubleAnimation()
    {
        From = Canvas.GetTop(rect),
        To = p.Y,
        Duration = new Duration(TimeSpan.FromMilliseconds(500)),
    };
    Storyboard.SetTarget(yAnimation, rect);
    //设置Y轴方向动画所影响的对象属性为Canvas.Top
    Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Canvas.Top)"));
    storyboard.Children.Add(yAnimation);
    
    //播放动画
    storyboard.Begin();
}

从上面代码我们可以看到,首先获取鼠标点击点相对于LayoutRoot中的坐标位置p,然后创建故事板storyboard和Double类型动画doubleAnimation,接着通过Storyboard.SetTarget()和Storyboard.SetTargetProperty()分别设置动画的目标及要修改的动画目标属性,再下来将doubleAnimation添加进storyboard中,这样重复两次分别实现X轴和Y轴方向的动画。一切就绪后,通过代码storyboard.Begin()来开始动画。大家按Ctrl+F5,然后在窗体上随便点点,方块是不是会移动了呢?呵呵。

小结:Storyboard动画是基于时间线的矢量动画,它与传统的基于图片轮换形成的动画不同,它的原理是通过时时的改变对象属性而形成的,第一次接触的朋友们可能会觉得比较吃力,但是慢慢体会一下,多练习一下就会渐渐的理解了。

CompositionTarget动画

第二种方法,CompositionTarget动画,官方描述为:CompositionTarget对象可以根据每个帧回调来创建自定义动画。其实直接点,CompositionTarget创建的动画是基于每次界面刷新后触发的,与窗体刷新率保持一致,所以频率是固定的,很难人工介入控制。那么如何使用它?xaml的界面代码还是和上面中描述的一样,那么接下来就是创建对象并注册事件,全部代码如下:

namespace SilverlightAnimation2
{
    public partial class MainPage : UserControl
    {
        Rectangle rect; //创建一个方块作为演示对象       

        public MainPage()
        {
            InitializeComponent();

            rect = new Rectangle();
            rect.Fill = new SolidColorBrush(Colors.Red);
            rect.Width = 50;
            rect.Height = 50;
            rect.RadiusX = 5;
            rect.RadiusY = 5;
            //将矩形添加进画布容器中
            LayoutRoot.Children.Add(rect);
            //注册画布容器的鼠标左键点击事件
            LayoutRoot.MouseLeftButtonDown += new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);
        }        

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            
        }
    }
}

因为是基于画面不停的刷新实现的对象位置改变,因此此时的鼠标左键事件中我们需要做的是记录鼠标移动的目的以及每次移动X、Y方向的速度增量(根据三角函数中的等边比例算出)和移动次数,同时依据具体情况注册/注销CompositionTarget.Rendering事件:

bool isRendering = false; //是否启动了刷新
double speed = 10; //每次移动10像素
double xSpeed, ySpeed; //X、Y方向的速度
int num; //需要移动次数
int count; //统计移动次数

private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (!isRendering)
    {
        CompositionTarget.Rendering += new EventHandler(Rendering);
        isRendering = true;
    }
    Point start = new Point(Canvas.GetLeft(rect), Canvas.GetTop(rect));
    Point end = e.GetPosition(LayoutRoot);
    double distance = Math.Sqrt(Math.Pow((end.X - start.X), 2) + Math.Pow((end.Y - start.Y), 2));
    xSpeed = (end.X - start.X) * speed / distance;
    ySpeed = (end.Y - start.Y) * speed / distance;
    num = (int)(distance / speed);
    count = 0;
}

最后编写Rendering方法体逻辑实现矩形移动:

private void Rendering(object sender, EventArgs e)
{
    double x = Canvas.GetLeft(rect);
    double y = Canvas.GetTop(rect);
    Canvas.SetLeft(rect, x + xSpeed);
    Canvas.SetTop(rect, y + ySpeed);
    if (count == num)
    {
        CompositionTarget.Rendering -= Rendering;
        isRendering = false;
    }
    count++;
}

大体思路是:每次移动后记数count++,当移动到总次数num时将CompositionTarget.Rendering事件注销(取消订阅)掉,从而实现到达目的地时对象自动停止的效果。

DispatcherTimer动画

DispatcherTimer顾名思义是一个计时器,它的Tick事件类似于在Silverlight界面线程中进行Join操作;并且基于Silverlight/WPF的Dispatcher机制,我们无需考虑任何跨线程问题。因此DispatcherTimer在Silverlight游戏开发中应用非常广泛。

同样具备“心跳”特性的DispatcherTimer实现对象移动逻辑非常类似CompositionTarget,唯一不同的是DispatcherTimer是完全可控的。于是我们仅仅需要做的是在前面的代码基础上稍动些手脚即可,xaml界面代码仍然沿用前面的代码,那么接下来我们在后台代码中创建相关对象,具体如下:

  1. 定义一个间隔为10毫秒的DispatcherTimer对象: //计时器对象 DispatcherTimer dispatcherTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
  2. 在MainPage()中注册Tick事件:dispatcherTimer.Tick += new EventHandler(Rendering);
  3. 将CompositionTarget.Rendering += new EventHandler(Rendering);修改为dispatcherTimer.Start();
  4. 同样的将CompositionTarget.Rendering -= Rendering;修改为dispatcherTimer.Stop();

很简单对吧。由此可见DispatcherTimer实现动画的原理与CompositionTarget确实非常类似,只是我们可以通过Start()和Stop()更方便的启动/停止DispatcherTimer对象。具体代码如下:

namespace SilverlightAnimation3
{
    public partial class MainPage : UserControl
    {
        Rectangle rect; //创建一个方块作为演示对象

        //计时器对象
        DispatcherTimer dispatcherTimer = new DispatcherTimer()
        {
            Interval = TimeSpan.FromMilliseconds(10)
        };

        public MainPage()
        {
            InitializeComponent();

            rect = new Rectangle();
            rect.Fill = new SolidColorBrush(Colors.Red);
            rect.Width = 50;
            rect.Height = 50;
            rect.RadiusX = 5;
            rect.RadiusY = 5;
            //将矩形添加进画布容器中
            LayoutRoot.Children.Add(rect);
            //注册画布容器的鼠标左键点击事件
            LayoutRoot.MouseLeftButtonDown += new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);
            dispatcherTimer.Tick += new EventHandler(Rendering);
        }

        bool isRendering = false; //是否启动了刷新
        double speed = 10; //每次移动10像素
        double xSpeed, ySpeed; //X、Y方向的速度
        int num; //需要移动次数
        int count; //统计移动次数

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!isRendering)
            {
                dispatcherTimer.Start();
                isRendering = true;
            }
            Point start = new Point(Canvas.GetLeft(rect), Canvas.GetTop(rect));
            Point end = e.GetPosition(LayoutRoot);
            double distance = Math.Sqrt(Math.Pow((end.X - start.X), 2) + Math.Pow((end.Y - start.Y), 2));
            xSpeed = (end.X - start.X) * speed / distance;
            ySpeed = (end.Y - start.Y) * speed / distance;
            num = (int)(distance / speed);
            count = 0;
        }

        private void Rendering(object sender, EventArgs e)
        {
            double x = Canvas.GetLeft(rect);
            double y = Canvas.GetTop(rect);
            Canvas.SetLeft(rect, x + xSpeed);
            Canvas.SetTop(rect, y + ySpeed);
            if (count == num)
            {
                dispatcherTimer.Stop();
                isRendering = false;
            }
            count++;
        }
    }
}

是不是很简单?这样的话可以很轻松的通过Interval 来控制刷新一个对象属性的频率了。接下来我们同样使用Ctrl+F5来测试一下成果。呵呵,结果和第二种动画方法是一样的。

那么到此,三种动态创建动画的方法都已经详细介绍过了,大家可能会有种感觉,比较钟情于第一种WPF/Silverlight推荐的Storyboard动画,既直观又方便使用,而且仿佛不易出错。其实这3种动画都有它特定的使用场合。

第一种动画适合创建简单的对象位移及直接性质的属性更改(在后面的教程中,我还将更深入的挖掘Storyboard动画的潜力,动态创建更复杂的基于KeyFrame的关键帧动画)。

第二种动画适合全局属性的时时更改,例如我们后面要讲到的敌人或NPC以及地图等全体性的相对位移及属性更改时就要用到它了。

第三种动画则非常适合运用在Spirit(角色)的个人动画中,例如角色的移动,战斗,施法等动作。

源代码SilverlightAnimation.zip下载

文件下载(已下载 2993 次)

发布时间:2011/3/31 上午10:06:01  阅读次数:12722

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号