Windows Phone 迷宫






4.95/5 (52投票s)
一个使用加速度计模拟器和 Farseer 物理引擎的 Windows Phone 应用程序
目录
- 引言
- 系统要求
- Windows Phone SDK 7.1 中的新功能
- 加速度计模拟器
- Farseer 物理引擎
- 创建第一个对象:球
- 设置第二个对象:迷宫
- 触觉反馈,又称振动反馈
- 每秒60帧,感谢 Mango 更新!
- 看起来很酷,但这还不是一个游戏
- 想法,想法,想法...
- 最终思考
- 历史
引言
上个月(2011年9月),我收到一个好消息:Windows Phone SDK 7.1 发布了。我打开了“新功能”会话,它充满了很棒的新功能。我特别感兴趣的是新的 Windows Phone 模拟器,它现在能够模拟设备传感器,例如 Windows Phone 加速度计。
本文旨在展示如何使用这个新的加速度计模拟功能,并附带一个使用它的简单应用程序。
系统要求
要使用本文提供的 Windows Phone 迷宫,您必须直接从 Microsoft 下载并安装以下100%免费的开发工具
无论您是熟悉还是初次接触 Silverlight 和 XNA Game Studio 编程,Visual Studio 2010 Express for Windows Phone 都提供了您开始构建 Windows Phone 应用程序所需的一切。
Windows Phone 软件开发工具包 (SDK) 7.1 为您提供了开发 Windows Phone 7.0 和 Windows Phone 7.5 设备上的应用程序和游戏所需的所有工具。
Windows Phone SDK 7.1 中的新功能
新的 Windows Phone SDK 7.1 和新文档现在包含许多新功能
- 多目标和应用兼容性
- 多任务处理
- 执行模型和快速应用切换
- 闹钟和提醒
- 后台代理
- 后台文件传输
- 后台音频
- 媒体
- Silverlight 4
- 传感器
- 套接字支持
- 网络信息
- 推送通知
- 动态磁贴
- Silverlight 和 XNA 集成
- 应用性能分析
- Visual Basic 支持
- 广告
- WebBrowser 控件
- 设备状态
- 本地数据库
- 独立存储资源管理器
- 启动器和选择器
- 联系人和日历
- 加密凭证存储
- 用户体验设计指南
- 认证要求
- 市场测试套件
- 相机
- 图片扩展性
- 搜索扩展性
- 应用程序栏
- 屏幕键盘
- 系统托盘和进度指示器
- OData 客户端
- 全球化和本地化
- 创建试用应用程序
在所有这些功能中,本文将介绍的是传感器。但不是所有类型的传感器(因为“传感器”包括加速度计、陀螺仪传感器、组合运动和旋转速率)。目前,我们只玩加速度计。希望在不久的将来,我们可以专注于 WP7 的其他功能。
加速度计模拟器
访问新的加速度计模拟器非常容易。首先,启动 Windows Phone 模拟器。然后将鼠标移到模拟器窗口上。此时,将显示您在模拟器上一版本中找到的相同垂直图标栏。不同之处在于您可以在图标栏底部找到的“>>”图标。单击它,将打开一个名为“附加工具”的新窗口
只有第一个选项卡“加速度计”对我们很重要。它有设备的3D渲染(请注意,此渲染不显示模拟器的相同屏幕)。这是一个带有静态瓷砖的假屏幕。但是,嘿,微软,如果这里有实际的设备屏幕,那会很酷,你不觉得吗?
加速度计模拟器中需要注意的第二件事是方向下拉列表。我们首先需要选择正确的方向(纵向或横向,以及站立或平放)才能正确执行模拟。我们的应用程序只有在设备处于水平位置时才能正常工作,因此您必须选择纵向平放或横向平放方向。
加速度计模拟器中需要注意的第二件事是窗口中心的小红圈。拖动它并在屏幕上移动,以模拟加速度计。
当您移动加速度计控制点时,X、Y 和 Z 坐标会发生变化并显示在屏幕上。此外,这些数据还会实时传递给 Windows Phone 模拟器,因此您所做的任何更改都可以被应用程序读取。
在代码中,加速度计功能是
using Microsoft.Devices.Sensors;
应用程序启动后,我们立即启动加速度计,它每20毫秒触发一次更新事件
private void StartAccelerometer()
{
if (accelerometer == null)
{
// Instantiate the accelerometer.
accelerometer = new Accelerometer();
// Specify the desired time between updates. The sensor accepts
// intervals in multiples of 20 ms.
accelerometer.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
}
accelerometer.Start();
}
当加速度计传感器值改变时,我们只需改变
void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
{
// Note that this event handler is called from a background thread
// and therefore does not have access to the UI thread. To update
// the UI from this handler, use Dispatcher.BeginInvoke() as shown.
if (World != null)
{
World.Gravity = new Vector2(e.SensorReading.Acceleration.Y * -ACC_GRAVITY_COEF, e.SensorReading.Acceleration.X * -ACC_GRAVITY_COEF);
}
}
Farseer 物理引擎
Farseer 是一个出色的开源物理引擎,基于原始的 Box 2D 开源项目(顺便说一句,愤怒的小鸟游戏就使用了 Box2D)。不同之处在于 Box2D 用 C++ 编写(并已移植到多种语言),而 Farseer 用 C# 编写,目标是 Silverlight 和 XNA。此外,Farseer 网站声称此框架除了原始 Box2D 版本提供的功能外,还有许多其他功能。
创建第一个对象:球
默认情况下,Farseer 将圆形对象渲染为带有特定边框和纹理的椭圆形。我们的球不过是一个 Farseer 圆形,用钢球的静态纹理渲染而成。我们首先加载球的纹理并定义球的初始位置
public override void LoadContent()
{
base.LoadContent();
LoadTextures();
DefineScreenRectangle();
InitializeBall();
InitializeLabyrinth();
StartAccelerometer();
}
private void InitializeLabyrinth()
{
List<Vertices> verticesList = GetVerticesFromLabyrinthTexture();
ScaleVertices(verticesList);
CreateLabyrinthBody(verticesList);
}
private void CreateLabyrinthBody(List<Vertices> verticesList)
{
//Create a single body with multiple fixtures
labyrinth = BodyFactory.CreateCompoundPolygon(World, verticesList, 1f, BodyType.Dynamic);
labyrinth.BodyType = BodyType.Dynamic;
labyrinth.IsStatic = true;
}
private void ScaleVertices(List<Vertices> verticesList)
{
//scale the vertices from graphics space to sim space
Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * SCALE_FOR_WP_RESOLUTION;
foreach (Vertices vertices in verticesList)
{
vertices.Scale(ref vertScale);
}
}
private List<Vertices> GetVerticesFromLabyrinthTexture()
{
Vertices textureVertices;
//Create an array to hold the data from the texture
uint[] data = new uint[labyrinthTexture.Width * labyrinthTexture.Height];
//Transfer the texture data to the array
labyrinthTexture.GetData(data);
//Find the vertices that makes up the outline of the shape in the texture
textureVertices = PolygonTools.CreatePolygon(data, labyrinthTexture.Width, false);
//The tool return vertices as they were found in the texture.
//We need to find the real center (centroid) of the vertices for 2 reasons:
//1. To translate the vertices so the polygon is centered around the centroid.
Vector2 centroid = -textureVertices.GetCentroid();
textureVertices.Translate(ref centroid);
//2. To draw the texture the correct place.
labyrinthOrigin = -centroid;
//We simplify the vertices found in the texture.
textureVertices = SimplifyTools.ReduceByDistance(textureVertices, 4f);
//Since it is a concave polygon, we need to partition it into several smaller convex polygons
return BayazitDecomposer.ConvexPartition(textureVertices);
}
private void InitializeBall()
{
Vector2 startPosition = new Vector2(-20f, -11f);
Vector2 endPosition = new Vector2(20, -11f);
ball = new Ball(World, this, startPosition, 1.5f, false);
ball.BallCollided += new Labyrinth.Ball.BallCollisionEventHandler(ball_BallCollided);
}
private void DefineScreenRectangle()
{
var rectangle = ScreenManager.GraphicsDevice.Viewport.Bounds;
screenRectangle = new Rectangle(-rectangle.Width, -rectangle.Height, rectangle.Width * 2, rectangle.Height * 2);
}
private void LoadTextures()
{
backgroundTexture = ScreenManager.Content.Load<Texture2D>("Common/woodenbackground");
ballofSteelTexture = ScreenManager.Content.Load<Texture2D>("Samples/ballofsteel");
ballShadowTexture = ScreenManager.Content.Load<Texture2D>("Samples/ballshadow");
woodenForegroundTexture = ScreenManager.Content.Load<Texture2D>("Samples/woodenforeground");
labyrinthShadowTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinthshadow");
labyrinthTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinth");
}
然后我们使用预加载的纹理绘制球
public override void Draw(GameTime gameTime)
{
BeginSpriteBatch();
DrawBackground();
DrawLabyrinthShadow();
DrawBall();
DrawLabyrinth();
EndSpriteBatch();
base.Draw(gameTime);
}
private void DrawLabyrinth()
{
ScreenManager.SpriteBatch.Draw(labyrinthTexture, ConvertUnits.ToDisplayUnits(labyrinth.Position),
null, Color.Azure, labyrinth.Rotation, labyrinthOrigin, SCALE_FOR_WP_RESOLUTION, SpriteEffects.None, 0f);
}
private void DrawBall()
{
var ballShadowVector = new Vector2(World.Gravity.X, World.Gravity.Y) * ACC_BALL_SHADOW_COEF;
ball.Draw(ballofSteelTexture, ballShadowTexture, ballShadowVector);
}
private void DrawLabyrinthShadow()
{
var shadowVector = new Vector2(World.Gravity.X, World.Gravity.Y) * -ACC_SHADOW_COEF;
ScreenManager.SpriteBatch.Draw(labyrinthShadowTexture, ConvertUnits.ToDisplayUnits(labyrinth.Position),
null, Color.Azure, labyrinth.Rotation, labyrinthOrigin - shadowVector, SCALE_FOR_WP_RESOLUTION, SpriteEffects.None, 0f);
}
private void DrawBackground()
{
ScreenManager.SpriteBatch.Draw(backgroundTexture, screenRectangle, Color.White);
}
private void EndSpriteBatch()
{
ScreenManager.SpriteBatch.End();
}
private void BeginSpriteBatch()
{
ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View);
}
设置第二个对象:迷宫
迷宫本身通过3种纹理渲染:木质背景、迷宫阴影和迷宫木质边框
因此,阴影有助于根据加速度计数据提供的旋转来模仿迷宫的3D外观和感觉
迷宫纹理首先像前面提到的钢球一样被加载
public override void LoadContent()
{
.
.
.
//load texture that will represent the physics body
labyrinthTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinth");
.
.
.
}
现在我们通过连接一系列矩形来创建迷宫,对吗?错了!与球不同,迷宫是从迷宫的纹理图像创建的。这是 Farseer 引擎的一个非常棒的功能,它使我们的生活变得轻松多了!Farseer 内置函数从我们的迷宫平面图像创建一个实体对象,前提是我们把空的地方留成透明颜色,就像这样
现在想象一下创建新关卡是多么容易……太棒了,不是吗?Farseer 函数将为我们完成繁重的工作,在下面的代码中,最值得一提的部分是 BayazitDecomposer.ConvexPartition 方法,它从一个大的凹多边形中创建较小的凸多边形
public override void LoadContent()
{
.
.
.
//Create an array to hold the data from the texture
uint[] data = new uint[labyrinthTexture.Width * labyrinthTexture.Height];
//Transfer the texture data to the array
labyrinthTexture.GetData(data);
//Find the vertices that makes up the outline of the shape in the texture
Vertices textureVertices = PolygonTools.CreatePolygon(data, labyrinthTexture.Width, false);
//The tool return vertices as they were found in the texture.
//We need to find the real center (centroid) of the vertices for 2 reasons:
//1. To translate the vertices so the polygon is centered around the centroid.
Vector2 centroid = -textureVertices.GetCentroid();
textureVertices.Translate(ref centroid);
//2. To draw the texture the correct place.
labyrinthOrigin = -centroid;
//We simplify the vertices found in the texture.
textureVertices = SimplifyTools.ReduceByDistance(textureVertices, 4f);
//Since it is a concave polygon, we need to partition it into several smaller convex polygons
List<Vertices> list = BayazitDecomposer.ConvexPartition(textureVertices);
//Adjust the scale of the object for WP7's lower resolution
scale = 2f;
//scale the vertices from graphics space to sim space
Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * scale;
foreach (Vertices vertices in list)
{
vertices.Scale(ref vertScale);
}
//Create a single body with multiple fixtures
labyrinth = BodyFactory.CreateCompoundPolygon(World, list, 1f, BodyType.Dynamic);
labyrinth.BodyType = BodyType.Dynamic;
labyrinth.IsStatic = true;
StartAccelerometer();
}
触觉反馈,又称振动反馈
“触觉技术”,或“触觉”,是设备根据游戏中或某种虚拟现实模拟中发生的某些事件,向用户提供的触觉反馈。
虽然这个项目可能不被称为“虚拟现实”,但它确实试图模仿那些里面有小球的旧式玻璃盖木制迷宫盒子。触觉反馈的加入无疑改善了用户体验,这就是我们使用它的原因。但如何在 Windows Phone 中实现呢?
智能手机编程的一个优点是,这些小玩意儿拥有许多台式电脑所没有的功能/设备/传感器。因此,在台式电脑编程中,你针对的是键盘/鼠标/屏幕/扬声器/磁盘,而在智能手机中,你现在可以访问内置的加速度计/振动/陀螺仪/多点触控等等。
我们在 Windows Phone 中实现触觉反馈,只需访问 Microsoft.Devices.VibrateController
类 (http://msdn.microsoft.com/en-us/library/microsoft.devices.vibratecontroller(v=vs.92).aspx),并根据需要启动并保持振动。就这么简单。但是我们何时以及振动设备多久呢?
首先,我们必须处理球与迷宫之间的碰撞。然后,我们应该测量碰撞的影响,最后告诉手机根据影响进行振动。冲击越强烈,振动持续的时间就越长。
至于实现,我们首先使用对 Microsoft.Devices
命名空间的引用
using Microsoft.Devices;
然后我们声明两个常量,一个用于振动的最大间隔(毫秒),另一个用于启动振动所需的最小速度。
private const float MAX_VIBRATION_MILLISECS = 16f;
private const float MIN_DELTAVELOCITY_FOR_VIBRATING_DEVICE = 8f;
接下来,我们实现球实例的 BallCollided
事件
private void InitializeBall()
{
.
.
.
ball.BallCollided += new Labyrinth.Ball.BallCollisionEventHandler(ball_BallCollided);
}
void ball_BallCollided(float deltaVelocity)
{
if (deltaVelocity > MIN_DELTAVELOCITY_FOR_VIBRATING_DEVICE)
{
VibrateController.Default.Start(TimeSpan.FromMilliseconds(
deltaVelocity > MAX_VIBRATION_MILLISECS ? MAX_VIBRATION_MILLISECS : deltaVelocity)
);
}
}
请注意,并非所有碰撞都会引起振动。球的撞击速度必须符合预设的最小值,并且振动时间不能过长。我们施加这些限制是因为否则反馈将不如我们预期的真实。例如,轻微的撞击会使手机一直振动,而大的撞击会产生长时间的振动。这会让用户感到恼火。我发现最好的方法是将振动保持在这两个限制之下。
每秒60帧,感谢 Mango 更新!
如果你在 Windows Phone Mango 更新之前尝试开发游戏,你可能会因为 XNA 只能以每秒 30 帧的速度运行(无论你的硬件有多强大)而感到失望。这在三年前可能是一个不错的规格,但对于当前的智能手机平台来说,30 帧每秒对于动作游戏来说是糟糕的。
幸运的是,Mango 更新消除了这一障碍,让我们能够为我们的硬件启用尽可能高的帧率,目前是 60 fps。但是要从这个功能中受益,您必须首先将您的应用程序配置为以 60 fps 运行。
首先,在你的 Game
类中实现 GraphicsDeviceManager
的 PreparingDeviceSettings
事件
public class LabyrinthGame : Game
{
private GraphicsDeviceManager _graphics;
public LabyrinthGame()
{
.
.
.
_graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(_graphics_PreparingDeviceSettings);
.
.
.
}
然后,在 PreparingDeviceSettings
事件内部,将 PresentationInterval
设置为 PresentInterval.One
void _graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
{
e.GraphicsDeviceInformation.PresentationParameters.PresentationInterval = PresentInterval.One;
}
欲了解更多信息,请参阅“Windows Phone OS 7.1 中更改的 API”文档
API | Windows Phone OS 7.0 行为 | Windows Phone OS 7.1 行为 |
PresentationParameters.PresentationInterval 属性 | Windows Phone OS 7.0 上的 XNA Framework 针对 30 Hz 的刷新率,无论您设置了哪个 PresentationInterval。 | Windows Phone OS 7.1 上的 XNA Framework 支持本机设备刷新率,最高可达 60 Hz。如果您将 PresentationParameters.PresentationInterval 属性设置为 PresentInterval.One,则使用 PresentInterval.One 值,针对本机设备刷新率。 |
就这样。也许在未来的更新中,这将是默认设置,但目前请记住在您的应用程序中启用它。
看起来很酷,但这还不是一个游戏
是的,到目前为止,这还不是一个“迷宫游戏”,这只是“迷宫”。我在这里的目的不是创建一个完整的游戏,只是指明方向。现在你可以创建一个像样的分数控制、“地板上的洞”、陷阱、风扇、线圈、电梯,各种各样的障碍等等。我认为这有很大的潜力成为一个非常棒的游戏。
想法,想法,想法...
从现在开始,您可以使用 Windows Phone 加速度计和/或 Farseer 物理引擎创建新的应用程序。这里有一些您可能正在考虑的想法,例如
- 一个像《愤怒的小鸟》一样的游戏
- 一个像《俄罗斯方块》一样的游戏(使用加速度计定位方块)
- 飞行模拟器
- 第一人称射击游戏
- 台球游戏
- 火箭发射游戏
- 赛车游戏
这只是其中一部分。这些是我在几秒钟内就能想到的清单。这些技术 certainly 具有巨大的潜力。
最终思考
我们很高兴“Mango”更新之后的 Windows Phone SDK 7.1 为我们开发者带来了许多新工具。我相信这将有助于提高 Windows Phone 应用商店中应用程序的数量和质量。
如果您有兴趣为应用商店开发新的、令人兴奋的应用程序,我强烈建议您随时了解最新的工具和技术。首先,阅读“Windows Phone SDK 7.1 中的新功能”文档。此外,下载该页面上的示例,并花时间了解其工作原理。
请记住,这个一岁的应用商店中“只有”36,000多个应用程序,但根据 Gartner Group 的说法,Windows Phone 倾向于在未来几年占据更重要的位置。这为您提供了学习和为该平台创建出色应用程序的绝佳机会。
历史
- 2011-10-31: 初始版本。
- 2011-11-06: 代码重构。新增:触觉反馈。
- 2011-11-08: 新增:“每秒60帧”部分。