Skip to content

Navigation Menu

Sign in
Sign up

fix(story-player): imagerotate 旋转中心锚定画面中心 - #347

Draft
daflyinbed wants to merge 1 commit into
main from
fix/story-player-imagerotate
Draft

fix(story-player): imagerotate 旋转中心锚定画面中心 #347
daflyinbed wants to merge 1 commit into
main from
fix/story-player-imagerotate

Conversation

@daflyinbed

@daflyinbed daflyinbed commented Aug 24, 2026

Copy link
×ばつ fadetime`,与同 panel tween 执行器的 bypass 不对称);`ease` 参数**不读**,硬编码 `SetEase(Ease.Linear)`;先 `DOKill(this._rectTransform)` 失效旧旋转 tween,再用 `AVGUtils.CreateRotateTween` @ `0x183e40090` 旋转,`OnKill` 闭包里 `if (block) FinishCommand()`;返回原始 `block`,无 effectiveBlock 合成。 - **操作对象是 `_rectTransform`(panel 自己的 RectTransform,场景 `panel_image` 节点),全程不碰前景 `_foreImage`**;旋转连带 fore/back 两个 Image 子节点,角度跨 `[Image]` 切图保留。 - **`panel_image` 的 pivot 是场景序列化值,默认 (0.5, 0.5) = 面板中心 ≈ 画面中心,因此 native 的旋转是原地倾斜**。 - `CreateRotateTween` 的有符号扫角:三次 `Mathf.Repeat(..., 360)`(current=世界 eulerAngles.z、end=angle、delta=end-current),`delta > 180 → delta -= 360`;`inverse` 时 `delta < 0 → += 360` 后 `+ circles*360`,非 inverse 时 `-circles*360` 且 `delta > 0 → -= 360`(符号判断都在加 circles 之前;`inverse` 即使 circles=0 也是方向开关);结果作相对增量交给 `DORotate(RotateMode.LocalAxisAdd)`。前端 `SceneGeometry.rotateTweenDelta` 已逐分支等价移植,本 PR 不动。 ## 修复内容 ### 1. 旋转中心绕舞台左上角而非画面中心(P0) - **native 行为**:旋转 `panel_image` 自己的 RectTransform,pivot 为场景序列化值(默认 (0.5,0.5) = 面板中心 ≈ 画面中心),画面**原地倾斜**。 - **前端行为**:旋转 `imageLayer` Container,其 `position`/`pivot` 从未设置(均为 (0,0)),而前景 sprite 摆在 (640,360) 附近,所有旋转实际绕**舞台左上角 (0,0)** 公转:`angle=180`(act39side_st01:802)画面整体翻出屏幕;`angle=-4`(level_main_11-01_end:55)画面中心点位移约 sin(4°)×ばつ743px ≈ 52px。全量 14 条样本无一正确。 - **修法**:构造函数中 `imageLayer.pivot.set(STORY_WIDTH/2, STORY_HEIGHT/2)` + `imageLayer.position.set(STORY_WIDTH/2, STORY_HEIGHT/2)`(与 character `rotationLayer` 的既有做法一致)。`pivot == position` 使子坐标平移恒等,sprite 仍按 (640,360) 中心摆放,坐标语义不变;旋转则绕画面中心,对齐 RectTransform 的 (0.5,0.5) pivot。 ### 2. 补全 runtime.ts 截断的 provenance 注释(注释项) - `engine/runtime.ts` 的 `case "imagerotate"` 注释原来截断在 "Unlike the",补全为:与 panel 的 tween 执行器不同,`fadetime` 过 `CalculateFadetime`(animateRatio 缩放);`ease` 参数不读,native 硬编码 `Ease.Linear`。 ### 3. 更新 setImageRotate 的 provenance 注释(注释项) - 原注释只说 "`imageLayer` is the corresponding PIXI adaptation",未点破旋转中心。现注明:构造期 pivot 对应 `panel_image` RectTransform 的序列化 (0.5,0.5) pivot;并记录差异 #3 维持现状的依据——native 仅 `OnReset`(剧情开始/skip)归零角度,widget 每剧情销毁重建 player/renderer,无需显式复位。 ### 未处理项(及原因) - **P2 差异 #2(切图冻结进行中的旋转 tween)**:`setImage`/`clearImage` 递增 `imageRotateSessionId` 的去除,与 `image` 命令 review 差异 #4 同源同一修复,由并行的 image/imagetween PR 统一处理,避免双改冲突。 - **P3 差异 #4(旋转连带 web-only largeimg 内容)**:`largeimg` 为已证伪的非 native 命令、纯 web 兼容层,review 建议为"考虑"级,不在本 PR 收敛范围。 - **P3 差异 #5(circles 非整数取整)**:全量样本 `circles` 0 命中且脚本只写整数,不可达,review 明示"可不改"。 ## 验证用剧情文件 语料根目录 `torappu/storage/asset/gamedata/latest/story`,以下相对路径全部命中 P0 修复(修复前所有 imagerotate 都绕左上角公转,修复后原地倾斜): - `activities/act39side/level_act39side_st01.txt`:802(`angle=180` —— 修复前画面整体翻出屏幕的极端样本)、818(`angle=0,fadetime=0,block=true` 瞬时复位+立即解除阻塞) - `obt/main/level_main_11-01_end.txt`:55(`angle=-4` —— 修复前伴随约 52px 位移的轻微倾斜)、59(`angle=-60`)、68(`[imagerotate]` 全默认参数) - `activities/act53side/level_act53side_st03.txt`:330、342(`inverse=true` 方向开关) - `activities/act49side/level_act49side_06_end.txt`:512、514(`image=` 与 `ease="OutCirc"` 参数均被两端忽略) - `obt/main/level_main_13-05_beg.txt`:904(`angle=-180`) - `obt/main/level_main_13-19_end.txt`:801、804 - `activities/act20side/level_act20side_04_end.txt`:327 - `activities/act25side/level_act25side_06_beg.txt`:404、408 - `activities/act33side/level_act33side_04_beg.txt`:526 - `activities/act34side/level_act34side_08_beg.txt`:768(`isblock` typo → false) 共 16 处 / 12 个文件。扫角算法(`rotate-tween.spec.ts` 5 用例)与参数映射(`runtime.spec.ts` "maps imagerotate strict parameters")已有单测覆盖且不受本改动影响;旋转中心由新增单测 `tests/renderer.spec.ts` "rotates the image panel around its center instead of the stage origin" 锁定(断言 pivot=(640,360)、angle=180 后中心 sprite 全局坐标不变、左上角 sprite 翻到右下角)。 ## 测试 - `pnpm test`:20 个文件 **223 个用例全部通过**(基线 222 + 新增 1)。 - `pnpm exec vue-tsc -b`:通过。 - `pnpm exec eslint <改动文件>`:通过。 " data-view-component="true"> Copy Markdown
Contributor

本 PR 修复 StoryPlayer 对 AVG imagerotate 命令移植中的 P0 旋转中心错误,并补全两处 Native provenance 注释。依据是对明日方舟官服客户端(2.7.61 / build 2761,Unity IL2CPP)GameAssembly.dll 的 IDA 反编译复核,关键结论已在下文直接给出,不依赖外部文档。

背景:native 的 imagerotate 机制(2.7.61 逆向结论)

  • 注册入口:Torappu.AVG.AVGImagePanel.GetExecutors @ 0x183e56250"imagerotate" 注册到 AVGImagePanel._ExecuteImageRotate @ 0x183e57140(BackgroundPanel 不注册此键,前景层专有)。
  • _ExecuteImageRotate:读 5 个参数键 fadetime/angle/block/inverse/circles(全明文);fadetimeCalculateFadetime @ 0x183e56070(= AVGController.animateRatio ×ばつ fadetime,与同 panel tween 执行器的 bypass 不对称);ease 参数不读,硬编码 SetEase(Ease.Linear);先 DOKill(this._rectTransform) 失效旧旋转 tween,再用 AVGUtils.CreateRotateTween @ 0x183e40090 旋转,OnKill 闭包里 if (block) FinishCommand();返回原始 block,无 effectiveBlock 合成。
  • 操作对象是 _rectTransform(panel 自己的 RectTransform,场景 panel_image 节点),全程不碰前景 _foreImage;旋转连带 fore/back 两个 Image 子节点,角度跨 [Image] 切图保留。
  • panel_image 的 pivot 是场景序列化值,默认 (0.5, 0.5) = 面板中心 ≈ 画面中心,因此 native 的旋转是原地倾斜
  • CreateRotateTween 的有符号扫角:三次 Mathf.Repeat(..., 360)(current=世界 eulerAngles.z、end=angle、delta=end-current),delta > 180 → delta -= 360;inversedelta < 0 → += 360+ circles*360,非 inverse 时 -circles*360delta > 0 → -= 360(符号判断都在加 circles 之前;inverse 即使 circles=0 也是方向开关);结果作相对增量交给 DORotate(RotateMode.LocalAxisAdd)。前端 SceneGeometry.rotateTweenDelta 已逐分支等价移植,本 PR 不动。

修复内容

1. 旋转中心绕舞台左上角而非画面中心(P0)

  • native 行为:旋转 panel_image 自己的 RectTransform,pivot 为场景序列化值(默认 (0.5,0.5) = 面板中心 ≈ 画面中心),画面原地倾斜
  • 前端行为:旋转 imageLayer Container,其 position/pivot 从未设置(均为 (0,0)),而前景 sprite 摆在 (640,360) 附近,所有旋转实际绕舞台左上角 (0,0) 公转:angle=180(act39side_st01:802)画面整体翻出屏幕;angle=-4(level_main_11-01_end:55)画面中心点位移约 sin(4°)×ばつ743px ≈ 52px。全量 14 条样本无一正确。
  • 修法:构造函数中 imageLayer.pivot.set(STORY_WIDTH/2, STORY_HEIGHT/2) + imageLayer.position.set(STORY_WIDTH/2, STORY_HEIGHT/2)(与 character rotationLayer 的既有做法一致)。pivot == position 使子坐标平移恒等,sprite 仍按 (640,360) 中心摆放,坐标语义不变;旋转则绕画面中心,对齐 RectTransform 的 (0.5,0.5) pivot。

2. 补全 runtime.ts 截断的 provenance 注释(注释项)

  • engine/runtime.tscase "imagerotate" 注释原来截断在 "Unlike the",补全为:与 panel 的 tween 执行器不同,fadetimeCalculateFadetime(animateRatio 缩放);ease 参数不读,native 硬编码 Ease.Linear

3. 更新 setImageRotate 的 provenance 注释(注释项)

  • 原注释只说 "imageLayer is the corresponding PIXI adaptation",未点破旋转中心。现注明:构造期 pivot 对应 panel_image RectTransform 的序列化 (0.5,0.5) pivot;并记录差异 Refactor: isEvent #3 维持现状的依据——native 仅 OnReset(剧情开始/skip)归零角度,widget 每剧情销毁重建 player/renderer,无需显式复位。

未处理项(及原因)

  • P2 差异 ISEvent: Init #2 (切图冻结进行中的旋转 tween):setImage/clearImage 递增 imageRotateSessionId 的去除,与 image 命令 review 差异 ci: update ci and some minor changes #4 同源同一修复,由并行的 image/imagetween PR 统一处理,避免双改冲突。
  • P3 差异 ci: update ci and some minor changes #4 (旋转连带 web-only largeimg 内容):largeimg 为已证伪的非 native 命令、纯 web 兼容层,review 建议为"考虑"级,不在本 PR 收敛范围。
  • P3 差异 XbMapViewer: Init #5 (circles 非整数取整):全量样本 circles 0 命中且脚本只写整数,不可达,review 明示"可不改"。

验证用剧情文件

语料根目录 torappu/storage/asset/gamedata/latest/story,以下相对路径全部命中 P0 修复(修复前所有 imagerotate 都绕左上角公转,修复后原地倾斜):

  • activities/act39side/level_act39side_st01.txt:802(angle=180 —— 修复前画面整体翻出屏幕的极端样本)、818(angle=0,fadetime=0,block=true 瞬时复位+立即解除阻塞)
  • obt/main/level_main_11-01_end.txt:55(angle=-4 —— 修复前伴随约 52px 位移的轻微倾斜)、59(angle=-60)、68([imagerotate] 全默认参数)
  • activities/act53side/level_act53side_st03.txt:330、342(inverse=true 方向开关)
  • activities/act49side/level_act49side_06_end.txt:512、514(image=ease="OutCirc" 参数均被两端忽略)
  • obt/main/level_main_13-05_beg.txt:904(angle=-180)
  • obt/main/level_main_13-19_end.txt:801、804
  • activities/act20side/level_act20side_04_end.txt:327
  • activities/act25side/level_act25side_06_beg.txt:404、408
  • activities/act33side/level_act33side_04_beg.txt:526
  • activities/act34side/level_act34side_08_beg.txt:768(isblock typo → false)

共 16 处 / 12 个文件。扫角算法(rotate-tween.spec.ts 5 用例)与参数映射(runtime.spec.ts "maps imagerotate strict parameters")已有单测覆盖且不受本改动影响;旋转中心由新增单测 tests/renderer.spec.ts "rotates the image panel around its center instead of the stage origin" 锁定(断言 pivot=(640,360)、angle=180 后中心 sprite 全局坐标不变、左上角 sprite 翻到右下角)。

测试

  • pnpm test:20 个文件 223 个用例全部通过(基线 222 + 新增 1)。
  • pnpm exec vue-tsc -b:通过。
  • pnpm exec eslint <改动文件>:通过。

native _ExecuteImageRotate(0x183e57140)旋转的是 panel_image 自己的
RectTransform,pivot 序列化为 (0.5,0.5)=面板中心,画面原地倾斜;前端
imageLayer 的 pivot/position 均为 (0,0),所有旋转实际绕舞台左上角公转
(angle=180 翻出屏幕,angle=-4 位移约 52px,全量样本无一正确)。
构造函数中把 imageLayer 的 pivot/position 设为 (640,360),对齐序列化
pivot;子坐标平移恒等,sprite 摆放语义不变。另补全 runtime.ts 截断的
provenance 注释(fadetime 过 CalculateFadetime、ease 硬编码 Linear),
并在 setImageRotate 注释中记录 pivot 对应关系与角度不复位的生命周期依据。
daflyinbed force-pushed the fix/story-player-imagerotate branch from b1a8b85 to 457ea0b Compare September 3, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /