2020-6-25文章
全新的Genesis Plaza不仅看起来漂亮,而且还包含许多非常酷的交互内容。我们想分享一些创建它的技巧和窍门,因为它肯定可以帮助很多创建者。
我们已经为Plaza开源程序编写了代码,因此任何人都可以探索和学习其中的内容。看一看!
https://github.com/decentraland-scenes/Genesis-Plaza
为避免您不知所措,我们整理了一些较小的存储库,重点放在Plaza内的特定机制上。这些易于消化,杂乱无章。
文章的第2部分将重点介绍与Plaza一起引入的一些新功能。在第一篇文章中,我们将讨论一些常规建议和最佳做法,并介绍任何Decentraland开发人员应在其工具箱中使用的经常被忽视但必不可少的工具。我们还将探讨这些工具如何帮助使Plaza成为现实,以及它们如何使我们的生活变得更加轻松。
设置实体的位置相当容易,但是构建Plaza需要为很多实体设置坐标,包括显示器上的可穿戴设备,传送装置等等。如果我们只是通过猜测和调整来尝试放置每个对象,那么我们仍将继续努力。这个小技巧为我们节省了很多时间。
在开始之前,需要注意的是,在许多情况下,使用Builder来设置项目的位置,然后导出场景以继续使用SDK进行项目是一个更好的选择。但是,在我们的情况下,这不是一个选择,因为Plaza是10x10的场景,而Builder无法处理如此大的场景。另外,这个小技巧可让您拥有更加灵活的工作流程,因为您无需为所添加或移动的每个模型都从Builder导出新场景。
相反,我们只是将这几行代码添加到game.ts中。
Input.instance.subscribe('BUTTON_DOWN', ActionButton.PRIMARY, false, (e) => {
log(`pos: `, Camera.instance.position)
log(`rot: `, Camera.instance.rotation)
})
这样,您每次按E键,就可以将播放器的位置和旋转记录到浏览器的控制台(view> Developers> JavaScript Console)中。然后,您可以将这些坐标和旋转值复制到要定位的任何实体的“变换”组件中。
使用第三方相机可以更准确地找到正确的位置。请注意,以第三人称,您仍然会记录化身的位置,而不是摄像机的位置。因此,请随意在播放器周围旋转相机,这不会影响位置值,只会影响旋转值。
此处的旋转表示为四元数,因为我们可以使用它直接复制到Transform中,但是如果您愿意,可以通过记录Camera.instance.rotation.eulerAngles将此旋转表示为欧拉角。
要了解有关如何设置实体位置和旋转的更多信息,请参阅以下页面:https://docs.decentraland.org/development-guide/entity-positioning/
注意:完成场景创建后,请记住注释掉这段代码,因为记录这些消息会影响场景的性能!
如果您还没有发现decentraland-ecs-utils库,那么它非常有用。它包含许多方便的功能,可大大简化常见任务,例如延迟动作,随时间推移平稳移动实体等。
https://www.npmjs.com/package/decentraland-ecs-utils
我们在Genesis Plaza中广泛使用的该库中的一件事是触发盒。例如,这里的所有电梯都由触发框触发,因此只要玩家踏入电梯,电梯就会被激活。
// entity to use as trigger
const moonTowerTriggerEntity = new Entity()
moonTowerTriggerEntity.addComponent(new Transform(triggerPos))
// dimensions and relative position of the trigger box
let moonTowerTriggerBox = new utils.TriggerBoxShape(triggerScale, Vector3.Zero())
// add trigger component
moonTowerTriggerEntity.addComponent(
new utils.TriggerComponent(
moonTowerTriggerBox, //shape
0, 0, null, null, //ignored parameters
//onCameraEnter function:
() => {
log('triggered platform')
moonTower_Elevator.activate()
}
)
)
// add trigger entity to engine
engine.addEntity(moonTowerTriggerEntity)
我们还使用了类似的触发框来激活负载繁重或不想持续播放的不同事物。例如,东北角的大视频屏幕被大触发框包围,只有当播放器靠近它时,视频流才会打开。
创世纪广场(Genesis Plaza)必须是您与周围其他人共享的一种体验,当务之急是在广场中拥有一些多人游戏机制。当玩家更改某项内容时,那里的其他玩家也需要查看该内容。
有多种方法可以实现此目的,但最简单的方法是使用场景的Message Bus。
上面共享的电梯代码是对我们实际工作的简化。电梯不是直接由触发区域激活的,而是它们通过消息总线发送消息,然后我们正在监听该消息并对其做出反应。
在单人游戏场景中,一切都一样,因为玩家会收到自己的消息并对消息做出反应。但是,如果有多个玩家在场,那么他们也都会收到彼此的消息,请确保他们都看到电梯在做同样的事情!
// add trigger component
moonTowerTriggerEntity.addComponent(
new utils.TriggerComponent(
moonTowerTriggerBox, //shape
0, 0, null, null, //ignored parameters
//onCameraEnter function:
() => {
log('triggered platform')
// send a message
sceneMessageBus.emit('moonElevatorActivated', {})
}
)
)
// listen for message
sceneMessageBus.on('moonElevatorActivated', (e) => {
moonTower_Elevator.activate()
log('moon tower elevator')
})
气球和飞行火车与电梯使用相同的机制,另外要考虑的一点是:我们包括一个等待期,以便其他玩家在飞行中不会再次激活触发区域。
这是侦听气球激活消息的代码。请注意,我们首先检查布尔值的值。如果是真的,我们什么也不做,否则我们将其设置为true,然后使用utils库将其在150秒后(气球动画的持续时间)设置为false。
sceneMessageBus.on('balloonActivated', (e) => {
if (ballonIsFlying) {
log('balloon was already in flight')
return
}
ballonIsFlying = true
balloon.activate()
balloon.addComponentOrReplace(
new utils.Delay(150 * 1000, () => {
ballonIsFlying = false
})
)
})
然后是落地钢琴。这也许是将消息总线和触发区域结合在一起的最终示例。这架钢琴不是创世纪广场第一版的一部分,因此您可能没有看过,但您可以从鲸鱼大厦后面的广场中心向北直上来找到它。
钢琴仅包含一组触发区域,每个触发区域一个。激活后,每个触发区域都会通过消息总线发送一条消息,告诉您和其他人弹奏该钢琴音符的相应声音文件。
您可以在以下单独的回购中找到钢琴的更清洁版本:https://github.com/decentraland-scenes/piano-floor-example-scene
要了解有关使用消息总线的更多信息,请在docs中查看以下页面:https://docs.decentraland.org/development-guide/remote-scene-considerations/#p2p-messaging
在下一篇文章中,我们将介绍实现多人游戏机制的更多方法。
跨多个实体重复了场景中的许多功能。例如,我们展示了50种可穿戴设备,走廊上的16幅画和5部电梯。如果事先知道您将不得不经常重复部分代码,那么将其抽象化为可重用的内容是有好处的。不必为这些实体中的每个实体重复拼写整个组件,而是需要定义自己的类,该类具有许多隐式内置的功能。
如果您对Genesis Plaza仓库进行分类,您会发现我们做了很多。否则,该代码将变得更长且更难维护。
去年,我们与HardlyDifficult的Nick一起在“密室逃脱”教程中很好地涵盖了这个想法:
这是与此技巧最相关的视频:https://www.youtube.com/watch?v=_kksSC91DKE&list=PLAcRraQmr_GOhimaVZSlJrkzCvo8crIBQ&index=10
完整的播放列表如下:https://www.youtube.com/watch?v=j7XbiTZ9GN0&list=PLAcRraQmr_GOhimaVZSlJrkzCvo8crIBQ
对我们而言,重要的是,玩家在参观创世纪广场时,必须在Decentraland上拥有流畅的初体验。这是一个挑战,因为Plaza很大,并且有很多内容同时加载到内存中。如前所述,我们使用了一些触发区域来关闭某些功能,而玩家却无法充分欣赏它,但是我们也非常注意使Plaza中的所有内容都尽可能轻巧。
场景性能的主要瓶颈通常是在场景代码和引擎之间发送消息。如果要优化场景的运行流畅度,则需要注意的最重要的一件事情就是从SDK发送到引擎的消息数量。
您如何知道该消息流是否正在成为瓶颈?这是一个不错的小技巧:在预览模式下运行场景时,请注意在右上角显示“ P = Toggle Panel”。您很有可能从未注意到它在那里,但是它已经存在了很长时间。按下P键,将打开一个面板,其中包含一些有用的信息,这些信息会在您进行互动时实时更新。
在与SDK和引擎之间涉及消息的事物进行交互时,您会注意到“已处理消息”的数量将会增加。只要“待处理队列”数字保持为0或接近0,就可以了。如果该数字开始增加,则表明您已经进入危险区域,并且您知道需要进行更多优化。
注意:请勿在不使用面板时使其保持打开状态,因为它会影响性能。
每当您要在场景中移动某些东西时,都需要考虑一个权衡:它是根据动画移动还是应该通过代码移动?有充分的理由使用一种方法或另一种方法。这一切都取决于您要做什么。就广场而言,所有移动对象都是通过动画来完成的,包括所有电梯,气球和飞行火车。就资源而言,播放动画比逐帧移动对象要有效得多。
如果我们通过播放动画来移动电梯,我们只需要向引擎发送一条消息,告诉它播放动画,然后引擎从那里处理其余部分。另一方面,如果我们通过代码移动电梯,则需要向引擎发送一条新消息,该消息在每个帧上都带有新位置。如果我们在场景中同时有几件事像这样移动,则所有这些加起来。
使用动画而非代码的各种弊端之一是,我们无法在给定的时间获取移动实体的位置,因为存储在Transform组件中的实体的位置不会随着动画的播放而真正改变。如果需要在任何给定时间获取气球的位置,则必须通过代码移动它。就我们而言,我们选择了最简单的方法,因为这是我们的优先考虑。
我们在第一部分中分享了很多有用的想法。请花一些时间来消化一切,探索仓库,自己尝试一下。敬请持续关注有关我们如何构建Genesis Plaza的其它方面的更多文章。
选择您常用的频道加入与我们联系,关注Decentraland(MANA)的最新动态
DCL基金会全球社区:
【Official Website】
【Telegram】
【Blog】
【Twitter】
【Discord】
DCL中文社区:
【电报群】
【推特】
【微博】
【微信群】请加微信ID ChinWaan
【微信公众号】manalandcn