介绍:
因此,我构建的是一种类似Projectile Motion的模拟器,其中给定了初始速度和角度,每次单击“射击”按钮时,都会使用MatrixAnimationUsingPath沿着计算的路径生成新的项目符号并对其进行动画处理。该路径仅计算一次,并用于所有射弹。
问题:
我遇到的问题是,在获得应用程序性能问题之前,可以动画化的弹丸数量恰好(例如:30)似乎相对较低,并且取决于初始速度或角度,其变化范围很大。发生问题时,“开火”按钮的响应速度会大大降低,并且弹丸似乎会突然排成一列并发射,而不是在单击按钮时。
我尝试解决的问题:
最初,当我第一次编写它时,每次单击按钮时都会计算每个项目符号的路径,因此我最初认为这可能是问题所在。如果初始值相同,我重写它以重用相同的路径。它没有解决问题。
然后我想也许我可以将子弹动画化到单独的线程中。那是在我的研究中,我了解了使用线程相似性的困难,但还是尝试了一下。我为每个新线程提供了一种执行方法,这就是其中的一种方法:this.Dispatcher.Invoke((Action)(() => { /*Code for creating and starting animation*/ }));
对于创建和设置项目符号动画效果很好,但它并没有解决甚至没有改善主要问题。也许这不是解决问题的正确方法。
最近,我玩过降低帧速率的方法,这很有帮助,但幅度很小。
题:
可能还有什么其他选择,或者我应该对线程使用其他方法,还是仅仅是我正在使用的矩阵动画的性质,我是否应该考虑使用另一种类型的动画?
我注意到一种可能的相关性是,弹丸需要覆盖的初始速度或距离越大,将减少平滑动画子弹的数量或反过来增加该数量(例如,速度越慢或覆盖的水平距离越小,则数量增加)。
我已经在笔记本电脑和高端台式机上运行了该应用程序,并获得了类似的结果。
下面是我的主要代码,负责创建新的项目符号,然后对其进行动画处理而无需多线程。我本来会提供屏幕截图来帮助说明,但目前无法这样做。
在此先感谢您的贡献!
这是指向压缩的演示项目的投递箱链接以及屏幕截图:
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();
}
}
这样可以解决您爆破子弹的问题。
由于许多按钮单击消息可能正在爆炸消息队列而发生问题,系统将按照自己的速度处理消息队列。因此,我们需要检查那些点击事件。我们使用阈值来实现,并以0.2秒的间隔处理点击。您的代码可以做更多的改进。我正在使用FileBulletDelegate,我们也可以将Bullet用于队列项目,但这将带来更多代码更改。使用委托的好处之一是异步调用。
关于c# - 表演是否受到“少数” Storyboard /动画的打击?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32960234/