从“一小撮”故事板/动画中受到的性能影响?

3

介绍:

我建立了一个类似于抛物线运动模拟器的东西,给定初始速度和角度,在每次“Fire”按钮被点击时,就会生成一个新的子弹,并沿着计算路径进行动画展示。路径只计算一次并用于所有子弹。

问题:

我遇到的问题是在应用程序性能出现问题之前可以正常进行动画处理的项目数量(例如30个)似乎相对较低,并且这取决于初始速度或角度。当出现问题时,“Fire”按钮的响应速度明显变慢,而子弹似乎排队并以突发方式发射,而不是在按钮单击时发射。

解决该问题的尝试:

  • 最初编写时,每次点击按钮都会为每个子弹计算路径,因此我首先认为这可能是问题所在。我将其重写为如果初始值相同,则重复使用相同的路径。但它没有解决问题。
  • 然后我想也许我可以将一个子弹的动画责任交给一个单独的线程。在我的研究中,我了解到处理线程亲和力的困难,但仍然尝试了一下。我为每个新线程提供了一个要执行的方法,其中包含以下内容:this.Dispatcher.Invoke((Action)(() => { /*Code for creating and starting animation*/ }));它可以很好地创建和动画化一个子弹,但并没有解决或甚至改善主要问题。也许这不是正确的方法。
  • 最近,我尝试降低帧率,这有所帮助,但只是稍微有所改善。

问题:

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

我注意到可能存在的相关性是,初始速度或需要覆盖的水平距离越大,平稳动画的子弹数量就会减少,反之亦然(例如,速度较慢或需要覆盖的水平距离较小将增加数量)。

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

以下是负责创建新子弹并进行动画处理而不使用多线程的主要代码部分。我本来想包括屏幕截图以帮助解释,但目前无法这样做。

提前感谢您的所有贡献!

这是一个Drop Box链接,包含一个压缩的演示项目和屏幕截图: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;
    }
}

当您创建更多的子弹时,您遇到了问题吗?当您制作5个子弹时,问题不会发生。如果我错了,请纠正我。 - AnjumSKhan
同时发布ProjectileMotion类的代码。如果可能的话,请制作一个可行的解决方案并上传到dropbox.com。 - AnjumSKhan
@AnjumSKhan 您是正确的,但该数字可能会根据给定的速度和角度而变化。我已经添加了ProjectileMotion类以及一个Drop Box链接到代码上面的演示项目中,感谢您抽出时间回复。 - Erik
1个回答

1
我看过你的代码,建议采用以下解决方案。这个方案可以在不做大的代码更改的情况下立即带来所需的改进。
添加命名空间: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();
            }
        }

这解决了您批量子弹爆炸的问题。

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


忘记提到 DispatcherTimer:计时器不能保证在时间间隔发生时精确执行,但它们保证不会在时间间隔之前执行。这是因为 DispatcherTimer 操作与其他操作一样被放置在 Dispatcher 队列上。当 DispatcherTimer 操作执行取决于队列中的其他作业及其优先级。很多事情取决于特定要求。https://msdn.microsoft.com/zh-cn/library/system.windows.threading.dispatchertimer(v=vs.110).aspx - AnjumSKhan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接