【Silverlight】Farseer 引擎做的游戏

    这是用 Farseer 物理引擎制作的一个小游戏。玩法特别简单:用鼠标控制白球的位置,尽量不要让白球碰到黑球。距离中心越远,得分越少。

    这是我这几天闲来无事,学习了 Farseer 这个玩意。本人也是第一次接触物理引擎,只用了引擎的极少功能。把学习和制作的经验写下,仅供参考。 【没有讲到的细节,请参考源码。】

    对于从未了解过物理引擎的人来说,必须要明确物理引擎这个概念。物理引擎是一个对物体的数学模型进行模拟计算的引擎,其内部使用的都是数据模型,而不是你所看见的实际界面。

    Farseer 物理引擎的官方文档:http://farseerphysics.codeplex.com/documentation

    Farseer 的一个例子:http://www.cnblogs.com/Aimeast/archive/2011/03/14/1984268.html

    要模拟一个移动的物体,需要有四样东西:The world, a body, a shape and a fixture。使用 Farseer 物理引擎开发一个游戏,你需要做的事情是:利用物理引擎建立你需要模拟物体的数据模型,并且利用循环机制驱动引擎运行;然后再把整个模型与输入输出系统关联起来。

    首先我们建立一个循环机制。使用 Silverlight 提供的 Storyboard 进行建立循环。

1
2
3
4
5
6
7
8
9
10
11
Storyboard _gameLoop = new Storyboard();
_gameLoop.Completed += new EventHandler(GameLoop);
_gameLoop.Begin();
 
 
private void GameLoop(object sender, EventArgs e)
{
    //ToDo
 
    _gameLoop.Begin();
}

    没有加任何控制的游戏循环会导致 CPU 资源耗尽。所以我们还需要加上时间控制参数,给 StoryBoard.Duration 属性设置一个等待时间。

    然后建立物体运动区域边界和运动物体的模型。由于 Farseer 引擎不支持直接建立内空的模型,所以圆形边界是用若干线段拼接而成。

1
2
3
4
5
6
7
8
9
10
11
12
13
List<Vertices> borders = new List<Vertices>(GameSettings.NumberOfEdges);
 
float stepSize = (float)(2.0 * Math.PI / GameSettings.NumberOfEdges);
for (int i = 0; i < GameSettings.NumberOfEdges; i++)
{
    Vertices v = PolygonTools.CreateLine(
        new Vector2(_gameRadius * (float)Math.Cos(i * stepSize), _gameRadius * (float)Math.Sin(i * stepSize)),
        new Vector2(_gameRadius * (float)Math.Cos((i + 1) * stepSize), _gameRadius * (float)Math.Sin((i + 1) * stepSize))
        );
    v.Translate(_gameCenter);
    borders.Add(v);
}
_border = BodyFactory.CreateCompoundPolygon(_world, borders, 1);

    建立球的模型(Ball 类见下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_balls = new List<Ball>();
Ball ball = new Ball(_gameCanvas, _world, Colors.White, false);
ball.Body.Position = _gameCenter;
ball.Body.OnCollision += new OnCollisionEventHandler(Ball_OnCollision);
_balls.Add(ball);
for (int i = 0; i < GameSettings.InitializeBallCount; i++)
{
    ball = new Ball(_gameCanvas, _world, Colors.Black, true);
    ball.Body.LinearVelocity = new Vector2(Tools.NextRangeFloat(GameSettings.BallInitializeVelocity),
        Tools.NextRangeFloat(GameSettings.BallInitializeVelocity));
    ball.Body.Position = _gameCenter;
 
    _balls.Add(ball);
}

    建立球的显示界面。用一个 Ball.cs 去驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
 
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
 
namespace SpeedGame
{
    public class Ball
    {
        private FrameworkElement element = null;
 
        public Body Body { get; private set; }
 
        public Ball(Canvas canvas, World world, Color color, bool isDynamic)
        {
            Body = BodyFactory.CreateCircle(world, GameSettings.BallRadius, 1);
            if (isDynamic)
            {
                Body.BodyType = BodyType.Dynamic;
                Body.Friction = 0f;
                Body.Mass = 0f;
                /*
                 * 0 = fully absorb the collision : dont bounce at all : inelastic collision
                 * 1 = perfect reflection : fully bounce back : elastic collision
                 */
                Body.Restitution = 1.0f;
 
                Body.SleepingAllowed = false;
            }
 
            element = new Ellipse
            {
                Width = ConvertUnits.ToDisplayUnits(GameSettings.BallRadius * 2),
                Height = ConvertUnits.ToDisplayUnits(GameSettings.BallRadius * 2),
                Fill = new SolidColorBrush(color)
            };
 
            //Add objects to Game content so it renders
            canvas.Children.Add(element);
        }
 
        public void Update()
        {
            //Move to correct location
            TranslateTransform TT = new TranslateTransform();
            TT.X = ConvertUnits.ToDisplayUnits(Body.Position.X) - element.ActualWidth / 2;
            TT.Y = ConvertUnits.ToDisplayUnits(Body.Position.Y) - element.ActualHeight / 2;
 
            TransformGroup transformGroup = new TransformGroup();
 
            transformGroup.Children.Add(TT);
 
            element.RenderTransform = transformGroup;
        }
    }
}

    另外还需要建立输入信息处理模块、计分模块等等。这里不贴代码了,自己到 源码 Input 文件夹下查看。

    再说说建立过程中遇到的一些问题。

  1. 模拟环境和显示环境使用的长度单位不一致,需要对两者的单位进行转换。我这里使用的是 ConvertUnits 类进行转换的。
  2. Farseer 引擎默认设置物体运行速度是不能超过 64 的。可以在源码的 Settings.MaxTranslation 设置此参数。
  3. Farseer 引擎具有自动适应功能。长时间没有操作的时候可能会进入“休眠”状态。
  4. 默认物体碰撞是非弹性碰撞。可以通过设置 x.Body.Restitution 来决定是弹性碰撞(1)还是非弹性碰撞(0),或者介于两者之间。

    这样说着建立一个游戏是挺简单的,但凡自己动手做起来,还是有一定的难度,并且有许多细节性问题需要考虑。有兴趣的朋友们赶快动手吧,利用物理引擎做一个属于你自己的游戏!

 

    PS:此游戏只是纯属无聊才做出来的,有许多游戏细节都未考虑到。比如说计分策略就非常不够完善,有许多漏洞可以钻。还请朋友们能够多给意见和建议。本地运行测试请选择用 Debug 配置环境。

 

    另:有没有研究过 Farseer 官方 Farseer Physics Engine 3.3.1 SimpleSamples Silverlight 的朋友,这个例子中是怎么自动的把 Body 转换成界面上 Element 的?

 

源码下载:https://files.cnblogs.com/Aimeast/SLSpeedGame.zip

posted @   Aimeast  阅读(2709)  评论(8编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示