所有文章 > API使用场景 > 使用虚幻引擎提供的谷歌地图API等Web API(仅限蓝图)
使用虚幻引擎提供的谷歌地图API等Web API(仅限蓝图)

使用虚幻引擎提供的谷歌地图API等Web API(仅限蓝图)

随着Cesium for Unreal推出来自谷歌地图平台的3D瓦片(3D Tiles),所有人都将可以访问来自世界各地覆盖区域内的摄影测量资产。这就非常神奇,因为现在你可以通过虚幻引擎提供的真实感,在全球各地驾车驰骋和自由飞翔。但是数字孪生不仅涉及3D多边形,它还需要根据请求提供语义数据。因此,我们想尝试利用谷歌提供的其他API,在这种3D可视化基础上添加更多服务。

介绍

概念形成随着Cesium for Unreal推出来自谷歌地图平台的3D瓦片,所有人都将可以访问来自世界各地覆盖区域内的摄影测量资产。这就非常神奇,因为现在你可以通过虚幻引擎提供的真实感,在全球各地驾车驰骋和自由飞翔。但是数字孪生不仅仅涉及3D多边形,它还需要根据请求提供语义数据。因此,我们想尝试利用谷歌提供的其他API,在这种3D可视化基础上添加更多服务。本教程涉及多个基本概念,但不在此详细阐释。蓝图、游戏对象、地理参考、UMG控件。

在阅读下文前,请先了解这些概念。本文并非逐步再现概念验证流程的详细指南,但会介绍在虚幻引擎中处理异步设计的一些最佳实践。在本教程中,我们不会粘贴任何蓝图片段以呈现更好的视觉效果,但是BP_PlacesAPI_Helper蓝图将在本教程最后“按原样”提供。这意味着它“不受支持”,只是为了快速验证相关概念,生产就绪版本需要更多的验证步骤。

什么是Web API?

Web API是一个可扩展的框架,用于构建基于HTTP的服务,这些服务可以在不同平台(例如Web端、Windows、移动端等)上的不同应用程序中访问。

遵循REST架构约束的Web服务API被称为RESTful API。基于HTTP的RESTful API由以下几个方面定义:

被用作起点(有时叫做端点或入点)的一个或多个资源的URI

所有可能的资源表示的编码。这意味着服务器会使用资源的表示进行响应(现在通常是HTML、XML或JSON文档)。

可能的状态转换以及它们可能发生的位置

REST API来源:https://en.wikipedia.org/wiki/Representational_state_transfer

总的说来,要使用REST API,就需要向服务器发出HTTP请求,服务器会按已知的格式给我们返回答案,比如JSON或XML。

虚幻引擎拥有两个非常实用的插件:

Http Blueprint – 向服务器发送HTTP。

JSON Blueprint Utilities – 解析和解码JSON对象。

这些插件在C++和蓝图中公开了它们的功能,不用编写代码就可以查询这些API!

初始设置

设置谷歌云(Google Cloud)服务

要使用REST API,通常需要个人API密钥(Personal API Key)。

这个密钥会识别请求的来源,以便在出现与服务相关的费用时,根据你的使用情况收取费用。

使用这些服务必须有谷歌地图API密钥。参阅此处了解关于密钥创建的更多信息。

对本实验而言,我们在项目密钥(Project Key)中添加了以下API:

  • 地图瓦片API(Map Tile API) – 使用Cesium for Unreal插件加载3D瓦片内容
  • 地图地点API(Maps Places API) – 查询地点信息
  • 地图海拔API(Maps Elevation API) – 了解某一位置的海拔数据

发送请求时要小心,因为有可能产生相关成本。请避免在循环中发送请求,因为这样有可能超过每秒查询数的限制,还有可能造成成本浪费。为此,设置API密钥限制和使用警报非常重要。

创建虚幻项目

新建一个虚幻引擎5.1项目,在模拟类别(Simulation Category)中选择空白项目模板,仅使用蓝图。

打开编辑器之后,启用以下插件

  • Cesium for Unreal(可从虚幻商城获取)。
  • Http Blueprint(它会自动向Json Blueprint Utilities添加一个依赖项)

在模拟模板中启用SunSky和GeoReferencing插件。
然后,你要将Photorealistic 3D Tiles添加到关卡中。

Cesium在本教程中记录了该过程。遵循其中描述的步骤,但是有以下例外情形:

  • 不需要使用Cesium Sunky,模拟模板已经设置好虚幻引擎SunSky。
  • 不要使用Cesium动态Pawn(Cesium Dynamic Pawn)。我们使用GeoReferencing插件提供的BP_RoundPlanetPawn。

确保为以下内容设置相同的地理参考点:

  • Cesium地理参考(Cesium Georeference)
  • 虚幻引擎地理参考系统(UE Georeferencing System)
  • SunSky

这一步是必须要做的,因为三个插件各自独立运行,我们不想在它们之间创建依赖关系。

确保所有地理参考对象都同步

玩家出生点在巴黎凯旋门附近

下面我们就来使用API

创建需要的资产

设置基本游戏对象

确保:

  • 关卡中有一个玩家出生点
  • 有可以使用的自定义游戏模式设置
    • 你自己的玩家控制器(我们使用修改过的BP_SimPlayerController副本)
    • Georeferencing插件中有可用的BP_RoundPlanetPawn在项目设置中设置以下游戏模式和初始地图。

设计顾虑

在这个演示项目中,我们实现了3个主要功能

  • 自动完成位置搜索并飞到该位置
  • 查询附近的兴趣点(餐馆、酒吧),生成地理标记(GeoMarker)
  • 点击地理标记获取详细信息。

3个主要功能
为了实现这些功能,我们创建了以下对象:

  • BP_PlaceAPI_Helper:负责调用谷歌地图API的蓝图Actor
  • BP_Place:存储地点(位置)属性的纯蓝图数据对象
  • UMG_GeoQueries:主应用程序UMG控件
  • BP_GeoMarker:作为地点视觉表示的蓝图Actor
  • BP_MarkerManager:用于管理地理标记的蓝图Actor

使用REST API要注意的主要问题是,发送请求是一个非阻塞调用。调用者将在之后收到请求结果,我们不希望在等待请求结果的过程中阻塞程序。因此,我们将使用大量的异步编程模式,利用事件分发器。

自定义事件VS函数

在使用Http发布请求节点(Http Post Request Node)发布请求时,你可以在右上角看到一个小时钟。这意味着该节点将执行异步调用。事件图表将在这里暂停执行(除非在请求处理(Request Processing)引脚连接其他内容)。收到结果后,它将通过“请求成功(Request Was Successful)”或“请求不成功(Request Was Not Successful)”执行引脚恢复执行。

发布请求是一个异步调用!

这会带来了一个重要的设计约束。函数中不能包含任何带异步调用的蓝图节点。只能在事件图表中使用。因此,你必须在一个事件图表中、在一个自定义事件中发送请求。

但是如果在事件图表中实现所有内容,整个图表很快就会变得非常混乱。因此,我们仍然使用函数将同步操作(例如构建请求和处理应答)组合在一起,然后将这些调用堆叠在自定义事件下。

谁知道谁?在对象之间创建引用的正确方法!

另一种常见的重大设计缺陷在于用户界面和底层对象的绑定方式。

在应用程序编程中,我们有不同的设计模式,包括文档/视图、模型-视图-控制器(MVC)、模型-视图-视图模型(MVVM)。

无需输入太多细节,表示层(UI)知道它所显示的对象,但对象本身必须对表示层不可知。

从数据对象(如BP_PlacesAPI_Helper对象)调用控件更新是非法的!

建议让BP_PlacesAPI_Helper在数据可用或发生更改时触发事件。因此,我们要创建“事件分发器”,其他应用程序对象,比如UMG控件或玩家控制器将订阅事件分发器!

所有可能的请求都将遵循类似下面的模式:

请求的剖析

注意,搜索附近地点功能有一个小技巧:为了限制服务器的负载,请求只返回前20个结果。

要想获取更多结果,需要重新发起相同的查询,但这次有一个特殊选项,就是和第一个查询结果一起收到的token。这样就可以得到后续的20个地点,以及再往后的20个地点,最多60个地点。

为了处理这种特殊情况,我们有两个针对附近地点的事件,一个用于获取前20个地点,另一个用于获取后续的20个地点。

聚焦BP_PlacesAPI_Helper Actor

根据上面阐述的设计,BP_PlacesAPI_Helper将包含下列成员:

  • 在事件图表中:主入口点函数
  • 自动完成地点寻找(Find Place Autocomplete)
    • In:部分地址,Out:预测的BP_Places数组
    • 完成时,事件分发器“PredictedPlacesAvailable”
  • 查询地点详情(Query Place Details)
    • In:BP_Place, GetAltitude(可选)。Out:更新输入BP_Place
    • 完成时,事件分发器“Place Details Received”
  • 搜寻附近地点(Search Nearby Places)
    • In:类型字符串、半径,Out:附近BP_Places数组
    • 每当收到一批地点时,事件分发器“Nearby Places Received”
  • 作为函数
  • 格式化请求URL的函数
  • 处理JSON结果的函数
  • 效用函数
    • 净化URL(用%20替换空格,用%2C替换逗号)
    • 检查JSON响应中的错误

BP_PlacesAPI_Helper函数

BP_PlacesAPI_Helper变量和事件分发器

格式化请求

API文档中有关于格式化请求的解释。

例如,从地点自动完成API(Places Autocomplete API)文档中,我们了解到URL必须按这种方式格式化,可能的其他输入参数要用“¶m=value”隔开。

URL格式

URL示例

大多数时候,我们只是附加字符串,最后是我们的API密钥。

查找海拔请求

发送请求

Post Request需要一个URL和说明这是一个JSON请求的请求头。

如果需要添加其他可选参数,可以提供一个可选的请求体。

注意,我们为实际请求发送统计添加了计数器,用于监控实际发送的请求数量。

发送请求(注意异步调用)

处理结果

处理结果是整个过程中最枯燥的部分。

你将收到一个采用通用编码方式编码的JSON对象。

你可以请求一个字段值,你将得到:

  • 一个带值的字符串,如果是“叶节点(Leaf Node)”。
  • 又一个JSON对象,如果是“子节点(Child Node)”。
  • 其他JSON对象的数组,在循环中处理。

所有这些情况都由“获取字段(Get Field)”蓝图节点处理。

你要知道结果的格式,以便处理结果。好在它会被记录下来。

专业意见:在尝试迭代解析之前,最好先在浏览器中输入请求URL来运行查询。如果JSON对象以字符串形式返回,会难以读取。

但是有些在线JSON查看器,比如这个,你可以将这个字符串粘贴到这个查看器中,它会创建一个更容易读取的节点层级!

原始JSON响应字符串

在线查看器中显示的相同JSON字符串

这种表示方式更便于在层级结构中导航,也更容易了解获取字段节点是需要返回一个字符串,还是另一个JSON对象或一个JSON对象数组。

在创建获取字段节点时,数值(Value)引脚默认显示灰色。它会根据你连接的引脚发生改变。因此,在连接引脚之前,必须先创建包含目标引脚的节点。

始终将数值引脚从目标绑定到源,以获得正确的类型!

由于JSON的结构一目了然,并且知道获取字段的规则,解码响应就变得非常简单,比如下面的“查找地点自动完成(Find Place Autocomplete)”。

注意,在生产中,建议在处理过程中采用更好的错误处理流程。

JSON文件处理示例

处理过程的最后一步是将结果通知给感兴趣的对象。

我们事先不知道谁会对结果感兴趣。可能是显示地点细节的UI,然后由玩家控制器决定是否让Pawn飞到这个位置。

为此,我们添加了一个事件分发器,其中可能包含将随事件传递的输入,订阅该事件的其他对象将接收该输入。

在BP_PlacesAPI_Helper中,我们在处理完结果之后触发事件分发器

在UMG控件中,我们订阅了这个事件,并相应地重新构建了建议列表。

用户界面

除了ListView系统之外,UMG控件并没有什么特别之处。

在UMG中,使用控件填充垂直框是一个非常常见的操作。但是根据元素的数量,具体过程可能比较复杂或者比较缓慢。ListView是一个虚拟视图,可以帮助我们处理列表中的大量对象,具有很大的灵活性。

  • 为列表的每一行创建一个特殊控件。
  • ListView将专门用于这个控件。
  • 在向列表中添加元素时,该元素会被传输到行控件用于显示。

为了整理这个列表,我们需要:

  • 创建一个UMG用户控件(UMG User Widget)作为行控件。(这里是UMG_PlaceWidgetEntry)
  • 在本POC中只是个简单的文本框,但它可以包含一个图标。
  • 在“图表(Graph)”面板中,点击“类设置(Class Settings)”,在细节面板中找到“接口(Interfaces)”类别,将“用户对象列表条目(User Object List Entry)”添加到已实现接口列表中。
  • 右键单击“列表项目对象集上(OnListItemObjectSet)”,选择实现事件。
  • 在蓝图图表中,将这个对象转换为需要显示的实际类型,然后用它的属性来更新UI。

行控件(Row Widget)需要实现“用户对象列表条目”接口。

需要实现“列表项目对象集上”事件。

然后,在主控件上添加一个列表视图控件(ListView Widget),将条目控件类(Entry Widget Class)设为刚才创建的类。每当向列表中添加一个项目,就会创建相应的行控件,显示底层对象。

将点击项目(Item Clicked)事件绑定到一个自定义事件处理器上,这样处理就结束了。

定义与每一行相关的控件类(Widget Class),绑定点击项目事件。

事件处理器负责填充列表,对点击做出响应。

地理标记及其管理器

地理标记蓝图由一个简单的缩放立方体、一个用于显示大头钉的自发光材质和一个2D文本控件构成。

大头钉稍微有点特殊,我们将根据距离对它进行缩放,以呈现单像素线条的效果。

在我们的例子中,如果使用3D瓦片,在下载瓦片时,几何体将随着时间的推移不断改进。为了确保始终正确对齐,2s定时器会将控件重新放在地平面上。

按距离缩放大头钉网格体

限制在地平面上(使用海拔高度偏移)

确保控件组件(Widget Component)使用你的自定义控件,

并在“屏幕”模式下按“期望的大小”呈现。

为了实现交互性,2D控件包含一个带鼠标键按下绑定的换行边界,

它会发送一个“Clicked”事件分发器。(可以是一个透明的按钮)

在处理多个这样的对象时,一种明智的做法是创建一个专门的对象来管理这些对象。在这种情况下,标记管理器(Marker Manager)非常简单,它只处理一组标记类型,但可以发展到处理不同的标记层,带可见性开关……

创建一个BP_MarkerManager Actor,一个生成地理标记(SpawnGeoMarkers)事件。在这个事件中,我们要:

  • 移除所有现有标记,
  • 获取样式信息(见下文)
  • 计算变换(向上向量不是Z+,我们在圆形的地球上)
  • 生成标记
  • 设置属性
  • 绑定事件“Clicked”,该事件将发送标记与BP_Place 

删除标记时,一定要取消绑定事件“Clicked”!

生成标记1/2

生成标记2/2

样式

在启动这样的应用程序时,我们事先不知道会给标记使用哪种颜色(Colors)或图标(Icons)。随着时间的推移,我们可能还会添加其他类型的标记。如果开始对这些值进行硬编码,之后的编辑过程将会十分痛苦。

要解决这个问题,数据表是一个非常好的选择。

  • 利用颜色和图标属性创建一个简单的S_MarkerStyle。
  • 基于这一结构创建数据表,并为你希望提供支持的标记类型添加行。
  • 将这个数据表设为BP_MarkerManager的属性(Property)。
  • 在生成标记时,管理器将使用这些数据来选择样式。通过这种方式,你可以逐渐改进自己的样式,甚至为最终用户提供不同的自定义样式!

基于样式内容构建数据表

玩家控制器处理高级逻辑

现在,MarkerManager和PlaceAPI Helper可以在事情发生时发送事件,接下来我们要处理逻辑。

这是在玩家控制器中完成的,它会生成UI并获取对MarkerManager和PlaceAPI Helper的引用。

绑定地点相关事件

绑定标记相关事件

总结

通过Http蓝图和JSON蓝图实用程序,在虚幻引擎中使用Web API变得越来越容易。

借助这些模块,所有人都可以使用这些API,不需要编写代码。

由于请求的异步性,采用事件驱动型设计非常重要,但是通过事件分发器和将回调绑定到这些事件上,一切就简单多了。

这个简短的概念验证过程是在一天内完成的,还有很多地方需要改进。但我们的目标是说明查询外部服务其实非常容易,而这也是数字孪生应用程序的关键一环!

希望大家喜欢!

文章转自微信公众号@虚幻引擎

#你可能也喜欢这些API文章!