feat(2d): support rotated sprites in atlas#2990
Conversation
WalkthroughThe PR expands sprite UV handling to support a full 16-vertex (4×4) grid with atlas 90° rotation awareness. Sprite's UV storage and computation are rewritten; size calculation accounts for rotated atlas dimensions; the loader explicitly initializes the rotation flag; and three assemblers adapt their UV reading to use column-major indexing into the new grid. ChangesUV Grid Expansion and Atlas Rotation Support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/2d/sprite/Sprite.ts`:
- Around line 299-310: The sprite's cached size and UVs must be invalidated when
_atlasRotated changes: in the atlasRotated setter (the code that flips
this._atlasRotated), after toggling the boolean clear the cached computed size
and UVs by setting this._automaticWidth and this._automaticHeight to undefined
(or null) and force UV recompute/clear by invoking or resetting whatever
_getUVs() cache (e.g., call this._getUVs() or set the UV cache to null) and mark
the sprite dirty so width/height/_getUVs() will be recalculated; apply the same
invalidation logic wherever _atlasRotated can change.
- Around line 352-366: The rotated branch misapplies trim offsets—when
atlasRotated is true you must rotate the offsets and border axes to match the
packed axes mapping (original region/offset left/top/right/bottom →
bottom/left/top/right); update the computations that set left/top/right/bottom
and bLeft/bTop/bRight/bBottom to use the swapped offsets and border components:
for the X-side math (calculations that use atlasRegionW/realWidth and
regionBottom/regionTop) use offsetBottom/offsetTop and border.y/w where
appropriate, and for the Y-side math (calculations that use
atlasRegionH/realHeight and regionLeft/regionRight) use offsetLeft/offsetRight
and border.x/z accordingly so trimmed rotated sprites and 9-slice boundaries
align correctly (references: atlasRotated, realWidth, realHeight,
atlasRegionX/Y/W/H, regionLeft/Top/Right/Bottom, offsetLeft/Top/Right/Bottom,
border.x/y/w/z, regionW/regionH).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c7a666d6-7c92-41c5-b8a9-6232f629a763
📒 Files selected for processing (5)
packages/core/src/2d/assembler/SimpleSpriteAssembler.tspackages/core/src/2d/assembler/SlicedSpriteAssembler.tspackages/core/src/2d/assembler/TiledSpriteAssembler.tspackages/core/src/2d/sprite/Sprite.tspackages/loader/src/SpriteAtlasLoader.ts
| const { _texture, _atlasRegion, _atlasRegionOffset, _region, _atlasRotated } = this; | ||
| const ppuReciprocal = 1.0 / Engine._pixelsPerUnit; | ||
| // 先算 atlas 中绝对像素(texture 不一定是方形,必须各自乘对应维度) | ||
| const atlasPxW = _texture.width * _atlasRegion.width; | ||
| const atlasPxH = _texture.height * _atlasRegion.height; | ||
| // atlas 顺时针 pack 90°:原图 W×H 在 atlas 中占 H×W 区域,仅交换 atlasPx 的 W/H | ||
| const originWidth = _atlasRotated ? atlasPxH : atlasPxW; | ||
| const originHeight = _atlasRotated ? atlasPxW : atlasPxH; | ||
| this._automaticWidth = | ||
| ((_texture.width * _atlasRegion.width) / (1 - _atlasRegionOffset.x - _atlasRegionOffset.z)) * | ||
| _region.width * | ||
| pixelsPerUnitReciprocal; | ||
| (originWidth / (1 - _atlasRegionOffset.x - _atlasRegionOffset.z)) * _region.width * ppuReciprocal; | ||
| this._automaticHeight = | ||
| ((_texture.height * _atlasRegion.height) / (1 - _atlasRegionOffset.y - _atlasRegionOffset.w)) * | ||
| _region.height * | ||
| pixelsPerUnitReciprocal; | ||
| (originHeight / (1 - _atlasRegionOffset.y - _atlasRegionOffset.w)) * _region.height * ppuReciprocal; |
There was a problem hiding this comment.
Invalidate cached size and UVs when atlasRotated changes.
These paths now depend on _atlasRotated, but the setter at Lines 121-124 still only flips the boolean. If width, height, or _getUVs() has already been evaluated, changing sprite.atlasRotated leaves stale cached results until some other property dirties the sprite.
Suggested fix
set atlasRotated(value: boolean) {
- if (this._atlasRotated != value) {
+ if (this._atlasRotated !== value) {
this._atlasRotated = value;
+ this._dispatchSpriteChange(SpriteModifyFlags.atlasRegion);
+ if (this._customWidth === undefined || this._customHeight === undefined) {
+ this._dispatchSpriteChange(SpriteModifyFlags.size);
+ }
}
}Also applies to: 345-392
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/2d/sprite/Sprite.ts` around lines 299 - 310, The sprite's
cached size and UVs must be invalidated when _atlasRotated changes: in the
atlasRotated setter (the code that flips this._atlasRotated), after toggling the
boolean clear the cached computed size and UVs by setting this._automaticWidth
and this._automaticHeight to undefined (or null) and force UV recompute/clear by
invoking or resetting whatever _getUVs() cache (e.g., call this._getUVs() or set
the UV cache to null) and mark the sprite dirty so width/height/_getUVs() will
be recalculated; apply the same invalidation logic wherever _atlasRotated can
change.
| const realWidth = atlasRegionW / (1 - offsetLeft - offsetRight); | ||
| const realHeight = atlasRegionH / (1 - offsetTop - offsetBottom); | ||
| // Coordinates of the four boundaries. | ||
| const left = Math.max(regionX - offsetLeft, 0) * realWidth + atlasRegionX; | ||
| const top = Math.max(regionBottom - offsetTop, 0) * realHeight + atlasRegionY; | ||
| const right = atlasRegionW + atlasRegionX - Math.max(regionRight - offsetRight, 0) * realWidth; | ||
| const bottom = atlasRegionH + atlasRegionY - Math.max(regionY - offsetBottom, 0) * realHeight; | ||
| const { x: borderLeft, y: borderBottom, z: borderRight, w: borderTop } = this._border; | ||
| // Left-Bottom | ||
| uv[0].set(left, bottom); | ||
| // Border ( Left-Bottom ) | ||
| uv[1].set( | ||
| (regionX - offsetLeft + borderLeft * regionW) * realWidth + atlasRegionX, | ||
| atlasRegionH + atlasRegionY - (regionY - offsetBottom + borderBottom * regionH) * realHeight | ||
| ); | ||
| // Border ( Right-Top ) | ||
| uv[2].set( | ||
| atlasRegionW + atlasRegionX - (regionRight - offsetRight + borderRight * regionW) * realWidth, | ||
| (regionBottom - offsetTop + borderTop * regionH) * realHeight + atlasRegionY | ||
| ); | ||
| // Right-Top | ||
| uv[3].set(right, top); | ||
| // 4 个外边界 + 4 个 9-slice 内边界 | ||
| let left: number, top: number, right: number, bottom: number; | ||
| let bLeft: number, bTop: number, bRight: number, bBottom: number; | ||
| if (atlasRotated) { | ||
| // 原图 region/offset (left/top/right/bottom) 在 atlas 中映射为 (bottom/left/top/right) | ||
| left = Math.max(regionBottom - offsetLeft, 0) * realWidth + atlasRegionX; | ||
| top = Math.max(regionLeft - offsetTop, 0) * realHeight + atlasRegionY; | ||
| right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetRight, 0) * realWidth; | ||
| bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetBottom, 0) * realHeight; | ||
| bLeft = (regionBottom - offsetLeft + border.y * regionH) * realWidth + atlasRegionX; | ||
| bTop = (regionLeft - offsetTop + border.x * regionW) * realHeight + atlasRegionY; | ||
| bRight = atlasRegionW + atlasRegionX - (regionTop - offsetRight + border.w * regionH) * realWidth; | ||
| bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetBottom + border.z * regionW) * realHeight; |
There was a problem hiding this comment.
Rotate the trim offsets with the packed axes.
In the rotated branch, atlas-X is derived from the sprite’s vertical span and atlas-Y from the horizontal span, but the code still feeds offsetLeft/offsetRight into the X-side math and offsetTop/offsetBottom into the Y-side math. That breaks trimmed rotated sprites when horizontal and vertical trims differ, and the 9-slice boundaries drift with them.
Suggested fix
- const realWidth = atlasRegionW / (1 - offsetLeft - offsetRight);
- const realHeight = atlasRegionH / (1 - offsetTop - offsetBottom);
+ const realWidth = atlasRotated
+ ? atlasRegionW / (1 - offsetTop - offsetBottom)
+ : atlasRegionW / (1 - offsetLeft - offsetRight);
+ const realHeight = atlasRotated
+ ? atlasRegionH / (1 - offsetLeft - offsetRight)
+ : atlasRegionH / (1 - offsetTop - offsetBottom);
@@
if (atlasRotated) {
- left = Math.max(regionBottom - offsetLeft, 0) * realWidth + atlasRegionX;
- top = Math.max(regionLeft - offsetTop, 0) * realHeight + atlasRegionY;
- right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetRight, 0) * realWidth;
- bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetBottom, 0) * realHeight;
- bLeft = (regionBottom - offsetLeft + border.y * regionH) * realWidth + atlasRegionX;
- bTop = (regionLeft - offsetTop + border.x * regionW) * realHeight + atlasRegionY;
- bRight = atlasRegionW + atlasRegionX - (regionTop - offsetRight + border.w * regionH) * realWidth;
- bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetBottom + border.z * regionW) * realHeight;
+ left = Math.max(regionBottom - offsetBottom, 0) * realWidth + atlasRegionX;
+ top = Math.max(regionLeft - offsetLeft, 0) * realHeight + atlasRegionY;
+ right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetTop, 0) * realWidth;
+ bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetRight, 0) * realHeight;
+ bLeft = (regionBottom - offsetBottom + border.y * regionH) * realWidth + atlasRegionX;
+ bTop = (regionLeft - offsetLeft + border.x * regionW) * realHeight + atlasRegionY;
+ bRight = atlasRegionW + atlasRegionX - (regionTop - offsetTop + border.w * regionH) * realWidth;
+ bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetRight + border.z * regionW) * realHeight;
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const realWidth = atlasRegionW / (1 - offsetLeft - offsetRight); | |
| const realHeight = atlasRegionH / (1 - offsetTop - offsetBottom); | |
| // Coordinates of the four boundaries. | |
| const left = Math.max(regionX - offsetLeft, 0) * realWidth + atlasRegionX; | |
| const top = Math.max(regionBottom - offsetTop, 0) * realHeight + atlasRegionY; | |
| const right = atlasRegionW + atlasRegionX - Math.max(regionRight - offsetRight, 0) * realWidth; | |
| const bottom = atlasRegionH + atlasRegionY - Math.max(regionY - offsetBottom, 0) * realHeight; | |
| const { x: borderLeft, y: borderBottom, z: borderRight, w: borderTop } = this._border; | |
| // Left-Bottom | |
| uv[0].set(left, bottom); | |
| // Border ( Left-Bottom ) | |
| uv[1].set( | |
| (regionX - offsetLeft + borderLeft * regionW) * realWidth + atlasRegionX, | |
| atlasRegionH + atlasRegionY - (regionY - offsetBottom + borderBottom * regionH) * realHeight | |
| ); | |
| // Border ( Right-Top ) | |
| uv[2].set( | |
| atlasRegionW + atlasRegionX - (regionRight - offsetRight + borderRight * regionW) * realWidth, | |
| (regionBottom - offsetTop + borderTop * regionH) * realHeight + atlasRegionY | |
| ); | |
| // Right-Top | |
| uv[3].set(right, top); | |
| // 4 个外边界 + 4 个 9-slice 内边界 | |
| let left: number, top: number, right: number, bottom: number; | |
| let bLeft: number, bTop: number, bRight: number, bBottom: number; | |
| if (atlasRotated) { | |
| // 原图 region/offset (left/top/right/bottom) 在 atlas 中映射为 (bottom/left/top/right) | |
| left = Math.max(regionBottom - offsetLeft, 0) * realWidth + atlasRegionX; | |
| top = Math.max(regionLeft - offsetTop, 0) * realHeight + atlasRegionY; | |
| right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetRight, 0) * realWidth; | |
| bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetBottom, 0) * realHeight; | |
| bLeft = (regionBottom - offsetLeft + border.y * regionH) * realWidth + atlasRegionX; | |
| bTop = (regionLeft - offsetTop + border.x * regionW) * realHeight + atlasRegionY; | |
| bRight = atlasRegionW + atlasRegionX - (regionTop - offsetRight + border.w * regionH) * realWidth; | |
| bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetBottom + border.z * regionW) * realHeight; | |
| const realWidth = atlasRotated | |
| ? atlasRegionW / (1 - offsetTop - offsetBottom) | |
| : atlasRegionW / (1 - offsetLeft - offsetRight); | |
| const realHeight = atlasRotated | |
| ? atlasRegionH / (1 - offsetLeft - offsetRight) | |
| : atlasRegionH / (1 - offsetTop - offsetBottom); | |
| // 4 个外边界 + 4 个 9-slice 内边界 | |
| let left: number, top: number, right: number, bottom: number; | |
| let bLeft: number, bTop: number, bRight: number, bBottom: number; | |
| if (atlasRotated) { | |
| // 原图 region/offset (left/top/right/bottom) 在 atlas 中映射为 (bottom/left/top/right) | |
| left = Math.max(regionBottom - offsetBottom, 0) * realWidth + atlasRegionX; | |
| top = Math.max(regionLeft - offsetLeft, 0) * realHeight + atlasRegionY; | |
| right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetTop, 0) * realWidth; | |
| bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetRight, 0) * realHeight; | |
| bLeft = (regionBottom - offsetBottom + border.y * regionH) * realWidth + atlasRegionX; | |
| bTop = (regionLeft - offsetLeft + border.x * regionW) * realHeight + atlasRegionY; | |
| bRight = atlasRegionW + atlasRegionX - (regionTop - offsetTop + border.w * regionH) * realWidth; | |
| bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetRight + border.z * regionW) * realHeight; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/2d/sprite/Sprite.ts` around lines 352 - 366, The rotated
branch misapplies trim offsets—when atlasRotated is true you must rotate the
offsets and border axes to match the packed axes mapping (original region/offset
left/top/right/bottom → bottom/left/top/right); update the computations that set
left/top/right/bottom and bLeft/bTop/bRight/bBottom to use the swapped offsets
and border components: for the X-side math (calculations that use
atlasRegionW/realWidth and regionBottom/regionTop) use offsetBottom/offsetTop
and border.y/w where appropriate, and for the Y-side math (calculations that use
atlasRegionH/realHeight and regionLeft/regionRight) use offsetLeft/offsetRight
and border.x/z accordingly so trimmed rotated sprites and 9-slice boundaries
align correctly (references: atlasRotated, realWidth, realHeight,
atlasRegionX/Y/W/H, regionLeft/Top/Right/Bottom, offsetLeft/Top/Right/Bottom,
border.x/y/w/z, regionW/regionH).
GuoLei1990
left a comment
There was a problem hiding this comment.
已关闭问题清单
| 问题 | 状态 |
|---|---|
| rotated 分支变量命名 atlas 语义翻转 | ✅ 已关闭(逻辑正确) |
| rotated 分支 border 映射公式 | ✅ 已关闭(公式推导正确) |
| UV 网格填充注释 / Y 轴方向说明 | ✅ 已处理 |
总结
将 Sprite._uvs 从 4 点扩展为 4×4 共 16 点网格(column-major),_updateUVs 完整处理 atlasRotated/non-rotated 的 UV 坐标变换和 9-slice border 映射。SimpleSpriteAssembler/SlicedSpriteAssembler/TiledSpriteAssembler 均已更新索引方式,Assembler 侧不感知 rotation,层次清晰。_calDefaultSize 中交换 atlas 宽高也是必要修正。整体方向正确。
问题
[P1] Sprite.ts:112-115 — atlasRotated setter 未触发 UV 和 Size dirty,运行时动态修改无效(第 7 轮提出,仍未修复)
已通过读取代码确认,当前 HEAD(commit 0a0c4a7)的 setter 实现:
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
// ← 无任何 dirty 通知
}
}此 PR 的 _updateUVs 和 _calDefaultSize 新增了对 _atlasRotated 的依赖,但 setter 从未补充对应的 dirty 通知。对比同文件 atlasRegion setter,后者修改时调用 _dispatchSpriteChange(SpriteModifyFlags.atlasRegion),触发 SpriteUpdateFlags.automaticSize | SpriteUpdateFlags.uvs。
修复只需两行,类比 atlasRegion setter:
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
this._dispatchSpriteChange(SpriteModifyFlags.atlasRegion); // 触发 uvs + automaticSize dirty
if (this._customWidth === undefined || this._customHeight === undefined) {
this._dispatchSpriteChange(SpriteModifyFlags.size);
}
}
}当前 SpriteAtlasLoader 在 Sprite 刚创建时赋值(全脏状态),所以 loader 路径不暴露此 bug。但编辑器 Inspector 动态切换 atlasRotated、序列化回写等场景会静默失效——UV 和尺寸保持旧值,无任何报错。这是 setter 语义契约的根本缺失,和 loader 路径是否安全无关。
GuoLei1990
left a comment
There was a problem hiding this comment.
增量审查(2026-05-15)
已关闭问题清单
| 问题 | 状态 |
|---|---|
| rotated 分支变量命名 atlas 语义翻转 | ✅ 已关闭(逻辑正确) |
| rotated 分支 border 映射公式 | ✅ 已关闭(公式推导正确) |
| UV 网格填充注释 / Y 轴方向说明 | ✅ 已处理 |
以下问题仍未修复:
[P1] Sprite.ts — atlasRotated setter 未触发 UV 和 Size dirty
当前 HEAD 的 setter 实现:
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
// ← 无任何 dirty 通知
}
}此 PR 的 _updateUVs 和 _calDefaultSize 新增了对 _atlasRotated 的依赖,但 setter 从未补充对应的 dirty 通知。
对比同文件 atlasRegion setter——修改时调用 _dispatchSpriteChange(SpriteModifyFlags.atlasRegion),触发 SpriteUpdateFlags.automaticSize | SpriteUpdateFlags.uvs。
当前 SpriteAtlasLoader 在 Sprite 刚创建时赋值(全脏状态),所以 loader 路径不暴露此 bug。但编辑器 Inspector 动态切换 atlasRotated、序列化回写等场景会静默失效——UV 和尺寸保持旧值,无任何报错。
修复(类比 atlasRegion setter):
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
this._dispatchSpriteChange(SpriteModifyFlags.atlasRegion); // 触发 uvs + automaticSize dirty
if (this._customWidth === undefined || this._customHeight === undefined) {
this._dispatchSpriteChange(SpriteModifyFlags.size);
}
}
}此问题已在第 7 轮 review 指出,当前 HEAD 仍未修复。修复后可合入。
GuoLei1990
left a comment
There was a problem hiding this comment.
总结
为 SpriteAtlas 添加 TexturePacker 风格的 90° 旋转 Sprite 支持。实现覆盖了完整的链路:Sprite._updateUVs 中区分 atlasRotated 分支重新计算 16 个 UV 点,三个 Assembler 的 updateUVs 方法适配新的 16-UV 布局,_calDefaultSize 修正旋转后宽高互换的计算,SpriteAtlasLoader 补上 atlasRotated ?? false 默认值。
核心修改正确:atlasRotated 时 column 对应 atlas Y 轴(倒序)、row 对应 atlas X 轴,UV 网格填充的两个分支在注释辅助下可验证。
问题
[P2] Sprite.ts:350+(_updateUVs rotated 分支)— regionBottom 语义需验证
非旋转分支中:
const regionBottom = 1 - regionTop - regionH;旋转分支中对 left/top/right/bottom 的映射为:
left = regionBottom → offset left
top = regionLeft → offset top
...
这里 regionBottom = 1 - regionTop - regionH 实际上是原图的"下边距",在 atlas 顺时针旋转 90° 后它对应 atlas 的左边。border 分量(border.x/.y/.z/.w)在旋转分支中做了 x↔y、z↔w 的交换,符合旋转语义。建议在分支顶部加一行注释说明坐标轴映射关系(如"CW 90°: display-left ↔ atlas-bottom,display-top ↔ atlas-left"),方便后续维护者验证。不阻塞合入。
[P2] TiledSpriteAssembler.ts(updateUVs)— rotated atlas 的 tiling 行为未验证
TiledSpriteAssembler 取的是 allUVs[0]/[5]/[10]/[15](4 个边界点),用于平铺计算。当 atlasRotated = true 时,_updateUVs 填充了正确的旋转后 UV,但 TiledAssembler 在平铺过程中会插值生成中间 UV 点——这些中间点的插值方向(U or V)在旋转后是否与视觉方向一致?不影响此 PR 合入(tiling + rotated atlas 是罕见组合),但建议在后续版本中补充相关 e2e 用例。
可合入
GuoLei1990
left a comment
There was a problem hiding this comment.
增量审查(2026-05-15)
已关闭问题清单
| 问题 | 状态 |
|---|---|
| rotated 分支变量命名 atlas 语义翻转 | ✅ 已关闭(逻辑正确) |
| rotated 分支 border 映射公式 | ✅ 已关闭(公式推导正确) |
| UV 网格填充注释 / Y 轴方向说明 | ✅ 已处理 |
以下问题仍未修复:
[P1] Sprite.ts — atlasRotated setter 未触发 UV 和 Size dirty
当前 atlasRotated setter(根据 diff,已有 _atlasRotated 字段):
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
// ← 无任何 dirty 通知
}
}此 PR 的 _updateUVs 和 _calDefaultSize 都新增了对 _atlasRotated 的分支处理,说明这个字段会影响 UV 和 Size 的计算结果。但 setter 变更后没有触发对应 dirty flag,导致:
- 运行时动态修改
sprite.atlasRotated时,UV 和 Size 不会重新计算 - 通过
SpriteAtlasLoader加载时会先new Sprite()后 setatlasRotated = true,如果 UV/Size 在 set 之前已经计算过,结果是错的
对照同文件中其他影响 UV/Size 的 setter(如 atlasRegion、border、region),它们均调用了 this._dirtyUpdateFlag |= SpriteUpdateFlags.uvs 或相关 dirty 通知。atlasRotated 的 setter 应做同样处理:
set atlasRotated(value: boolean) {
if (this._atlasRotated != value) {
this._atlasRotated = value;
this._dirtyUpdateFlag |= SpriteUpdateFlags.uvs;
// 如果 _calDefaultSize 依赖 atlasRotated 且 size 是 auto,还需触发 size dirty
}
}请修复后重新提交。
Summary
Adds support for rotated sprites in
SpriteAtlas— TexturePacker-style atlases that pack sprites with 90° rotation to optimize space.Changes
Sprite.ts— extends UV calculation to handle rotated source rect (~97 lines updated)SimpleSpriteAssembler/SlicedSpriteAssembler/TiledSpriteAssembler— vertex/UV generation respects rotation flagSpriteAtlasLoader.ts— propagates rotation flag from atlas metadata to SpriteTest Plan
Summary by CodeRabbit
Bug Fixes
Refactor