feat(2d): add FilledSpriteAssembler and sprite filled mode support#2989
feat(2d): add FilledSpriteAssembler and sprite filled mode support#2989cptbtptpbcptdtptp wants to merge 2 commits into
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughThis PR adds filled-mode sprite rendering to the Galacean 2D engine. It introduces new enums ( ChangesFilled Sprite Rendering
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev/2.0 #2989 +/- ##
===========================================
- Coverage 78.25% 77.62% -0.64%
===========================================
Files 900 903 +3
Lines 99234 99970 +736
Branches 10172 10178 +6
===========================================
- Hits 77657 77599 -58
- Misses 21406 22200 +794
Partials 171 171
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
packages/core/src/2d/assembler/FilledSpriteAssembler.ts (3)
113-115: 💤 Low value
@ts-ignorehides an interface gap onrenderer._bounds.The
_boundsfield isn't declared onISpriteRenderer, so this access requires@ts-ignore. Other assemblers presumably need the same hatch. Consider wideningISpriteRendererto include_bounds: BoundingBox(or expose a small_setBounds(box)method) so the suppression can be removed and the interface accurately reflects what the assemblers depend on.🤖 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/assembler/FilledSpriteAssembler.ts` around lines 113 - 115, The `@ts-ignore` exists because assemblers access renderer._bounds which isn't declared on ISpriteRenderer; update the renderer interface (ISpriteRenderer) to include a private/internal _bounds: BoundingBox (or add a small method like _setBounds(box: BoundingBox) and use that) so FilledSpriteAssembler (where BoundingBox.transform(renderer._getBounds(), modelMatrix, renderer._bounds) is called) no longer needs `@ts-ignore`; change the interface accordingly and remove the suppression from FilledSpriteAssembler (and any other assemblers using _bounds).
137-140: 💤 Low valueExtract repeated
amount <= 0.001short-circuit and the magic threshold.The same
if (amount <= 0.001) { renderer._subChunk.indices.length = 0; return; }guard is repeated in 4_filled*methods, and the threshold itself is a magic number. Consider hoisting both into a single named constant + helper (or moving the early-out up intoupdatePositionsso the per-mode functions don't have to duplicate it).Also applies to: 222-225, 288-291, 383-386
🤖 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/assembler/FilledSpriteAssembler.ts` around lines 137 - 140, Extract the repeated small-amount guard into a named constant and single helper (or move the early-out into updatePositions) to remove duplication: define a descriptive constant (e.g. MIN_FILL_AMOUNT = 0.001) in FilledSpriteAssembler and replace the four occurrences of the literal check (the lines in the _filled* methods that do `if (amount <= 0.001) { renderer._subChunk.indices.length = 0; return; }`) with a call to a new helper like shouldSkipFill(amount, renderer) or performSkipFillIfNeeded(amount, renderer) so the threshold is centralized and the index-clear/return logic is not duplicated across _filled* methods. Ensure you update all four locations mentioned (the guards at the shown snippet and the ones around lines 222-225, 288-291, 383-386) and keep behavior identical.
244-271: ⚡ Quick winRadial handlers silently no-op on unexpected origins.
_filledRadial90only handles the 4 corner origins,_filledRadial180and_filledRadial360only handle the 4 edge origins. Thedefault: break;branches leave_inPositions/_inUVspopulated from a previous invocation (radial 90/180) orstartAngle = 0(radial 360), producing visually wrong geometry without any indication of misuse.Since
SpriteRenderer.filledOriginsetter does not validate againstfilledMode, a user can put the renderer in a bad state. Consider either:
- Validating in
SpriteRenderer.filledOriginsetter (reject/coerce origins that don't match the current mode), or- Adding an explicit warning/
Logger.warnin the default branch here.Also applies to: 320-371, 389-404
🤖 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/assembler/FilledSpriteAssembler.ts` around lines 244 - 271, The switch default branches in _filledRadial90, _filledRadial180 and _filledRadial360 silently leave inPositions/inUVs from prior calls causing wrong geometry; update each default case to log a warning (use Logger.warn or the project logger) mentioning the function name and the invalid origin, and then clear/reset the arrays (inPositions and inUVs) or set them to safe defaults so stale data isn't reused; alternatively, add validation/coercion in the SpriteRenderer.filledOrigin setter to reject or coerce origins incompatible with the current filledMode, but if you choose the logger approach make sure to reference the origin value and the method name in the message.packages/core/src/2d/sprite/SpriteRenderer.ts (1)
179-186: ⚡ Quick winConsider validating
filledOriginagainst the activefilledMode.The setter accepts any
SpriteFilledOrigin, but eachfilledModeonly renders correctly for a subset of origins (Horizontal: Left/Right; Vertical: Top/Bottom; Radial90: corners; Radial180/360: edge midpoints). Combined with the silentdefault: break;branches inFilledSpriteAssembler, an invalid combination produces broken geometry with no diagnostic.Validating here (e.g.,
Logger.warnand coercing to a valid origin, or clamping based on_filledMode) would help users catch misuse early. Same applies to thefilledModesetter when the previously-set origin is no longer valid for the new mode.🤖 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/SpriteRenderer.ts` around lines 179 - 186, The filledOrigin setter currently accepts any SpriteFilledOrigin even when incompatible with the active _filledMode, which can produce broken geometry; update the set filledOrigin(value: SpriteFilledOrigin) to validate value against the current _filledMode (e.g., allowed origins for Horizontal = Left/Right, Vertical = Top/Bottom, Radial90 = corners, Radial180/360 = edge midpoints), and if invalid call Logger.warn with a clear message and coerce/clamp to a valid origin before assigning _filledOrigin and setting the dirty flag (_dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV) for SpriteDrawMode.Filled; also mirror this validation in the filledMode setter so when changing _filledMode you adjust or coerce the existing _filledOrigin (and warn) to a compatible value to avoid relying on the silent default branches in FilledSpriteAssembler.packages/core/src/2d/assembler/ISpriteRenderer.ts (1)
20-20: ⚡ Quick winConsider renaming
filledClockWise→filledClockwise."Clockwise" is a single English word; splitting the W creates an unusual public API name. Since this property is also exposed via
SpriteRendererand is part of a 2.0 release, normalizing it now avoids a breaking rename later. Same rename should be applied inSpriteRenderer.ts(private field, getter/setter) andFilledSpriteAssembler.ts(read site).♻️ Suggested rename
- filledClockWise?: boolean; + filledClockwise?: boolean;🤖 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/assembler/ISpriteRenderer.ts` at line 20, Rename the public property and all usages from filledClockWise to filledClockwise: update the ISpriteRenderer interface property (filledClockWise → filledClockwise), rename the private field and its getter/setter in SpriteRenderer (ensure method names and internal references use filledClockwise), and update the read site in FilledSpriteAssembler to read the new property name; keep behavior unchanged and run tests to catch any missed references.
🤖 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/assembler/FilledSpriteAssembler.ts`:
- Around line 117-119: The FilledSpriteAssembler.updateUVs is a no-op which
leaves UVs stale when atlasRegion changes because SpriteRenderer._onSpriteChange
sets only SpriteRendererUpdateFlags.UV for SpriteModifyFlags.atlasRegion and
_render will call updateUVs (skipped updatePositions); fix this by changing
SpriteRenderer._onSpriteChange to treat atlasRegion the same as other
UV-affecting changes for filled mode: when atlasRegion is detected, set the
update flags to WorldVolumeAndUV (or include WorldVolume) instead of only UV so
FilledSpriteAssembler.updatePositions runs and recomputes baked UVs; update
references to SpriteModifyFlags.atlasRegion and SpriteRendererUpdateFlags.*
accordingly.
In `@packages/core/src/2d/sprite/SpriteRenderer.ts`:
- Around line 160-170: The filledMode setter currently sets _filledOrigin to
Bottom for all non-Radial90 modes which makes Horizontal default to an invalid
Bottom origin; update the logic in the set filledMode(value: SpriteFilledMode)
to choose a mode-appropriate default: when value === SpriteFilledMode.Horizontal
set _filledOrigin to SpriteFilledOrigin.Left (so
FilledSpriteAssembler._filledLinear sees originIsStart true), when value ===
SpriteFilledMode.Radial90 set it to SpriteFilledOrigin.BottomLeft, otherwise
keep SpriteFilledOrigin.Bottom; keep the existing branch that sets
_dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV when
this._drawMode === SpriteDrawMode.Filled.
---
Nitpick comments:
In `@packages/core/src/2d/assembler/FilledSpriteAssembler.ts`:
- Around line 113-115: The `@ts-ignore` exists because assemblers access
renderer._bounds which isn't declared on ISpriteRenderer; update the renderer
interface (ISpriteRenderer) to include a private/internal _bounds: BoundingBox
(or add a small method like _setBounds(box: BoundingBox) and use that) so
FilledSpriteAssembler (where BoundingBox.transform(renderer._getBounds(),
modelMatrix, renderer._bounds) is called) no longer needs `@ts-ignore`; change the
interface accordingly and remove the suppression from FilledSpriteAssembler (and
any other assemblers using _bounds).
- Around line 137-140: Extract the repeated small-amount guard into a named
constant and single helper (or move the early-out into updatePositions) to
remove duplication: define a descriptive constant (e.g. MIN_FILL_AMOUNT = 0.001)
in FilledSpriteAssembler and replace the four occurrences of the literal check
(the lines in the _filled* methods that do `if (amount <= 0.001) {
renderer._subChunk.indices.length = 0; return; }`) with a call to a new helper
like shouldSkipFill(amount, renderer) or performSkipFillIfNeeded(amount,
renderer) so the threshold is centralized and the index-clear/return logic is
not duplicated across _filled* methods. Ensure you update all four locations
mentioned (the guards at the shown snippet and the ones around lines 222-225,
288-291, 383-386) and keep behavior identical.
- Around line 244-271: The switch default branches in _filledRadial90,
_filledRadial180 and _filledRadial360 silently leave inPositions/inUVs from
prior calls causing wrong geometry; update each default case to log a warning
(use Logger.warn or the project logger) mentioning the function name and the
invalid origin, and then clear/reset the arrays (inPositions and inUVs) or set
them to safe defaults so stale data isn't reused; alternatively, add
validation/coercion in the SpriteRenderer.filledOrigin setter to reject or
coerce origins incompatible with the current filledMode, but if you choose the
logger approach make sure to reference the origin value and the method name in
the message.
In `@packages/core/src/2d/assembler/ISpriteRenderer.ts`:
- Line 20: Rename the public property and all usages from filledClockWise to
filledClockwise: update the ISpriteRenderer interface property (filledClockWise
→ filledClockwise), rename the private field and its getter/setter in
SpriteRenderer (ensure method names and internal references use
filledClockwise), and update the read site in FilledSpriteAssembler to read the
new property name; keep behavior unchanged and run tests to catch any missed
references.
In `@packages/core/src/2d/sprite/SpriteRenderer.ts`:
- Around line 179-186: The filledOrigin setter currently accepts any
SpriteFilledOrigin even when incompatible with the active _filledMode, which can
produce broken geometry; update the set filledOrigin(value: SpriteFilledOrigin)
to validate value against the current _filledMode (e.g., allowed origins for
Horizontal = Left/Right, Vertical = Top/Bottom, Radial90 = corners,
Radial180/360 = edge midpoints), and if invalid call Logger.warn with a clear
message and coerce/clamp to a valid origin before assigning _filledOrigin and
setting the dirty flag (_dirtyUpdateFlag |=
SpriteRendererUpdateFlags.WorldVolumeAndUV) for SpriteDrawMode.Filled; also
mirror this validation in the filledMode setter so when changing _filledMode you
adjust or coerce the existing _filledOrigin (and warn) to a compatible value to
avoid relying on the silent default branches in FilledSpriteAssembler.
🪄 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: 24f0fb05-7587-4d34-9703-6bb15d3ffab5
📒 Files selected for processing (7)
packages/core/src/2d/assembler/FilledSpriteAssembler.tspackages/core/src/2d/assembler/ISpriteRenderer.tspackages/core/src/2d/enums/SpriteDrawMode.tspackages/core/src/2d/enums/SpriteFilledMode.tspackages/core/src/2d/enums/SpriteFilledOrigin.tspackages/core/src/2d/index.tspackages/core/src/2d/sprite/SpriteRenderer.ts
| static updateUVs(renderer: ISpriteRenderer): void { | ||
| // UVs are computed in updatePositions. | ||
| } |
There was a problem hiding this comment.
Empty updateUVs leaves UVs stale on atlas-region changes.
updateUVs is a no-op because UVs are baked inside updatePositions. However, SpriteRenderer._onSpriteChange handles SpriteModifyFlags.atlasRegion by setting only SpriteRendererUpdateFlags.UV (no WorldVolume). In _render, this triggers updateUVs while updatePositions is skipped — so for filled mode the UVs are never refreshed when the sprite's atlas region changes (e.g., dynamic atlas repacking). Other assemblers don't have this regression because they have a real updateUVs.
Two reasonable fixes:
- In
SpriteRenderer._onSpriteChange, treatatlasRegionlike other UV-affecting events for filled mode (setWorldVolumeAndUV), or - Implement a real UV-only path here (extract the UV computation from each
_filled*function so it can be invoked independently).
Option 1 is the minimal fix.
🛠 Minimal fix in SpriteRenderer._onSpriteChange
case SpriteModifyFlags.atlasRegion:
- this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.UV;
+ this._dirtyUpdateFlag |=
+ this._drawMode === SpriteDrawMode.Filled
+ ? SpriteRendererUpdateFlags.WorldVolumeAndUV
+ : SpriteRendererUpdateFlags.UV;
break;🤖 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/assembler/FilledSpriteAssembler.ts` around lines 117 -
119, The FilledSpriteAssembler.updateUVs is a no-op which leaves UVs stale when
atlasRegion changes because SpriteRenderer._onSpriteChange sets only
SpriteRendererUpdateFlags.UV for SpriteModifyFlags.atlasRegion and _render will
call updateUVs (skipped updatePositions); fix this by changing
SpriteRenderer._onSpriteChange to treat atlasRegion the same as other
UV-affecting changes for filled mode: when atlasRegion is detected, set the
update flags to WorldVolumeAndUV (or include WorldVolume) instead of only UV so
FilledSpriteAssembler.updatePositions runs and recomputes baked UVs; update
references to SpriteModifyFlags.atlasRegion and SpriteRendererUpdateFlags.*
accordingly.
| if (isHorizontal) { | ||
| const originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Left; | ||
| const startX = originIsStart ? lPosLB.x : lPosRB.x - (lPosRB.x - lPosLB.x) * amount; | ||
| const endX = originIsStart ? lPosLB.x + (lPosRB.x - lPosLB.x) * amount : lPosRB.x; | ||
| const startU = originIsStart ? left : right - (right - left) * amount; | ||
| const endU = originIsStart ? left + (right - left) * amount : right; | ||
| (x0 = startX), (y0 = lPosLB.y), (u0 = startU), (v0 = bottom); | ||
| (x1 = endX), (y1 = lPosRB.y), (u1 = endU), (v1 = bottom); | ||
| (x2 = startX), (y2 = lPosLT.y), (u2 = startU), (v2 = top); | ||
| (x3 = endX), (y3 = lPosRT.y), (u3 = endU), (v3 = top); | ||
| } else { | ||
| const originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Bottom; | ||
| const startY = originIsStart ? lPosLB.y : lPosLT.y - (lPosLT.y - lPosLB.y) * amount; | ||
| const endY = originIsStart ? lPosLB.y + (lPosLT.y - lPosLB.y) * amount : lPosLT.y; | ||
| const startV = originIsStart ? bottom : top - (top - bottom) * amount; | ||
| const endV = originIsStart ? bottom + (top - bottom) * amount : top; | ||
| (x0 = lPosLB.x), (y0 = startY), (u0 = left), (v0 = startV); | ||
| (x1 = lPosRB.x), (y1 = startY), (u1 = right), (v1 = startV); | ||
| (x2 = lPosLT.x), (y2 = endY), (u2 = left), (v2 = endV); | ||
| (x3 = lPosRT.x), (y3 = endY), (u3 = right), (v3 = endV); | ||
| } |
There was a problem hiding this comment.
_filledLinear silently treats any non-Left/non-Bottom origin as the opposite end.
originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Left (resp. Bottom) means any other origin value — including corner origins or an unrelated default — quietly behaves as Right (resp. Top). Combined with the default-origin choice in SpriteRenderer.filledMode setter (see comment there), filledMode = Horizontal currently fills from the right side by default, which is almost certainly not intended.
Once the default-origin issue in SpriteRenderer is fixed, consider tightening the check here to be explicit (e.g., assert/validate the origin matches the mode, or compare against both valid values and warn otherwise). At minimum, an explicit comparison documents intent better than a single-sided equality.
| set filledMode(value: SpriteFilledMode) { | ||
| if (this._filledMode !== value) { | ||
| this._filledMode = value; | ||
| // Reset origin to a valid default for the new mode | ||
| this._filledOrigin = | ||
| value === SpriteFilledMode.Radial90 ? SpriteFilledOrigin.BottomLeft : SpriteFilledOrigin.Bottom; | ||
| if (this._drawMode === SpriteDrawMode.Filled) { | ||
| this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
filledMode setter picks an invalid default origin for Horizontal.
The expression value === SpriteFilledMode.Radial90 ? BottomLeft : Bottom assigns SpriteFilledOrigin.Bottom to every non-Radial90 mode, including Horizontal. For horizontal fills, only Left/Right are meaningful origins, and in FilledSpriteAssembler._filledLinear:
const originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Left;So when a user writes renderer.filledMode = SpriteFilledMode.Horizontal, the renderer ends up with origin = Bottom → originIsStart = false → fills from the right side. That's almost certainly not the intended default.
🛠 Suggested fix
set filledMode(value: SpriteFilledMode) {
if (this._filledMode !== value) {
this._filledMode = value;
// Reset origin to a valid default for the new mode
- this._filledOrigin =
- value === SpriteFilledMode.Radial90 ? SpriteFilledOrigin.BottomLeft : SpriteFilledOrigin.Bottom;
+ switch (value) {
+ case SpriteFilledMode.Horizontal:
+ this._filledOrigin = SpriteFilledOrigin.Left;
+ break;
+ case SpriteFilledMode.Radial90:
+ this._filledOrigin = SpriteFilledOrigin.BottomLeft;
+ break;
+ default:
+ this._filledOrigin = SpriteFilledOrigin.Bottom;
+ break;
+ }
if (this._drawMode === SpriteDrawMode.Filled) {
this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV;
}
}
}🤖 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/SpriteRenderer.ts` around lines 160 - 170, The
filledMode setter currently sets _filledOrigin to Bottom for all non-Radial90
modes which makes Horizontal default to an invalid Bottom origin; update the
logic in the set filledMode(value: SpriteFilledMode) to choose a
mode-appropriate default: when value === SpriteFilledMode.Horizontal set
_filledOrigin to SpriteFilledOrigin.Left (so FilledSpriteAssembler._filledLinear
sees originIsStart true), when value === SpriteFilledMode.Radial90 set it to
SpriteFilledOrigin.BottomLeft, otherwise keep SpriteFilledOrigin.Bottom; keep
the existing branch that sets _dirtyUpdateFlag |=
SpriteRendererUpdateFlags.WorldVolumeAndUV when this._drawMode ===
SpriteDrawMode.Filled.
GuoLei1990
left a comment
There was a problem hiding this comment.
总结
新增 FilledSpriteAssembler,支持 sprite filled 模式(Horizontal/Vertical/Radial90/180/360),对标 Unity Image.Type.Filled。径向裁剪 _radialCut 的 tan 插值 + 象限分解结构清晰,静态 scratch 复用得当。但有两个 code-confirmed 的 P1 仍未修,作者自上轮 review 后未推新提交。
问题
P1
-
SpriteRenderer._onSpriteChange的 atlasRegion 分支在 Filled 模式下不刷新 UVatlasRegioncase 只置UV脏标记 → 调updateUVs,而FilledSpriteAssembler.updateUVs是 no-op(UV 烘焙在updatePositions里),且updatePositions不会因UV标记运行。结果:sprite atlas region 变了,filled 渲染的 UV 不更新。最小修法是让 Filled 模式下 atlasRegion 变更置WorldVolumeAndUV(参考已有的 size 变更 →WorldVolumeUVAndColor分支)。 -
SpriteRenderer.filledModesetter 为 Horizontal 选了错误的默认 originsetter 里非 Radial90 一律 reset 成
Bottom;但_filledLinear的 horizontal 分支判originIsStart = origin === Left,Bottom不等于Left→ 从右往左填。Unity 默认:Horizontal→Left、Vertical→Bottom、Radial180/360→Bottom、Radial90→BottomLeft。建议按 mode 给默认 origin,而不是统一Bottom。顺带
_filledLinear对非Left/Bottom的 origin 是静默 fallthrough 当成反向端,建议对非法 origin 至少有个明确处理(而非静默走反向)。
P2
-
FilledSpriteAssembler.resetData每帧 realloc subChunk +indices = []是为可变顶点数服务的有意设计(径向象限数不定),但没注释,容易被误读成浪费。补一句说明。 -
ISpriteRenderer缺_bounds声明,导致 assembler 里BoundingBox.transform(..., renderer._bounds)要@ts-ignore。建议在接口上补_bounds,消掉 ts-ignore。
P3
- 测试覆盖缺失(codecov patch 20%)。filled 模式的 fillAmount 边界(0/1)、各 origin、cw/ccw 至少要有链路级用例。
撤回之前的两条 P1(自查后判定非缺陷,不再坚持)
- "静态 scratch
_vertexOffset/_indicesOffset跨 renderer 污染"——实际每个径向入口(_filledRadial*/_processRadialGrid)在用之前都 reset 成 0,linear 路径不用它们,单线程顺序渲染下不构成 bug。撤回。 - "fillAmount=0 残留脏顶点"——所有
_filled*在amount<=0.001时indices.length=0提前返回,0 索引画不出东西(不是 garbage,是 no-draw)。撤回。
自检
atlasRegion UV-staleness 与 Horizontal default-origin 两条 P1 已对照 base SpriteRenderer._onSpriteChange(atlasRegion 仅置 UV)与 _filledLinear(origin === Left 判据)实际代码确认。之前提的 scratch 并发 / fillAmount=0 两条经复查不成立,已主动撤回,不重复坚持。已关闭的 spriteUVs[0]/[15](#2990 依赖)、updateColor 16-slot(P3)不重提。
Summary
Adds support for Sprite Filled draw mode — render only a portion of a sprite based on a fill ratio along a configurable axis/origin. Useful for progress bars, cooldown indicators, etc.
Changes
FilledSpriteAssembler(~613 lines) handles vertex generation for filled modeSpriteFilledMode— direction (Horizontal / Vertical / Radial90 / Radial180 / Radial360)SpriteFilledOrigin— anchor point for the fill directionSpriteDrawModewithFilledISpriteRendererinterfaceSpriteRendererintegrates filled assemblerTest Plan
SpriteFilledMode×SpriteFilledOrigincombinationsSummary by CodeRabbit
Release Notes