C# 实现贝塞尔曲线小车加速减速效果

284 阅读4分钟

前言

计算机图形学和用户界面设计中,贝塞尔曲线常用于创建平滑、自然的运动路径。

不管是游戏开发还是交互式应用程序,利用贝塞尔曲线可以实现复杂而流畅的动画效果。特别是在模拟物体的移动轨迹时,贝塞尔曲线能够提供非常逼真的视觉体验。

本文将介绍如何在 C# 应用程序中使用贝塞尔曲线来实现一个小车的加速和减速效果。

实现原理

使用三次贝塞尔曲线创建运动路径

实现缓动函数控制运动速度

使用GDI+绘制曲线和小车

使用计时器控制动画

完整代码实现

using System.Drawing.Drawing2D;
using Timer = System.Windows.Forms.Timer;

namespace WinFormsApp7
{
    public partial class Form1 : Form
    {
        // 贝塞尔曲线的控制点
        private Point startPoint = 
        new Point(50, 200);
        private Point controlPoint1 =
        new Point(150, 50);
        private Point controlPoint2 =
        new Point(350, 350);
        private Point endPoint = 
        new Point(450, 200);

        // 动画参数
        privatefloat currentT = 0f;
        private Timer timer1;
        privatebool isAnimating = false;
        public Form1()
        {
            InitializeComponent();

            // 窗体设置
            this.DoubleBuffered = true;
            this.Text = "贝塞尔曲线小车动画";

            // 初始化计时器
            timer1 = new Timer();
            timer1.Interval = 16; 
            // 约60fps
            timer1.Tick += timer1_Tick;

            // 添加开始按钮
            Button btnStart = new Button();
            btnStart.Text = "开始/重置";
            btnStart.Location = new Point(10, 10);
            btnStart.Click += (s, e) =>
            {
                currentT = 0f;
                isAnimating = true;
                timer1.Start();
            };
            this.Controls.Add(btnStart);
        }


        private void timer1_Tick(object sender, EventArgs e)
        {
            // 更新动画进度
            if (currentT >= 1.0f)
            {
                timer1.Stop();
                isAnimating = false;
                return;
            }

            // 使用缓动函数计算实际进度
            currentT += 0.01f;
            this.Invalidate(); 
            // 触发重绘
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Graphics g = e.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;

            // 绘制贝塞尔曲线路径
            using (Pen pathPen = new Pen(Color.Gray, 2))
            {
                g.DrawBezier(pathPen, startPoint, controlPoint1, 
                controlPoint2, endPoint);
            }

            // 计算当前位置(使用缓动函数)
            float easedT = EaseInOutCubic(currentT);
            PointF currentPosition = CalculateBezierPoint(easedT);

            // 计算切线角度用于车辆方向
            PointF tangent = CalculateBezierTangent(easedT);
            float angle = (float)(Math.Atan2
            (tangent.Y, tangent.X) * 180.0 / Math.PI);

            // 绘制小车
            DrawCar(g, currentPosition, angle);

            // 绘制控制点(调试用)
            using (Brush controlPointBrush = new SolidBrush(Color.Red))
            {
                g.FillEllipse(controlPointBrush, startPoint.X - 4,
                startPoint.Y - 4, 8, 8);
                g.FillEllipse(controlPointBrush, controlPoint1.X - 4,
                controlPoint1.Y - 4, 8, 8);
                g.FillEllipse(controlPointBrush, controlPoint2.X - 4,
                controlPoint2.Y - 4, 8, 8);
                g.FillEllipse(controlPointBrush, endPoint.X - 4,
                endPoint.Y - 4, 8, 8);
            }
        }

        // 缓动函数:三次方缓入缓出
        private float EaseInOutCubic(float t)
        {
            return t < 0.5f ? 4 * t * t * t : 1 - (float)
            Math.Pow(-2 * t + 2, 3) / 2;
        }

        // 计算贝塞尔曲线上的点
        private PointF CalculateBezierPoint(float t)
        {
            float u = 1 - t;
            float t2 = t * t;
            float u2 = u * u;
            float u3 = u2 * u;
            float t3 = t2 * t;

            float x = u3 * startPoint.X +
                     3 * u2 * t * controlPoint1.X +
                     3 * u * t2 * controlPoint2.X +
                     t3 * endPoint.X;

            float y = u3 * startPoint.Y +
                     3 * u2 * t * controlPoint1.Y +
                     3 * u * t2 * controlPoint2.Y +
                     t3 * endPoint.Y;

            returnnew PointF(x, y);
        }

        // 计算贝塞尔曲线上的切线方向
        private PointF CalculateBezierTangent(float t)
        {
            float u = 1 - t;
            float t2 = t * t;
            float u2 = u * u;

            float x = -3 * startPoint.X * u2 +
                     3 * controlPoint1.X * (1 - 4 * t + 3 * t2) +
                     3 * controlPoint2.X * (2 * t - 3 * t2) +
                     3 * endPoint.X * t2;

            float y = -3 * startPoint.Y * u2 +
                     3 * controlPoint1.Y * (1 - 4 * t + 3 * t2) +
                     3 * controlPoint2.Y * (2 * t - 3 * t2) +
                     3 * endPoint.Y * t2;

            returnnew PointF(x, y);
        }

        // 绘制小车
        private void DrawCar(Graphics g, PointF position, float angle)
        {
            // 保存当前变换状态
            Matrix originalMatrix = g.Transform;

            // 设置变换矩阵
            Matrix rotationMatrix = new Matrix();
            rotationMatrix.RotateAt(angle, position);
            g.Transform = rotationMatrix;

            // 绘制小车形状(简单矩形表示)
            Rectangle carRect = new Rectangle(
                (int)(position.X - 15),
                (int)(position.Y - 10),
                30,
                20
            );

            using (Brush carBrush = new SolidBrush(Color.Blue))
            {
                g.FillRectangle(carBrush, carRect);
            }

            // 绘制车轮
            using (Brush wheelBrush = new SolidBrush(Color.Black))
            {
                g.FillEllipse(wheelBrush, position.X - 12, position.Y - 12, 8, 8);
                g.FillEllipse(wheelBrush, position.X + 4, position.Y - 12, 8, 8);
            }

            // 恢复变换状态
            g.Transform = originalMatrix;
        }

    }
}

代码说明

贝塞尔曲线控制点:

startPoint:起点

controlPoint1:第一控制点

controlPoint2:第二控制点

endPoint:终点

动画控制参数:

currentT:当前动画进度(0-1)

animationTimer:动画计时器

isAnimating:动画状态标志

关键方法说明

贝塞尔曲线计算
private PointF CalculateBezierPoint(float t)

此方法使用三次贝塞尔曲线公式计算曲线上的点位置:

参数t范围为0-1,表示曲线进度

返回曲线上对应的坐标点

缓动函数
private float EaseInOutCubic(float t)

实现加速减速效果的核心函数:

在动画前半段加速

在动画后半段减速

使用三次方曲线实现平滑过渡

切线计算
private PointF CalculateBezierTangent(float t)

计算曲线上某点的切线方向,用于确定小车朝向。

小车绘制
private void DrawCar(Graphics g, PointF position, float angle)

总结

本文展示了如何使用C#和GDI+实现贝塞尔曲线上的小车动画效果。通过组合使用贝塞尔曲线、缓动函数和GDI+绘图,我们创建了一个平滑的动画效果。这个例子可以作为更复杂动画效果的基础,通过扩展和修改可以实现更多有趣的功能。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/jWFLqN1dEM-azcU4iMG2lg

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!