介绍
使用 Silverlight 2.0(c#, Farseer Physics Engine) 开发一个射击游戏:星际竞技场
玩法
W 或者 ↑ = 前进;S 或者 ↓ = 后退:A 或者 ← = 左转;D 或者 → = 右转;J 或者 Ctrl = 开火
思路
1、使用一个开源的 Silverlight 物理引擎:Farseer Physics Engine
2、将 Farseer Physics Engine 中的物理运算器 PhysicsSimulator 放到一个全局变量中,对 Body 和 Geom 做即时运算,
3、写个 IPhysicsControl 接口,用于描述物理对象的各个属性,需要运动和碰撞的对象,要实现该接口抽象出来的各个属性
4、写个抽象类(Sprite),在其内封装好物理引擎。各种类型的物理对象的模拟器,都需要重写该抽象类的两个方法GetForce()和GetTorque()即可,其分别要返回对象在当前时刻所受到的牵引力和力矩
5、写个 IFire 接口,所有可开火的对象都要实现该接口
6、写个控件 PhysicsBox,用于包装 IPhysicsControl,从而将模拟器计算出的运动和碰撞结果呈现到界面上
关键代码Sprite.cs(Sprite 模拟器的基类)
复制
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using FarseerGames.FarseerPhysics; using FarseerGames.FarseerPhysics.Mathematics; using FarseerGames.FarseerPhysics.Dynamics; using FarseerGames.FarseerPhysics.Collisions; namespace YYArena.Core { /**//// <summary> /// Sprite 基类 /// </summary> public abstract class Sprite { private PhysicsSimulator _physicsSimulator; protected PhysicsBox playerBox; protected Geom playerGeometry; /**//// <summary> /// 构造函数 /// </summary> /// <param name="physicsSimulator">PhysicsSimulator</param> /// <param name="physicsControl">IPhysicsControl</param> /// <param name="position">初始位置</param> /// <param name="angle">初始转角</param> /// <param name="originalVelocity">初始速度</param> public Sprite(PhysicsSimulator physicsSimulator, IPhysicsControl physicsControl, Vector2 position, float angle, float originalVelocity) { _physicsSimulator = physicsSimulator; playerBox = new PhysicsBox(physicsControl); playerBox.Body.Position = position; playerBox.Body.Rotation = (float)Helper.Angle2Radian(angle); playerBox.Body.LinearVelocity = Helper.Convert2Vector(originalVelocity, (float)Helper.Angle2Radian(angle)); // Body 和 Geom 的 Tag 保存为 Sprite,方便引用 playerBox.Body.Tag = this; playerBox.Geom.Tag = this; playerBox.Update(); } /**//// <summary> /// 即时计算力和力矩 /// </summary> void CompositionTarget_Rendering(object sender, EventArgs e) { if (Enabled) { var force = GetForce(); var torque = GetTorque(); playerBox.Body.ApplyForce(force); playerBox.Body.ApplyTorque(torque); playerBox.Update(); } } /**//// <summary> /// 返回 Sprite 当前受的力 /// </summary> protected abstract Vector2 GetForce(); /**//// <summary> /// 返回 Sprite 当前受的力矩 /// </summary> protected abstract float GetTorque(); public PhysicsBox PhysicsBox { get { return playerBox; } } private bool _enabled = false; /**//// <summary> /// 是否启用此 Sprite /// </summary> public bool Enabled { get { return _enabled; } set { _enabled = value; if (value) { CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); _physicsSimulator.Add(playerBox.Body); _physicsSimulator.Add(playerBox.Geom); } else { CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering); GC.SuppressFinalize(this); _physicsSimulator.Remove(playerBox.Body); _physicsSimulator.Remove(playerBox.Geom); } } } } }
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.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
#p#
PlayerSprite.cs(玩家 Sprite 模拟器)
复制
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using FarseerGames.FarseerPhysics.Mathematics; using FarseerGames.FarseerPhysics; using FarseerGames.FarseerPhysics.Collisions; namespace YYArena.Core { /**//// <summary> /// 玩家 Sprite /// </summary> public class PlayerSprite : Sprite, IFire { private List<Key> _upKeys { get; set; } private List<Key> _downKeys { get; set; } private List<Key> _leftKeys { get; set; } private List<Key> _rightKeys { get; set; } private List<Key> _fireKeys { get; set; } private KeyboardHandler _keyHandler; private IPhysicsControl _physicsControl; /**//// <summary> /// 构造函数 /// </summary> /// <param name="physicsSimulator">PhysicsSimulator</param> /// <param name="physicsControl">IPhysicsControl</param> /// <param name="position">初始位置</param> /// <param name="angle">初始转角</param> /// <param name="originalVelocity">初始速度</param> /// <param name="keyboardHandler">KeyboardHandler</param> /// <param name="up">操作玩家向前移动的按键集合</param> /// <param name="down">操作玩家向后移动的按键集合</param> /// <param name="left">操作玩家向左转动的按键集合</param> /// <param name="right">操作玩家向右转动的按键集合</param> /// <param name="fire">操作玩家开火的按键集合</param> public PlayerSprite(PhysicsSimulator physicsSimulator, IPhysicsControl physicsControl, Vector2 position, float angle, float originalVelocity, KeyboardHandler keyboardHandler, List<Key> up, List<Key> down, List<Key> left, List<Key> right, List<Key> fire) : base(physicsSimulator, physicsControl, position, angle, originalVelocity) { PrevFireDateTime = DateTime.MinValue; MinFireInterval = 500d; _upKeys = up; _downKeys = down; _leftKeys = left; _rightKeys = right; _fireKeys = fire; _keyHandler = keyboardHandler; _physicsControl = physicsControl; CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); } void CompositionTarget_Rendering(object sender, EventArgs e) { if (Enabled) { // 如果按了开火键,是否可开火 if (_keyHandler.AnyKeyPressed(_fireKeys) && (DateTime.Now - PrevFireDateTime).TotalMilliseconds > MinFireInterval) { PrevFireDateTime = DateTime.Now; if (Fire != null) Fire(this, EventArgs.Empty); } } } public DateTime PrevFireDateTime { get; set; } public double MinFireInterval { get; set; } public event EventHandler<EventArgs> Fire; protected override Vector2 GetForce() { Vector2 force = Vector2.Zero; if (_keyHandler.AnyKeyPressed(_upKeys)) force += Helper.Convert2Vector(_physicsControl.ForceAmount, playerBox.Body.Rotation); if (_keyHandler.AnyKeyPressed(_downKeys)) force += Helper.Convert2Vector(_physicsControl.ForceAmount, playerBox.Body.Rotation - Helper.Angle2Radian(180)); // 最大线性速度限制 if (playerBox.Body.LinearVelocity.Length() > _physicsControl.MaxLinearVelocity) force = Vector2.Zero; return force; } protected override float GetTorque() { float torque = 0; if (_keyHandler.AnyKeyPressed(_leftKeys)) torque -= _physicsControl.TorqueAmount; if (_keyHandler.AnyKeyPressed(_rightKeys)) torque += _physicsControl.TorqueAmount; // 用于修正 RotationalDragCoefficient (在没有任何 Torque 的情况下,如果转速小于 1.3 则设其为 0) // 如果不做此修正的话,转速小于 1.3 后还会转好长时间 if (!_keyHandler.AnyKeyPressed(_leftKeys) && !_keyHandler.AnyKeyPressed(_rightKeys) && Math.Abs(playerBox.Body.AngularVelocity) < 1.3) playerBox.Body.AngularVelocity = 0; // 最大转速限制 if (Math.Abs(playerBox.Body.AngularVelocity) > _physicsControl.MaxAngularVelocity) torque = 0; return torque; } } }
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.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
#p#
AISprite.cs(敌军 Sprite 模拟器)
复制
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using FarseerGames.FarseerPhysics.Mathematics; using FarseerGames.FarseerPhysics; using FarseerGames.FarseerPhysics.Collisions; using FarseerGames.FarseerPhysics.Dynamics; namespace YYArena.Core { /**//// <summary> /// 敌军 Sprite /// </summary> public class AISprite : Sprite, IFire { private Sprite _attackTarget; private int _aiLevel; private IPhysicsControl _physicsControl; /**//// <summary> /// 构造函数 /// </summary> /// <param name="physicsSimulator">PhysicsSimulator</param> /// <param name="physicsControl">IPhysicsControl</param> /// <param name="position">初始位置</param> /// <param name="angle">初始转角</param> /// <param name="originalVelocity">初始速度</param> /// <param name="attackTarget">攻击目标</param> /// <param name="aiLevel">ai等级</param> public AISprite(PhysicsSimulator physicsSimulator, IPhysicsControl physicsControl, Vector2 position, float angle, float originalVelocity, Sprite attackTarget, int aiLevel) : base(physicsSimulator, physicsControl, position, angle, originalVelocity) { // 上次开火时间 PrevFireDateTime = DateTime.Now.AddSeconds(3); // 最小开火间隔 MinFireInterval = 3000d; _attackTarget = attackTarget; _aiLevel = aiLevel; _physicsControl = physicsControl; InitAI(); CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); } private void InitAI() { // 根据 ai 等级设置最小开火间隔 double fireCoefficient = 1 + 30 / _aiLevel; MinFireInterval = Helper.GenerateRandom((int)MinFireInterval, (int)(fireCoefficient * MinFireInterval)); } void CompositionTarget_Rendering(object sender, EventArgs e) { if (Enabled && AttackTarget.Enabled) { // 是否开火 if ((DateTime.Now - PrevFireDateTime).TotalMilliseconds > MinFireInterval) { PrevFireDateTime = DateTime.Now; if (Fire != null) Fire(this, EventArgs.Empty); } } } public DateTime PrevFireDateTime { get; set; } public double MinFireInterval { get; set; } public event EventHandler<EventArgs> Fire; public Sprite AttackTarget { get { return _attackTarget; } set { _attackTarget = value; } } protected override Vector2 GetForce() { Vector2 force = Vector2.Zero; if (!_attackTarget.Enabled) return force; force += Helper.Convert2Vector(_physicsControl.ForceAmount, playerBox.Body.Rotation); // 根据 ai 等级做最大线性速度限制 if (playerBox.Body.LinearVelocity.Length() > _physicsControl.MaxLinearVelocity * Helper.GenerateRandom(50, 200) / 1000) force = Vector2.Zero; return force; } protected override float GetTorque() { float torque = 0f; if (!_attackTarget.Enabled) return torque; // 按某个方向旋转,原则是以最小的旋转角度对准目标 Vector2 relativePosition = _attackTarget.PhysicsBox.Body.Position - playerBox.Body.Position; double targetRotation = Helper.Convert2Rotation(relativePosition); double currentRotation = playerBox.Body.Rotation; double relativeAngle = Helper.Radian2Angle(targetRotation - currentRotation); if (relativeAngle < 0) relativeAngle += 360; if (relativeAngle > 1) { if (relativeAngle < 180 && relativeAngle > 0) torque += _physicsControl.TorqueAmount; else if (relativeAngle > 180 && relativeAngle < 360) torque -= _physicsControl.TorqueAmount; } else { playerBox.Body.AngularVelocity = 0; } // 最大转速限制 if (Math.Abs(playerBox.Body.AngularVelocity) > _physicsControl.MaxAngularVelocity) torque = 0; return torque; } } }
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.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
原文链接:http://www.cnblogs.com/webabcd/archive/2009/06/22/1508042.html