如何在运行时缩短.Net C#中System.Windows.Shapes.Line的长度以实现箭矢移动动画?
嘿,我来帮你搞定这个箭矢动画的问题!根据你描述的场景——射手在正方形中心,点击角落的目标后箭矢(用System.Windows.Shapes.Line表示)飞过去,其实核心就是动态调整Line的端点位置,模拟箭矢逐步飞向目标的过程。下面给你两种简单靠谱的实现方式,都是纯C# + WPF的原生写法,不用依赖任何外部东西:
方法一:用WPF原生动画框架(推荐,更流畅)
WPF自带的动画框架已经帮我们处理好了流畅度和线程问题,是最省心的方案。我们只需要把Line的起点固定在射手位置,然后通过动画让终点从射手位置逐步移动到目标位置就行。
首先在XAML里先把场景搭好:
<Canvas Name="gameCanvas" Width="400" Height="400"> <!-- 射手(正方形中心) --> <Ellipse Name="archer" Fill="DarkGreen" Width="20" Height="20" Canvas.Left="190" Canvas.Top="190"/> <!-- 四个角落的目标 --> <Rectangle Name="targetTopLeft" Fill="Crimson" Width="30" Height="30" Canvas.Left="0" Canvas.Top="0" MouseDown="Target_MouseDown"/> <Rectangle Name="targetTopRight" Fill="Crimson" Width="30" Height="30" Canvas.Left="370" Canvas.Top="0" MouseDown="Target_MouseDown"/> <Rectangle Name="targetBottomLeft" Fill="Crimson" Width="30" Height="30" Canvas.Left="0" Canvas.Top="370" MouseDown="Target_MouseDown"/> <Rectangle Name="targetBottomRight" Fill="Crimson" Width="30" Height="30" Canvas.Left="370" Canvas.Top="370" MouseDown="Target_MouseDown"/> <!-- 箭矢:初始时端点和射手重合 --> <Line Name="arrowLine" Stroke="SaddleBrown" StrokeThickness="3" X1="200" Y1="200" X2="200" Y2="200"/> </Canvas>
然后在C#后台代码里处理点击事件和动画:
private void Target_MouseDown(object sender, MouseButtonEventArgs e) { // 拿到被点击的目标控件 FrameworkElement target = sender as FrameworkElement; if (target == null) return; // 计算射手在Canvas上的绝对中心坐标 Point archerCenter = new Point( Canvas.GetLeft(archer) + archer.Width / 2, Canvas.GetTop(archer) + archer.Height / 2 ); // 计算被点击目标的绝对中心坐标 Point targetCenter = new Point( Canvas.GetLeft(target) + target.Width / 2, Canvas.GetTop(target) + target.Height / 2 ); // 重置箭矢到射手位置 arrowLine.X2 = archerCenter.X; arrowLine.Y2 = archerCenter.Y; // 创建X轴方向的动画:从射手X坐标到目标X坐标 DoubleAnimation animX = new DoubleAnimation { From = archerCenter.X, To = targetCenter.X, Duration = TimeSpan.FromSeconds(0.5), // 动画时长0.5秒,可调整 EasingFunction = new LinearEase() // 匀速飞行,想改加速度可以换其他Ease }; // 创建Y轴方向的动画:从射手Y坐标到目标Y坐标 DoubleAnimation animY = new DoubleAnimation { From = archerCenter.Y, To = targetCenter.Y, Duration = TimeSpan.FromSeconds(0.5), EasingFunction = new LinearEase() }; // 启动动画,WPF会自动处理流畅过渡 arrowLine.BeginAnimation(Line.X2Property, animX); arrowLine.BeginAnimation(Line.Y2Property, animY); }
这种方法的好处是不用自己操心帧同步,WPF会自动优化动画的流畅度,还能轻松更换缓动函数(比如想让箭矢先加速后减速,就把LinearEase换成QuadraticEase并设置EasingMode.EaseInOut)。
方法二:用DispatcherTimer逐帧更新(适合精细控制)
如果你需要在箭矢飞行过程中加一些自定义逻辑(比如碰撞检测、中途改变方向),那用定时器逐帧更新会更灵活。
代码实现如下(XAML部分和上面一样,只改后台代码):
private DispatcherTimer arrowTimer; private Point archerPos; private Point targetPos; private double flightProgress = 0; private double flightSpeed = 0.02; // 每帧前进的进度(0到1之间,值越大速度越快) private void Target_MouseDown(object sender, MouseButtonEventArgs e) { FrameworkElement target = sender as FrameworkElement; if (target == null) return; // 计算射手和目标的中心坐标 archerPos = new Point( Canvas.GetLeft(archer) + archer.Width / 2, Canvas.GetTop(archer) + archer.Height / 2 ); targetPos = new Point( Canvas.GetLeft(target) + target.Width / 2, Canvas.GetTop(target) + target.Height / 2 ); // 重置飞行状态和箭矢位置 flightProgress = 0; arrowLine.X2 = archerPos.X; arrowLine.Y2 = archerPos.Y; // 初始化定时器(每16毫秒触发一次,约60帧/秒,保证流畅) if (arrowTimer == null) { arrowTimer = new DispatcherTimer(); arrowTimer.Interval = TimeSpan.FromMilliseconds(16); arrowTimer.Tick += ArrowFlightTick; } arrowTimer.Start(); } private void ArrowFlightTick(object sender, EventArgs e) { // 更新飞行进度 flightProgress += flightSpeed; if (flightProgress >= 1) { flightProgress = 1; arrowTimer.Stop(); // 到达目标后停止定时器 } // 用线性插值计算当前箭矢终点的位置 double currentX = archerPos.X + (targetPos.X - archerPos.X) * flightProgress; double currentY = archerPos.Y + (targetPos.Y - archerPos.Y) * flightProgress; // 更新Line的终点 arrowLine.X2 = currentX; arrowLine.Y2 = currentY; }
这种方法的优势是你可以在ArrowFlightTick方法里加任何自定义逻辑——比如每帧检查箭矢是否碰到了其他障碍物,或者随机改变飞行速度,完全由你掌控。
小补充
如果你的原始需求真的是“缩短”Line的长度来实现动画(比如初始时Line是从目标拉到射手的全长,然后慢慢缩短让箭矢看起来从射手飞出去),那只需要把Line的X1/Y1固定在目标位置,然后动画或者逐帧更新X2/Y2从目标位置移动到射手位置就行,逻辑完全反过来,原理是一样的。
另外,要是想让箭矢更逼真,可以给Line加个箭头头部:用Polygon画个三角形,然后在动画或者定时器里同步更新它的位置和旋转角度,让它始终指向飞行方向,视觉效果会好很多~
备注:内容来源于stack exchange,提问作者TEDSON

