表现来自一个"少数"故事板/动画?

时间:2015-10-06 01:14:20

标签: c# wpf animation

说明:

所以我所建造的是各种类型的弹丸运动模拟器,每次给出初始速度和角度,每次"火"单击按钮将生成一个新项目符号,并使用MatrixAnimationUsingPath沿计算路径生成动画。该路径仅计算一次,用于所有射弹。

问题:

我遇到的问题是弹丸的数量,在我遇到应用程序性能问题之前可以很好地设置动画(例如: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;
    }
}

1 个答案:

答案 0 :(得分: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作为队列项,但这将带来更多的代码更改。使用委托的一个好处可能是异步调用。