欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【WPF学习】第五十六章 基于帧的动画

程序员文章站 2024-01-22 12:50:58
除基于属性的动画系统外,WPF提供了一种创建基于帧的动画的方法,这种方法只使用代码。需要做的全部工作是响应静态的CompositionTarge.Rendering事件,触发该事件是为了给每帧获取内容。这是一种非常低级的方法,除非使用标准的基于属性的动画模型不能满足需要(例如,构建简单的侧边滚动游戏 ......

  除基于属性的动画系统外,wpf提供了一种创建基于帧的动画的方法,这种方法只使用代码。需要做的全部工作是响应静态的compositiontarge.rendering事件,触发该事件是为了给每帧获取内容。这是一种非常低级的方法,除非使用标准的基于属性的动画模型不能满足需要(例如,构建简单的侧边滚动游戏、创建基于物理的动画式构建粒子效果模型(如火焰、雪花以及气泡)),否则不会希望使用这种方法。

  构建基于帧的动画的基本技术很容易。只需要为静态的compositiontarget.rendering事件关联事件处理程序。一旦关联事件处理程序,wpf就开始不断地调用这个事件处理程序(只要渲染代码的执行速度足够快,wpf每秒将调用60次)。在渲染事件处理程序中,需要在窗口中相应地创建或调整元素。换句话说,需要自行管理全部工作。当动画结束时,分离事件处理程序。

  下图显示了一个简单示例。在此,随机数量的圆从canvas面板的顶部向底部下落。它们(根据随机生成的开始速度)以不同速度下降,但一相同的速率加速。当所有的圆到达底部时,动画结束。

【WPF学习】第五十六章 基于帧的动画

  在这个示例中,每个下落的圆由ellipse元素表示。使用自定义的ellipseinfo类保存椭圆的引用,并跟踪对于物理模型而言十分重要的一些细节。在这个示例中,只有如下信息很重要——椭圆沿x轴的移动速度(可很容易地扩张这个类,使其包含沿着y轴运动的速度、额外的加速信息等)。

public class ellipseinfo
    {
        public ellipse ellipse
        {
            get;
            set;
        }

        public double velocityy
        {
            get;
            set;
        }

        public ellipseinfo(ellipse ellipse, double velocityy)
        {
            velocityy = velocityy;
            ellipse = ellipse;
        }
    }

  应用程序使用集合跟踪每个椭圆的ellipseinfo对象。还有几个窗口级别的字段,它们记录计算椭圆下落时使用的各种细节。可很容易地使这些细节变成可配置的。

private list<ellipseinfo> ellipses = new list<ellipseinfo>();

private double accelerationy = 0.1;
private int minstartingspeed = 1;
private int maxstartingspeed = 50;
private double speedratio = 0.1;
private int minellipses = 20;
private int maxellipses = 100;
private int ellipseradius = 10;

  当单击其中某个按钮时,清空集合,并将事件处理程序关联到compositiontarget.rendering事件:

        private bool rendering = false;
        private void cmdstart_clicked(object sender, routedeventargs e)
        {
            if (!rendering)
            {
                ellipses.clear();
                canvas.children.clear();

                compositiontarget.rendering += renderframe;
                rendering = true;
            }
        }
        private void cmdstop_clicked(object sender, routedeventargs e)
        {
            stoprendering();
        }

        private void stoprendering()
        {
            compositiontarget.rendering -= renderframe;
            rendering = false;
        }    

  如果椭圆不存在,渲染代码会自动创建它们。渲染代码创建随机数量的椭圆(当前为20到100个),并使他们具有相同的尺寸和颜色。椭圆被放在canvas面板的顶部,但他们沿着x轴随机移动:

 private void renderframe(object sender, eventargs e)
        {
            if (ellipses.count == 0)
            {
                // animation just started. create the ellipses.
                int halfcanvaswidth = (int)canvas.actualwidth / 2;

                random rand = new random();
                int ellipsecount = rand.next(minellipses, maxellipses + 1);
                for (int i = 0; i < ellipsecount; i++)
                {
                    ellipse ellipse = new ellipse();
                    ellipse.fill = brushes.limegreen;
                    ellipse.width = ellipseradius;
                    ellipse.height = ellipseradius;
                    canvas.setleft(ellipse, halfcanvaswidth + rand.next(-halfcanvaswidth, halfcanvaswidth));
                    canvas.settop(ellipse, 0);
                    canvas.children.add(ellipse);

                    ellipseinfo info = new ellipseinfo(ellipse, speedratio * rand.next(minstartingspeed, maxstartingspeed));
                    ellipses.add(info);
                }
            }
        }    

  如果椭圆已经存在,代码处理更有趣的工作,以便进行动态显示。使用canvas.settop()方法缓慢移动每个椭圆。移动距离取决于指定的速度。

            else
            {
                for (int i = ellipses.count - 1; i >= 0; i--)
                {
                    ellipseinfo info = ellipses[i];
                    double top = canvas.gettop(info.ellipse);
                    canvas.settop(info.ellipse, top + 1 * info.velocityy);
            }

  为提高性能,一旦椭圆到达canvas面板的底部,就从跟踪集合中删除椭圆。这样,就不需要再处理它们。当遍历集合时,为了能够工作而不会导致丢失位置,需要向后迭代,从集合的末尾向起始位置迭代。

  如果椭圆尚未到达canvas面板的底部,代码会提高速度(此外,为获得磁铁吸引效果,还可以根据椭圆与canvas面板底部的距离来设置速度):

                    if (top >= (canvas.actualheight - ellipseradius * 2 - 10))
                    {
                        // this circle has reached the bottom.
                        // stop animating it.
                        ellipses.remove(info);
                    }
                    else
                    {
                        // increase the velocity.
                        info.velocityy += accelerationy;
                    }    

  最后,如果所有椭圆都已从集合中删除,就移除事件处理程序,然后结束动画:

                    if (ellipses.count == 0)
                    {
                        // end the animation.
                        // there's no reason to keep calling this method
                        // if it has no work to do.
                        stoprendering();
                    }        

  示例完整xaml标记如下所示:

<window x:class="animation.framebasedanimation"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        title="framebasedanimation" height="396" width="463.2">
    <grid margin="3">
        <grid.rowdefinitions>
            <rowdefinition height="auto"></rowdefinition>
            <rowdefinition></rowdefinition>
        </grid.rowdefinitions>

        <stackpanel orientation="horizontal">
            <button margin="3" padding="3" click="cmdstart_clicked">start</button>
            <button margin="3" padding="3" click="cmdstop_clicked">stop</button>
        </stackpanel>
        <canvas name="canvas" grid.row="1" margin="3"></canvas>
    </grid>
</window>
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using system.windows;
using system.windows.controls;
using system.windows.data;
using system.windows.documents;
using system.windows.input;
using system.windows.media;
using system.windows.media.imaging;
using system.windows.shapes;

namespace animation
{
    /// <summary>
    /// framebasedanimation.xaml 的交互逻辑
    /// </summary>
    public partial class framebasedanimation : window
    {
        public framebasedanimation()
        {
            initializecomponent();
        }

        private bool rendering = false;
        private void cmdstart_clicked(object sender, routedeventargs e)
        {
            if (!rendering)
            {
                ellipses.clear();
                canvas.children.clear();

                compositiontarget.rendering += renderframe;
                rendering = true;
            }
        }
        private void cmdstop_clicked(object sender, routedeventargs e)
        {
            stoprendering();
        }

        private void stoprendering()
        {
            compositiontarget.rendering -= renderframe;
            rendering = false;
        }

        private list<ellipseinfo> ellipses = new list<ellipseinfo>();

        private double accelerationy = 0.1;
        private int minstartingspeed = 1;
        private int maxstartingspeed = 50;
        private double speedratio = 0.1;
        private int minellipses = 20;
        private int maxellipses = 100;
        private int ellipseradius = 10;

        private void renderframe(object sender, eventargs e)
        {
            if (ellipses.count == 0)
            {
                // animation just started. create the ellipses.
                int halfcanvaswidth = (int)canvas.actualwidth / 2;

                random rand = new random();
                int ellipsecount = rand.next(minellipses, maxellipses + 1);
                for (int i = 0; i < ellipsecount; i++)
                {
                    ellipse ellipse = new ellipse();
                    ellipse.fill = brushes.limegreen;
                    ellipse.width = ellipseradius;
                    ellipse.height = ellipseradius;
                    canvas.setleft(ellipse, halfcanvaswidth + rand.next(-halfcanvaswidth, halfcanvaswidth));
                    canvas.settop(ellipse, 0);
                    canvas.children.add(ellipse);

                    ellipseinfo info = new ellipseinfo(ellipse, speedratio * rand.next(minstartingspeed, maxstartingspeed));
                    ellipses.add(info);
                }
            }
            else
            {
                for (int i = ellipses.count - 1; i >= 0; i--)
                {
                    ellipseinfo info = ellipses[i];
                    double top = canvas.gettop(info.ellipse);
                    canvas.settop(info.ellipse, top + 1 * info.velocityy);

                    if (top >= (canvas.actualheight - ellipseradius * 2 - 10))
                    {
                        // this circle has reached the bottom.
                        // stop animating it.
                        ellipses.remove(info);
                    }
                    else
                    {
                        // increase the velocity.
                        info.velocityy += accelerationy;
                    }

                    if (ellipses.count == 0)
                    {
                        // end the animation.
                        // there's no reason to keep calling this method
                        // if it has no work to do.
                        stoprendering();
                    }
                }
            }
        }
    }
    public class ellipseinfo
    {
        public ellipse ellipse
        {
            get;
            set;
        }

        public double velocityy
        {
            get;
            set;
        }

        public ellipseinfo(ellipse ellipse, double velocityy)
        {
            velocityy = velocityy;
            ellipse = ellipse;
        }
    }
}

  显然,可扩展的这个动画以使圆跳跃和分散等。使用的技术是相同的——只需要使用更复杂的公式计算速度。

  当构建基于帧的动画时需要注意如下问题:它们不依赖与时间。换句话说,动画可能在性能好的计算机上运动更快,因为帧率会增加,会更频繁地调用compositiontarget.rendering事件。为补偿这种效果,需要编写考虑当前时间的代码。

  开始学习基于帧的动画的最好方式是查看wpf sdk提供的每一帧动画都非常详细的示例。该例演示了几种粒子系统效果,并且使用自定义的timetracker类实现了依赖与时间的基于帧的动画。