NarraLeaf

应用

Surface 导航、应用窗口,以及一个 Page 对自身的认知

App 节点是蓝图伸手够到它所运行的那个应用的地方:打开一个 Surface、在它上面再压一层、调整窗口大小、跳转一个链接、退出应用。Surface 模型本身——栈、层叠、游戏舞台——在 Surfaces 中说明

约定

  • 这里的每个节点都只支持 eventmacro,只有 Is Layer Mounted 例外:它是 pure,也能用于 function 图和蓝图值。本分类再没有第二个
  • 其中两个是尾节点。 Go PageQuit Application 根本没有 next 引脚。它们之后是另一个 Surface,或者根本没有应用了——这张图没有剩下什么可以继续跑
  • type id 并不都写 app 这一组在长出窗口、叠层和应用节点之前叫 Page。id 是要存进文档的东西,所以从未改名:blueprint.page.*blueprint.frame.*blueprint.layer.*blueprint.app.* 全都属于这同一个分类
  • 宿主。 Get Page Props 和三个 Is Surface … 读取节点可用于 Surface、控件和 Blueprint Value 蓝图;Get Page ParamEmit Page Event 可用于 Surface 和控件蓝图。它们都不存在于全局蓝图中——全局蓝图没有当前 Page 可问。导航、叠层、窗口、指针与链接节点没有宿主限制
  • 编辑器画布没有应用可以对话。 在作者时画布上,这里每个有副作用的节点都会以 Host API unavailable (use Dev Mode) 失败;纯读取节点则安静降级,Get Page Props 读到 {},三个 Is Surface … 读到 false。在故事编辑器的游戏界面预览里,导航和退出会打到桩上什么都不做,两个全屏节点则报告在那里不可用。这些都要在开发模式中验证

Go Page

blueprint.page.go · Latent

Go Page(前往页面)打开卡片上选中的 Surface,走的是运行时切换 Page 时的同一条导航路径——同样的进退场动画、同样的 Surface 生命周期、同样的控件蓝图。选择 None 则改为关闭当前顶层 Page 叠层

Go Page替换玩家眼前的东西。要把一页压在当前这一页之上并拿到一个句柄,用 Show Layer。要关掉一层,优先用 Go back:导航是一个栈,对着玩家来时的那一页再 Go Page,是在已有的两页上再压上第三页,而不是回到它。带 NoneGo Page 则关闭当前顶层 Page 叠层

引脚方向类型说明
inin · exec
surfaceIdin · datastring可选。连线即在运行时决定目标;未连线时用卡片上的 Page 字段
propsin · datajson作为目标 Page 的 Page props 传入,在那边由Get Page Props读取。可选——未连线时为{}

卡片字段

字段说明
Page要打开的 Surface,从项目的 app Surface 中选择。选None则改为关闭当前顶层 Page 叠层

游戏一旦跑起来,Page 栈会被藏到舞台下面。此时 Go Page 会把目标作为 UI 叠层打开在舞台之上None 则关闭该叠层、重新露出舞台。要离开游戏本身,请用 Game 里的 Quit Game——它自己指定要返回的 Page

Go back

blueprint.page.back · Latent

弹出顶层 Page 叠层,回到它下面的那一层。

游戏在运行中的故事上打开的每一个页面 —— 存档、读档、设置、回顾 —— 都需要一条出路,而 Go Page 不是:导航是一个栈,“去我来时那一页”会在已有的两页上再压上第三页,游戏依旧埋在底下。

关掉最后一页是空操作而不是错误。从标题界面进来的那一页就是栈底,如果在那里抛错,同一个返回按钮就会因为玩家怎么走过来的而时灵时不灵。

引脚方向类型说明
inin · exec
nextout · exec

Clear Page

blueprint.page.clear · Latent

把运行中的游戏身上披着的每一页都摘掉,只留下舞台 —— 在任何页面上按 Escape 都成立,图不必知道玩家现在有多深。

三种“关闭”并不能互换。Go back 只弹一层,所以在两页深的游戏上按 Escape 只会落到下面那一页。带 NoneGo Page 会清空整个栈,在标题界面上那会把标题本身也关掉。Clear Page 只清掉一次游戏会话叠上去的那些页面,而在游戏之外它什么也不做 —— 这正是 Clear Page → Go back 能当作一个通用 Escape 处理的原因:读起来就是“在游戏里就离开游戏,否则退一步”。

引脚方向类型说明
inin · exec
nextout · exec

Show Layer

blueprint.layer.show · Latent

把一页盖在屏幕上已有的东西之上,而不是替换掉它,并交回一个句柄指代这一次显示。

叠层的深度是没法寻址的。叠放次序就是挂载次序,而唯一能指代某一层的东西就是那个句柄 —— 对任何没拿到它的图来说毫无意义。这是刻意的:一个界面无法被写成依赖于“自己在从上往下第三层”,于是第一个想在暂停菜单之上再加一层的工程,不会把整套合成变成一个谁也改不动的编号方案。

引脚方向类型说明
inin · exec
nextout · exec
propsin · datajson这一层的 Page props,在里面由 Get Page Props 读取。可选
modalin · databoolean挡住与下面的交互。可填字面量。未连线读作 false
dismissiblein · databoolean玩家能否不靠图自己把它关掉。可填字面量。未连线读作 true —— 玩家出不来的一层是一个决定,没动过的引脚不该替人做这个决定
groupin · datastring可选。声明同一分组的层会互相排队,而不是叠在一起。可填字面量
layerout · datastring句柄。交给 Hide LayerWait For LayerIs Layer Mounted

卡片字段

字段说明
Page要显示的 Surface,与 Go Page 选的是同一份列表。一层就是一页;给两份不同的列表会暗示它们不是一回事

指向工程里没有的 Page 时,节点会带着宿主自己那句话失败,于是失败落在你能改的那一行上。

Hide Layer

blueprint.layer.hide · Latent

关掉句柄指代的那一层。

引脚方向类型说明
inin · exec
nextout · exec
layerin · datastring来自 Show Layer 的句柄

指向不存在的句柄 —— 从未设置过、或者已经关掉了 —— 是空操作而不是错误,和 Go back 在栈底做的是同一笔交易:那一层已经不在了,本来就是这个节点被要求达成的结果。

Wait For Layer

blueprint.layer.wait · Latent

挂起,直到那一层关闭,然后带着它关闭时给出的东西继续。

引脚方向类型说明
inin · exec
nextout · exec
layerin · datastring来自 Show Layer 的句柄
resultout · datajsonClose This Layer 收到的东西。被玩家关掉、或者关闭时什么都没给,都是 null

Close This Layer

blueprint.layer.closeSelf · Latent

关掉当前蓝图所在的那一层,并可以给等着它的人一个答复。这是 Wait For Layer 的另一半:被显示出来的那一页自己报告结果,而不是打开它的人伸手进去读。

引脚方向类型说明
inin · exec
nextout · exec
resultin · datajson可选。未连线时这一层以 null 关闭

Is Layer Mounted

blueprint.layer.isMounted · Pure

句柄指代的那一层是否还在屏幕上。本分类里唯一的 pure 节点,也就是唯一能支撑蓝图值的一个 —— 比如一个在自己的菜单开着时把自己禁用掉的暂停按钮。

引脚方向类型说明
layerin · datastring来自 Show Layer 的句柄
mountedout · databoolean空句柄或未知句柄都读作 false

Show Confirm

blueprint.layer.confirm · Latent

用你自己的一页去问一个问题,并从玩家按下的那个按钮走出去。

底下没有任何“确认框”专用机制。这个节点把选中的 Page 作为一个模态、可关闭、归在 confirm 分组的叠层显示出来 —— 于是第二个问题会排在第一个后面而不是压在它上面 —— 并通过普通的 Page props 把问题交给它:message,以及 buttons,形如 { id, text, index, disabled },可以直接给 List 绑定。用 Show Layer 手搭的一页读到的是一模一样的 props。

引脚方向类型说明
inin · exec
messagein · datastring问题本身。可填字面量
tagin · datastring可选。原样传给页面,供一页回答多个问题时区分。可填字面量
datain · datajson可选。原样传给页面
dismissedout · exec玩家没有作答就关掉了问题时走这里
indexout · datainteger按钮的位置;走 dismissed 时为 -1
labelout · datastring按钮的文字;走 dismissed 时为 ""

卡片字段

字段说明
Page画出这个问题的 Surface

卡片上的 Add Button 会追加一个 string 输入 button_N_label 和与它配对的 exec 输出 button_N_pressed,插在固定的 dismissed 之上。配对按 id 而不是按位置,所以从中间删掉一个按钮不会让编号错位,也不会把分支接错。页面用 Close This Layer 关闭自己来报告答案 —— 直接给一个下标,或者给一个带 index 字段的对象。其它任何东西,包括关闭时什么都不给,都走 dismissed

Keep Window Open

blueprint.app.keepWindowOpen

把当前这一次窗口关闭请求挂住,让游戏来决定,而不是窗口在它脚下关掉。想问一句“真的要退出吗”的游戏先跑它,显示自己的询问,玩家确认后再调 Quit Application

它不阻止任何其它东西。它不是用来吃掉一个按键、关掉一个页面或者阻断某个元素的事件的 —— 而且在关闭请求之外它根本无物可作用,那是一个执行错误而不是静默空操作,因为把它放在那里的图是在索要一个没人给它的保证。

只能用于全局与 Surface 图。

引脚方向类型说明
inin · exec
nextout · exec

Quit Application

blueprint.page.quit · Latent

Quit Application(退出应用程序)请求运行时干净地关闭应用——给状态保存、崩溃上报留出余地——而不是直接杀进程。在开发模式中,它结束当前开发模式会话并把你送回 Studio;它不会关闭 Studio 本身

引脚方向类型说明
inin · exec

这是运行时发起的退出,因此它刻意触发 On Window Close Requested。那个事件是为玩家关闭窗口而存在的,挂在它上面的确认处理不应该被一个已经决定要退出的节点触发

Set Fullscreen

blueprint.app.setFullscreen · Latent

Set Fullscreen(设置全屏状态)让应用窗口进入或退出全屏

引脚方向类型说明
inin · exec
nextout · exec

卡片字段

字段说明
ModeEnter FullscreenExit FullscreenToggle Fullscreen。下拉未设置时按 toggle 处理——这是全屏按钮最有用的默认值

Toggle Fullscreen 会先读当前窗口状态再取反,所以一个按钮就能覆盖两个方向,你不必自己记录状态

Get Fullscreen

blueprint.app.getFullscreen · Latent

Get Fullscreen(获取全屏状态)读取应用窗口当前是否处于全屏

引脚方向类型说明
inin · exec
nextout · exec
isFullscreenout · databoolean

它是 latent 而不是 pure,因为答案来自窗口而不是来自图——这也意味着它无法支撑 Blueprint Value。请改在事件图里驱动全屏指示

Get Window Size

blueprint.app.getWindowSize · Latent

舞台当前的像素尺寸,无论窗口处在什么状态。

引脚方向类型说明
inin · exec
nextout · exec
widthout · datafloat
heightout · datafloat

Set Window Size

blueprint.app.setWindowSize · Latent

把舞台调整到指定的像素尺寸。

项目 → 应用 → 窗口里列出的那些尺寸,是给设置界面搭出来用的,而不是图能提出什么要求的上限:尺寸另有来源的游戏 —— 它记住的一个值、它量到的一块显示器、玩家自己填的一个数 —— 就在这里说出来。它做不到的是跑出屏幕:尺寸会被收进显示器的工作区,并受窗口自身最小值约束,因为比桌面还大的窗口谁也用不了。

全屏与最大化会先被退出,理由和 Set Window Scale 一样:两者都是同一个问题的答案,在它们底下改出来的尺寸,玩家一退出就会弹回去。

引脚方向类型说明
inin · exec
nextout · exec
widthin · datafloat
heightin · datafloat

Get Window Scale

blueprint.app.getWindowScale · Latent

窗口当前的大小,写成设计尺寸的倍数。

引脚方向类型说明
inin · exec
nextout · exec
scaleout · datafloat1 是设计尺寸,0.5 是它的一半

Set Window Scale

blueprint.app.setWindowScale · Latent

把窗口调整到设计尺寸的某个倍数。

工程没有提供的倍数会被换成它提供的最接近的一档,而不是被拒绝:这一档档梯子是作者的,算出 0.8 的图应该把窗口挪一挪而不是失败。全屏与最大化会先被退出。

引脚方向类型说明
inin · exec
nextout · exec
scalein · datafloat不是正数的值会回落到 1

Get Window Scale Options

blueprint.app.getWindowScaleOptions · Latent

设置界面可以提供的那些尺寸,就是作者在项目 → 应用 → 窗口里写下的那份。把 List 绑到它上面,提供哪些尺寸就来自工程,而不是来自图里敲进去的数字。

引脚方向类型说明
inin · exec
nextout · exec
scalesout · dataarray设计尺寸的倍数

空是一个答案,不是失败。 没有自己窗口可调的宿主 —— Web 导出、开发模式、故事预览 —— 在这里什么也不返回,据此搭出来的那一行在那里也就什么都不画。这正是要的效果:一个动不了窗口的尺寸控件比没有更糟。

blueprint.app.openExternal · Latent

把一个地址交给玩家本来就在用的浏览器:商店页、更新说明、支持表单。

别处没有任何要声明的东西。图是作者写的,所以图里的地址就是作者的决定。宿主检查的是协议,而且只在真正要打开页面的那个进程里检查、绝不在渲染进程里 —— 能到达的只有 http:https:mailto:。这是一份允许清单,而不是把已知有害的那几个列出来禁掉:shell.openExternal 会把地址交给操作系统为它登记的任何程序,而玩家装的任何软件都能登记一个新协议。清单之外的协议 —— 最常被问到的是 steam: —— 要通过插件到达,那条路上模式写在 manifest 里,安装时逐条具名批准。

引脚方向类型说明
inin · exec
urlin · datastring连线优先于卡片。空地址是执行错误
nextout · exec页面已经交出去时走这里
failedout · exec
errorout · datastring是哪一种拒绝

卡片字段

字段说明
URL直接填写的地址。url 引脚未连线时用它

failed 同时覆盖“这个节点打不开的协议”和“浏览器没能打开这一页”。它们共用一个引脚,因为作者对两者的应对是同一个 —— 玩家没拿到这一页,那就给他看点别的。

不是网络权限。这里不发请求,也没有任何字节回到游戏里,所以它不受工程网络设置的约束,把网络关掉也不会禁用它。见安全

Move Mouse To

blueprint.app.movePointerTo · Latent

把玩家真正的光标放到 Surface 上的某个点。这与 Windows 在“自动移到默认按钮”打开时替你做的是同一件事,也是对手柄友好的菜单需要、而只认鼠标的菜单假装不出来的东西。

坐标是 Surface 的设计坐标 —— 和 Get Measured RectGet Bounds 以及每个鼠标事件的 x / y 是同一套。作者看不见窗口,也不该被要求去想它。

引脚方向类型说明
inin · exec
pointin · dataVector2DData 里的 Make Vector2D 造一个
nextout · exec
failedout · exec
errorout · datastring

卡片字段

字段说明
Duration (s)0 是立刻放过去;任何正值都会让它走过去
EasinglineareaseIneaseOuteaseInOut

只在桌面构建与开发模式下有效。 Web 导出无法摆布系统指针,节点在那里走 failed;而当一个含有这些节点的工程被导出到非桌面目标时,构建控制台会给出警告 —— 作者该从构建里知道这件事,而不是从玩家那里。

Move Mouse To Element

blueprint.app.movePointerToElement · Latent

同一件事,但瞄准某个控件的中心,而且是量出来的,不是从文档里算出来的。

它是一个独立的节点,而不是 Get Element Measured RectMove Mouse To,因为两半必须对“这个控件最后被画在哪个 Surface 上”达成一致。组件实例是被放到哪里就在哪里渲染内容的,那未必是这个元素被写下时所属的 Surface。

引脚方向类型说明
inin · exec
elementin · dataelement
nextout · exec
failedout · exec
errorout · datastring

卡片字段

字段说明
Duration (s)同上
Easing同上

Get Page Props

blueprint.page.getProps · Pure

Get Page Props(获取页面属性)读取当前蓝图所在 Page 的完整 props 对象。由 Go Page 打开的 Page 读到的是该节点的 props 输入;被 nl.frame 嵌入的 Page 读到的是 Frame 的 params。它永远不为 null——什么都没传就打开的 Page 读到 {}

引脚方向类型说明
propsout · datajson

要取出其中某个字段,用 Data 里的 Get JSON Field,它接受点分路径

Get Page Param

blueprint.frame.getParam · Pure

Get Page Param(获取页面参数)按名字读取当前 Page props 中的单个字段

这个节点不在创建浮窗里。 你无法新增它;这里记录它,是因为更早写下的文档里仍然存在它,而且它仍然能跑。它的替代是 Get Page PropsGet JSON Field,后者读的是嵌套路径而不是一个扁平的 key

引脚方向类型说明
keyin · datastring可在节点卡上直接填字面量
valueout · datajson字段不存在时读到null

Emit Page Event

blueprint.frame.emit · Latent

Emit Page Event(触发页面事件)把事件从被嵌入的 Page 向上发给嵌入它的那个 nl.frame 元素。Frame 自己的蓝图通过 Events 里的 Page Event 事件 Head 接收,并原样拿到同一个名字和载荷

引脚方向类型说明
inin · exec
nextout · exec
eventin · datastring事件名。可在节点卡上直接填字面量
datain · datajson交给处理方的载荷

顶层 Page 没有父级 Frame。节点仍会正常完成、执行也会继续从 next 出去——只是这个事件没有任何人能收到,而且没有任何提示。空事件名则相反:那是执行错误,不是静默的 no-op

Is Surface Entering

blueprint.page.isSurfaceEntering · Pure

Is Surface Entering(画面是否进入中):从该 Surface 的 runtime scope 挂载那一刻起,直到它的进入动画结束为止,返回 true

引脚方向类型说明
isEnteringout · databoolean

这个标记会在 After Surface Enter 触发之前被清掉,所以在该事件里读它会得到 false。这是刻意的:事件跑起来的时候,Surface 已经进入完成了

Is Surface Exiting

blueprint.page.isSurfaceExiting · Pure

Is Surface Exiting(画面是否退出中):从 Before Surface Exit 触发、退出动画开始那一刻起,直到 Surface 卸载为止,返回 true。可以用它拒绝一个正在离开的界面上的输入

引脚方向类型说明
isExitingout · databoolean

Is Surface Transitioning

blueprint.page.isSurfaceTransitioning · Pure

Is Surface Transitioning(画面是否过渡中):上面两者任一为 true 时它就为 true——用一次读取回答这个界面是否还没稳定下来

引脚方向类型说明
isTransitioningout · databoolean

本页目录