65.9K
CodeProject 正在变化。 阅读更多。
Home

HoloLens 开发 UWP 入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2016年6月16日

CPOL

33分钟阅读

viewsIcon

32138

downloadIcon

277

在本文中,我将介绍 HoloLens 开发环境的设置,应用程序与其他 UWP 平台之间的兼容性,并介绍如何使用 Unity 创建 3D 应用程序。

引言

下载 HololensIntroduction.zip

如果您和我一样,还没有机会购买自己的 HoloLens。在我写这篇文章的时候,我正处于购买 HoloLens 的邀请的第五波。虽然我自己还没有头戴设备,但 Razorfish 团队的 Emerging Experiences 团队给了我使用一台设备进行一段时间高质量体验的机会。这篇介绍性文章代码量不多,但仍是入门性的。HoloLens 是一种全新的体验,与其他 Windows 10 设备截然不同,这种新的体验值得关注。在这篇文章中,我只想部署几个“Hello World”类型的应用程序到 HoloLens,并在可能的情况下将相同的程序部署到其他设备上。

它是什么?

微软对 HoloLens 的描述之一是“Windows 10 是第一个支持全息计算的平台,它提供的 API 能够在一个独立的设备上实现视线、手势、语音和环境理解”。另一个描述是“Microsoft HoloLens 是第一个完全独立的、全息计算机,它使您能够在现实世界中与高清全息图像进行交互”。我认为有必要对词语的使用发表评论。词语的用法有时在“规定性观点”和“描述性观点”之间存在分歧。规定性观点认为词语必须以特定的方式使用,不符合用法就是错误的。描述性观点认为词语的意思由人们的意图决定。只要说话者和听众就预期的用法达成一致,宇宙就是和谐的。我并不完全属于其中一种,因为我可能会根据上下文进行切换。我倾向于对“全息图”(hologram)这个词采取规定性观点。通过 HoloLens 看到的并不是传统意义上的全息图;(目前)还不是通过光场重建实现的图像。尽管 HoloLens 与全息术无关,但也有理由支持这里的用法。我在最近写的一些内容中提到了理由。话虽如此,我将不再反对使用“全息图”这个词,而是为了能够与他人就 HoloLens 进行沟通而采取描述性观点。对于那些对语言持规定性观点的人,请查看 Ammon Shea 的《Bad English》一书。这本书是许多关于词语用法曾经令人反感但现在已成为日常语言一部分的书籍之一。

stereoscopic imaging history

更广泛地说,HoloLens 是一个混合现实或增强现实平台。它能够将应用程序窗口或 3D 对象叠加到用户所见的真实世界中。这与虚拟现实(VR)不同,VR 会阻挡用户对真实世界的视野。HoloLens 也是一个基于 Windows 10 的平台。针对 Windows 10 通用 Windows 平台 (UWP) 的应用程序通常不仅可以在 HoloLens 上运行,还可以在 Windows 10 桌面/笔记本电脑、Windows 10 移动版、Xbox One 或支持的 IoT 平台(如 Raspberry Pi)上运行。目前,这种互操作性并非没有一些限制。例如,Xbox One 开发者预览版支持基于 HTTP/WebSocket 的网络调用,但不支持 TCP/UDP 调用。与 SMS 相关的调用在不具备 SMS 功能的平台上将无法工作。这些设备往往具有不同的主要交互模式:语音、游戏控制器、触摸键盘、物理键盘、手势和视线。我无法制作一张有意义的图表来说明哪些设备具有哪些交互形式,因为在许多设备类型上它们是可选的;一台计算机可能连接了键盘,也可能没有。HoloLens 支持通过蓝牙连接键盘和鼠标,但我通常不指望它们会连接到设备上。微软已经做了一些工作来抽象化这些设备类型之间的差异。但这些差异是需要注意的。

关于交互

作为一个 Windows 10 设备,HoloLens 让我想起了移动版 Windows 设备(如 Windows RT 平板电脑或手机)的体验。发布时,一次只能运行一个应用程序。然而,在 2016 年 5 月更新后,最多可以同时运行 3 个应用程序。它也是一个单用户设备。一次只能有一个活动账户。更改设备使用的活动账户需要擦除其内存。HoloLens 上的用户输入通常通过手势、注视元素和语音输入来完成。我第一次使用 HoloLens 时,并没有充分利用语音输入。文本可以通过注视虚拟键盘上的单个按键并选择它们来输入;这是一种打字方式,对于大量文本输入来说可能会有点累。但不必以这种方式输入文本。听写效果要好得多。通过将头部转向将视线对准元素,然后在空中做出点击手势,就可以选择一个元素。HoloLens 还附带一个配件,上面有一个按钮,可以点击代替空中点击手势。对于允许滚动的元素

设置开发环境

要进行 HoloLens 开发,您需要一台运行 Windows 10 的计算机和 Visual Studio Update 2。您需要安装 HoloLens 启用的 Unity 版本之一(撰写本文时为 beta 版)以及 HoloLens SDK。进行 HoloLens 开发的计算机的内存要求取决于您是否拥有物理硬件。有物理硬件的情况下,8GB 内存的电脑就足够了。尝试使用模拟器时,8GB 内存不够。12GB 内存还可以。我建议至少 16GB。如果您使用模拟器,您还需要使用 Windows 10 的专业版或企业版。否则,您将无法使用模拟器。开发 2D XAML 应用程序时,可以在本地计算机上进行测试,无需模拟。如果您想创建沉浸式体验(微软称之为“全息应用程序”,具有“体积视图”),您需要模拟器或 HoloLens。

与常规的 departure

如果您已经为 Windows 平台开发过,但对 Windows 应用商店应用(WinRT 或 Windows 10 通用应用)的开发经验不多,那么在非 WSA 环境下执行任务的方式在 WSA 中可能不适用。在 WSA 之前,潜在的“长时间运行”调用(即任何可能阻塞 UI 线程导致 UI 响应迟缓的调用)即使不是最佳选择,仍然可以在 UI 线程上调用。对于希望应用程序更具响应性的开发人员,可以使用异步调用。通常,长时间运行的调用涉及文件或网络调用。在 WSA 之前,异步处理其中一些调用是可选的。WSA 将强制实施更具响应性的实现。除了写入设备文件外,默认情况下没有太多机制可以阻止 WSA 之前的应用程序写入文件系统的任意位置。系统上的文件权限将阻止对某些位置的读取或写入操作成功,但 API 层面并没有阻止尝试。这一点随着 WSA 的推出而改变。应用程序拥有自己的独立存储(这个概念以前就存在,但开发人员可以选择不使用它)。应用程序无法访问彼此的应用程序存储。还有一些可以写入数据的通用区域,并提供了一种将信息导出到用户可以访问或与其他应用程序共享的位置的方法。当使用这种类型的存储时,应用程序无法访问保存数据的实际绝对路径。如果您还没有以这种方式使用 WSA 或独立存储来处理文件,您将需要一些时间来适应。

为了说明区别,下面的两个代码块分别来自一个写入“Hello World”到文件的桌面 .Net 应用程序和一个写入相同文本到文件的 UWP 应用程序。对于这两个代码块,我都尽量只使用最少的调用来完成工作。

//writing to a files on a .Net desktop application
using(StreamWriter sw = new StreamWriter("readme.txt"))
{
	sw.WriteLine("Hello World");
}
//writing to application storage in a UWP application
StorageFolder storageFolder =  ApplicationData.Current.LocalFolder;
StorageFile storageFile = await storageFolder.CreateFileAsync("readme.txt", CreationCollisionOption.ReplaceExisting);
using (Stream stream = await storageFile.OpenStreamForWriteAsync())
{
    using (StreamWriter sr = new StreamWriter(stream))
        sr.Write("Hello World");
}

虽然第二个代码块在写入文件时调用次数更多,但它在安全性和应用程序响应性方面具有优势。在安全性方面,对文件保存的完整路径的访问可能会间接向应用程序泄露个人的身份信息。对于写入数据量如此之小的情况,这两种文件写入方式在响应性方面的影响差异可能无法辨别。随着要写入的数据量或要执行的操作变得更加复杂,这两种文件写入方法的最终用户体验差异将会扩大。

应用程序视图类型

在 HoloLens 上运行的应用程序可以分为具有二维视图或全息视图。使用 2D 视图的应用程序通常可以在其他 UWP 实现上运行。它们是我们常说的普通应用程序。在 HoloLens 上运行时,2D 视图可以放置在空间中并保持其位置。如果文本输入字段获得焦点,虚拟键盘将在 HoloLens 上显示。虽然 HoloLens 支持鼠标,但用户通常不会使用鼠标,而是通过注视要选择的元素并进行选择手势来控制项目选择。全息应用程序会完全控制 HoloLens 的视野。运行时不会显示其他应用程序。

构建我们的第一个 UWP 应用程序

第一个应用程序将是极其简单的。我们将构建一个文本编辑器。虽然这是一个简单的应用程序,但如果您没有进行任何 UWP 开发,它可能会向您介绍一些尚未熟悉的概念。作为一项可选练习,您可以尝试将此应用程序部署到 UWP 系列的其他设备类型。启动 Visual Studio 2015 Update 2 并创建一个新项目。选择“空白应用(通用应用)”,并将项目命名为 *TextEditor*。您将看到一个对话框,允许您选择应用程序支持的最大和最小构建号。为了跨设备兼容性(因为您的设备可能具有不同的构建版本),请将最小支持值保留为可用最低值。在项目中创建一个名为 ViewModels 的新文件夹。目前,将为文件名和文档内容这两个属性添加到类中。这些属性将通过数据绑定公开。INotifyPropertyChanged 将为此实现。视图模型实现如下所示。

using System;
using System.ComponentModel;
using System.Linq.Expressions;
namespace TextEditor.ViewModels


{
    public class MainViewModel : INotifyPropertyChanged
    {

        string _fileName;
        public string FileName
        {
            get { return _fileName;  }
            set
            {
                if(_fileName!=value)
                {
                    _fileName = value;
                    OnPropertyChanged(() => FileName);
                }
            }
        }

        string _text;
        public string Text
        {
            get { return _text;  }
            set
            {
                if(_text != value )
                {
                    _text = value;
                    OnPropertyChanged(() => Text);
                }
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
        {
            OnPropertyChanged(((MemberExpression)expression.Body).Member.Name);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

在项目中也创建一个名为 Views 的新文件夹。编辑器视图将包含一个文本编辑框和一个保存按钮。在 Views 文件夹内,创建一个名为 EditorView.xaml 的新 UserControl。

<UserControl
    x:Class="TextEditor.Views.EditorView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TextEditor.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Button Content="Save" />
        <TextBox Grid.Row="1" TextWrapping="Wrap"  AcceptsReturn="True" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Grid.Row="2" >
            <Run Text="Character Count:" />
            <Run Text="{Binding CharacterCount}" />
        </TextBlock>

    </Grid>
</UserControl>

如果您按下调试按钮,它将立即在您的机器上运行。在我们尝试在 HoloLens 上运行之前,让我们添加保存和加载功能。必须使用 FileOpenPickerSaveFilePicker 来获取文件流的引用。程序能够访问的文件信息只有文件名,而没有其路径。文件选择器需要应用程序想要处理的文件扩展名列表。调用时,文件选择器将显示,用户选择一个文件,然后文件流将返回给我们的程序。

public MainViewModel()
{
    FileTypeList = new Dictionary<string, IList<string>>();
    FileTypeList.Add("Text Document", new List<string>() { ".txt", ".text" });
    FileTypeList.Add("HTML Document", new List<string>() { ".htm", ".html" });
}

async void  SaveFile()
{            
    FileSavePicker fileSavePicker = new FileSavePicker();
    foreach(string key in FileTypeList.Keys)
    {
        fileSavePicker.FileTypeChoices.Add(key, FileTypeList[key]);
    }
    StorageFile file = await fileSavePicker.PickSaveFileAsync();
    if(file != null)
    {
        CachedFileManager.DeferUpdates(file);
        await FileIO.WriteTextAsync(file, Text);
        FileUpdateStatus status = await CachedFileManager.CompleteUpdatesAsync(file);
        FileName = file.Name;
    }
}

async void OpenFile()
{
    FileOpenPicker fileOpenPicker = new FileOpenPicker();
    foreach (string key in FileTypeList.Keys)
    {
        foreach(string extension in FileTypeList[key])
        {
            fileOpenPicker.FileTypeFilter.Add(extension);
        }
    }
    StorageFile file = await fileOpenPicker.PickSingleFileAsync();
    if (file != null)
    {               
        Text = await FileIO.ReadTextAsync(file);
        FileName = file.Name;
    }
}

还需要将界面中的按钮绑定到命令,以便可以在用户界面中调用它们。需要编辑 EditorView.xaml 的 XAML。

 <Button Content="Save" HorizontalAlignment="Stretch" Command="{Binding SaveFileCommand}" />
<Button Content="Load" Grid.Column="1" HorizontalAlignment="Stretch" Command="{Binding OpenFileCommand}" />

在本地计算机上运行程序。添加几行并保存文件。如果您将文件保存到 OneDrive 帐户,则可以在其他设备上访问它。虽然所有这些设备都允许应用程序运行,正如我们将看到的,其中一些设备可能尚未安装文件选择服务。虽然“一次编写,到处运行”的梦想尚未实现。但它已经接近了。不同机器之间的差异在于功能和服务的存在或缺失。可以跨设备构建共享更多源代码和逻辑的应用程序。

跨设备部署

本节的其余大部分内容是如何部署到支持 UWP 应用程序的各种设备类型。如果您对其他设备不感兴趣,可以跳到HoloLens 部署部分。您可以将程序部署到您正在编程的设备,部署到通过 USB 线连接到开发计算机的设备,或者通过网络连接到设备。您还可以定位不同的处理器架构。这两种都需要指定。您的工具栏中有一个面板,允许您选择“调试”或“发布”版本,是针对“x86”、“x64”还是“ARM”处理器。运行按钮中还有一个下拉列表,可用于选择您打算的部署目标。

处理器选择下拉列表和运行按钮

 

  • 本地计算机 - 在您正在开发的计算机上运行程序
  • 模拟器 -
  • 远程计算机 - 连接到同一网络的计算机。需要其 IP 地址。
  • 设备 - 通过 USB 线连接到您的本地计算机的设备。可以是 Windows 10 移动设备或 HoloLens。
  • HoloLens 模拟器 - HoloLens 的软件模拟器。
  • Mobile Emulator xxxx - Windows 10 移动设备的模拟器。名称后面的数字标识了模拟器支持的 Windows 版本。

 

如果您选择“远程计算机”作为目标,则需要设置目标设备的 IP 地址或名称。为此,请右键单击解决方案资源管理器中的项目,然后选择“属性”。在“调试”选项卡中,您可以设置“远程计算机”地址或名称。只有当选择“远程计算机”作为部署目标时,此选项才可用。

Windows 10 移动版部署

如果您有 Windows 10 移动设备,也可以通过一些设置更改将其部署到手机。对于部署设置的下拉列表,将处理器架构从 `x86` 更改为 `ARM`。如果您以前从未部署到 Windows 10 移动设备,则需要更改一项设置。在设备上,导航到“设置”、“更新和安全”和“开发者选项”。在“使用开发者功能”下,选择“开发者模式”。使用 USB 线将手机连接到计算机并运行应用程序。它将部署到手机并运行。

Windows 10 Mobile  Deployment Settings

Xbox One 部署

在 Xbox One 应用商店中有一个名为“Dev Mode Activation”的应用程序。下载并运行该应用程序。它将使您的 Xbox One 为开发部署做好准备。此应用程序需要 30GB 的可用硬盘空间。通过该应用程序启用开发模式后,Xbox One 将重启。最好将其视为从不同分区运行的另一个操作系统实例。开发模式下可用的 Xbox One 内容与主分区上的内容是隔离的。Xbox 重启后,选择“Dev Mode Home”。(注意:此后我连接了一个 USB 键盘到 Xbox,因为用真正的键盘编辑一些文本设置比屏幕键盘更容易)。记下 Xbox 的 IP 地址。它将显示在此屏幕上。在 Visual Studio 中,右键单击您的项目并选择“属性”。在“调试”选项卡中,确保选择了“远程计算机”,然后在其中输入您的 Xbox 的 IP 地址。将“平台”设置更改为 `x86`。当您尝试运行程序时,将收到一条提示,告知您需要一个代码来将设备与 Visual Studio 配对。从 DEV HOME 控制台选择“Pair with Visual Studio”获取代码。输入 Xbox 屏幕上显示的密码。应用程序将部署并运行。但是,您会注意到 FileOpenPickerFileSavePicker 没有响应。Xbox One 尚不支持文件选择器。如果计划支持这些平台,您需要查看尚未支持的 API(参见此处),并使您的应用程序优雅退出或使用替代功能。

Windows IOT 核心部署

撰写本文时,有几种嵌入式设备可以运行 Windows IOT 核心。这包括 Raspberry Pi 2 和 3、Minnowboard Max 以及其他一些开发板。这些开发板的设置说明通常包括将内存卡插入计算机,并运行 Windows IoT Dashboard。它有一个设置新设备的选项。选择您的设备类型和内存卡,然后选择“下载并安装”。

Windows IOT Dashboard

内存卡写入完成后,将其插入设备,连接网线,并让其启动。请注意,设备首次启动可能需要很长时间。设备启动后,除了其他信息外,它还应显示其 IP 地址。部署需要此信息。您也可以从 Windows IOT Dashboard 获取设备的 IP 地址。

Windows IOT Device List

部署应用程序将与 Xbox One 类似。右键单击项目,选择“属性”,然后选择“调试”选项卡。将“目标设备”更改为“远程计算机”,在“远程计算机”设置中输入 Windows IOT 设备的 IP 地址,并将“身份验证模式”设置为“通用(未加密协议)”。运行应用程序,它将部署并运行到 Windows IoT 设备。您将能够成功编辑文本,但与 Xbox One 一样,SaveFilePicker 和 OpenFilePicker 似乎不起作用。解决方案是首先检测运行程序的计算机是否可以访问文件选择器,并在操作系统不提供时显示自己的文件选择器。我将在未来的帖子中讨论如何做到这一点。

Windows IOT

我的目标为 Raspberry Pi 的部署设置

部署到 HoloLens

部署到 HoloLens 与部署到其他设备非常相似。您需要获取 HoloLens 的 IP 地址。如果打开“设置”应用程序并选择“网络和 Internet”,则在选择“高级选项”时将显示 IP 地址。在项目属性中更新 IP 地址。导航回“设置”应用程序的主页,选择“更新和安全”。选择“开发者选项”选项卡,然后打开“开发者模式”。现在您可以运行程序了。Visual Studio 将部署设备并开始运行。最初,您会注意到 FileOpenPickerFileSavePicker 不起作用,但会弹出一个对话框,告知您通过安装额外的程序它们将可用。打开设备的应用程序商店,搜索并安装 OneDrive。安装后,如果您返回到应用程序,选择器将响应。

 

The Editor running in the Emulator

构建具有全息视图的应用程序

我演示的第一个应用程序没有利用 HoloLens 将渲染对象叠加到现实世界的能力。下一个应用程序将做到这一点。仍然需要 Visual Studio 2015 Update 2,但我们将从 Unity 开始。撰写本文时,用于 HoloLens 开发的 Unity 版本仍处于 beta 阶段。如果您还没有安装,您需要从 Unity 网站获取最新版本。您需要在此站点安装 Unity 编辑器和 UWP 运行时。安装完成后,启动 Unity。如果您已有 Unity 帐户,则需要登录。如果没有,创建帐户很容易且免费。登录后,选择创建新项目的选项。

New Unity Project Dialog

确保启用了 3D 选项,然后选择“创建项目”。

在接下来的部分中,我将引用 Unity 编辑器中的区域名称。下图将帮助您识别这些区域的位置。请注意,此布局假定您使用的是默认布局,并且未更改 UI 元素的位置。

  • Hierarchy(层级) - 显示构成场景的对象。您可以将场景视为游戏关卡。
  • Project(项目) - 显示您正在工作的项目所包含的文件夹。选择一个文件夹将显示其中的资源。
  • Assets(资源) - 如脚本、图形和场景等构成您项目的独立资源。
  • Inspector(检查器) - 显示所选对象属性。可以在其他面板中选择任何对象,以在此窗口中查看更多详细信息和设置。
  • Scene(场景) - 当前正在操作的场景或预制对象的布局。

The names of the panels in Unity

项目创建后,Unity 中需要更改一些设置以适应 HoloLens。打开“编辑”菜单,选择“项目设置”,然后选择“玩家”。玩家设置将出现在右侧窗格中。确保选中“虚拟现实”复选框,并且“Windows Holographic”显示为支持的头戴设备。如果看不到“Windows Holographic”,请单击其下方的加号按钮进行添加。摄像机的设置也需要更新。在场景布局(窗口左侧区域)中,单击 Main Camera。摄像机属性将出现在右侧。在“变换”下,将 X、Y 和 Z 设置都设为零。在“摄像机”下,将“清除标志”设置为“纯色”,并将“背景颜色”设置为黑色。这将在没有对象的地方导致 HoloLens 渲染黑色。在 HoloLens 上,黑色等同于透明。

如果我们现在运行应用程序,将看不到任何东西。让我们在世界空间中放置一些对象,以便我们有东西可以看到。目前,我们将使用 Unity 中内置的基本几何对象。在 Hierarchy 视图中,右键单击空白区域,选择“3D 对象”,然后选择“立方体”。单击场景层级中的立方体以编辑其属性。将其位置设置为 0.0, 0.5, 4。这将把立方体放在摄像机前面,位置看起来像是 4 米远。为了确保其位置正确,请单击

Preview within Unity

如果一切正常,下一步是从 Unity 将项目导出到 Visual Studio。打开 File 菜单并选择 Build Settings。从平台列表中选择 Windows Store。将 SDK 版本设置为“Universal 10”。选择“Build”。如果您以前处理过 Unity 项目,Unity 通常会在此阶段构建一个可运行的应用程序。对于此项目,这仍然会创建一个 Visual Studio 项目。该项目将从 Visual Studio 部署。选择“Build”时,将提示您输入要写入项目的文件夹。我通常会在项目文件夹的子文件夹中创建一个名为 App 的文件夹并选择它。Unity 将构建项目,完成后会打开文件资源管理器显示包含新项目的文件夹。打开文件夹找到项目的解决方案文件并打开它。您可以使用之前部署到 HoloLens 的相同步骤来部署到 HoloLens。当应用程序开始运行时,您应该会看到立方体漂浮在您面前。您可以四处走动观察立方体。HoloLens 将负责跟踪您的移动并根据您的移动调整摄像机。这在我们没有编写任何代码的情况下就可以工作。让我们尝试一些不同的东西。

Unity、GameObjects 和代码

Unity 中的对象由多个组件构成。构成对象的组件在右侧的层级结构中显示,当从左侧的场景中选择一个对象时。我们将创建一个更具交互性的项目。它将能够识别点击手势、语音命令,并且视线将在交互中发挥作用。我们将制作一个简单的游戏,其中有飞机在空中飞行,我们可以瞄准并射击它们。在 Unity 中,打开 File 菜单并选择 New Project。像以前一样启用 UWP 应用程序的 VR 支持。

我们需要创建两个视觉对象来表示导弹和飞机。理想情况下,这些将使用导入到 Unity 的外部资产制作。我将使用 Unity 中内置的几何对象创建占位符。稍后我们可以替换它们。让我们先制作导弹。在右侧的层级面板中,右键单击空白区域,选择“Create Empty”。这个空对象将作为构成导弹的所有对象的共同父级。在右侧检查器中选择新创建的游戏对象,将其重命名为 Missile。我们将利用 Unity 内置的物理引擎。在 Inspector 面板中,单击“Add Component”并选择“Physics”和“Rigid Body”。确保其位置为 (0,0,0),旋转角度为零,X 轴、Y 轴和 Z 轴的比例都设置为 0。右键单击新重命名的空游戏对象,选择“3D Object”,然后选择“Cylinder”。这将构成导弹的主体。确保圆柱体的位置为 (0,0,0)。只将 X 轴旋转 90 度。将 Y 轴的比例设置为 2。右键单击 Missile 并创建一个新的球体。将其位置设置为 (0,0,2)。这将是导弹的头部。在导弹内创建一个立方体。将其 Z 轴位置设置为 -1.5。将其 X 轴比例设置为 1.5,Y 轴比例设置为 0.1,Z 轴比例保持为 1。创建另一个立方体并将其 Z 轴位置设置为 -1.5。X 轴比例需要设置为 0.1,Y 轴比例设置为 1.5。此构造的最终结果如下图所示。

 

对飞机重复相同的过程;创建一个空对象并向其添加 3D 对象以近似飞机。我已将构成飞机的子对象及其设置列在下表。

形状 职位 旋转 Scale
X Y Z X Y Z X Y Z
圆柱体 0 0 0 90 0 0 1 2.5 1
立方体 0 0 0 0 0 0 5 0.1 1
立方体 0 0 -2 0 0 0 2.5 0.1 1
立方体 0 0.75 -1.8 0 0 0 0.1 0.7 1
球体 0 0 -2.5 0 0 0 1 1 1
球体 0 0 2.5 90 0 0 1 1 1

如果您按照上述修改创建了对象,您应该会看到类似以下的内容。

The constructed plane model

添加代码

在屏幕底部的“Project”面板中,单击“Assets”文件夹。在该文件夹右侧的窗格中,右键单击空白区域,选择“Create”,然后选择“C# Script”。将此新脚本命名为“PlaneBehaviour”。双击该脚本以在编辑器中打开它。您在 Unity 中创建的大多数类都将继承自基类 MonoBehaviour。虽然您可能在 Unity 中声明派生类的自定义实现时使用过方法重写,但在 Unity 中,此任务通过简单地具有同名方法并使用参数来完成。它不需要实际使用 override 关键字。您类中任何需要的初始化都将在 Start() 方法中完成。名为 Update() 的方法将以固定间隔调用。我们的大部分游戏逻辑都将在此处。新类中将已经存在空的 Start()Update()。还可以添加其他方法。

PlaneBehaviour 类将跟踪飞机是正在飞行还是正在坠毁,它已经飞行或坠毁了多长时间,以及如果一定时间过去就将其移除。

using UnityEngine;
using System;
using System.Collections;

public class PlaneBehaviour : MonoBehaviour {

	public enum PlaneState
	{
		Flying,
		Crashing
	}

	public PlaneState State; 
	public double CrashTime = 5.5d;
	public float TimeToLive = 30;

	// Use this for initialization
	void Start () {
	}
	
	// Update is called once per frame
	void Update () {
		if (State == PlaneState.Crashing) {
			CrashTime -= Time.deltaTime;	
			if (CrashTime < 0)
				Destroy (this.gameObject);	
		} else {
			TimeToLive -= Time.deltaTime;
			if (TimeToLive <= 0)
				Destroy (this.gameObject);
		}
	}
}

对于我们的导弹,我们将有类似的需求。创建另一个名为 MissleBehaviour 的 C# 脚本。如果导弹撞上飞机,它将受到重力的影响。如果碰巧撞上另一架飞机,它也会导致其坠毁。Unity 将负责检测碰撞。我们只需要决定如何对它们做出反应。除了 Start()Update() 方法外,我们还将有一个 OnCollisionEnter(Collision col) 方法。此方法接收的参数是关于与我们的导弹碰撞的对象的信息。我们将检查其他对象是否为飞机。如果是,则重力将允许影响导弹的移动。要做到这一点,我们将获取对 GameObject 的引用

using UnityEngine;
using System.Collections;

public class MissleBehavour : MonoBehaviour {
	public float Lifetime = 5;

	// Use this for initialization
	void Start () {
	}
	
	// Update is called once per frame
	void Update () {
		Lifetime -= Time.deltaTime;
		if (Lifetime < 0)
			Destroy (this.gameObject);	
	}

	void OnCollisionEnter(Collision col)
	{
		var plane = col.gameObject.GetComponent<PlaneBehaviour> ();
		if (plane != null) {
			var rigidBody = GetComponent<Rigidbody> ();
			rigidBody.useGravity = true;
		}
	}
}

我们希望飞机在与导弹碰撞时进入坠毁状态。返回到 PlaneBehaviour 类并为其添加一个 OnCollisionEnter 方法。如果飞机已经在坠毁,该方法应不做任何操作并立即返回。如果它确实与某物碰撞,我们将检查它是什么。如果其他碰撞体是飞机,则不做任何操作(尽管物理引擎可能会导致飞机开始翻滚,但没关系)。如果其他体是导弹,则将飞机的当前状态标记为坠毁,并允许它受到重力的影响。我还要为飞机添加引擎声音效果。但是,如果飞机开始坠毁,我希望关闭该声音。

void OnCollisionEnter(Collision col)
{
	if (State == PlaneState.Crashing)
		return;
	var missle = col.gameObject.GetComponent<MissleBehavour> ();
	if (missle != null) {
		State = PlaneState.Crashing;
		GetComponent<Rigidbody> ().useGravity = true;
		AudioSource source = gameObject.GetComponent<AudioSource> ();
		if (source != null)
			source.enabled = false;
	}
}

要为我的飞机添加声音效果,我需要找到我电脑上的合适声音文件(Unity 支持多种音频文件格式),然后将其从文件资源管理器拖到 assets 文件夹中。选择项目层级中的“Plane”,我可以在检查器中选择“Add Component”,选择“Audio”,然后选择“AudioSource”。将声音分配给 AudioSource,点击并从 Assets 面板中将声音文件拖到检查器中的 AudioClip 设置。

生成飞机和导弹

现在我们已经定义了飞机和导弹,我们需要将它们保存为可重用的形式。以它们当前的 [zui] 状,如果我们运行程序,空中将只有一架飞机和一枚导弹,它们什么也不做。我希望飞机能够实例化它们各自的副本,并为它们分配不同的速度。每个实例都需要维护自己的状态。为此,我们需要将 Plane 和 Missile 转换为 Prefabs。从 Hierarchy 面板将作为飞机根对象的 gameObject 点击并拖到 Assets 面板。对 Missile 也这样做。在确认它们都出现在 Assets 中后,您可以将它们从场景中删除。

创建一个名为 PlaneGeneratorBehaviour 的新 C# 脚本。此行为将具有两个可更改的设置。一个设置是实例化新飞机之间等待的时间。另一个将是该类将要生成的 GameObject 类型。该类将需要的其他信息是到下一架飞机可以生成为止的剩余时间。我们还需要一个随机数生成器来定位新飞机。随机数生成器将在该类的 Start() 方法中初始化。

public float ProductionCooldownTime  = 1;
public GameObject PlanePrefab;
private float _cooldownTimeRemaining ;
System.Random _random;

// Use this for initialization
void Start () {
	_random = new System.Random ();
}

对于每个 Update() 周期,该类将从上次 Update() 运行以来经过的时间量(在 Time.deltaTime 中找到)中减去 _cooldownTimeRemaining 字段。如果 _cooldownTimeRemaining 大于零,则无需执行任何操作,方法将立即返回。如果它小于或等于零,那么就该生成新的飞机了。重置字段的值以开始下一个倒计时,并使用 Unity 的 GameObject Instantiate(GameObject source) 方法创建飞机的一个副本。

飞机的相对位置在名为 newPositionVector3 实例中定义。它使用落在范围内的随机值进行实例化。定义一条穿过飞机的垂直轴,并将飞机旋转一定随机度数以选择其方向。使用 Unity 内置的物理引擎,我们将获取飞机 RigidBody 组件的引用,并在飞机受力相同的方向施加力,以赋予其动量。

using UnityEngine;
using System.Collections;

public class PlaneGeneratorBehaviour : MonoBehaviour {

	public float ProductionCooldownTime  = 1;
	public GameObject PlanePrefab;
	private float _cooldownTimeRemaining ;
	System.Random _random;

	// Use this for initialization
	void Start () {
		_random = new System.Random ();
	}
	
	// Update is called once per frame
	void Update () {
	
		_cooldownTimeRemaining -= Time.deltaTime;
		if (_cooldownTimeRemaining > 0)
			return;
		_cooldownTimeRemaining = ProductionCooldownTime;

		Vector3 newPosition = new Vector3 ();
		newPosition.x = (float) _random.NextDouble () * 20 - 10;
		newPosition.y = (float) _random.NextDouble () * 10 - 5;
		newPosition.z = (float) _random.NextDouble () * 20 - 10;

		var newPlane = Instantiate (PlanePrefab);
		newPlane.transform.position = newPosition;
		Vector3 rotationAxis1 = newPlane.transform.position;
		Vector3 rotationAxis2 = newPlane.transform.position + new Vector3 (0, 1, 0);
		Vector3 newAngle = new Vector3 (0, (float)_random.NextDouble () * 180, 0);
		newPlane.transform.Rotate (newAngle);

		Rigidbody _body = newPlane.GetComponent&ht;Rigidbody> ();
		_body.AddForce (Quaternion.Euler(newAngle) * new Vector3(0,0,90));
	}
}

保存脚本。在层级结构中创建一个空的gameObject,并将 PlaneGeneratorBehaviour 从 assets 面板拖到您刚刚创建的空 gameObject 上。如果您单击场景上方的 Play 按钮,您将看到场景的预览。

Preview of the scene

我们需要再编写一个脚本来处理用户交互。从前面的示例中,您看到摄像机会自动跟随用户的移动。我们将查看摄像机的位置和方向,以便当她开火时,导弹会以相同的方向出现并向前移动。此脚本将利用 Missile prefab。将提供两种开火方式。可以执行空中点击手势,或者用户可以说出“fire”这个词。

HoloLens 支持两种语音识别模式。一种模式是听写并将用户所说的任何内容转换为文本。另一种模式是让 HoloLens 监听预定义短语列表,并在听到某个短语时触发一个事件。我们将使用后一种模式。要使用此模式,我们需要初始化一个 KeywordRecognizer。它期望一个字符串数组,其中包含它将识别的短语。对于我们的程序,此数组将包含一个元素,字符串为 "fire"。识别此词将设置一个标志,表明用户已请求发射导弹。

对于空中点击手势的识别,我们将使用 InteractionManager。此类将提供用户手部移动的信息。唯一对我们重要的是用户刚刚执行了空中点击手势。检测到时,将设置标志,表明用户已请求发射导弹。

	public float CoolOffTime = 2;
	public GameObject MisslePrefab;
	private float CoolOffTimeRemaining = 0;
	private bool IsFirePressed = false;

	KeywordRecognizer _keywordRecognizer;

void Start () {
	InteractionManager.SourcePressed += (e) => {
        if(e.pressed)
		    IsFirePressed = true;
	};

	_keywordRecognizer = new KeywordRecognizer (new string[] { "fire" });
	_keywordRecognizer.OnPhraseRecognized += (o) => {
		if(o.text=="fire")
		{
			IsFirePressed = true;
		}
	};
	_keywordRecognizer.Start ();
}

发射请求并不一定导致实际发射。我只允许每隔几秒钟发射一次导弹。如果我们仍在冷却期内,Update() 方法将立即返回。如果冷却期结束,我们将检查发射标志是否已设置。如果已设置,则清除标志,重置冷却时间,并实例化导弹。我们查看 Camera 对象,获取其位置和旋转,并将其应用于导弹。最后,给导弹的 RigidBody 组件一个向前的推力。

void Update () {
	CoolOffTimeRemaining -= Time.deltaTime;
	if (CoolOffTimeRemaining > 0)
		return;
	if ((IsFirePressed)) {
		IsFirePressed = false;
		CoolOffTimeRemaining = CoolOffTime;
		GameObject missleObject = Instantiate (MisslePrefab);

		Camera camera = GetComponent<Camera> ();
		missleObject.transform.position = camera.transform.position + camera.transform.forward * 2.0f;
		missleObject.transform.rotation = camera.transform.rotation;
		missleObject.gameObject.GetComponent<Rigidbody> ().AddForce (camera.transform.forward * 250);
	}
}

此脚本必须拖到 MainCamera 组件上。您还需要将 prefab 的导弹拖到组件上显示的 MisslePrefab 属性。完成此操作后,您可以创建另一个 Visual Studio 生成。在 Visual Studio 中部署生成之前,双击名为 Package.appmanifest 的文件。程序将需要使用设备麦克风的权限,我们使用清单请求权限。当清单打开时,选择 Capabilities 选项卡,向下滚动到 Microphone 功能。确保它已被选中。

导入资产

3D 建模是一门独立的专业。我在这里不介绍如何使用 3D 建模工具。确实存在一些网站,建模师可以在上面以不同的使用许可证出售他们的创作。为了演示使用外部资产,我将去 TurboSquid 寻找价格在 0.00 美元到 1.00 美元之间的导弹模型。

Browsing models on Turbosquid

我选择并下载了一个导弹模型。它们有多种格式。我选择了 OBJ 文件并将其拖到我的 Unity Assets 中。一旦成为 Assets 的一部分,我便将其拖入场景。调整了尺寸,直到达到我满意的大小。通过单击并拖动将 MissleBehaviour 脚本添加到它。使用检查器中的 Add Components 按钮,我添加了一个 RigidBody 组件(在 Physics 下),并将其质量设置为 0.24。同样,在 Physics 下,我添加了一个 Capsule Collider。我调整了 Capsule Collider 的大小和位置,直到它恰好包围了导弹。当我满意一切外观后,我将实例命名为 SmartMissle,然后将其拖回 Assets 以创建新的 prefab。最后,我在 PlayerBehaviour 脚本(在摄像机对象上)中用新的 SmartMissle 替换了 Missile。再次运行程序,我看到高级细节导弹代替了之前的模型。如果您没有 HoloLens 并且想看看这个程序是什么样的,我已经将一个录像上传到了 YouTube,可以通过此处访问。

闭幕词

HoloLens 开发还有很多内容需要讲解。这包括 Unity 的许多概念和技术,这些概念和技术可以移植到 Unity 支持的其他平台,以及许多 HoloLens 特有的内容。如果您是 HoloLens 开发新手,我建议深入研究三个领域。深入研究 UWP 应用程序开发,虽然它不特定于 HoloLens,但将有助于您学习适用于包括 HoloLens 在内的所有 UWP 设备的aught 概念、代码和 API。更深入地研究 Unity 将有助于学习如何为 HoloLens 构建交互。微软的网站上还有一个名为 Holographic Acadamy 的区域,其中将包含大量关于 HoloLens 特有的 Unity 功能的信息,例如扫描环境以了解其中的其他对象。

我计划撰写更多关于 UWP 开发(包括 HoloLens)的内容。如果您对 UWP 的特定领域感兴趣,并希望我在未来撰写相关内容,请务必在下方留言。

我还要感谢 K.A.P.,他的鼓励促使我发表了这篇以及即将发布的许多其他文章。

历史

  • 2016 年 6 月 16 日 - 首次发布
© . All rights reserved.