敏捷:感官超载





5.00/5 (13投票s)
传感器控制的XNA隧道游戏与VR头部追踪。
引言
本次竞赛提交的作品名为**Celerity: Sensory Overload**。它将经典的隧道游戏类型与传感器控制相结合,最值得一提的是它对3D的全新演绎。本文将展示我们制作这款游戏的过程、我们面临的诸多困难以及我们如何克服它们。
标志
游戏内
菜单用户界面
视频
在YouTube上观看我们的应用快速演示
背景
这款游戏的主要灵感来源于
- 老式隧道游戏(如Super Stardust中的奖励关卡)
- Johnny Chung Lee惊人的VR头部追踪演示
- 说明中的这一挑战:"*只管疯狂地使用传感器——倾斜、摇晃、举起或旋转您的Ultrabook™*"
我怎么能抗拒呢?
时间很紧,我是在竞赛细节公布后才开始的。我邀请了一些才华横溢的朋友帮忙;@Dave_Panic(3D技巧大师)、@BreadPuncher(游戏理论家和图形设计师)以及@PatrickYtting(大师)。
我当时考虑做一些基于陀螺仪的转向和/或视角的东西,Dave建议用网络摄像头实现头部追踪效果。将这个概念与经典的隧道游戏结合起来,我们就有了创新型传感器游戏的基石。你知道玩家在玩某些游戏时会潜意识地倾斜和移动头部,即使这没有任何区别吗?现在它将会有区别!
仅用网络摄像头实现VR头部追踪
在上面Johnny Lee的视频中,他通过红外(IR)LED和红外摄像头检测用户在3D空间中的头部位置。
效果非常棒,但这需要一个特殊的红外传感器,并且用户需要佩戴一个发射红外光的设备。我需要仅使用Intel® Ultrabook™上的传感器,而无需其他设备来创建这种效果。值得庆幸的是,Ultrabook™有一个集成网络摄像头。
应用程序轮询网络摄像头获取帧,并将其传递给计算机视觉(CV)图像处理库EMGUCV。这会返回一个矩形,代表在摄像头视野的边界框内检测到的面部。当用户移动面部时,矩形会相对于边界移动,从而给我一个用户面部的相对X/Y偏移量。3D世界的视图的X和Y可以相对于用户自身的物理位置进行倾斜。请注意,我们需要水平翻转图像,因为网络摄像头的方向与我们相反。
这种效果可以进一步发挥,因为代表用户面部的矩形本身具有大小。当他们靠近网络摄像头时,矩形会变大,因此我们也可以确定相对Z位置。
如果用户开始时头部大致居中且不要太近,会有所帮助,我们在入门菜单设计中鼓励这样做。用户可以在开始前查看他们是否大致“校准”过。
设计用户界面
我希望在用户界面中使用Metro设计原则,因为尽管这是一个桌面游戏而不是Metro应用程序,但我重视一致性,并且认为如果UI与操作系统Windows 8共享约定,它会更直观。
由于我们正在用XNA构建应用程序,我通常的UI矢量方法帮助不大。即使做非常简单的UI元素也需要付出很大的努力,因此我预先尽可能地减少了UI元素,同时保持了直观的导航方式。
以下是UI的原始布局模型。回顾一下,看看哪些保持不变,哪些在开发截止日期和过程中发现的限制的压力下演变,这很有趣。
构建应用
我想把这个应用做成一个XNA应用,因为它足够快,而且编写代码也很容易。我还不懂C++,也没有足够的时间去学习和完成游戏。不幸的是,XNA与Windows 8结合起来有点麻烦,因为它在Visual Studio 2012中不受支持,至少在撰写本文时是这样。这带来了很多麻烦,以至于一些问题威胁到项目的可行性。
在本文中,我将展示我们如何在为包含Win8的AppUp® Intel® Ultrabook™开发XNA时克服这些关键困难
- VS2012不支持XNA项目
- VS2012无法生成可接受的安装程序(InstallShield LE在我的VS上无法工作)
- 安装程序必须包含XNA Redistributable依赖项
- XNA Touch在Windows上被故意禁用(啊!)
- 在我的Windows 7开发环境中无法访问Windows 8传感器
步骤1:项目之痛
在VS2012中建立稳定的XNA项目浪费了大量精力和时间。我至少从头开始了三次,可能更多。值得庆幸的是,您可以从我的许多泪水中受益,因为我可以在下面向您展示一种有效的方法,并且已经从项目创建到最终用户安装都经过了测试。
首先,以下是我尝试过但对我无效的一些方法
XNAML解决方案
感谢Sacha Barber的提示,我研究了XNAML。XNAML是一个非常巧妙的解决方案,解决了“如何将类似WPF的UI元素与XNA结合起来?”的问题。它使用了一个巧妙的技巧,将两个窗口叠在一起,一个XAML窗口,一个XNA窗口。即使对于像这个小型项目,这*本可以*节省数小时的UI构建时间,因为在XNA中实现最简单的交互式UI元素都是一个大麻烦。虽然我让它运行起来了,但我遇到了一个问题,即根据操作系统的设置,两个叠加窗口以不同的DPI运行。我喜欢XNAML的想法,但不幸的是,我无法克服这个问题,因此不得不放弃在这个项目中使用它。
Ibrahim的解决方案
我要感谢Ibrahim的解决方案,它帮助我首次在VS2012中使XNA工作。这对我来说一直运行良好,直到我尝试在Windows 8中运行项目,并遇到了各种空白屏幕等问题。这可能只是我的代码中的一个错误导致它无法工作,但我使用以下方法取得了更大的成功...
步骤2:一个可用的“XNA on VS2012 & Win8”配置
面临的主要两个问题是
- XNA Game Studio 4(通常)无法在Windows 8上安装
- VS2012无法识别XNA项目
首先,进行以下准备工作
- 安装VS2010(您很快就会明白为什么)
- 安装VS2012
为了让XNA Game Studio运行,我使用了Aaron Stebner的解决方案,其核心是
- 安装Games for Windows LIVE(这对我来说不明显,但**是必不可少的一步**)
- 安装Windows Phone SDK 7.1
- 安装XNA Game Studio 4
安装Games for Windows LIVE似乎是允许XNA相关SDK安装而没有错误的诀窍。
在Win 8上安装XNA Game Studio后,Steve Beaugé的解决方案可以在VS2012中启用XNA项目,具体步骤是
- 将VS2010的XNA扩展文件夹复制到VS2012相应的,但缺失的目录中
- 手动编辑
extension.vsixmanifest
文件以包含支持的版本
XNA项目不仅能够很好地打开和运行,而且标准的内容管道也完美地工作。
步骤3:SpriteFont 变通方法
由于上述在Win8上在VS2012中获取XNA的方法,本节和下一节有些多余,但我会把它们留在文章中以备不时之需。本质上,如果您没有XNA内容项目来导入内容,那么您可以使用此方法加载SpriteFonts。
问题在于,虽然您可以访问 SpriteFont
类,但您可能没有 SpriteFont
作为 VS2012 中的项目项,所以这里有一个变通方法。我的解决方案是简单地在 VS**2010** 中创建一个 Windows 应用程序,在那里插入一个 SpriteFont
,构建项目,然后将 bin 目录中的 **.XNB** 文件作为资源复制过来。
然后可以这样加载
// I have a file called "SegoeUILight56.xnb" in a subfolder Fonts within the main Content folder
// Note that unlike with Images, the code doesn't refer to "Content" or ".xnb", as these are assumed
SpriteFont f = contentManager.Load<SpriteFont>(@"Fonts\SegoeUILight56");
步骤4:音乐之声
“歌曲”音频内容文件可以像SpriteFonts一样加载。我使用VS2010导入了一个.MP3内容文件。它在构建时会将其转换为**WMA**并生成一个**XNB**文件。像以前一样从bin目录复制这两个文件,然后像这样加载
Song s = contentManager.Load<Song>(@"Audio\mySongName");
正常播放你的歌曲
MediaPlayer.Play(s);
结果是,我们最终的代码中不需要使用这个。我们有比简单播放MP3更宏伟的想法,而这将需要一个更强大的音频引擎。
音乐乐谱的基本想法是,我们将有几层或几个声道的音乐同时播放。根据游戏中当前的紧张程度,越来越多的层会被淡入,以增加乐谱的动态纹理。
XACT登场。
XACT是微软的“跨平台音频工具”。它是一个用于管理和执行音频的高级库和工具集。许多XNA开发者会很熟悉它,因为XNA和XACT配合得非常好。
我使用“音频创作工具”导入了许多音乐层和音效。在这个工具中,您可以轻松地将声音分组到“类别”中,这些类别在您的游戏代码中可以被视为音频通道。类别可以动态地发送各种参数,例如源位置(哇!)和音量。波形文件本身可以声明为循环,这样在游戏中就可以自动循环。
在项目进行到一半时第一次看到它,我对采用这种不熟悉的技术感到紧张,但我强烈推荐它。多亏了这个非常简单的XACT教程,我在大约一个小时内就开始运行了。
在这个项目中我只触及了它的皮毛,但即使是基本功能,我们也拥有了动态音乐配乐,在我看来,这相当酷!
当然,要实现动态配乐,一部分是与作曲家紧密合作。我必须感谢Patrick的专业精神、一贯的冷静和令人难以置信的快速周转。
这是我的AudioModule
副本,我用于处理游戏中音频逻辑的类。注意Play
(用于一次性SFX)和PlayCue
(用于循环播放的WAV歌曲)之间的区别。
namespace Celerity.Audio
{
using Celerity.Data;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
public class AudioModule
{
bool hasMusicStarted = false;
// XACT Objects
AudioEngine audio;
WaveBank waveBank;
SoundBank soundBank;
// Categories / Layers
AudioCategory music01Category;
AudioCategory music02Category;
AudioCategory music03Category;
AudioCategory music04Category;
AudioCategory ambienceCategory;
AudioCategory sfxCategory;
// Category volumes
float music01CategoryVolume = 1.0f;
float music02CategoryVolume = 1.0f;
float music03CategoryVolume = 1.0f;
float music04CategoryVolume = 1.0f;
float ambienceCategoryVolume = 1.0f;
float sfxCategoryVolume = 1.0f;
public void Initialize()
{
// Init XACT Objects
audio = new AudioEngine("Content\\Audio\\Waves\\CelerityAudio.xgs");
waveBank = new WaveBank(audio, "Content\\Audio\\Waves\\Wave Bank.xwb");
soundBank = new SoundBank(audio, "Content\\Audio\\Waves\\Sound Bank.xsb");
// Init categories / layers
music01Category = audio.GetCategory("MusicLayer01");
music02Category = audio.GetCategory("MusicLayer02");
music03Category = audio.GetCategory("MusicLayer03");
music04Category = audio.GetCategory("MusicLayer04");
ambienceCategory = audio.GetCategory("Ambience");
sfxCategory = audio.GetCategory("SFX");
}
public void Update(GameTime gameTime, float chaosFactor)
{
// Mute multiplier lets me mute all via multiplying channel volumes by 0
float muteMultiplier = GlobalGameStates.MuteState == MuteState.Muted ? 0f : 1f;
// Play the simultaneous music layers if the first time
if (!hasMusicStarted)
{
hasMusicStarted = true;
if (CeleritySettings.PlayMusic)
{
soundBank.PlayCue("Celerity_Music_Layer1");
soundBank.PlayCue("Celerity_Music_Layer2");
soundBank.PlayCue("Celerity_Music_Layer3");
soundBank.PlayCue("Celerity_Music_ShortIntro");
soundBank.PlayCue("Celerity_FX_ambience");
}
}
// Calculate the channel volumes
ambienceCategoryVolume = 1f * muteMultiplier;
music01CategoryVolume = 1f * muteMultiplier;
music02CategoryVolume = chaosFactor > 0.3 ? chaosFactor * muteMultiplier : 0f;
music03CategoryVolume = chaosFactor > 0.5 ? chaosFactor * muteMultiplier : 0f;
music04CategoryVolume = 1f * muteMultiplier;
sfxCategoryVolume = 1f * muteMultiplier;
// Set the channel volumes
ambienceCategory.SetVolume(ambienceCategoryVolume);
music01Category.SetVolume(music01CategoryVolume);
music02Category.SetVolume(music02CategoryVolume);
music03Category.SetVolume(music03CategoryVolume);
music04Category.SetVolume(music04CategoryVolume);
sfxCategory.SetVolume(sfxCategoryVolume);
// Call last
audio.Update();
}
public void PlayClick()
{
soundBank.PlayCue("Celerity_Blip");
}
public void PlayCrash()
{
soundBank.PlayCue("Celerity_Ship_CrashNburn");
}
public void PlaySmartBomb()
{
soundBank.PlayCue("Celerity_Music_SmartBomb");
soundBank.PlayCue("Celerity_Ship_CrashNburn");
}
}
}
步骤5:网络摄像头之痛(和EMGUCV)
在这个项目中,我使用的是EMGUCV 2.4.0版本。我之前使用的是2.4.2版本,但2.4.0版本可以获得更小的DLL文件大小,同时仍保留了我需要的所有功能。多亏了他们简单的教程,从网络摄像头进行基本捕获非常容易,但是当我尝试更具野心的事情时,我遇到了两个相关的问题。
- EMGUCV给我的是
System.Drawing.Bitmap
而不是Texture2D
- 性能变得糟糕透顶
我在网上找到了许多转换器的例子,并尝试了几种,但仍然存在性能问题。我怀疑这是一个单线程运行的问题,但在网上搜索时,我发现许多人警告不要在XNA中使用多线程。最终,我忽略了他们的建议,尝试了这种图像转换方法和我第一次使用Parallel.Invoke()
的组合。突然之间,它就奇迹般地工作了,而我甚至不需要完全弄清楚它的原理!你是不是也很喜欢这种情况发生?
这是我天真但极其有效的解决方案
Parallel.Invoke(() => QueryCamera(elapsedMilliseconds));
只需像这样调用我现有的方法,而不是直接从我的Update方法调用,问题就立即解决了。太棒了!
是的,好吧,我应该阅读一下它到底在做什么,但首先我得做个游戏。
关于头部追踪的最后一点说明;随着时间的推移,所得矩形的外观在 Z 轴方向上有些抖动。这是因为它经常捕获每个面部两个矩形,但有时只捕获一个。这导致尺寸频繁变化。我通过记录最近的样本历史并取滚动平均值来平滑它解决了这个问题。
编辑:网络摄像头之痛仍在继续...
在我的桌面开发机器上一切运行良好,我以大约30fps的速度轮询我简单的第三方网络摄像头,这提供了用户面部的流畅反馈。当我在Ultrabook™上尝试应用程序时遇到了问题。
不幸的是,我的未发布版Ultrabook™没有专用摄像头驱动程序,并且使用EMGUCV,我只能安全地以大约8fps的速度轮询Ultrabook™的集成网络摄像头。再快的话,应用程序就会几乎停滞。
我通过为应用程序的头部追踪添加三种“模式”来解决这个问题:“关闭”、“慢速”和“快速”。我添加了一个警告,说明“快速”模式仅受某些摄像头支持。
8fps的“慢速”模式仍然能让你感受到反应式3D头部追踪效果的些许魅力,特别是当你将设备伸直时,但我确实建议使用第三方摄像头(你可以暂时禁用集成设备)来尝试游戏,看看它能达到什么效果。我希望如果我能获得摄像头的专用驱动程序,性能会大放异彩,但在比赛时间内我无法做到。
我期待的另一种可能性是,外观精美的CV库FaceAPI有望很快提供C# API。我希望这不仅会更快,而且还能比EMGUCV更好地处理偏轴面部的追踪。
步骤6:Alpha问题
我们的2D UI资产渲染遇到了一个有趣的问题。 Thomas在Inkscape中设计图标和类似素材方面做得很好,但当我在XNA中渲染这些文件时,出现了许多alpha伪影。
我花了很长时间尝试各种XNA渲染模式组合,虽然其中一些模式对他的图像产生了所需的效果,但它们会干扰其他资产(如字体)的渲染。
值得庆幸的是,我偶然发现了一个提示,可以使用程序PixelFormer。虽然它主要用于编辑,但我们只是用它来打开Inkscape中的图像,然后重新导出它们,但启用了预乘Alpha。互联网上的各种资料都表明Inkscape已经导出了预乘Alpha,但对我们来说似乎并非如此。通过PixelFormer处理图像对我们来说很有效。
步骤7:隧道尽头的黑暗
Dave一直在努力开发隧道的自定义3D引擎。以下是他对如何完成的几句话...
这款游戏将以一条充满障碍的连续隧道为特色,玩家必须在其中穿梭。为了实现这一点,首先我们需要一条隧道。我决定将问题分解为两个类:一个处理我们实际能看到的隧道部分,即TunnelSection
,另一个处理整个隧道,即Tunnel
。
TunnelSection
将负责以下工作:
- 构建此
Tunnel
部分的顶点。 - 为每个顶点构建纹理坐标。
- 绘制隧道。
Tunnel
将
- 保持
TunnelSection
的位置(我们将移动隧道而不是玩家,以防止坐标变得太大)。 - 定义整个隧道的实际形状或曲率。
今天我们将探讨创建基本的隧道形状。以下是TunnelSection
类的基本轮廓。
由三角形构成的隧道具有以下属性
public float Radius { get; set; } //the radius of the tunnel walls
public int NumSegments { get; set; } //the number of segments in the wall. 5 would make a pentagonal tunnel.
public int TunnelLengthInCells { get; set; } //number of rings of vertices in the tunnel section
private float cellSize; //distance between the rings of vertices
以下是我们创建顶点的方法
private void ConstructVertices()
{
int numVertex = NumSegments * TunnelLengthInCells;
vertices = new VertexPositionColorTexture[numVertex];
float sectionAngle = 2 * (float)Math.PI / NumSegments;
int vertexCounter = 0;
for (int i = 0; i < TunnelLengthInCells; i++)
{
for (int j = 0; j < NumSegments; j++)
{
Matrix rotationMatrix = Matrix.CreateRotationZ(j * sectionAngle);
vertices[vertexCounter].Position = Vector3.Transform(
new Vector3(0.0f, this.Radius, 0.0f), rotationMatrix);
vertices[vertexCounter].Position.Z = -cellSize * i;
vertexCounter++;
}
}
}
我们在这里所做的是,首先创建一个x和z坐标为零、y坐标等于我们期望半径的新顶点。然后,我们将该点围绕原点旋转适当的角度,并移动到下一个点。当我们完成一个完整的圆后,我们重复这个过程,但将点进一步移开,距离由cellSize
定义。
由于我们希望组成隧道的方形部分看起来仍然是方形,我们需要计算顶点环中两点之间的距离。我们可以通过在(0, radius, 0)
处创建一个点,围绕Z轴旋转适当的角度(2 * (float)Math.PI / NumSegments)
,然后测量两点之间的距离来实现,如下所示
private float CalculateSectionSize()
{
Vector3 point1 = new Vector3(0.0f, this.Radius, 0.0f);
Vector3 point2 = Vector3.Transform(point1, Matrix.CreateRotationZ(2 * (float)Math.PI / NumSegments));
return Vector3.Distance(point1, point2);
}
现在我们有了顶点,我们需要填充索引缓冲区。索引缓冲区告诉GPU在哪个三角形中使用哪个顶点。它是一个指向顶点数组中每个顶点索引的列表。
private void ConstructIndices()
{
int indexCount = TunnelLengthInCells * NumSegments * 6;
indices = new short[indexCount];
int indexCounter = 0;
for (int i = 0; i < vertices.GetUpperBound(0) - NumSegments; i += NumSegments)
{
for (int j = 0; j < NumSegments; j++)
{
//triangle 1
indices[indexCounter] = (short)(i + j);
indices[indexCounter + 1] = (short)(i + j + NumSegments);
indices[indexCounter + 2] = (short)(i + j + 1);
if (j == NumSegments - 1) indices[indexCounter + 2] = (short)i;
//triangle 2
if (j < NumSegments - 1)
{
indices[indexCounter + 3] = (short)(i + j + 1);
indices[indexCounter + 4] = (short)(i + j + NumSegments);
indices[indexCounter + 5] = (short)(i + j + NumSegments + 1);
}
else
{
indices[indexCounter + 3] = (short)(i + j + NumSegments);
indices[indexCounter + 4] = (short)(i);
indices[indexCounter + 5] = (short)(i + j + 1);
}
indexCounter += 6;
}
}
}
我们这里正在按照创建顶点时的相同顺序循环遍历它们,同时连接三角形。诀窍是让它们都顺时针缠绕,这样背面剔除就不会使三角形不可见,这需要一些心理可视化来弄清楚在三角形的哪个位置要将哪些顶点连接到你的三角形中。另外,你会注意到当到达顶点环的末尾时有一个特殊情况。如果没有这个特殊情况,我们最终会创建沿隧道螺旋的三角形,并在隧道部分的开始和结束处留下一个三角形的间隙。
这是一个TunnelSection
这里有几个TunnelSection
缝合在一起形成一个Tunnel
,并带有一个漂亮的曲线,以增加效果。
步骤8:占卜
访问传感器并不像我希望的那么简单。在为Windows 8开发时,另一个潜在的意外是传感器命名空间是WinRT独有的,因此桌面应用程序无法使用。值得庆幸的是,英特尔提供了解决方案。诀窍是打开.csproj
文件,只需添加一个目标平台版本号。这允许您添加对“Windows”的引用,否则这将不可用。在这个库中是Windows.Devices.Sensors
命名空间。
我将此与我的XNA项目直接结合时遇到了问题,但是只需将传感器放在一个单独的项目中,所有内容都可以顺利构建和交互。我将任何对WinRT对象的直接引用封装在一个代理类中,这样我的XNA项目就只与简单数据类型交互。
我目前从这个命名空间使用的传感器是
- **倾角计**用于告知我设备向左/向右倾斜了多少以进行转向
- **加速度计**用于给我一个“震动”事件,我用它来触发智能炸弹
读取这些值的代码非常简单
倾斜计
提供的读数是Pitch
、Roll
和Yaw
;这些可以分别看作是X、Y和Z。为了向左和向右倾斜屏幕,我只需将我的代理订阅到Roll
的ReadingChanged
事件,公开一个最后读取值的公共属性,然后每次调用Update
时从XNA读取该属性。
类似这样的东西
public class SensorProxy()
{
const uint inclineDesiredInterval = 16;
Inclinometer incline;
public double InclineY { get; set; }
public SensorProxy()
{
incline = Inclinometer.GetDefault();
if(incline != null)
{
// Set interval
uint minInclineInterval = incline.MinimumReportInterval;
incline.ReportInterval = minInclineInterval;
// Wire Events
incline.ReadingChanged += (s, e) => { InclineY = e.Reading.RollDegrees; };
}
}
}
加速度计
连接加速度计甚至比倾角计更容易,因为它有一个预构建的震动事件。这意味着作为开发人员,我不需要担心校准灵敏度。我只需要监听一个事件并响应它。
由于之前的问题,我希望完全封装所有传感器命名空间对象,因此我将直接事件保持在代理类内部,并在处理程序中引发一个新的普通EventHandler
事件。这是为了防止调用者必须引用AccelerometerShakenEventArgs
对象。
public event EventHandler OnShaken;
Accelerometer accel;
//...
accel = Accelerometer.GetDefault();
if(accel != null)
{
accel.Shaken += (s, e) => { If (OnShaken != null) OnShaken(this, new EventArgs()); };
}
屏幕方向问题
当我第一次测试倾角计转向时,我立即遇到了一个有点讽刺的问题。我向左倾斜屏幕,读数持续了一两秒钟,然后我的屏幕开始翻转。简单的方向传感器检测到角度变化,并假设我想要切换到纵向模式。当XNA以全屏模式运行时,这变得我只能称之为“有点疯狂”。自动缩放的恐怖!
这显然会干扰应用程序的核心概念之一,所以我必须扼杀它。值得庆幸的是,我的SensorProxy
中一个非常简单的方法(在初始化阶段从XNA调用)就能解决问题。
public void LockOrientation()
{
DisplayProperties.AutoRotationProperties = DisplayOrientations.Landscape;
}
步骤9:深度不足(深度线索的影响)
待续。
无深度线索
有深度线索
步骤10:失去联系(我如何在Win8上的XNA中实现触摸功能)
正如Shawn Hargreaves所写,XNA中的触摸功能不幸地被故意禁用在Windows上,仅限于在Windows Phone 7上使用。这意味着尽管Microsoft.Xna.Framework.Input.Touch
命名空间确实包含易于使用的TouchPanel
类,但它根本不起作用。它什么也不做。
幸好,肖恩确实指出了两种使其工作的方法。要在Windows 8中使触摸功能工作,我们实际上转向了Windows 7的触摸实现。
我采用了Shawn的第一个建议,即使用.NET互操作库。在.zip
文件中隐藏着关键的程序集Windows7.Multitouch.dll
。一旦我们将对该库的引用添加到我们的项目中,在XNA中使用它需要稍作绕道,即我们必须向我们的触摸处理类提供应用程序窗口的IntPtr
。这听起来很棘手,但实际上我们只需从我们的主Game
实例中传递以下内容
input = new InputModule(this.Window.Handle);
在接收端,这是我的InputModule
的构造函数,我用它来处理各种形式的输入
using Windows7.Multitouch;
using Windows7.Multitouch.Win32Helper;
public InputModule(IntPtr windowHandle)
{
touchHandler = Factory.CreateHandler<TouchHandler>(windowHandle);
touchHandler.TouchUp += (s, e) =>
{
lastTouchPoint = e.Location;
hasUnprocessedTouch = true;
};
}
虽然IntPtr
类型对某些人来说可能有些吓人,但我们让内置类处理细节。我们只需要处理简单友好的TouchUp
事件(或根据您的需要处理其他事件)。您可能从上面看出,这在技术上并不是一个多点触控实现。我们简单的UI并不真正需要完全的多点触控,因为目前只需要处理单按钮点击,但我可能会在未来的更新中重做它,以便实现拇指控制的武器。
步骤11:碰撞路径(创建障碍并撞击它们)
待续。
步骤12:释放压力
在此次竞赛之前,我只接触过非常简单的安装程序和代码签名证书。我使用ClickOnce创建安装程序,这几乎和点击“发布”一样简单,而代码签名证书则由其他人安排。
竞赛要求安装程序是一个文件,并用我自己的代码签名证书进行签名。这排除了ClickOnce,因为它会创建一整个文件夹的安装相关文件,也意味着我必须去获取一个证书。
获取代码签名证书
与许多其他竞争者一样,我遇到了默认供应商Comodo的一些问题。我不会在这篇文章中浪费太多篇幅,但足以说明我非常失望。沟通标准很糟糕,我被搞得团团转。
谢天谢地,英特尔介入并推荐我转向赛门铁克。这在服务上是一个天壤之别。我按照他们更清晰、更合理的程序完成后,迅速收到了我的证书。唯一值得注意的是,我需要找一位“公证人”。这在不同文化中似乎意味着不同的东西,但在英格兰,律师只需支付少量费用就能很好地完成任务。
创建安装程序
我很早就知道安装程序会很麻烦。我们的项目是XNA,除非安装程序能提示用户下载XNA Game Studio 4可再发行组件,否则它无法在系统上安装和运行。由于不熟悉创建安装程序,我怀疑我可能需要某种形式的自定义脚本。
我考虑过WiX,但它看起来有点棘手,难以快速上手。我尝试使用InstallShield LE,但它与我的VS2012 Professional版本不兼容,只在我的VS2010副本上工作。遗憾的是,VS2010无法构建VS2012项目,因此InstallShield被排除了。
我最终回到了我之前读到的一个建议,它是一个标题平淡无奇的高级安装程序。我尝试了它,开始对找到一种无需脚本即可通过单个文件安装XNA游戏的方法失去希望,但这被证明是完美的。对我来说,主要优点是
- 理解并实现对XNA Game Studio 4 Redistributable的依赖
- 拥有易于使用的图形用户界面,对初学者来说很直观
- 指向构建目录,因此我无需每次都进行配置
- 自动为我签署安装程序
我确实不得不为XNA部分购买专业版,但至少我现在可以轻松地发布Celerity的更新了。
AppUp® 提交
我们的提交过程大部分都很顺利,但由于忘记在元数据中添加商标符号而遭到了一次拒绝。哎呀!幸运的是,二进制验证一次性通过,这是我最担心的地方。
项目回顾
我们做得好的地方
- 核心理念非常强大
- 范围控制(许多我们很想添加但会超出截止日期的东西)
- 对我们的能力和截止日期而言,技术难度适中
- 坚持不懈(例如,将XNA强行整合到Win8上的XS2012中)
我们做得不好的地方
- 项目工作流程(Git、SkyDrive和复制粘贴备份的混合体)
- 代码/生活平衡
- 在最后一刻的恐慌中浪费钱购买了高级安装程序(应该早点研究WiX)
项目趣闻
项目最后几天遇到的技术故障
- 尝试安装Windows 8时我的DVD驱动器坏了
- 我有两个U盘坏了
- Ultrabook触摸屏暂时坏了,让我以为我的代码有问题(只需重启)
- 我的互联网中断了一小时
- 我的互联网速度在24小时内降至正常速度的1/10
熬夜到最晚
- 凌晨4点(再也不了!别这样,各位!)
项目结束时我的桌面状态
我通常非常整洁,所以对我来说,这就像一场噩梦。几乎每个文件或文件夹都有相同的前缀。这不利于快速找到东西。
制作成本
- **£3.26** - 玩家飞船3D模型
- **£10** - 发送给Comodo的传真费用
- **£10** - 律师在代码签名证书申请过程中验证我身份的费用
- **£227.12** - 高级安装程序
...加上一点电话费。
约等于 **£250** / **$400**
Ultrabook™专用功能
我故意将V1版本应用程序保持尽可能简单,以确保按时完成。我们将在未来的更新中添加更多功能并丰富现有功能。重点是做得好,而不是急于加入许多功能并草率实施。以下是基本的“V1”传感器功能集
- 基于网络摄像头的VR头部追踪——将Ultrabook™平举到手臂长度,效果最佳
- 基于倾角计的转向——简单、直观/自然
- 基于加速度计(摇晃激活)的智能炸弹和菜单导航——只需摇晃即可开始游戏,无需调整握姿;游戏内摇晃可发射3枚智能炸弹之一
- 基本触摸式用户界面——这在Win8 XNA项目中实际上是一项成就!
为了在Ultrabook™上获得最佳效果,请尝试以下操作:
- 不要把脸凑得太近 / 伸直手臂对我来说似乎恰到好处
- 摄像机清晰视野中只有一张人脸
- 除非您有专用驱动程序,否则请使用“慢速”或“关闭”摄像头跟踪
潜在的未来功能
以下是我们正在考虑在未来更新中添加的一些功能
- **多点触控拇指武器控制** - 为什么躲避障碍物,而不是将其炸毁,对吧?这将是屏幕上的两个区域,它们独立响应拇指点击。甚至可以为不同的武器设置不同的手势,这取决于旋转Ultrabook时拇指手势的可行性。
- **环境光传感器辅助对比度调整** - 我想保持游戏的外观相似,但例如,灰色背景上的白色字体可以变成黑色背景上的白色字体。
- 程式化的爆炸效果——我们没有足够的时间完成这项工作,但它在优先列表中非常靠前。
- **附加游戏机制** - 道具、替代游戏模式等。
源代码
比赛结束后,我将在此处发布所有源代码。
**注意:**为了打开和构建项目,您需要按照本文中提到的一些步骤进行操作,例如安装组件和添加VS扩展。这意味着您需要Windows 8和VS2012。
项目新闻
Celerity已在此处发布,在Intel AppUp®商店及时供竞赛评委评审。
我们的首次更新已完成约三分之二,将包含基本的游戏平衡调整和视觉爆炸效果。如果您有任何特定的功能请求,请在评论中发布,我们将考虑将其纳入。
欢迎收藏本文或关注我的Twitter动态,我将持续更新未来进展。
竞赛类别
此应用程序旨在作为桌面游戏类别提交。
历史
- V1 初稿
- V2 微小修正与进度更新
- V3 修正了拼写错误和少量更新
- V4 增加了隧道进展和操作指南
- V5 提交后的重大更新,大部分章节已更新
- V6 修复了损坏的代码示例并添加了“释放压力”部分
- V7 添加了“失联”,润色了音频部分 + 小幅修复
- V8 添加了应用视频演示链接、AppUp商店链接并更新了新闻部分