原文:一头雾水的"Follow The Pointer"

一头雾水的"Follow The Pointer"
                                                                                                     周银辉

Microsoft Expression Blend中有一个示例程序"Follow The Pointer", 看程序演示会觉得很酷,看代码(点击下载)会觉得一头雾水, 不过现在我便借此介绍一下WPF中的CompositionTarget 以及该示例中设计到了一些物理知识.

一, CompositionTarget
虽然我们拥有Storyboard(故事板)以及Microsoft Expression Blend为WPF动画制作提供强有力的支持,但你会发现这是不够的,也许你希望能通过代码来控制动画中的每一帧,以便显示更加复杂和视觉效果更好的动画, 那么你就得了解CompositionTarget 类.

CompositionTarget 类用于呈现你的应用程序显示表面, 每次绘制时其都会触发其Rendering事件, 其绘制次数取决于你计算机的FPS.
我们可以自定义Rendering事件的处理器来进行一些自定义的绘制或其他工作(注意其会根据计算机的FPS来不停地调用改事件处理器,比如每秒60次,所以不应该在这里进行复杂费时的操作) 比如:

一头雾水的"Follow The Pointer"-LMLPHPCompositionTarget.Rendering += delegate
一头雾水的"Follow The Pointer"-LMLPHP一头雾水的"Follow The Pointer"-LMLPHP一头雾水的"Follow The Pointer"-LMLPHP{
一头雾水的"Follow The Pointer"-LMLPHP    this.UpdateColor();
一头雾水的"Follow The Pointer"-LMLPHP};

这里有一个简单的例子说明CompositionTarget的用法,你可以点击这里下载

二, "Follow The Pointer"中的物理知识
编写视觉效果稍稍好一点的动画时不使用数学或物理知识几乎是不可能的. "Follow The Pointer"示例中用到了力,速度,加速度以及流动摩擦(其变形形式).
动画的简单的流程: 当用户移动鼠标时,程序会计算可移动控件(以下称"小方块",就是跟随鼠标的那个控件)的位置到鼠标位置所形成的向量, 并将该向量作为施加到小方块上的作用力.该作用力会使小方块朝鼠标所在位置移动.假设小方块处于空气(或水等)环境中,小方块的移动会产生流摩擦力,该摩擦力与速度成正比,其会使小方块减速并最终停止下来.
具体如何表现这些物理知识请参考下面的两段代码
这里是原示例代码:

一头雾水的"Follow The Pointer"-LMLPHPprivate void CompositionTarget_Rendering(object sender, EventArgs e)
一头雾水的"Follow The Pointer"-LMLPHP一头雾水的"Follow The Pointer"-LMLPHP        一头雾水的"Follow The Pointer"-LMLPHP{
一头雾水的"Follow The Pointer"-LMLPHP            // Current position of mouse pointer relative to the movable control.
一头雾水的"Follow The Pointer"-LMLPHP            // The Mouse class is defined in the System.Windows.Input namespace.
一头雾水的"Follow The Pointer"-LMLPHP            Point mousePos = Mouse.GetPosition(this.MovableControl); 
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            TimeSpan currentTime = this.stopwatch.Elapsed;
一头雾水的"Follow The Pointer"-LMLPHP            double elapsedTime = (currentTime - this.prevTime).TotalSeconds;
一头雾水的"Follow The Pointer"-LMLPHP            this.prevTime = currentTime;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            // The vector to the mouse pointer represents the force currently acting on the movable control.
一头雾水的"Follow The Pointer"-LMLPHP            Vector force = new Vector(mousePos.X, mousePos.Y);
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            // The smaller the value of damping, the more iterations it takes to approach the mouse pointer, thus allowing velocity to grow larger.
一头雾水的"Follow The Pointer"-LMLPHP            force = (force * this.SpringSlider.Value - this.velocity * this.DampingSlider.Value) * elapsedTime;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            // The current force causes an acceleration (a change in velocity).
一头雾水的"Follow The Pointer"-LMLPHP            this.velocity += force;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            // If the eye won't notice any further motion then don't animate on this iteration.
一头雾水的"Follow The Pointer"-LMLPHP            if (velocity.Length < epsilon)
一头雾水的"Follow The Pointer"-LMLPHP                return;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            // Distance equals speed times time.
一头雾水的"Follow The Pointer"-LMLPHP            this.translation.X += this.velocity.X * elapsedTime; 
一头雾水的"Follow The Pointer"-LMLPHP            this.translation.Y += this.velocity.Y * elapsedTime;
一头雾水的"Follow The Pointer"-LMLPHP        }

这里是改写与简化后的示例代码:

一头雾水的"Follow The Pointer"-LMLPHPvoid CompositionTarget_Rendering(object sender, EventArgs e)
一头雾水的"Follow The Pointer"-LMLPHP一头雾水的"Follow The Pointer"-LMLPHP        一头雾水的"Follow The Pointer"-LMLPHP{
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //上次绘制到此时的时间间隔
一头雾水的"Follow The Pointer"-LMLPHP            TimeSpan currentTime = this.stopwatch.Elapsed;
一头雾水的"Follow The Pointer"-LMLPHP            double elapsedTime = (currentTime - this.prevTime).TotalSeconds;           
一头雾水的"Follow The Pointer"-LMLPHP            this.prevTime = currentTime;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //鼠标相对于小方块的位置
一头雾水的"Follow The Pointer"-LMLPHP            Point mouseLoc = Mouse.GetPosition(this.rectangle1);
一头雾水的"Follow The Pointer"-LMLPHP            //由于改相当位置是相对于小方块左上角的,将其纠正到相当于小方块中心
一头雾水的"Follow The Pointer"-LMLPHP            mouseLoc.Offset(-this.rectangle1.ActualWidth / 2, -this.rectangle1.ActualHeight / 2);
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //使用鼠标相对于小方块的位置作为外力
一头雾水的"Follow The Pointer"-LMLPHP            Vector force = new Vector(mouseLoc.X, mouseLoc.Y);
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //流动摩擦力系数假设为该值
一头雾水的"Follow The Pointer"-LMLPHP            double coefficient = 5;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //假设小方块质量为1,则加速度为a = force/1;
一头雾水的"Follow The Pointer"-LMLPHP            //那么在elapsedTime内其速度的变化量为a*elapsedTime
一头雾水的"Follow The Pointer"-LMLPHP            //由于流动摩擦力与速度成正比,那么流动摩擦力为coefficient * this.velocity
一头雾水的"Follow The Pointer"-LMLPHP            //所以速度的变化为(force * 200 - coefficient * this.velocity) * elapsedTime
一头雾水的"Follow The Pointer"-LMLPHP            //这里为了演示中小方块的速度更快一点,我们将外力扩大了200倍
一头雾水的"Follow The Pointer"-LMLPHP            Vector velocityDelta = (force * 200 - coefficient * this.velocity) * elapsedTime;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //当前速度
一头雾水的"Follow The Pointer"-LMLPHP            this.velocity += velocityDelta;
一头雾水的"Follow The Pointer"-LMLPHP
一头雾水的"Follow The Pointer"-LMLPHP            //小方块的新位置
一头雾水的"Follow The Pointer"-LMLPHP            this.translation.X += this.velocity.X * elapsedTime;
一头雾水的"Follow The Pointer"-LMLPHP            this.translation.Y += this.velocity.Y * elapsedTime;
一头雾水的"Follow The Pointer"-LMLPHP            
一头雾水的"Follow The Pointer"-LMLPHP        }
一头雾水的"Follow The Pointer"-LMLPHP       

下载Demo以及源代码

05-11 04:02