/* * ============================================================ * 小球弹跳(MG 弹跳球)· AE 2022 一键搭建脚本 * AE 2022 (22.x) / ExtendScript / Windows & macOS 通用 * * 用法:AE 2022 → 文件 → 脚本 → 运行脚本文件… → 选这个 .jsx * (或放到 Support Files/Scripts/ 下,从「窗口」菜单直接调用) * * 它做的事(和手动做一模一样,只是把重复动作交出去): * 1. 新建 1920x1080 / 25fps / 6 秒合成,黄底 * 2. 画 200px 品红球,锚点钉在球底 * 3. 位置 13 个关键帧(真实衰减:每次高度 x0.49) * 4. 顶点速度 0、触地速度按段落自动算(=2x 段均速),复刻重力曲线 * 5. 挤压拉伸:A 型 / B 型两套块,触地帧线性、其余缓动 * 6. 地面白线 * 7. 影子:位置/不透明度/缩放全用表达式跟着球,不 K 一帧 * 8. 开运动模糊 * ============================================================ */ (function ballBounceBuilder() { // ------------------ 可调参数 ------------------ var CFG = { compName: "小球弹跳", width: 1920, height: 1080, fps: 25, duration: 6, groundY: 900, bgColor: "FFB800", ballColor: "D800D8", lineColor: "FFFFFF", ballSize: 200, shadowW: 240, shadowH: 60, shadowY: 903 }; // 位置关键帧 [帧, y] —— 13 个,弹跳高度每次乘以 0.49 var POS_KEYS = [ [ 0, 300], [ 20, 900], [ 34, 606], [ 48, 900], [ 58, 756], [ 68, 900], [ 75, 829], [ 82, 900], [ 87, 865], [ 92, 900], [ 95, 883], [ 98, 900], [150, 900] ]; // 哪些是触地帧(true = 触地) var IS_GROUND = [false, true, false, true, false, true, false, true, false, true, false, true, false]; // 缩放关键帧 [帧, sx, sy] —— A 型(间隔>=12帧)/ B 型(间隔<12帧) var SCALE_KEYS = [ [ 0, 100, 100], [ 18, 88, 114], [ 20, 128, 72], [ 23, 96, 104], [ 26, 100, 100], [ 46, 88, 114], [ 48, 128, 72], [ 51, 96, 104], [ 54, 100, 100], [ 67, 90, 112], [ 68, 124, 76], [ 70, 100, 100], [ 81, 90, 112], [ 82, 124, 76], [ 84, 100, 100], [ 91, 90, 112], [ 92, 124, 76], [ 94, 100, 100], [ 97, 90, 112], [ 98, 124, 76], [100, 100, 100] ]; var SCALE_GROUND = { 20: 1, 48: 1, 68: 1, 82: 1, 92: 1, 98: 1 }; // ------------------ 小工具 ------------------ function hex2rgb(hex) { hex = String(hex).replace("#", ""); return [parseInt(hex.substr(0, 2), 16) / 255, parseInt(hex.substr(2, 2), 16) / 255, parseInt(hex.substr(4, 2), 16) / 255, 1]; } function T(frame) { return frame / CFG.fps; } // ------------------ 开始 ------------------ app.beginUndoGroup("小球弹跳 自动搭建"); try { var comp = app.project.items.addComp(CFG.compName, CFG.width, CFG.height, 1, CFG.duration, CFG.fps); comp.bgColor = hex2rgb(CFG.bgColor); comp.motionBlur = true; // ===== 1. 背景 ===== var bg = comp.layers.addSolid(hex2rgb(CFG.bgColor), "BG", CFG.width, CFG.height, 1); // ===== 2. 球(形状图层)===== var ball = comp.layers.addShape(); ball.name = "Ball"; var ballGroup = ball.property("ADBE Root Vectors Group").addProperty("ADBE Vector Group"); ballGroup.name = "球"; var ballContents = ballGroup.property("ADBE Vectors Group"); var ellipse = ballContents.addProperty("ADBE Vector Shape - Ellipse"); ellipse.name = "椭圆路径 1"; ellipse.property("ADBE Vector Ellipse Size").setValue([CFG.ballSize, CFG.ballSize]); var ballFill = ballContents.addProperty("ADBE Vector Graphic - Fill"); ballFill.name = "填充 1"; ballFill.property("ADBE Vector Fill Color").setValue(hex2rgb(CFG.ballColor)); // 锚点钉在球底正中间:球底 = 位置 Y,挤压时往上扁,不会陷进地面 var ballT = ball.property("ADBE Transform Group"); ballT.property("ADBE Anchor Point").setValue([0, CFG.ballSize / 2]); ball.motionBlur = true; // ===== 3. 位置关键帧 ===== var pos = ballT.property("ADBE Position"); var i; for (i = 0; i < POS_KEYS.length; i++) { pos.setValueAtTime(T(POS_KEYS[i][0]), [CFG.width / 2, POS_KEYS[i][1]]); } // ===== 4. 重力缓动(复刻「关键帧速度」那一步)===== // 顶点:速度 0、影响 60% // 触地:进入速度 = 2 x 上一段均速,离开 = 同值反向,影响 10% var peak = 0; for (i = 0; i < POS_KEYS.length; i++) { var idx = i + 1; var inEase, outEase; if (IS_GROUND[i]) { var prev = POS_KEYS[i - 1]; var dy = POS_KEYS[i][1] - prev[1]; // 像素,向下为正 var dt = (POS_KEYS[i][0] - prev[0]) / CFG.fps; // 秒 peak = dt > 0 ? 2 * (dy / dt) : 0; // 触地瞬时速度 var isLastRest = (i === POS_KEYS.length - 2); // 98 帧之后就不弹了 inEase = new KeyframeEase(peak, 10); outEase = new KeyframeEase(isLastRest ? 0 : -peak, 10); } else { inEase = new KeyframeEase(0, 60); outEase = new KeyframeEase(0, 60); } pos.setInterpolationTypeAtKey(idx, KeyframeInterpolationType.BEZIER, KeyframeInterpolationType.BEZIER); pos.setTemporalEaseAtKey(idx, [inEase], [outEase]); } // ===== 5. 挤压拉伸 ===== var scale = ballT.property("ADBE Scale"); for (i = 0; i < SCALE_KEYS.length; i++) { var kf = SCALE_KEYS[i]; scale.setValueAtTime(T(kf[0]), [kf[1], kf[2]]); } for (i = 0; i < SCALE_KEYS.length; i++) { var sidx = i + 1; if (SCALE_GROUND[SCALE_KEYS[i][0]]) { // 触地瞬间保持线性,才有「砸」的力度 scale.setInterpolationTypeAtKey(sidx, KeyframeInterpolationType.LINEAR, KeyframeInterpolationType.LINEAR); } else { scale.setInterpolationTypeAtKey(sidx, KeyframeInterpolationType.BEZIER, KeyframeInterpolationType.BEZIER); var e = new KeyframeEase(0, 33.33); scale.setTemporalEaseAtKey(sidx, [e, e], [e, e]); } } // ===== 6. 地面线 ===== var ground = comp.layers.addShape(); ground.name = "Ground"; var gGroup = ground.property("ADBE Root Vectors Group").addProperty("ADBE Vector Group"); gGroup.name = "线"; var gContents = gGroup.property("ADBE Vectors Group"); var gPath = gContents.addProperty("ADBE Vector Shape - Group"); var shape = new Shape(); shape.vertices = [[-760, 0], [760, 0]]; shape.closed = false; gPath.property("ADBE Vector Shape").setValue(shape); var gStroke = gContents.addProperty("ADBE Vector Graphic - Stroke"); gStroke.property("ADBE Vector Stroke Color").setValue(hex2rgb(CFG.lineColor)); gStroke.property("ADBE Vector Stroke Width").setValue(4); ground.property("ADBE Transform Group").property("ADBE Position") .setValue([CFG.width / 2, CFG.groundY]); // ===== 7. 影子(全表达式,不 K 帧)===== var shadow = comp.layers.addShape(); shadow.name = "Shadow"; var sGroup = shadow.property("ADBE Root Vectors Group").addProperty("ADBE Vector Group"); sGroup.name = "影子"; var sContents = sGroup.property("ADBE Vectors Group"); var sEllipse = sContents.addProperty("ADBE Vector Shape - Ellipse"); sEllipse.property("ADBE Vector Ellipse Size").setValue([CFG.shadowW, CFG.shadowH]); var sFill = sContents.addProperty("ADBE Vector Graphic - Fill"); sFill.property("ADBE Vector Fill Color").setValue([0, 0, 0, 1]); var sT = shadow.property("ADBE Transform Group"); sT.property("ADBE Anchor Point").setValue([0, 0]); sT.property("ADBE Position").setValue([CFG.width / 2, CFG.shadowY]); // 球越高 → 影子越淡越小;落地 → 最浓最大 var hExpr = 'var h = thisComp.layer("Ball").transform.position[1];\n'; sT.property("ADBE Opacity").expression = hExpr + 'linear(h, 300, 900, 8, 24);'; sT.property("ADBE Scale").expression = hExpr + 'var s = linear(h, 300, 900, 65, 118);\n[s, s];'; // 影子横向跟着球(球只上下动,写上是保险) sT.property("ADBE Position").expression = 'var p = thisComp.layer("Ball").transform.position;\n' + '[p[0], ' + CFG.shadowY + '];'; // ===== 8. 收尾 ===== comp.layer(1).selected = false; comp.openInViewer(); alert("搭建完成:" + CFG.compName + "\n\n" + "· 13 个位置关键帧 + 重力缓动\n" + "· A/B 两套挤压拉伸\n" + "· 表达式驱动影子\n" + "· 运动模糊已开\n\n" + "按 空格 预览。想改配色直接改图层颜色。"); } catch (err) { alert("脚本出错(第 " + err.line + " 行):\n" + err.toString()); } app.endUndoGroup(); })();