c# - Storyboard /动画的 "handful"影响性能?

标签 c# wpf animation

简介:

因此,我构建的是一种射弹运动模拟器,在给定初始速度和角度的情况下,每次单击“开火”按钮时都会产生一个新的子弹,并使用 MatrixAnimationUsingPath 沿着计算出的路径设置动画。该路径仅计算一次并用于所有射弹。

问题:

我遇到的问题是,在我遇到应用程序性能问题之前,可以很好地设置动画的射弹的数量(例如:30)似乎相对较低,并且可能会因初始设置而有很大差异速度或角度。当问题发生时,“开火”按钮的响应速度大大减慢,并且射弹似乎排成一列并以突发方式发射,而不是在单击按钮时发射。

我尝试解决问题:

  • 最初,当我第一次编写它时,每次单击按钮时都会计算每个项目符号的路径,所以起初我认为这可能是问题所在。如果初始值相同,我重写了它以重用相同的路径。它没有解决问题。
  • 然后我想也许我可以将制作子弹动画的责任交给一个单独的线程。就在那时,我在研究中了解了使用线程亲和性的困难,但还是尝试了一下。我为每个新线程提供了一个执行方法,这就是其中的内容:this.Dispatcher.Invoke((Action)(() => {/*用于创建和启动动画的代码*/})); 它可以很好地创建和动画子弹,但它没有解决甚至改善主要问题。也许这不是解决问题的正确方法。
  • 最近我尝试降低帧速率,这有帮助,但作用不大。

问题:

还有哪些其他选项,或者我应该对线程使用另一种方法,还是它只是我正在使用的矩阵动画的性质,我是否应该考虑使用另一种类型的动画?

我注意到一个可能的相关性是初始速度越大或射弹需要覆盖的距离越大,平滑动画子弹的数量就会减少或相反地增加该数量(例如,速度越慢或水平距离越小,子弹数量就会增加) .

我已经在我的笔记本电脑和我的高端台式机上运行了该应用程序,并获得了相似的结果。

下面是负责创建新项目符号然后在不使用多线程的情况下对其进行动画处理的主要代码。我会附上屏幕截图来帮助我的解释,但我目前无法这样做。

在此先感谢您的所有贡献!

这是指向压缩演示项目的投递箱链接以及屏幕截图: Projectile Motion Demo

public partial class MainWindow : Window
{
    Projectile bullet;
    ProjectilePathMovement projMove;


    private void spawnAndFireBullet()
    {
        // Create new bullet with: Velocity, Initial Angle, Damage
        bullet = new Projectile(100, 45, 0);
        bullet.Template = Resources["BulletTemplate"] as ControlTemplate;

        canvas.Children.Add(bullet);

        // Position the bullet at it's starting location.
        Canvas.SetLeft(bullet, 50);
        Canvas.SetTop(bullet, canvas.ActualHeight - 10);

        projMove.animateProjectile(bullet, mainWindow);
    }
}



public class ProjectilePathMovement
{
    Storyboard pathAnimationStoryboard;
    MatrixAnimationUsingPath projectileAnimation;
    MatrixTransform projectileMatrixTransform;
    ProjectileMotion pMotion = new ProjectileMotion();

    public void animateProjectile(Projectile projectile, Window window)
    {
        NameScope.SetNameScope(window, new NameScope());

        projectileMatrixTransform = new MatrixTransform();
        projectile.RenderTransform = projectileMatrixTransform;

        window.RegisterName("ProjectileTransform", projectileMatrixTransform);

        projectileAnimation = new MatrixAnimationUsingPath();
        projectileAnimation.PathGeometry = pMotion.getProjectilePath(projectile); // Get the path of the projectile.
        projectileAnimation.Duration = TimeSpan.FromSeconds(pMotion.flightTime);

        projectileAnimation.DoesRotateWithTangent = true;

        Storyboard.SetTargetName(projectileAnimation, "ProjectileTransform");
        Storyboard.SetTargetProperty(projectileAnimation, new PropertyPath(MatrixTransform.MatrixProperty));

        pathAnimationStoryboard = new Storyboard();

        pathAnimationStoryboard.Children.Add(projectileAnimation);

        pathAnimationStoryboard.Begin(window);
    }
}

class ProjectileMotion
{
    // Trajectory variables.
    public double trajRange = 0.0, trajHeight = 0.0, trajTime = 0.0;

    private double gravity = 9.81; // m/s^2
    private double velocity = 0.0; // m/s
    private double angle = 0.0; // In radians
    private double cosine, secant, tangent;
    private double deltaX, deltaY;
    private double x_component, y_component;
    private double t_maxHeight;
    private double start_x, start_y, current_x, current_y;
    private double previousAngle = 0.0, previousVelocity = 0.0;

    private PathGeometry projectilePath, previousProjectilePath; // The actual path of the object/projectile.
    private PathFigure pFigure; // projectilePath is comprised of pFigure.
    private PolyLineSegment polyLine; // polyLine is comprised of points.
    private PointCollection points; // points is comprised of a list of all points in the path 


    /// <summary>
    /// Returns the path the projectile would take given its initial velocity, initial angle, and starting point.
    /// Pass the angle in Degrees.
    /// </summary>
    /// <param name="projectile"></param>
    /// <param name="vel"></param>
    /// <param name="ang"></param>
    /// <param name="startPoint"></param>
    /// <returns></returns>
    public PathGeometry getProjectilePath(UIElement projectile, double vel, double ang, System.Windows.Point startPoint)
    {
        // Calculate the necessary values.
        calculateValues(projectile, ang, vel);

        // Derive the object's/projectile's path.
        return deriveProjectilePath(startPoint);
    }

    public double getGravity()
    {
        return gravity;
    }

    private void calculateValues(UIElement projectile, double ang, double vel)
    {
        // Convert the angle from Degrees to Radians.
        angle = ang * (Math.PI / 180);

        velocity = vel;

        cosine = Math.Cos(angle);
        secant = 1 / cosine;
        tangent = Math.Tan(angle);

        deltaX = Math.Cos(angle);
        deltaY = Math.Sin(angle);

        // Get current coordinates.
        start_x = Canvas.GetLeft(projectile);
        start_y = Canvas.GetTop(projectile);
        current_y = start_y;
        current_x = start_x;

        // Calculate the horizontal and vertical components of initial velocity. 
        // Xvo = Vo * Cos(angle)
        // Yvo = Vo * Sin(angle)
        x_component = velocity * Math.Cos(angle);
        y_component = velocity * Math.Sin(angle);

        // Calculate time to reach max height.  t max = Vyo / 9.8
        t_maxHeight = y_component / gravity;

        // Calculate max height of projectile. h = Yo + Vyo·t - 0.5·g·t^2
        trajHeight = 0 + (y_component * t_maxHeight) - (.5 * gravity * t_maxHeight * t_maxHeight);

        //Calulate max range of projectile.
        trajRange = (2 * (velocity * velocity) * Math.Sin(angle) * Math.Cos(angle)) / gravity;

        // Calculate flight time.
        trajTime = 2 * t_maxHeight;
    }

    private PathGeometry deriveProjectilePath(System.Windows.Point pt)
    {
        projectilePath = new PathGeometry();
        pFigure = new PathFigure();

        start_x = pt.X;
        start_y = pt.Y;
        current_y = start_y;

        pFigure.StartPoint = pt;

        polyLine = new PolyLineSegment();

        points = new PointCollection();

        // Checks if the angle and velocity for this projectile is the same as last time.  If it is the same there is no need to recalculate the path of the projectile, just use the same one from before.
        if (previousAngle != angle && previousVelocity != velocity)
        {
            // Loop to obtain every point in the trajectory's path.
            for (current_x = start_x; current_x <= trajRange; current_x++)
            {
                current_y = start_y - current_x * tangent + ((gravity * current_x * current_x) / (2 * (velocity * velocity * cosine * cosine)));  //  Y = Yo + X*tan - ( (g*X^2) / 2(v*cos)^2 )      Trajectory Formula to find the 'y' value  at a given 'x' value.

                points.Add(new System.Windows.Point(current_x, current_y));
            }

            // If the last x-coordinate value exceeds the actual range of projectile set x = to actual range to 
            // obtain actual y-coordinate value for final trajectory point.
            if (current_x > trajRange)
            {
                current_x = trajRange;

                current_y = start_y - current_x * tangent + ((gravity * current_x * current_x) / (2 * (velocity * velocity * cosine * cosine)));  //  Y = Yo + X*tan - ( (g*X^2) / 2(v*cos)^2 )      Trajectory Formula to find the 'y' coord given an 'x' value.

                points.Add(new System.Windows.Point(current_x, current_y));
            }

            polyLine.Points = points;
            pFigure.Segments.Add(polyLine);
            projectilePath.Figures.Add(pFigure);
        }
        else
        {
            projectilePath = previousProjectilePath;
        }

        // Freeze the PathGeometry for performance benefits?
        projectilePath.Freeze();

        previousVelocity = velocity;
        previousAngle = angle;
        previousProjectilePath = projectilePath;

        return projectilePath;
    }
}

最佳答案

我看过您的代码并推荐了以下解决方案。以下解决方案将立即带来所需的改进,而无需任何大的代码更改。

添加命名空间:System.Windows.Threading;

完全注释掉您的 fireButton_Click 函数。并按原样复制粘贴以下代码片段:

        Queue<FireBulletDelegate> bulletQueue = new Queue<FireBulletDelegate>();
        delegate void FireBulletDelegate();

        DispatcherTimer bulletQueueChecker;
        const int threshold = 100;

        private void fireButton_Click(object sender, RoutedEventArgs e)
        {
            if (bulletQueue.Count > threshold) return;

            FireBulletDelegate d = new FireBulletDelegate(spawnAndFireBullet);
            bulletQueue.Enqueue(d);

            if (bulletQueueChecker == null)
            {
                bulletQueueChecker = new DispatcherTimer(
                                TimeSpan.FromSeconds(0.2),
                                DispatcherPriority.Render,
                                (s1, e1) =>
                                {
                                    if (bulletQueue.Count > 0)
                                        (bulletQueue.Dequeue())();
                                    //spawnAndFireBullet();
                                },
                                fireButton.Dispatcher);
            }
            else if (!bulletQueueChecker.IsEnabled)
            {
                bulletQueueChecker.Start();
            }
        }

这解决了你的 bunch 子弹爆裂的问题。

出现问题的原因是许多按钮点击消息可能会爆炸消息队列,系统将按照自己的节奏处理消息队列。因此,我们需要检查这些点击事件。这是我们使用阈值实现的,并以 0.2 秒的间隔处理点击。可以在您的代码中进行更多改进。我正在使用 FileBulletDelegate,我们也可以将 Bullet 用于队列项,但这会带来更多代码更改。使用委托(delegate)的一个好处是异步调用。

关于c# - Storyboard /动画的 "handful"影响性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32960234/

相关文章:

c# - 如何根据名称删除标签/按钮? WPF Visual Studio 2015

c# - 如何拦截 UWP Webview 组件上的触摸事件?

ios - 如何为 ScrollView 设置动画?

android - ImageView 中只有两个图像的立方体动画

c# - C# 编译器如何使用泛型?

c# - AutoMapper 的映射问题?

c# - 如何将 xaml 属性绑定(bind)到另一个类中的静态变量?

jquery - 更改同位素滤镜动画效果

c# - 如何以编程方式将受众 uri 传递给 AudienceUriElementCollection?

c# - 将 Simple.Data.Query 转换为 POCO 对象