You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在运行时缩短.Net C#中System.Windows.Shapes.Line的长度以实现箭矢移动动画?

如何在运行时缩短.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 16:48:13