Phaser 3.60 九宫格(Nine Slice)游戏对象实战指南:打造不拉伸边角的自适应 UI 面板
【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser
Phaser 从 v3.60 起内置了原生 Nine Slice(九宫格)游戏对象,用于解决 UI 面板、按钮等元素在缩放时纹理被整体拉伸导致边角变形的痛点。本文以 changelog/v3/3.60/NineSliceGameObject.md 为主线,结合 NineSlice.js 等源码实现,完整讲解其布局原理、3 Slice / 9 Slice 的创建方式、全部构造参数、尺寸约束与底层顶点批量渲染机制,读完即可在项目中落地使用。
一、为什么需要 Nine Slice:UI 缩放的经典难题
在 HTML5 游戏中,按钮、面板、对话框等 UI 元素往往需要随屏幕分辨率或内容动态伸缩。如果直接把整张纹理等比拉伸,圆角、描边和装饰边角都会跟着变形,视觉上明显失真。
Nine Slice(又称九宫格 / 9-slice scaling)的解决思路是:把纹理拆成 3×3 的九块区域,四角固定不动,只有中间的连接区域参与拉伸。Phaser v3.60 将其实现为原生游戏对象,供 UI 与按钮类元素使用,同时支持一种更简化的「3 Slice」横向变体。该配置概念源自 Pixi 的 NineSlicePlane(见 NineSlice.js 源码注释)。
二、核心原理:9 区域布局结构
Nine Slice 游戏对象所使用的纹理必须遵循以下布局结构:
A B +---+----------------------+---+ C | 1 | 2 | 3 | +---+----------------------+---+ | | | | | 4 | 5 | 6 | | | | | +---+----------------------+---+ D | 7 | 8 | 9 | +---+----------------------+---+当改变该对象的宽和/或高时,各区域遵循如下伸缩规则:
| 区域 | 位置 | 缩放行为 |
|---|---|---|
| 1、3、7、9 | 四个角 | 完全不变形(保持原始尺寸) |
| 2、8 | 上、下中段 | 仅水平拉伸 |
| 4、6 | 左、右中段 | 仅垂直拉伸 |
| 5 | 中心区域 | 水平和垂直同时拉伸 |
三、3 Slice 变体:仅横向拉伸
如果元素只在水平方向上伸缩(如进度条、血条、横向菜单项),可以创建 3 Slice 游戏对象。它与 9 Slice 原理相似,但只能横向拉伸、不能改变高度,因此配置参数更少:
A B +---+----------------------+---+ | | | | C | 1 | 2 | 3 | | | | | +---+----------------------+---+改变宽度时:区域 1 和 3 保持不变,区域 2 水平拉伸。
判断方式很直接:只提供leftWidth和rightWidth即为 3 Slice;要创建 9 Slice 必须提供全部四个切片参数。在源码中,is3Slice通过(!topHeight && !bottomHeight)判定(NineSlice.js),创建后该属性只读,无法在 3 Slice 与 9 Slice 之间互相转换。
四、创建 Nine Slice 游戏对象
4.1 工厂方法 this.add.nineslice
工厂方法在 NineSliceFactory.js 中注册,用法为:
this.add.nineslice(x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight, tileX, tileY);完整参数说明(括号内为默认值):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x/y | number | — | 对象在世界中的中心位置 |
texture | string | Texture | — | 纹理键,或 Texture 实例 |
frame | string | number | — | 可选帧名/帧序号 |
width | number | 256 | 对象宽度,创建后仍可调整 |
height | number | 256 | 对象高度;3 Slice 对象的高度固定为纹理高度,无法修改 |
leftWidth | number | 10 | 左竖列(A 区域)尺寸 |
rightWidth | number | 10 | 右竖列(B 区域)尺寸 |
topHeight | number | 0 | 顶横行(C 区域)尺寸,置 0 或 undefined 即创建 3 Slice |
bottomHeight | number | 0 | 底横行(D 区域)尺寸,置 0 或 undefined 即创建 3 Slice |
tileX | boolean | false | 水平中段改为平铺而非拉伸 |
tileY | boolean | false | 垂直中段改为平铺而非拉伸 |
典型调用:
// 9 Slice:四角各 20 像素,宽度 300、高度 200 this.add.nineslice(400, 300, 'ui-panel', null, 300, 200, 20, 20, 20, 20); // 3 Slice:左右边角各 24 像素,宽度 240 this.add.nineslice(400, 300, 'ui-bar', null, 240, 24, 24, 24);4.2 配置对象方式 this.make.nineslice
使用this.make.nineslice(config)可以传入配置对象创建(NineSliceCreator.js),配置字段定义在 NineSliceConfig.js:
this.make.nineslice({ key: 'ui-panel', width: 300, height: 200, leftWidth: 20, rightWidth: 20, topHeight: 20, bottomHeight: 20, add: true // 创建后加入场景 });4.3 运行时重置:setSlices
setSlices(width, height, leftWidth, rightWidth, topHeight, bottomHeight, skipScale9)可以重置尺寸与切片配置(NineSlice.js),适合切换纹理复用同一对象,但注意它不能改变 3 Slice / 9 Slice 类型。
五、尺寸约束与常用 API
5.1 最小尺寸
- 最小宽度=
leftWidth+rightWidth; - 最小高度=
topHeight+bottomHeight(9 Slice)。
如果需求尺寸小于最小尺寸,应通过setScale/displayWidth缩放实现,而不是继续调小宽高。源码中的width与heightsetter 会强制钳制到下限(NineSlice.js、NineSlice.js),3 Slice 对象修改高度会被忽略。
5.2 常用方法与属性
| API | 作用 |
|---|---|
setSize(w, h) | 同时设置宽高,并同步更新命中区域(hitArea)尺寸(NineSlice.js) |
setDisplaySize(w, h) | 按显示尺寸调整scale(NineSlice.js) |
displayWidth/displayHeight | 读取/设置含缩放后的显示尺寸 |
setOrigin(x, y) | 设置原点并重建顶点(默认 0.5 居中) |
setTint(color)/clearTint() | WebGL 下设置/清除着色 |
setSizeToFrame() | 恢复为纹理帧尺寸 |
设置width、height或origin时,内部会自动调用updateVertices()重建顶点位置,无需手动干预。
六、源码原理:顶点与批量渲染
从源码实现看,Nine Slice 并非多个 Sprite 的组合,而是由内部维护的vertices顶点数组渲染的单一网格对象:
- 每个四边形由 6 个
NineSliceVertex顶点构成; - 3 Slice 对象包含18 个顶点(3 个四边形),9 Slice 对象包含54 个顶点(9 个四边形);
- 顶点按“左上、上中、右上、左中、中心、右中、左下、下中、右下”的顺序排列(NineSlice.js)。
渲染时,NineSliceWebGLRenderer.js 逐个四边形把顶点交给 batch 处理器,与场景中其他 Sprite、Graphics 的绘制调用一起合批提交,不产生额外的绘制开销(draw call)。因此:
- 1 个 3 Slice 对象 ≈ 并排 3 个 Sprite 的性能;
- 1 个 9 Slice 对象 ≈ 并排 9 个 Sprite 的性能。
WebGL 专属限制
截至 Phaser 3.60,Nine Slice仅支持 WebGL 渲染。在 NineSliceRender.js 中,renderCanvas为NOOP(空操作),只有WEBGL_RENDERER构建定义生效时才挂载 WebGL 渲染器;nineslice工厂方法同样只在WEBGL_RENDERER下注册。使用 Canvas 渲染器时该对象无法显示,请将render.type配置为Phaser.AUTO或Phaser.WEBGL。
七、进阶:平铺模式(tileX / tileY)
对于重复花纹(如齿轮纹理、网格背景),拉伸中段会造成图案变形。此时可启用平铺模式:tileX让水平中段重复铺开,tileY让垂直中段重复铺开(NineSlice.js)。
实现上,updateVertices()会先通过_calcRepeatCount()计算中段能容纳的整数平铺次数,再按平铺数重建顶点数组并更新 UV(NineSlice.js)。注意:每个瓦片仍会被轻微拉伸以凑整整数数量,因此纹理最好是无缝的,以免平铺接缝处出现可见瑕疵。
八、进阶:Texture Packer 7.1 的 scale9 数据
从 Phaser 3.70 起(Frame.js 的Frame.setScale9),Nine Slice 支持直接读取Texture Packer 7.1.0 及以上版本导出的 atlas JSON 中的 scale9 数据:
this.add.nineslice(400, 300, 'ui-panel');当帧带有frame.scale9数据时,构造函数与setSlices会自动从scale9Borders读取四个切片尺寸,无需再手动传参;width、height缺省时也会自动采用帧原始尺寸。需要说明的是,该对象不支持 Texture Packer 的裁剪(trimmed)纹理,因为裁剪会干扰纹理的正确拉伸。
九、测试与验证
仓库中 Nine Slice 相关单元测试位于 tests/gameobjects/nineslice/:
- NineSliceFactory.test.js:验证工厂模块在 WebGL 构建定义下可正常加载;
- NineSliceCreator.test.js、NineSliceRender.test.js、NineSliceVertex.test.js:分别覆盖创建器、渲染器与顶点类的基础行为。
由于 NineSlice 依赖完整的 WebGL 场景上下文,纯 Node 环境下的测试以导入冒烟测试为主,实际效果建议在浏览器示例中验证。
小结
Phaser 3.60 的 Nine Slice 游戏对象将九宫格缩放这一 UI 常用能力原生化:通过leftWidth/rightWidth/topHeight/bottomHeight四个参数即可声明边角保护区,配合width/height/setSize动态调整大小而保持四角不变形;3 Slice 变体满足纯横向拉伸场景;tileX/tileY与 Texture Packer scale9 数据则进一步覆盖了平铺与编辑器工作流需求。其全部顶点统一合批渲染,在 WebGL 下与 Sprite、Graphics 共存于显示列表而不产生额外开销,是构建自适应 UI 的理想选择。
【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考