界面控件
按钮、进度条这类控件在独立的包 @sapling2d/ui 里:
sh
pnpm add @sapling2d/ui作为插件安装:plugins: [ui()] 预加载一套中性的灰白圆角默认皮肤,并提供全局的按下反馈和点击音效默认值。不装插件也能用:传自己的贴图照常工作,texture: null 是纯色模式。
按钮
ts
import { CanvasLayer, Scene, sfx, v } from 'sapling2d'
import { Button, ui } from '@sapling2d/ui'
export const plugins = [ui({ clickSound: sfx('click.mp3') })] // 预加载默认皮肤;所有按钮点击都播这个音效
export class Menu extends Scene {
started = false
// 锚点按安全区摆放:开始按钮水平居中、离底边 200;商店按钮贴右上角。屏幕变化时自动重排
readonly start = new Button({ name: 'Start', text: '开始游戏', size: v(360, 110), anchor: { centerX: 0, bottom: 200 } })
readonly shop = new Button({ name: 'Shop', text: '商店', size: v(200, 80), anchor: { right: 24, top: 24 }, texture: null, backgroundColor: 0x6a4ad9 })
override ready() {
const hud = this.add(new CanvasLayer()) // 界面放在 CanvasLayer 里:不跟随相机,用设计坐标
hud.add(this.start)
hud.add(this.shop)
this.shop.disabled = true // 还没解锁:半透明、点不到
this.start.clicked.connect(() => (this.started = true))
}
}ts
g.pointerDown(375, 1079) // 开始按钮的中心(750×1334 的设计分辨率,安全区就是整屏)
g.step(8)
expect(g.scene.start.body.scale.x).toBeCloseTo(1.1, 2) // 按下反馈:视觉主体围绕中心放大,节点自身的 rect 不变
g.pointerUp(375, 1079)
g.step()
expect(g.scene.started).toBe(true)
expect(g.audio.log.map((s) => s.path)).toEqual(['click.mp3'])
g.tap(626, 64) // 禁用的商店按钮:点不到
expect(g.dump()).toContain('Shop (Button) position=(526, 24) alpha=0.5 size=(200, 80) anchor=right:24,top:24 disabled=true')Button 由九宫格底图和居中的 label 组成。底图可以是默认皮肤、自己的 texture,或 texture: null 加 backgroundColor 的纯色。
| 成员 | 说明 |
|---|---|
clicked | 按下并在按钮内抬起(Node2D 的信号) |
pressed / released、isPressed | 按下状态 |
text、label | 文字 |
disabled | 半透明,点不到 |
pressEffect | 按下反馈,默认 { scale: 1.1, tint?, duration: 0.08 },false 关闭 |
clickSound | 点击音效;null 关闭 |
按下反馈作用在内部的 body 上(原点在按钮中心),默认围绕中心放大,按钮自身的 scale 和 rect 不变。图标等装饰也加到 body 下。ui({ pressEffect }) 改全局默认。
手指的路径:
- 按下 → 视觉按下;滑出 → 恢复(仍然持有这根手指);滑回 → 再次按下;在按钮内抬起 →
clicked。 - 在外面抬起、切后台、按钮被隐藏或盖住、途中变成
disabled→ 只发released。 - 只认第一根手指。
点击音效:ui({ clickSound: sfx('click.mp3') }) 给所有按钮设默认值(由插件预加载)。按钮自己的 clickSound 优先,记得放进场景的 static assets。
Control 与锚点
Control(继承 Node2D)是界面控件的基类,直接实例化就是一个布局盒子。
size/rect,原点在左上角(和ColorRect一样);没设hitArea时,点击区域就是这个矩形。anchor: { left | right | centerX, top | bottom | centerY }是到safeRect各边或中心的距离,进树和屏幕变化时自动重排。同时给left和right时拉伸宽度。anchorTo: 'visible'改为相对整个可见区域(而不是安全区)。
锚点只在 CanvasLayer 里有意义
anchor 是屏幕位置(设计坐标)。控件放在场景里会跟着相机走,并警告一次。
进度条
ts
import { CanvasLayer, Scene, v } from 'sapling2d'
import { ProgressBar, ui } from '@sapling2d/ui'
export const plugins = [ui()]
export class Battle extends Scene {
hp = 100
// 血条:默认皮肤(深色底 + 圆角填充),范围直接用血量;Boss 血条从右往左消
readonly hpBar = new ProgressBar({ name: 'Hp', size: v(400, 36), maxValue: 100, value: 100, anchor: { left: 24, top: 24 } })
readonly bossBar = new ProgressBar({ name: 'Boss', size: v(600, 24), value: 1, fillMode: 'rightToLeft', fillColor: 0xe04040, anchor: { centerX: 0, top: 90 } })
override ready() {
const hud = this.add(new CanvasLayer())
hud.add(this.hpBar)
hud.add(this.bossBar)
}
hit(damage: number) {
this.hp = Math.max(0, this.hp - damage)
this.hpBar.createTween().to(this.hpBar, { value: this.hp }, 0.2) // value 可以补间:血条平滑地掉
this.hpBar.fillColor = this.hp > 30 ? 0x5ad26a : 0xe04040 // 血少了变红
}
}ts
g.scene.hit(60)
g.stepSeconds(0.3)
expect(g.scene.hpBar.value).toBe(40)
expect(g.scene.hpBar.ratio).toBeCloseTo(0.4)
// 填充节点始终铺满(388 = 400 - 两边各 6 的内缩),只是被 clipRect 裁到 40%:九宫格不变形
expect(g.scene.hpBar.fill.size).toEqual(v(388, 24))
expect(g.scene.hpBar.fill.clipRect?.width).toBeCloseTo(388 * 0.4)
expect(g.scene.hpBar.fillColor).toBe(0x5ad26a)
g.scene.hit(20)
g.stepSeconds(0.3)
expect(g.scene.hpBar.fillColor).toBe(0xe04040)
expect(g.dump()).toContain('Hp (ProgressBar) position=(24, 24) size=(400, 36) anchor=left:24,top:24 value=20')value夹在minValue..maxValue之间(默认 0..1,血条可以直接maxValue: 100);ratio是 0–1 的比例。value可以补间。valueChanged只在值真的变化时发出。fillMode:leftToRight(默认)、rightToLeft、bottomToTop、topToBottom。- 底
background和填充fill各是一张九宫格贴图(默认皮肤是深色面板 + 按fillColor染色的白色填充),也可以用纯色(textureBackground: null/textureFill: null加backgroundColor/fillColor)。fillPadding让填充相对底图内缩,默认皮肤是 6。 - 填充节点始终铺满整条,用
clipRect裁出已填充的部分,所以九宫格的圆角和高光在半满时不变形。不要用改scale.x的老办法。值为 0 时填充不显示,满时不裁剪。
暂停菜单
tree.paused 时控件默认不响应。暂停菜单的面板设 processMode: 'always'。