65.9K
CodeProject 正在变化。 阅读更多。
Home

控制绘制的形状 #2 (模拟器)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (6投票s)

2013 年 11 月 17 日

CPOL

7分钟阅读

viewsIcon

38082

downloadIcon

1159

添加物理引擎并研究其他 .NET 工具

引言

在我第一次发布几年后,我发布了一个升级版,其中我做了以下工作:

  • 对原始文章进行了修改和清理,以提高易用性(对编码员和用户而言)。
  • 添加了物理引擎(重力、动量和阻力)。
  • 将对象绑定到网格以改进调试。
  • 添加了 TypeConverter

在本技巧中,我将解释这些改进以及我遇到的一些注意事项。本技巧本身不是如何重现此内容的食谱,为此,您可以阅读代码。这里的任何内容都不复杂到无法理解。

运行此程序后,您将能够抓住许多对象在一个平面世界中并将它们扔出去。您将看到重力、质量、摩擦力如何相互作用。在此过程中,您将学习如何自己建模所有这些。

Using the Code

基础知识

与上次类似,我依赖一个名为 IShape 的接口。但是,我将其分解为 IDrawable IShapeIShape 是一个 IDrawable。主要区别在于,IDrawable 是画布中没有独立交互性的对象,而 IShape 则有。

Shapes

自上一篇文章以来,形状的变化非常小。我最大的改变是,我创建了更多的面向对象层次结构,以便将更多公共代码提取到基类 Shape 中,并在具体形状(如选择和绘制)中更好地重用它。我想这基本上遵循了模板模式。我还添加了一些字段,允许它与下面描述的物理引擎进行交互。

画布

这是主绘制区域,与上一篇文章非常相似,但我将解释一些我更改的概念。我添加了一个视场 (fov),它定义了视图的位置和大小。与原始文章中您必须定义缩放和角度不同,现在我定义了一个视场(http://en.wikipedia.org/wiki/Field_of_view),这使得定义画布的位置和大小变得容易得多,如下所示。在我的应用程序中,大多数单位都是以米为单位。

zoomPanel.Initialize(universe, new RectangleF(0, 0, 10, 10));
... 

largePanel.Initialize(universe, new RectangleF(-50, 0, 110, 55));
... 

在此,缩放面板大小为 10x10 米,而大面板尺寸为 110x55 米,并且在其中部包含缩放面板。画布拥有自己的宇宙集,这意味着它们可以显示不同的内容,我不确定这有多现实。我删除了旋转功能,因为我想更多地处理重力(稍后),所以向下始终是向下。

可以使用鼠标右键拖动画布。画布是 IDrawable ,因为我想确保它被绘制以告知用户其位置。它不是 IShape ,因为它不与形状交互。

调试面板

我之前在这里底部添加了一个小的日志区域。没什么花哨的,只是一个文本区域,您可以轻松访问。然而,我的应用程序变得过于冗长,所以我决定在网格上显示每个对象的位置信息。当然,我想多学一点,所以在搜索了一下之后,我直接将形状列表绑定到网格,这使我能够做更多花哨的事情,比如添加交互性,当然还有“自动更新”,因为对象在变化。

将宇宙绑定到网格就像这样简单:

private void BindShapesToGrid(ShapeGroup universe)
{
    BindingSource bs = new BindingSource();  
    bs.DataSource = new BindingList<IShape>(universe.Shapes);
    dgModel.DataSource = bs;
} 

在字段上定义了“setter”的字段也可以通过网格自动设置,因此我可以轻松地为任何对象添加特定的高度或速度,而无需通过鼠标移动进行估算。

我在这里花了大量时间,尽管它已经在这里教授过了。最后,我发现我必须让 IShape 继承 INotifyPropertyChanged ,而不是 Shape (它实际上包含 PropertyChangedEventHandler 事件)。我花了几个小时,因为我还以为 .NET 类会查看传递给事件的实际对象,而不是 BindingList 类型的声明。这真有点尴尬,我想以后不会再发生了。通知更改很容易完成,正如广告宣传的那样。

private void NotifyPropertyChanged(string name)
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(name));
}  

我在这里学到的另一个很酷的东西是 TypeConverter 框架。我想通过网格输入任何对象的新速度。为了解析 string 并将其转换为 Vector3,我添加了 Vector3Converter,这在 Microsoft 文档中有详细说明,但这里是基本知识。需要转换的类将被标记一个属性,该属性告诉它哪个类提供转换。

[Serializable]
[TypeConverter(typeof(Vector3Converter))]
public struct Vector3... 

然后,如果您查看 [Vector3Converter] 的类定义,它实现了 TypeConverter 并覆盖了一些简单的方法。请查看代码以获取详细信息。

物理引擎

最后,我们来到了物理引擎。我想能够

  • 四处扔东西
  • 让东西掉到地上
  • 让大气层产生一定程度的阻力

这一部分有两个基本部分:一个计算力如何作用于物体的模型器,以及一个模拟时间流逝的模拟器。给定力和质量,可以确定性地计算出物体在一段时间(delta T,或经过的时间)内移动了多远。时间流逝需要被模拟。

时间模拟器

这实际上非常简单,它就是一个 Timer ,间隔设置为 10 毫秒。这意味着每 10 毫秒左右,就会触发一个事件,执行物理模型器。正如您所看到的,我说的是“10 毫秒左右”,在这种分辨率下,通常“左右”的成分比直接的 10 毫秒要多。为了使事物真正准确,模拟器需要保留自己的时间,并将其作为两个事件之间的实际经过时间。为此,我使用了一个 StopWatch,它可以轻松地告诉我两个事件之间的实际时间。当我能够模拟一个物体从 1000 米处落下(假设阻力为 0)并且时间与数学计算相匹配时,我感到很高兴。如果您没有自己的计时方法,您的物体将会落下得太慢。

我想我会在我的下一个迭代中创建一个包含 Timer StopWatch 的简单类来实现模拟器。

物理模型器

首先是一些物理基础知识——并非我记得学校里学过多少,我查阅了很多维基百科来学习一些方程,特别是关于阻力的。

  • a (加速度) = 重力 = 9.8 m/s^2
  • v (速度) = u + at (t = 经过的时间,u = 初始速度,这实际上是 a 的积分)
  • s (位移) = ut + 0.5at^2
  • FDrag (阻力) - 请参阅 http://en.wikipedia.org/wiki/Drag_equation
  • f = ma (这使我能够在施加重力时考虑 FDrag)

我对阻力的应用不是很确定(您也可以从 500 的除数看出这一点),但基本原理似乎有效。一个更大的物体会因为其增加的受阻面积而下落得更慢。同样,我简化了假设所有物体重 1 公斤。我可能会在以后的文章中更改这一点,以尝试其他概念。如果您能告诉我应用阻力的正确方法,请告诉我!

一旦您熟悉了这些方程,只需插入和播放即可。重要的是要记住力是矢量,速度也是矢量。我想也许可以创建一个单一的矢量来描述环境中的净力,但我选择分别处理这些矢量的分量。我没那么高级……

在处理 float double 时,另一个部分是检查具有一定允许误差的值,在我的情况下,我还希望物体停止移动,否则它们会持续反弹很多,这很烦人。为此,我添加了这段简单的代码。

if (Utilities.CheckWithAbsTolerance((float)vx, 0f, 0.1f)) vx = 0f;
if (Utilities.CheckWithAbsTolerance((float)vy, 0f, 0.1f)) vy = 0f; 

就是这样!试试这段代码,玩玩吧……让我知道您的想法!

© . All rights reserved.