feat: refine match3d spawn visuals
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-08 22:52:13 +08:00
parent 23ba2703b4
commit a1e5c2150c
4 changed files with 49 additions and 9 deletions

View File

@@ -6,7 +6,7 @@
1. 产品名称:百梦。
2. 产品愿景百梦AI团队致力于打造AI互动内容UGC平台。
3. 产品slogan每个人都可以在10分钟内轻松创作出一款精品互动作品
3. 产品slogan不用代码不用美术10分钟把脑洞变成有趣的体验
4. 产品特点:低门槛创作、高完成度作品、玩过后可改造并发布。
5. 关键技术Harness Engineering、多Agent调度、AI创作工具、AI原生游戏框架。
6. 产品心智:想玩但找不到、玩到不满意、平台外体验不满意时,都可以来百梦做成自己满意的。
@@ -28,16 +28,14 @@
```text
百梦
AI互动内容UGC平台
把想玩的世界,亲手做出来
10分钟做自己的互动内容
```
第二层远读slogan
```text
每个人都可以在10分钟内
轻松创作出一款
精品互动作品
不用代码,不用美术,
10分钟把脑洞变成有趣的体验
```
第三层:产品特点
@@ -88,6 +86,8 @@ reference image: 百梦气泡共创logo方向图
output: output/imagegen/baimeng-expo-rollup/baimeng-rollup-background-gpt-image-2.png
```
2026-05-08 根据新文案重新调用 `gpt-image-2` 生成新版底图。新版底图在中上部保留更干净的两行 slogan 留白,并在下半部增加轻量内容卡、创作路径和 AI 辅助创作氛围,最终再叠加精确中文排版。
因为图片模型直接生成中文长文案存在错字风险最终稿采用“gpt-image-2 底图 + 本地精确中文排版”的方式生成:
```text

View File

@@ -250,3 +250,15 @@ cannon-es
3. 只有水平外接半径发生重叠的已有物体会影响本次生成高度;远处物体不能把新物体整体抬高,避免破坏原有随机洒落和分层节奏。
4. 该避让只解决“直接创建在已有模型内部”的初始穿插,后续沉降、翻滚、堆叠仍交给 cannon-es 物理模拟。
5. 本节不允许额外引入中心引力、扩大锅容量或修改模型生成规则;若后续仍需优化,只继续围绕生成高度、入场节拍和沉降窗口做局部迭代。
## 20. 从小到大的生成动画
2026-05-08 追加生成动画优化,参考原型中物体逐个出现、从小到大补入容器的观感。
编码口径:
1. 该优化只作用于前端 3D 表现层的可见 mesh 缩放,不改变后端快照、碰撞体尺寸、物品数量、锅半径、点击判定、备选栏、三消和胜负规则。
2. 物理 body 在创建时仍使用最终尺寸碰撞体,并立即加入 cannon-es 物理世界,确保生成动画过程中碰撞已经按完整体积稳定占位。
3. 可见 mesh 初始以较小比例显示,再用缓动动画放大到完整尺寸;视觉缩放不得反向修改 body shape、质量、边界半径或生成高度避让计算。
4. 入场动画继续服从第 18 节的创建限流和第 19 节的生成高度避让;不能为了动画效果把物体直接放进已有堆叠内部。
5. 动画结束后 mesh 缩放必须回到 `1`,避免影响后续点击可读性和托盘对应关系。

View File

@@ -140,6 +140,8 @@ const MATCH3D_ITEM_SPAWN_STAGGER_MS = 4;
const MATCH3D_ITEM_SPAWN_STACK_CLEARANCE = 0.14;
const MATCH3D_ITEM_SPAWN_STACK_RADIUS_PADDING = 0.08;
const MATCH3D_ITEM_SPAWN_ANIMATION_MS = 260;
const MATCH3D_ITEM_SPAWN_VISUAL_SCALE_START = 0.18;
const MATCH3D_ITEM_SPAWN_VISUAL_DROP_OFFSET = 0.04;
const MATCH3D_ITEM_EXTREME_VERTICAL_SPEED_LIMIT = 8.6;
const MATCH3D_ITEM_EXTREME_HORIZONTAL_SPEED_LIMIT = 4.4;
const MATCH3D_CENTER_GRAVITY_COEFFICIENT = 0;
@@ -558,6 +560,18 @@ function resolveSpawnAnimationProgress(entry: PhysicsEntry, now: number) {
);
}
export function resolveMatch3DSpawnVisualScale(progress: number) {
const clampedProgress = Math.min(
1,
Math.max(0, Number.isFinite(progress) ? progress : 0),
);
const easedProgress = 1 - Math.pow(1 - clampedProgress, 3);
return (
MATCH3D_ITEM_SPAWN_VISUAL_SCALE_START +
(1 - MATCH3D_ITEM_SPAWN_VISUAL_SCALE_START) * easedProgress
);
}
function applyCenterGravity(entry: PhysicsEntry) {
if (MATCH3D_CENTER_GRAVITY_COEFFICIENT <= 0) {
return;
@@ -1028,7 +1042,7 @@ function createPhysicsEntryFromPendingSpawn(
0.08,
0.08 + (pendingSpawn.item.layer % 4) * 0.02,
);
visual.mesh.scale.setScalar(0.82);
visual.mesh.scale.setScalar(MATCH3D_ITEM_SPAWN_VISUAL_SCALE_START);
runtime.world.addBody(body);
runtime.scene.add(visual.mesh);
@@ -1714,11 +1728,12 @@ export function Match3DPhysicsBoard({
applyStabilityPlanToBody(entry, activeRuntime.stabilityPlan);
constrainBodyInsidePot(entry);
const spawnProgress = resolveSpawnAnimationProgress(entry, now);
const spawnScale = 0.82 + spawnProgress * 0.18;
const spawnScale = resolveMatch3DSpawnVisualScale(spawnProgress);
entry.mesh.scale.setScalar(spawnScale);
entry.mesh.position.set(
entry.body.position.x,
entry.body.position.y - (1 - spawnProgress) * 0.06,
entry.body.position.y -
(1 - spawnProgress) * MATCH3D_ITEM_SPAWN_VISUAL_DROP_OFFSET,
entry.body.position.z,
);
entry.mesh.quaternion.set(

View File

@@ -31,6 +31,7 @@ import {
resolveMatch3DSpawnTimingPlan,
resolveMatch3DStackTargetY,
resolveMatch3DSpawnDelay,
resolveMatch3DSpawnVisualScale,
resolveMatch3DSpawnY,
resolveMatch3DTrayPreviewRotation,
resolveMatch3DTrayPreviewReferenceDimension,
@@ -591,6 +592,18 @@ test('3D 新物体生成高度会避让同位置已有堆叠', () => {
expect(unchangedSpawnY).toBe(plannedSpawnY);
});
test('3D 新物体生成动画只缩放可见模型并最终回到完整尺寸', () => {
const startScale = resolveMatch3DSpawnVisualScale(0);
const middleScale = resolveMatch3DSpawnVisualScale(0.5);
const endScale = resolveMatch3DSpawnVisualScale(1);
expect(startScale).toBeGreaterThan(0);
expect(startScale).toBeLessThan(0.25);
expect(middleScale).toBeGreaterThan(startScale);
expect(middleScale).toBeLessThan(endScale);
expect(endScale).toBe(1);
});
test('积木视觉键不会被统一兜底成红色苹字', () => {
const run = startLocalMatch3DRun(2);
run.items = run.items.slice(0, 2).map((item, index) => ({