房间和入口的高级使用

游戏回调

尽管遮挡剔除大大减少了需要渲染的对象数量,但除了最终渲染之外,在游戏中维护对象还有其他成本。例如,在 Godot 中,无论是否出现在屏幕上,动画对象仍然会被动画化。这会占用大量处理能力,尤其是对于使用软件蒙皮的对象(蒙皮在 CPU 中计算)。

不要担心,房间和入口可以解决这些问题,还有别的。

通过为我们的游戏关卡构建房间系统,我们不仅拥有遮挡剔除所需的信息,还轻松创建了了解哪些房间位于玩家(或相机)的局部 “gameplay area” 游戏区域所需的信息.仔细想想,在很多情况下,不需要对与游戏玩法无关的对象进行大量模拟。

游戏区域并不局限于你面前可以看到的物体。你身后的AI怪物仍然需要在你转身的时候攻击你。在Godot中,游戏区域被定义为从你当前所在的房间开始的房间的 潜在可见集合 (PVS) 。也就是说,如果房间的任意部分可以从你所在的房间的任何部分看到(甚至从一个角落),它就被认为是在PVS之内,因此也是游戏区域。

这是有效的,因为如果怪物在你自己或怪物完全看不见的区域,你就不太可能关心它在做什么。

怪物如何知道它是否在游戏区域内?

这个问题是有解的,入口系统包含称为 Gameplay Monitor 的游戏监控子系统,可以从 RoomManager 中打开或关闭。当开启时,任何在游戏区域内外移动的漫游对象都会收到回调(无论是通过移动自己,还是摄像机移动),让他们知道这一变化。

你可以选择以 signalsnotifications 的方式接收这些回调。

通知可以用GDScript或其他脚本语言来处理:

  1. func _notification(what):
  2. match what:
  3. NOTIFICATION_ENTER_GAMEPLAY:
  4. print("notification enter gameplay")
  5. NOTIFICATION_EXIT_GAMEPLAY:
  6. print("notification exit gameplay")

信号的发送就像其他信号一样。可以使用编辑器检查器附加到函数中。信号名为 gameplay_enteredgameplay_exited

事实上,不仅是这些 ROAMING 对象的收到回调。此外,房间和房间组也可以接收回调。例如,当玩家达到某个关卡的特定点时,您可以使用它来触发 AI 行为。

VisbilityNotifier / VisibilityEnabler

游戏回调还有一个有用的功能。在Godot中,默认情况下,无论物体是否在视野内,动画和物理都会被处理。这可能会降低性能,特别是在使用软件换肤时。

引擎对这个问题的解决方案是 VisibilityNotifier 节点,以及稍微容易使用的它的变体, VisibilityEnabler 节点。VisibilityEnabler可以用来关闭动画和休眠物理,当一个物体在视野范围之外时。你只需要在子场景中放置一个VisibilityEnabler节点就可以做到这一点(例如,一个怪物)。它将完成剩下的工作。请查阅 VisibilityEnabler 文档,以了解详细内容。

../../../_images/visibility_enabler.png

那么在VisibilityEnabler对象被遮挡时能关闭它们吗?是的,可以,你所要做的就是在RoomManager中启用Gameplay Monitor,就会自动进行。

RoomGroup

RoomGroup 是一个特殊的节点,它允许你一次处理一组房间,而不必单独书写代码。这在与游戏回调相结合时,很有用。RoomGroups房间组最重要的用途是划分 “内部” 和 “外部” 区域。

../../../_images/roomgroups.png

例如,当在外部时,你可能希望使用一个 DirectionalLight 来表示太阳。当外部的房间组收到 enter gameplay 回调时,你可以打开灯,当房间组退出游戏时,把它关掉。关闭灯光后,会提高性能,因为不需要在室内渲染。

这是一个简单的RoomGroup房间组脚本的例子,用来打开和关闭平行光照。注,你也可以选择使用信号进行回调:

../../../_images/roomgroup_notification.png

小技巧

你可以应用同样的方式来打开和关闭天气效果、天空盒和更多。

内部房间

还有一个技巧,房间组有他们的管道。一个常见需求是有室外和室内混合环境的游戏关卡。我们已经提到,房间可以用来表示建筑物房间,以及景观区域,如峡谷。

如果你希望在地形 ‘房间’ 里有房屋,怎么做?

使用到目前为止描述的功能,你可以做到这点,不过,你需要在房间的外部放置入口,在房间上方形成不必要的房间。这在很多游戏中都做过。但是有没有更简单的方法呢?

事实证明,有一种更简单的方法来处理这种情况。Godot 支持 房间\*套**房间(称为 *“内部房间”)。也就是说,你可以在地形房间内放置房子,甚至一栋建筑或一组建筑,在不同的地形房间内设置出口!

要创建内部房间,不需要在场景树把一个房间放置在另一个房间中,事实上,如果您尝试这样做,将收到警告。而是,将它们创建为常规房间。内部房间应该与父级房间组进行组合。查看房间组属性,会看到 Room Group Priority 默认为 0

如果您希望一个房间或一组房间位于内部,请使用房间组,将优先级设置为高于外部封闭房间的值。

在决定摄像机或物体位于哪个房间时,系统按照优先级设置优先考虑内部房间。更高优先级的 总是 获得。其他的都以类似的方式工作。

仅有的差别:

  • 内部房间和外部房间之间的入口应该总是放在内部房间

  • 内部房间的入口不视为外部房间范围的一部分。

  • 来自外部房间的 STATICDYNAMIC 对象不会扩展到内部房间。如果您希望物体穿过这些入口,请将它们放置在内部房间中。这是为了防止大型物体(如地形部分)扩展到整个建筑物,而在不需要时渲染。

内部房间示例

帐篷是位于地形房间(包含地面和树等)中的简单的房间。

../../../_images/tent.png

备注

使用内部房间作为建筑时,一般最好是把建筑的内部网格和外部分开。外部网格可以放在外部房间里(这样在外面就可以看到,而在里面看不到),内部网格应该放在内部房间里(所以只能在内部或者通过入口看到)。

../../../_images/tent_terrain.png

这非常适合提高开放世界游戏的性能。通常,您的建筑物可以是重复使用的场景,包括房间和入口。从外面看,内部大部分会被剔除,而从内部看其他建筑物和外面的大部分都会被剔除。建筑物内外的其他玩家和物体也是同样。

场景为“Diorama Eco scene”,作者 Odo,为了展示目的有细微修改。 CC 署名

内部房间场景

我们来仔细看一个开放世界的应用实例。我们想把房子(作为内部房间)放到岛上,并且每个房子都是一个独立的场景,同时包含房子的内外网格。

../../../_images/house_scene.png

我们创建了一个 Room 节点(会用作内部房间)并在其中放置内部网格。同时我们也创建了一个未链接的 Portal(所以会进行自动链接)。外部网格不在这个房间里。它会被自动放置到外部房间里,我们也是这样期望的。

然而问题来了。朴素的自动放置算法会根据外部网格的中心点的位置,去尝试把它放进内部房间。我们得想办法避免这种情况,因为外部网格的意义就是让它在外面渲染,所以必须放在外部房间里才行。

要解决这个问题,有一个特殊的设置,可以让你表达对自动放置到外部房间的偏好。每个对象都有一个 Autoplace Priority(自动放置优先级)的设置。设为 0 时没有偏好(对象会被放置到优先级最高的房间里)。

而如果我们把这个自动放置优先级设成 -1,那么自动放置时就会始终选择优先级为 -1 的房间(该位置存在这样的房间的话)。所以如果我们把外部房间的优先级设为 -1 ,就会始终把外部网格放到我们的“外部”房间了。

../../../_images/autoplace_priority.png

有了这个设置,我们就可以在类似的情况下执行进一步的控制,非常有用,整个系统也变得更加灵活。

备注

因为默认的自动放置优先级是 0,你很难把对象强制放进优先级为 0 的 RoomGroup 里。不过可用的优先级还有很多,所以在实践中应该不是问题。

最终的场景看上去就像这样,在巨大的外部房间里实例化了很多房子。

../../../_images/island.png

房子的外景放置在外部房间,从外面总是可以看到。只有当进入入口的可见视野时,内部才会被呈现出来。