当前位置: 移动技术网 > IT编程>开发语言>.net > 【WPF学习】第五十六章 基于帧的动画

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

2020年03月09日  | 移动技术网IT编程  | 我要评论

战斗司书与恋爱爆弹,快乐大本营2005年7月,dnf幽灵猎人套

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

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

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

  在这个示例中,每个下落的圆由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类实现了依赖与时间的基于帧的动画。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网