Phaser 3.60 BitmapText 增强详解:行距控制、显示尺寸、边界组件与字距(Kerning)修复
【免费下载链接】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 3.60.0 变更日志中 BitmapTextGameObject.md 的内容为骨架,系统讲解该版本为BitmapText游戏对象带来的四项核心变化:新增setLineSpacing行距控制、新增只读属性displayWidth/displayHeight并接入GetBounds组件、GetBitmapTextSize结果中新增字符索引idx,以及字距(kerning)偏移在 WebGL 与 Canvas 两种渲染器下的修复。读完本文,你将能够利用行距精确排版多行位图字体文本,正确获取 BitmapText 的尺寸与边界(包括放入Container之后的场景),并理解 kerning 从字体解析、尺寸计算到逐字符渲染的完整链路。
一、3.60 中 BitmapText 更新总览
在 changelog/v3/3.60/BitmapTextGameObject.md 中,Phaser 3.60.0 对BitmapText的变更可分为三类:
| 类别 | 变更内容 | 说明 |
|---|---|---|
| 新特性 | BitmapText.setLineSpacing方法及lineSpacing属性 | 控制多行位图文本的垂直行距,可正可负,用法与Text对象一致 |
| 更新 | 新增只读属性displayWidth、displayHeight | 使 BitmapText 能正确配合GetBounds组件使用 |
| 更新 | BitmapText 接入GetBounds组件 | 修复 #6237,可在Container中正确获取尺寸 |
| 更新 | GetBitmapTextSize结果新增idx字段 | 字符在原始文本中的索引,不受自动换行影响 |
| 修复 | 逐字符 kerning 偏移 | 渲染时正确应用字距偏移 |
| 修复 | GetBitmapTextSize中的 kerning 计算 | WebGL 与 Canvas 渲染结果一致 |
下文逐一展开每个变更的用法、源码实现与验证方式。
二、行距控制:setLineSpacing 与 lineSpacing
2.1 API 形态
3.60 之前,多行 BitmapText 的行与行之间只能使用字体文件自身定义的lineHeight,间距不可调整。3.60 起可以通过两种方式控制:
// 方式一:链式方法(默认值 0) bitmapText.setLineSpacing(8); // 方式二:直接读写属性 bitmapText.lineSpacing = 8;关键约定:
- 该值会叠加到字体本身的 lineHeight 之上,用于计算整体行高;
- 可正可负:正值加大行距,负值收紧行距;
- 仅对多行文本(以
\r、\n或\r\n分隔)生效,单行文本没有影响; - 传入
undefined时默认重置为0。
2.2 源码实现
方法定义位于 src/gameobjects/bitmaptext/static/BitmapText.js:
setLineSpacing: function (spacing) { if (spacing === undefined) { spacing = 0; } this.lineSpacing = spacing; return this; },内部通过lineSpacing属性(同一文件)写入私有字段_lineSpacing,并将_dirty标记为true,从而触发后续尺寸重算:
lineSpacing: { set: function (value) { this._lineSpacing = value; this._dirty = true; }, get: function () { return this._lineSpacing; } },_lineSpacing字段本身从 3.60.0 开始引入(src/gameobjects/bitmaptext/static/BitmapText.js)。在序列化方法toJSON中,lineSpacing也会随font、text、fontSize、letterSpacing、align一起被导出(同一文件),便于场景数据持久化。
2.3 行距在尺寸计算中的生效点
行距真正参与排版的位置在 src/gameobjects/bitmaptext/GetBitmapTextSize.js。当遍历到换行符(charCode 10)时,通过以下公式计算下一行文字的 y 偏移:
yAdvance = (lineHeight + lineSpacing) * currentLine;也就是说,第 N 行文字相对顶部的偏移是(lineHeight + lineSpacing) × N。这也解释了为什么行距对单行文本无影响——只有出现第二个换行符时currentLine才会递增。
2.4 实战示例
this.add.bitmapText(100, 100, 'desyrel', 'First Line\nSecond Line\nThird Line') .setLineSpacing(12); // 三行文本,行距加大 12 像素将行距设为负值可让多行文本更加紧凑:
bitmapText.setLineSpacing(-4); // 行距收紧 4 像素三、displayWidth / displayHeight 与 GetBounds 组件
3.1 背景:Issue #6237
3.60 之前,BitmapText 在放入Container后,Container无法正确计算其边界尺寸,因为 BitmapText 缺少GetBounds组件所依赖的displayWidth/displayHeight属性。3.60 为 BitmapText 补齐了这两个只读属性并接入GetBounds组件(src/gameobjects/bitmaptext/static/BitmapText.js 的 Mixins 列表包含Components.GetBounds)。
3.2 属性语义
displayWidth(源码)与displayHeight(源码)均为只读属性:
- 返回考虑了缩放因子的显示尺寸(内部委托给
width/height的取值器,它们基于getTextBounds计算出的global尺寸); - 允许赋值:setter 会先将 X/Y 方向缩放重置为 1,再根据目标尺寸反算
scaleX/scaleY,等价于“以目标显示尺寸反向设定缩放”; - 文档注释明确标注
@readonly,推荐通过setDisplaySize(3.61+)或直接设置scale来控制显示大小。
3.3 GetBounds 组件提供的能力
接入后,BitmapText 拥有了 src/gameobjects/components/GetBounds.js 提供的一整套边界查询方法:
getBounds():返回轴对齐包围矩形(AABB),会考虑旋转与父Container变换;getTopLeft()/getTopRight()/getBottomLeft()/getBottomRight():四角坐标;getCenter()/getTopCenter()/getLeftCenter()等:中心与边中点;- 所有方法都支持
includeParent参数,以纳入父Container的变换矩阵。
这些方法在内部均依赖this.displayWidth与this.displayHeight计算(参见 GetBounds.js 的getCenter实现),这正是新增两个属性的意义所在。
3.4 实战示例
const label = this.add.bitmapText(200, 150, 'desyrel', 'Score: 100'); // 直接读取显示尺寸(已包含 scale) console.log(label.displayWidth, label.displayHeight); // 获取边界矩形,用于碰撞检测或布局 const rect = label.getBounds(); console.log(rect.x, rect.y, rect.width, rect.height); // 放入 Container 后依然能正确计算 const group = this.add.container(0, 0, [label]); const boundsInGroup = label.getBounds(); // 已计入父容器变换四、GetBitmapTextSize 新增字符索引 idx
4.1 背景
GetBitmapTextSize在计算尺寸时,会把超长文本按maxWidth自动换行(src/gameobjects/bitmaptext/GetBitmapTextSize.js)。换行会向文本中插入\n,导致“换行后的字符下标”与“原始文本中的字符下标”不一致。原有i字段表示的是换行处理后的索引,无法定位回原始字符串。
4.2 idx 字段
3.60 起,characters数组中的每个BitmapTextCharacter对象新增idx字段(src/gameobjects/bitmaptext/GetBitmapTextSize.js):
characters.push({ i: charIndex, // 换行后的索引(原有) idx: i, // 原始文本中的索引(3.60 新增) char: text[i], code: charCode, // ... });类型定义位于 src/gameobjects/bitmaptext/typedefs/BitmapTextCharacter.js:
i:该字符在换行后文本中的索引;idx:该字符在原始文本(不包含自动换行)中的索引。
4.3 使用场景
当maxWidth触发自动换行时,idx可用来把字符级信息(如逐字染色、命中检测)映射回用户输入的原字符串:
const bounds = bitmapText.getTextBounds(); bounds.characters.forEach((char) => { // char.idx 指向原始文本下标,不受换行影响 console.log(char.idx, char.char, char.x, char.y); });注意:BitmapTextCharacter中x/y已按字号缩放,但尚未换算为游戏对象本地坐标,w/h为字形尺寸,t/r/b分别表示所在行的顶部、字符右沿与行底部(typedefs 说明)。
五、字距(Kerning)修复:从解析到渲染
5.1 问题
3.60 修复了两个 kerning 相关问题:
- 渲染阶段:BitmapText 渲染时没有应用逐字符的 kerning 偏移;
- 尺寸计算阶段:
GetBitmapTextSize中的 kerning 计算有误,导致文本尺寸与实际绘制不一致。
修复后,WebGL 与 Canvas 两种渲染器的表现统一。
5.2 字体解析:kerning 数据的来源
kerning 数据来自位图字体 XML 的<kerning>节点,解析逻辑在 src/gameobjects/bitmaptext/ParseXMLBitmapFont.js:
var kernings = xml.getElementsByTagName('kerning'); for (i = 0; i < kernings.length; i++) { var kern = kernings[i]; var first = getValue(kern, 'first'); var second = getValue(kern, 'second'); var amount = getValue(kern, 'amount'); data.chars[second].kerning[first] = amount; }每个字形对象持有kerning映射表(键为前一个字符的 charCode),在构造字形时初始化为空对象(ParseXMLBitmapFont.js)。
5.3 尺寸计算:kerning 的正确应用
GetBitmapTextSize遍历字符时,会查询当前字形相对上一个字符的 kerning 偏移(src/gameobjects/bitmaptext/GetBitmapTextSize.js):
if (lastGlyph !== null) { var kerningOffset = glyph.kerning[lastCharCode]; x += (kerningOffset !== undefined) ? kerningOffset : 0; }该偏移同时参与字符宽度计算与 x 方向推进(GetBitmapTextSize.js):
var charWidth = glyph.xOffset + glyph.xAdvance + ((kerningOffset !== undefined) ? kerningOffset : 0); // ... xAdvance += glyph.xAdvance + letterSpacing + ((kerningOffset !== undefined) ? kerningOffset : 0);5.4 渲染:BatchChar 逐字符提交
WebGL 渲染路径中,每个字符通过 src/gameobjects/bitmaptext/BatchChar.js 提交为四边形:
var x = (char.x - src.displayOriginX) + offsetX; var y = (char.y - src.displayOriginY) + offsetY; var xw = x + char.w; var yh = y + char.h;由于char.x已在尺寸计算阶段(5.3 节)计入 kerning 偏移,渲染时直接使用该位置即可,从而保证“量出来”的尺寸与“画出来”的结果一致。Canvas 渲染器同理,统一消费GetBitmapTextSize的输出。
5.5 验证:测试用例
仓库测试 tests/gameobjects/bitmaptext/GetBitmapTextSize.test.js 中包含对 kerning 的精确断言(describe('kerning')分组):
- 字形 B 声明相对 A(charCode 65)的 kerning 偏移为
-2; - 期望
'AB'的整体宽度为18(而非无 kerning 时的20),且字符 B 的x为8。
同一测试文件还覆盖了 3.60 新增特性的相关行为:
lineSpacing生效:'A\nB'且lineSpacing=4时,第二行 y 偏移为(16 + 4) × 1 = 20,整体高度为36(describe('multi-line text'));maxWidth自动换行写入wrappedText(describe('word wrap'));round、updateOrigin、letterSpacing、对齐等既有行为回归(describe('letter spacing')、describe('alignment')等)。
六、升级与使用建议
- 行距排版:3.60 起,多行 BitmapText 的垂直节奏可以像
Text一样自由控制,优先使用setLineSpacing而不是手动拼接带空行的字符串; - 布局与碰撞:新接入的
GetBounds组件让 BitmapText 在Container中也能得到正确边界,涉及 UI 面板、对话框、按钮文字对齐时可直接使用getBounds()/getCenter()等方法; - 字符级操作:如果需要把字符坐标映射回原始输入字符串(例如逐字描边、点击判字),优先使用
characters[].idx而非i; - 字体文件:kerning 修复后,携带
<kerning>节点的 BMFont 类字体在两种渲染器下表现一致;若发现个别字符间距异常,可从字体 XML 的first/second/amount节点数据入手排查。
完整的 3.60.0 变更汇总可查阅 changelog/v3/3.60/CHANGELOG-v3.60.md。
【免费下载链接】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),仅供参考