在本节中,我们将看到一些实质代码和world定位对象根本原理。首先,我们需要了解Farseer物理引擎对对象的测量。
了解Farseer物理引擎的测量方式
Farseer 是Box2D引擎的一个版本, 在 Box2D,如果我们要创建矩形,我们得告诉系统 宽度的一半值,高度的一半值和形状的中心点。
Farseer物理引擎也是使用相同的测量方式。在Silverlight或其他.NET应用程序中,我们试图创建矩形前先指定它的左上角的位置,然后提的高度和宽度的有关信息。
但是,这并不是Farseer物理引擎的使用方式,所以要记住的是,一个物理对象的位置总是指的是中心点不是左上角,但高度和宽度,应指100%的值,而不像Box2D中只填一半,这是唯一的不同。
现在如果你想创建单位为10X10矩形,并和你想要的Farseer物理对象一样大,那么你最后将得到一个非常大体积的对象,因为Farseer物理使用米为单位。
因此,我们必须始终使用非常小的大小或需要一个转换器转换成我们的像素值。我喜欢转换,因为它会让我在整个应用程序只考虑像素。因此,我们需要选择一个转换率,如1米= 15像素或类似的东西。这个比例将用于在整个应用程序转换显示的单位(像素)和模拟的单位(米),
最后在Farseer物理引擎中,旋转使用的是弧度单位,所以一定要在分配物理对象旋转量前转换为弧度。
在这里我不得不感谢一直支持我的卤面网版主,是他让我提起兴趣写了这么一篇文章,再次感谢卤面网,一个非常不错的wp7开发论坛,后面我也将再次向大家发布几篇高质量文章,请大家到卤面上找我吧,呵呵
进入正题:
Farseer物理引擎的使用演示
到目前为止,我们已经有了物理对象,我们知道我们需要遵循什么样的标准,但有没有UI相关的东西,因为Farseer 只是个物理引擎,不包括用户界面。因此如果我们想继续开发,在world中创造一个body,我们将不能在屏幕上看到任何东西。要想让Farseer物理对象出现在屏幕上,我们需要使用XNA 绘制他们。我们可以使用SpriteBatch类和DrawText方法在屏幕上绘制纹理和文字。下面是画10X10大小的矩形的示例,并贴了MyTexture的纹理。
SpriteBatch.Draw(MyTexture, new Rectangle(0, 0, 10, 10), Color.White);
因此,让我们的创建一个让矩形因重力下降的例子。
打开vs2010 for windows phone,创建新的项目,并选择XNA Game Studio 4.0 的项目模板,并命名它FarseerXNADemo1。它会给你生成2个项目,第一个是XNA的项目,另一个是内容项目用来管理资源(如图像,声音,字体等)
- 首先,我们需要引用WP7的DLL(Farseer物理引擎上可以找到http://farseerphysics.codeplex.com下载页面)。现在,我们需要得到需要绘制 的一些纹理(这里的纹理是指图像),添加空白PNG图像(blank.png)到项目内容。经过上述步骤后,我们的解决方案资源管理器将如下图:
public Game1() { Window.Title = "DEMO 1"; _graphics = new GraphicsDeviceManager(this); _graphics.PreferredBackBufferWidth = 480; _graphics.PreferredBackBufferHeight = 800; _graphics.IsFullScreen = true; Content.RootDirectory = "Content"; }
-我们先设置窗口标题,然后创建了一个新的GraphicsDeviceManager 对象,它使用了目前的游戏类的实例。现在我们指定宽度480,高度800,因为我们希望我们的电话是在竖屏模式。同时,我们正在开发游戏,所以我们不想手机上部分显示电池或连接相关的东西,因此打开全屏标志。(请注意,有时它是很好的做法,显示电池的详细信息,否则玩家可能会用尽电池,播放时,电话突然被关闭,这将是非常恼人)
- 在同一个文件中声明一个Texture2D对象名为MyTexture,一个世界对象名为MyWorld ,2个body的对象名为BoxBody和FloorBody
- 在initialize方法中,初始化所有4个对象,并给他们设置物理性能相关属性。
Texture2D MyTexture; World MyWorld; Body BoxBody, FloorBody; protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); base.LoadContent(); //Load Blank Texture so that we can render colors over it. MyTexture = Content.Load<Texture2D>("blank"); //Create New World with gravity of 10 units, downward. MyWorld = new World(Vector2.UnitY * 10); //Create Floor Fixture floorFixture = FixtureFactory.CreateRectangle(MyWorld, ConvertUnits.ToSimUnits(480), ConvertUnits.ToSimUnits(10), 10); floorFixture.Restitution = 0.5f; //Bounceability floorFixture.Friction = 0.5f; //Friction FloorBody = floorFixture.Body; //Get Body from Fixture FloorBody.IsStatic = true; //Floor must be stationary object //Create Box, (Note:Different way from above code, just to show it otherwise there is no difference) BoxBody = BodyFactory.CreateBody(MyWorld); FixtureFactory.CreateRectangle(ConvertUnits.ToSimUnits(50), ConvertUnits.ToSimUnits(50), 10, Vector2.Zero, BoxBody); foreach (Fixture fixture in BoxBody.FixtureList) { fixture.Restitution = 0.5f; fixture.Friction = 0.5f; } BoxBody.BodyType = BodyType.Dynamic; //Place floor object to bottom of the screen. FloorBody.Position = ConvertUnits.ToSimUnits(new Vector2(240, 700)); //Place Box on screen, somewhere BoxBody.Position = ConvertUnits.ToSimUnits(new Vector2(240, 25)); }
-让我们每次分析一行(为有关ConvertUnits类的更多信息,参见“ ConvertUnits “小节)。第一行初始化方法加载纹理并将对象保存供以后使用。然后我们创建一个新的世界,有着向下10个单位的重力。之后,我们创建地板的body,创建它的形状,使用FixtureFactory.CreateRectangle方法创建fixture 。它创建高度为20个单位的矩形。它同时也创建了正确的shape和body。然后我们设置地板的摩擦系数和弹性系数,并最终保存,以供以后的body对象使用
- 我们可以用同样的方法来创建物理对象,就像我们前面创造地板的方式一样,但我想向您展示其他方式,所以我们将创建略有不同的代码。我们首先创建一个body,如果我们传递world的对象到构造函数,那么我们同时将把该body加入该world。然后我们创建fixture,它会创建形状,并将它绑定到body(注意,我们这次在CreateRectangle方法中传递BoxBody作为参数)。然后我们提供body 弹性系数和摩擦系数。最后一行是设置正确的body类型。我们想在重力作用下移动箱子,所以我们这个对象为动态类型。
- 现在我们已经创建了地板和箱子,现在我们需要来定位他们在屏幕上的位置,地板需要放置在屏幕底部,而箱子要放置顶部某处
-ConvertUnits:这个类是用来转换Farseer物理单位(米)到显示单位(像素)。我们用16个单位的比例,即对我们1米= 16像素。对于800x480像素的屏幕,这个比率已经足够好了。在这个类中有3个静态方法,也有许多重载,方法详情如下:
ToSimUnits -这种方法将输入的值转换成模拟单位,也就是说把显示单元(像素)转换为模拟单位(米)。
ToDisplayUnits -这种方法将输入的值转换成显示单位,也就是说把模拟单位(米)转换为显示单元(像素)。
SetDisplayUnitToSimUnitRatio -此方法允许应用程序设置显示单位到模拟单位转换的转换率。请正确地使用它,因为错误设置的比例可能会导致意想不到的物理的效果(比如物体看起来太重...)。
In Farseer Physics measurements are in meters not in pixels. And position signifies centre point of object not the top-left corner.
- 做物理相关的东西后,我们需要使用XNA绘制这些对象。我们在Draw方法中添加以下代码。
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); //Draw Box, its Height is 50 and Width is 50. spriteBatch.Draw(MyTexture, new Rectangle((int)(ConvertUnits.ToDisplayUnits(BoxBody.Position.X) - 25), (int)(ConvertUnits.ToDisplayUnits(BoxBody.Position.Y) - 25), 50, 50), Color.Green); //Draw Floor, its Height is 10 and Width is 480. spriteBatch.Draw(MyTexture, new Rectangle((int)ConvertUnits.ToDisplayUnits(FloorBody.Position).X - 240, (int)ConvertUnits.ToDisplayUnits(FloorBody.Position).Y - 5, 480, 10), null, Color.Gray, FloorBody.Rotation, new Vector2(0, 0), SpriteEffects.None, 0); spriteBatch.End(); base.Draw(gameTime); }
-我们使用SpriteBatch类的Draw方法。在绘图box的同时,我们提供纹理,和目标矩形。第一和第二个参数分别是矩形的左上角坐标(记得Farseer 保存的是物体中心点位置,所以我们需要从左上角的位置减去一半的宽度和一半的高度,)。接下来的2个参数分别是高度和宽度。地板对象也是同理。
-如果你现在运行该项目,你会看到箱子和地板,但箱子不受重力的影响。这是因为我们没有更新世界。update方法需要添加的方法,看起来会像下面一样:,
protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // variable time step but never less then 30 Hz MyWorld.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f, (1f / 30f))); base.Update(gameTime); }
-按上面步骤写出的代码,效果会是:world会每秒前进30+个单位。现在,如果你运行的项目,你会感觉box受到重力下降一样。
-现在你可能会觉得用XNA整合物理对象是如此容易,我也这样认为,但如果您去尝试旋转,转换并组合更多形状于一体,你将不得不精确的计算每个物品在屏幕上应该怎么绘制,相信我,是不容易的,它似乎也将消耗大量的时间,因此要用一个通用的方法精确的得出要绘制对象的当前状态。我们应该怎么做......?答案是DebugDraw。
请大家关注下一节:
DebugDraw(XNA渲染)
我将在最后一节中放出所有源码与实例,毫无保留。
我希望你能喜欢我的文章!如果你有更多想法,请到卤面网 wp7开发论坛(codewp7.com)问答区联系我,我会很高兴知道你在想什么。同时wp7交流QQ群172765887中,也能找到我的身影,感谢大家