2.在XNA中使用Stun2Dphysics
上一篇文章1.质点运动学和Stun2DPhysics引擎中我们已经创建了一个最简陋的2D物理引擎,只需设置Body的初速度和加速度,引擎就会自动计算出任意时刻的速度和位置,下面我们需要将这个引擎整合到XNA程序中。
将Stun2DPhysics整合到XNA程序中
首先在上一篇文章中创建的Stun2Dphysics解决方案中新建一个名称为Stun2DPhysicsDemo的Windows Game(4.0)项目,确定后解决方案中会自动生成这个项目以及对应的Content项目,然后在Stun2DPhysicsDemo项目中添加对物理引擎Stun2Dphysics的引用,截图如下:

然后需要对Texture2D对象进行封装,附加引擎的Body对象,因此新建一个名为PhysicsObject的文件夹,在此文件夹中添加BoxObject.cs类,这个类可以生成一个具有物理属性的矩形,代码如下:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Stun2DPhysics.DrawingSystem;
using Stun2DPhysics.Dynamics;
namespace Stun2DPhysics.PhysicsObject
{
///
/// 具有物理属性的矩形
///
public class BoxObject
{
Texture2D texture;
Vector2 origin;
Vector2 position;
int width;
int height;
public Body Body { get; private set; }
///
/// 创建一个BoxObject
///
/// 宽
/// 高
/// 位置
public BoxObject(int setWidth, int setHeight, Vector2 setPosition)
{
width = setWidth;
height = setHeight;
position = setPosition;
}
///
/// 加载纹理并附加Body对象
///
/// 图形设备
/// 物理引擎
public void Load(GraphicsDevice graphicsDevice, PhysicsSimulator physicsSimulator)
{
texture = DrawingHelper.CreateRectangleTexture(graphicsDevice, width, height, Color.White, Color.Black);
origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
Body = new Body();
Body.Position = position;
physicsSimulator.Add(Body);
}
///
/// 绘制
///
///
/// 调制颜色
public void Draw(SpriteBatch spriteBatch, Color color)
{
spriteBatch.Draw(texture, Body.Position, null, color, 0f , origin, 1f, SpriteEffects.None, 0f);
}
}
}
在构造函数中设置矩形的宽、高和位置,然后在Load方法中创建纹理和附加Body,这个纹理的位置是由Body的Position属性决定的,而Body的Position属性是由物理引擎计算得出的。 BoxObject的纹理并不是由内容管理器加载的,而是在DrawingHelper类的CreateRectangleTexture方法中手动生成,DrawingHelper类的代码如下:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Stun2DPhysics.DrawingSystem
{
public static class DrawingHelper
{
///
/// 创建一个矩形纹理,无边框
///
/// 图形设备
/// 宽
/// 高
/// 填充颜色
///
public static Texture2D CreateRectangleTexture(GraphicsDevice graphicsDevice, int width, int height, Color color)
{
return CreateRectangleTexture(graphicsDevice, width, height, 0, 0, 2, color, color);
}
///
/// 创建一个矩形纹理,边框宽度为1
///
/// 图形设备
/// 宽
/// 高
/// 填充颜色
/// 边框颜色
///
public static Texture2D CreateRectangleTexture(GraphicsDevice graphicsDevice, int width, int height, Color color,
Color borderColor)
{
return CreateRectangleTexture(graphicsDevice, width, height, 1, 1, 2, color, borderColor);
}
///
/// 创建一个矩形纹理
///
/// 图形设备
/// 宽
/// 高
/// 边框宽度
/// 填充颜色
/// 边框颜色
///
public static Texture2D CreateRectangleTexture(GraphicsDevice graphicsDevice, int width, int height,
int borderWidth, Color color, Color borderColor)
{
return CreateRectangleTexture(graphicsDevice, width, height, borderWidth, 1, 2, color, borderColor);
}
///
/// 创建一张矩形纹理
///
/// 图形设备
/// 宽
/// 高
/// 边框宽度
/// 边框内过渡宽度
/// 边框外过渡宽度
/// 填充颜色
/// 边框颜色
///
public static Texture2D CreateRectangleTexture(GraphicsDevice graphicsDevice, int width, int height,
int borderWidth, int borderInnerTransitionWidth,
int borderOuterTransitionWidth, Color color, Color borderColor)
{
Texture2D texture2D = new Texture2D(graphicsDevice, width, height, false, SurfaceFormat.Color);
int y = -1;
int j;
int count = width * height;
Color[] colorArray = new Color[count];
Color[] shellColor = new Color[borderWidth + borderOuterTransitionWidth + borderInnerTransitionWidth];
float transitionAmount;
for (j = 0; j < borderOuterTransitionWidth; j++)
{
transitionAmount = (j) / (float)(borderOuterTransitionWidth);
shellColor[j] = new Color(borderColor.R, borderColor.G, borderColor.B, (byte)(255 * transitionAmount));
}
for (j = borderOuterTransitionWidth; j < borderWidth + borderOuterTransitionWidth; j++)
{
shellColor[j] = borderColor;
}
for (j = borderWidth + borderOuterTransitionWidth;
j < borderWidth + borderOuterTransitionWidth + borderInnerTransitionWidth;
j++)
{
transitionAmount = 1 -
(j - (borderWidth + borderOuterTransitionWidth) + 1) /
(float)(borderInnerTransitionWidth + 1);
shellColor[j] = new Color((byte)MathHelper.Lerp(color.R, borderColor.R, transitionAmount),
(byte)MathHelper.Lerp(color.G, borderColor.G, transitionAmount),
(byte)MathHelper.Lerp(color.B, borderColor.B, transitionAmount));
}
for (int i = 0; i < count; i++)
{
if (i % width == 0)
{
y += 1;
}
int x = i % width;
// 检查像素是否是矩形的边框
bool isInShell = false;
for (int k = 0; k < shellColor.Length; k++)
{
if (InShell(x, y, width, height, k))
{
colorArray[i] = shellColor[k];
isInShell = true;
break;
}
}
// 如果像素不是位于矩形的边框则位于内部填充区域
if (!isInShell)
{
colorArray[i] = color;
}
}
texture2D.SetData(colorArray);
return texture2D;
}
private static bool InShell(int x, int y, int width, int height, int shell)
{
// 检查矩形的四条边
if ((x == shell && IsBetween(y, shell, height - 1 - shell)) ||
(x == width - 1 - shell && IsBetween(y, shell, height - 1 - shell)))
{
return true;
}
if ((y == shell && IsBetween(x, shell, width - 1 - shell)) ||
(y == height - 1 - shell && IsBetween(x, shell, width - 1 - shell)))
{
return true;
}
return false;
}
private static bool IsBetween(float value, float min, float max)
{
if (value >= min && value <= max)
{
return true;
}
return false;
}
}
}
代码有点复杂,其实这个代码类似于flash中画出一个矢量矩形的过程,你需要设置边框颜色,格式以及内部填充颜色,如果你编程能力够强的画,也能实现flash中的渐变填充颜色的效果,类似的方法也可以实现椭圆,具体代码请参见FarseerPhysics2.1.3,为了简单起见,我并没有加入画椭圆的代码。
如果你并不想手动生成纹理,可以使用SpriteObject类,这个类直接传入Texture2D对象,原理与BoxObject是类似的,代码如下:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Stun2DPhysics.Dynamics;
namespace Stun2DPhysics.PhysicsObject
{
///
/// 具有物理属性的Sprite
///
public class SpriteObject
{
private Texture2D texture;
private Vector2 origin;
private Vector2 position;
public Body Body { get; private set; }
///
/// 创建一个SpriteObject
///
/// 初始位置
public SpriteObject(Vector2 setPosition)
{
position = setPosition;
}
///
/// 设置纹理并附加Body
///
/// 纹理
/// 物理引擎
public void Load(Texture2D setTexture, PhysicsSimulator physicsSimulator)
{
texture = setTexture;
origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
Body = new Body();
Body.Position = position;
physicsSimulator.Add(Body);
}
public void Draw(SpriteBatch spriteBatch, Color color)
{
spriteBatch.Draw(texture, Body.Position, null, color, 0f , origin, 1f, SpriteEffects.None, 0f);
}
}
}
步骤如下:
1. 你需要三个成员变量:引擎PhysicsSimulator,矩形物理对象BoxObject和直接使用Texture2D的物理对象SpriteObject。
2. 在Initialize()中初始化引擎,在LoadContent()方法中调用物理对象的Load()方法,设置两个物理对象的初速度和加速度,我设置为一个水平向右,一个水平向左,而加速度竖直向下,物理知识告诉我们,这样这两个对象做平抛运动。
3. 在Update()方法中更新引擎,引擎会计算所有Body对象的位置。
4. 在Draw()方法中绘制物理对象。
程序截图如下:

将Stun2DPhysics整合到StunEngine中
在引擎项目StunEngine中的SceneNodes文件夹中添加Physics2DsceneNode文件夹,在此文件夹中添加名为Physics2DsceneNode的类,代码如下:
using Microsoft.Xna.Framework;
using Stun2DPhysics.Dynamics;
using StunEngine.SceneManagement;
namespace StunEngine.SceneNodes
{
///
/// 具有物理属性的2D图像
///
public class Physics2DSceneNode :Renderable2DSceneNode
{
///
/// 纹理源矩形
///
protected Rectangle? rectSourceOrigin = null;
///
/// 创建一个新Physics2DSceneNode对象
///
/// 引擎
/// 所属场景
/// 纹理名称,带路径
public Physics2DSceneNode(StunXnaGE engine, Scene setScene, string setTextureName)
: base(engine, setScene, setTextureName)
{
if (Scene.PhysicsSimulator == null)
throw new System.InvalidOperationException("物理系统还未初始化");
Body = new Body();
Scene.PhysicsSimulator.Add(Body);
Origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
}
public Body Body { get; private set; }
///
/// 获取或设置控件的2D屏幕位置
///
public override Vector2 Position
{
get { return position; }
set
{
position = value;
Body.Position = position;
OnLocationChanged();
}
}
public override int Draw(GameTime gameTime)
{
//获取图像淡入淡出的透明颜色
alphaTextureColor = new Color(Color.R, Color.G, Color.B, Scene.TransitionAlpha);
//绘制图像
StunXnaGE.SpriteBatch.Draw(texture, new Rectangle((int)Body.Position.X, (int)Body.Position.Y, (int)size.X, (int)size.Y), rectSourceOrigin, alphaTextureColor, Rotation, Origin, SpriteEffect, LayerDepth);
return 1;
}
}
}
Physics2DsceneNode从绘制2D图像的Renderable2DsceneNode类继承,新添了Body属性,由物理引擎的Body类负责更新它的位置。
然后在Scene类中添加以下代码:
public class Scene
{
[…]
internal PhysicsSimulator PhysicsSimulator;
internal Scene(StunXnaGE engine, ushort sceneId, string name, int aproximateNodes)
{
[…]
PhysicsSimulator = new PhysicsSimulator();
}
public virtual void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
{
[…]
PhysicsSimulator.Update((float)gameTime.ElapsedGameTime .TotalSeconds);
}
}
以上代码在Scene类中添加了PhysicsSimulator的成员变量,在Scene的构造函数中进行初始化,在Update方法中更新物理引擎。
要使用Physics2DsceneNode类,单元测试的示例代码如下:
box1 = new Physics2DSceneNode(TestGame.engine,TestGame.scene,"Textures\\squares"); box1.Size = new Vector2(64); box1.Position = new Vector2(64); TestGame.scene.AddNode(box1); box1.Body.LinearVelocity = new Vector2(150, 0); box1.Body.Acceleration = new Vector2(0, 100);
单元测试位于StunEngine0.5Test项目的TestGame.cs的TestPhysics()方法中,截图如下:

发布时间:2011/6/1 下午9:13:32 阅读次数:7352
