一头雾水的"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"中的物理知识
编写视觉效果稍稍好一点的动画时不使用数学或物理知识几乎是不可能的. "Follow The Pointer"示例中用到了力,速度,加速度以及流动摩擦(其变形形式).
动画的简单的流程: 当用户移动鼠标时,程序会计算可移动控件(以下称"小方块",就是跟随鼠标的那个控件)的位置到鼠标位置所形成的向量, 并将该向量作为施加到小方块上的作用力.该作用力会使小方块朝鼠标所在位置移动.假设小方块处于空气(或水等)环境中,小方块的移动会产生流摩擦力,该摩擦力与速度成正比,其会使小方块减速并最终停止下来.
具体如何表现这些物理知识请参考下面的两段代码
这里是原示例代码:
下载Demo以及源代码
周银辉
Microsoft Expression Blend中有一个示例程序"Follow The Pointer", 看程序演示会觉得很酷,看代码(点击下载)会觉得一头雾水, 不过现在我便借此介绍一下WPF中的CompositionTarget 以及该示例中设计到了一些物理知识.
一, CompositionTarget
虽然我们拥有Storyboard(故事板)以及Microsoft Expression Blend为WPF动画制作提供强有力的支持,但你会发现这是不够的,也许你希望能通过代码来控制动画中的每一帧,以便显示更加复杂和视觉效果更好的动画, 那么你就得了解CompositionTarget 类.
CompositionTarget 类用于呈现你的应用程序显示表面, 每次绘制时其都会触发其Rendering事件, 其绘制次数取决于你计算机的FPS.
我们可以自定义Rendering事件的处理器来进行一些自定义的绘制或其他工作(注意其会根据计算机的FPS来不停地调用改事件处理器,比如每秒60次,所以不应该在这里进行复杂费时的操作) 比如:
CompositionTarget.Rendering += delegate
{
this.UpdateColor();
};
这里有一个简单的例子说明CompositionTarget的用法,你可以点击这里下载{
this.UpdateColor();
};
二, "Follow The Pointer"中的物理知识
编写视觉效果稍稍好一点的动画时不使用数学或物理知识几乎是不可能的. "Follow The Pointer"示例中用到了力,速度,加速度以及流动摩擦(其变形形式).
动画的简单的流程: 当用户移动鼠标时,程序会计算可移动控件(以下称"小方块",就是跟随鼠标的那个控件)的位置到鼠标位置所形成的向量, 并将该向量作为施加到小方块上的作用力.该作用力会使小方块朝鼠标所在位置移动.假设小方块处于空气(或水等)环境中,小方块的移动会产生流摩擦力,该摩擦力与速度成正比,其会使小方块减速并最终停止下来.
具体如何表现这些物理知识请参考下面的两段代码
这里是原示例代码:
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Current position of mouse pointer relative to the movable control.
// The Mouse class is defined in the System.Windows.Input namespace.
Point mousePos = Mouse.GetPosition(this.MovableControl);
TimeSpan currentTime = this.stopwatch.Elapsed;
double elapsedTime = (currentTime - this.prevTime).TotalSeconds;
this.prevTime = currentTime;
// The vector to the mouse pointer represents the force currently acting on the movable control.
Vector force = new Vector(mousePos.X, mousePos.Y);
// The smaller the value of damping, the more iterations it takes to approach the mouse pointer, thus allowing velocity to grow larger.
force = (force * this.SpringSlider.Value - this.velocity * this.DampingSlider.Value) * elapsedTime;
// The current force causes an acceleration (a change in velocity).
this.velocity += force;
// If the eye won't notice any further motion then don't animate on this iteration.
if (velocity.Length < epsilon)
return;
// Distance equals speed times time.
this.translation.X += this.velocity.X * elapsedTime;
this.translation.Y += this.velocity.Y * elapsedTime;
}
这里是改写与简化后的示例代码:{
// Current position of mouse pointer relative to the movable control.
// The Mouse class is defined in the System.Windows.Input namespace.
Point mousePos = Mouse.GetPosition(this.MovableControl);
TimeSpan currentTime = this.stopwatch.Elapsed;
double elapsedTime = (currentTime - this.prevTime).TotalSeconds;
this.prevTime = currentTime;
// The vector to the mouse pointer represents the force currently acting on the movable control.
Vector force = new Vector(mousePos.X, mousePos.Y);
// The smaller the value of damping, the more iterations it takes to approach the mouse pointer, thus allowing velocity to grow larger.
force = (force * this.SpringSlider.Value - this.velocity * this.DampingSlider.Value) * elapsedTime;
// The current force causes an acceleration (a change in velocity).
this.velocity += force;
// If the eye won't notice any further motion then don't animate on this iteration.
if (velocity.Length < epsilon)
return;
// Distance equals speed times time.
this.translation.X += this.velocity.X * elapsedTime;
this.translation.Y += this.velocity.Y * elapsedTime;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
//上次绘制到此时的时间间隔
TimeSpan currentTime = this.stopwatch.Elapsed;
double elapsedTime = (currentTime - this.prevTime).TotalSeconds;
this.prevTime = currentTime;
//鼠标相对于小方块的位置
Point mouseLoc = Mouse.GetPosition(this.rectangle1);
//由于改相当位置是相对于小方块左上角的,将其纠正到相当于小方块中心
mouseLoc.Offset(-this.rectangle1.ActualWidth / 2, -this.rectangle1.ActualHeight / 2);
//使用鼠标相对于小方块的位置作为外力
Vector force = new Vector(mouseLoc.X, mouseLoc.Y);
//流动摩擦力系数假设为该值
double coefficient = 5;
//假设小方块质量为1,则加速度为a = force/1;
//那么在elapsedTime内其速度的变化量为a*elapsedTime
//由于流动摩擦力与速度成正比,那么流动摩擦力为coefficient * this.velocity
//所以速度的变化为(force * 200 - coefficient * this.velocity) * elapsedTime
//这里为了演示中小方块的速度更快一点,我们将外力扩大了200倍
Vector velocityDelta = (force * 200 - coefficient * this.velocity) * elapsedTime;
//当前速度
this.velocity += velocityDelta;
//小方块的新位置
this.translation.X += this.velocity.X * elapsedTime;
this.translation.Y += this.velocity.Y * elapsedTime;
}
{
//上次绘制到此时的时间间隔
TimeSpan currentTime = this.stopwatch.Elapsed;
double elapsedTime = (currentTime - this.prevTime).TotalSeconds;
this.prevTime = currentTime;
//鼠标相对于小方块的位置
Point mouseLoc = Mouse.GetPosition(this.rectangle1);
//由于改相当位置是相对于小方块左上角的,将其纠正到相当于小方块中心
mouseLoc.Offset(-this.rectangle1.ActualWidth / 2, -this.rectangle1.ActualHeight / 2);
//使用鼠标相对于小方块的位置作为外力
Vector force = new Vector(mouseLoc.X, mouseLoc.Y);
//流动摩擦力系数假设为该值
double coefficient = 5;
//假设小方块质量为1,则加速度为a = force/1;
//那么在elapsedTime内其速度的变化量为a*elapsedTime
//由于流动摩擦力与速度成正比,那么流动摩擦力为coefficient * this.velocity
//所以速度的变化为(force * 200 - coefficient * this.velocity) * elapsedTime
//这里为了演示中小方块的速度更快一点,我们将外力扩大了200倍
Vector velocityDelta = (force * 200 - coefficient * this.velocity) * elapsedTime;
//当前速度
this.velocity += velocityDelta;
//小方块的新位置
this.translation.X += this.velocity.X * elapsedTime;
this.translation.Y += this.velocity.Y * elapsedTime;
}
下载Demo以及源代码