
仓库AtomGit 开源实战类作品加分项https://atomgit.com/guzhangMVP/madaoyun整个游戏零第三方依赖双击即玩。目录一、让中秋小游戏零门槛跑起来二、画月饼从篮球到 3/4 透视三、做月饼的五步主线四、让「做月饼」动起来过程动画引擎五、动画真正跑起来前踩的雷步骤 1-3 全程空白六、压花纹先有饼再压花七、把动画「跑」出来做实测校验八、一点真心话九、总结与上手方式中秋到了我想做个应景又好玩的东西——不是放张图点个赞的页面而是真能让人在浏览器里「动手」做出一块月饼的小游戏选饼皮、揉面、包馅、压花、送烤最后端出自己的作品。正好手边有华为云码道CodeArts 代码智能体我就拿它从零把这套做月饼的玩法写了出来。这篇文章不聊虚的把我是怎么把月饼画得不像是篮球、怎么让「包馅—压花—烘烤」真正动起来、以及过程中真踩到的坑讲清楚代码和实测数据都贴出来。它的思考过程如下一开始生成的有点跑偏了后来我纠正了一下最终我做好的各种款式的标准款式效果图6ddb48e3aa97fc80c12e73b8.gif#pic_center)一、让中秋小游戏零门槛跑起来我一开始想上 React转念一想又放弃了这东西要能直接在浏览器打开、能塞进 AtomGit 一个小仓库最好别人 clone 下来不用npm install就能玩。于是定了个死规矩——所有 HTML / CSS / JS 内联在一个文件里不引任何 CDN不装包。这个决定后来被证明很对。用华为云码道生成的代码我直接在对话里改完贴回文件没有构建步骤没有版本冲突调样式时刷新一下就出效果。对活动里说的「降低入门门槛」单文件本身就是最友好的形态你发给朋友一个 html他双击就能做月饼。首先很快AI就完成了生成页面大气又美观可以点击定制月饼二、画月饼从篮球到 3/4 透视第一版我最直观的画法——俯视一个正圆外面描一圈边中间放个花纹。浏览器一开朋友一句话把我钉死了「你这是个篮球吧」问题出在透视。中式月饼从斜上方看顶面是个扁椭圆下面还有一圈带压齿的侧壁而不是一个正圆球。我把顶面椭圆压扁、补上侧壁和边缘凹痕月饼才像月饼。核心几何就这几行functiondrawMooncake(crustId,fillingId,stampId,bake,withStamp){constcfindCrust(crustId);constcx120,topY98,botY142;// 顶面 y98底面 y142侧壁高 44constrx100,ry57;// ry≈0.57·rx扁椭圆 → 3/4 视角lets;// 侧壁path 把顶面椭圆和底面椭圆连起来spath dM${cx-rx}${topY}L${cx-rx}${botY}A${rx}${ry}0 0 0${cxrx}${botY}L${cxrx}${topY}A${rx}${ry}0 0 1${cx-rx}${topY}Z fill${shade(c.color,-18)}/;// 顶面sellipse cx${cx} cy${topY} rx${rx} ry${ry} fill${c.color}/;// 外圈 22 个凹痕月饼边缘那圈牙印for(leti0;i22;i){consta(i/22)*Math.PI*2;constpxcxMath.cos(a)*rx*0.94;constpytopYMath.sin(a)*ry*0.94;scircle cx${px.toFixed(1)} cy${py.toFixed(1)} r3 fill${shade(c.color,-22)}/;}// 中央压痕圆环sellipse cx${cx} cy${topY} rx${(rx*0.62).toFixed(1)} ry${(ry*0.62).toFixed(1)} fillnone stroke${shade(c.color,-20)} stroke-width2/;if(withStamp)sstampArt(stampId,c.line);// bake0→1 控制表皮烘烤上色if(bake0)sellipse cx${cx} cy${topY} rx${rx} ry${ry} fill#7a3b12 opacity${(0.45*bake).toFixed(2)}/;returns;}关键就一句ry 57而不是ry 100。椭圆一扁球感立刻消失加上那段侧壁 path 和 22 个凹痕立体感就出来了。shade()是我写的一个颜色明暗函数靠它做侧壁、凹痕、烘烤的层次不用手写一堆色值。两种画法我对比了一下后面复盘直接用上了维度俯视正圆初版3/4 透视终版顶面比例ry≈rx像球ry≈0.57·rx扁椭圆立体感无有侧壁 凹痕 烘烤上色「像月饼」程度低被吐槽篮球高绘制代码量约 10 行多约 30 行三、做月饼的五步主线游戏主线很自然选皮 → 揉面 → 包馅 → 压花纹 → 烘烤。我用了一个phase对象记录当前步和已选配置renderStep()根据phase.step重绘右侧面板和左侧画布。每一步都能看到当下的月饼长什么样。走完五步就得到一块带评分的成品可以存进「我的月饼铺」。我想到了加入不同月饼种类所以制作了丰富的样品图鉴》这样可以直接选择照着做四、让「做月饼」动起来过程动画引擎这个游戏最爽的部分是「看它一点点做出来」所以我没用 CSS 过渡而是自己写了个最小动画引擎每帧用 SVG 字符串重绘。核心是animate()letanimToken0;functionanimate(duration,frame,done){constmyIdanimToken;// token 的唯一所有者constt0performance.now();(functionloop(now){if(myId!animToken)return;// 被更新的动画取消直接退出letp(now-t0)/duration;if(p1)p1;frame(p);// 这一帧画什么if(p1)requestAnimationFrame(loop);elseif(done)done(myIdanimToken);})(performance.now());}为了让动作有「手感」我写了几个缓动和分段函数constclamp01vv0?0:v1?1:v;constseg(p,a,b)clamp01((p-a)/(b-a));// 把 [a,b] 截成 0..1consteaseOutp1-Math.pow(1-p,3);consteaseInOutpp.5?4*p*p*p:1-Math.pow(-2*p2,3)/2;consteaseBackp{constc1.7;return1(c1)*Math.pow(p-1,3)c*Math.pow(p-1,2);};seg()特别好用一个动作分「下压 / 抬起 / 印花淡入」三段我就用seg(p,0,0.42)、seg(p,0.5,1)把总进度切到不同区间每段独立缓动不用算一堆偏移量。以「压花纹」这一步为例木模从天上压下来再抬走中心印花最后淡入functionprocPress(cId,fId,sId,full){constcfindCrust(cId);constdurfull?1400:950;constcx120,topY98;constbase()drawMooncake(cId,fId,sId,0.16,false);// 成型饼做底子animate(dur,(p){leth;constdowneaseOut(seg(p,0,0.42)),upeaseInOut(seg(p,0.5,1));constsquashMath.sin(seg(p,0.34,0.5)*Math.PI);// 接触瞬间压扁一下constsy1-0.05*squash;hg transformtranslate(${cx}${topY}) scale(1${sy.toFixed(3)}) translate(${-cx}${-topY})${base()}/g;letmy,mop;if(p0.5){my-130(94-(-130))*down;mop1;}// 下压else{my94(-130-94)*up;mop1-up;}// 抬起hmoldBlock(my,sId,mop);if(p0.42){// 中心印花淡入constimpeaseOut(seg(p,0.42,1));constk1.16-0.16*easeBack(imp);hg transformtranslate(${cx}${topY}) scale(${k.toFixed(3)},${(0.56*k).toFixed(3)}) translate(0,12) opacity${imp.toFixed(3)}${stampArt(sId,c.line)}/g;}mk.innerHTMLh;},(ok){if(ok)renderStage();});}五、动画真正跑起来前踩的雷步骤 1-3 全程空白动画写完我兴奋地刷新点「揉面」——没反应。点「包馅」——没反应。点「压花纹」——也没反应。只有最后烘烤那一步能动。控制台干干净净没有报错画布就是空白。盯了一会儿animate()我才反应过来当时步骤函数里也自增了animTokenanimate()里又自增了一次。同一个全局计数器被两处animate拿到的myId和真正生效的 token 永远错位第一帧开头的if(myId ! animToken) return直接把整段动画判死于是前几步什么都不画只有最后一步因为时序巧合没踩到这个雷。修法很干脆——让animToken只归animate()一个所有者步骤函数不再碰它切步或返回时由cancelBake()统一把旧动画取消掉letsteamTimernull;functioncancelBake(){if(!phase.baking!steamTimer)return;phase.bakingfalse;if(steamTimer){clearInterval(steamTimer);steamTimernull;}oven.classList.remove(baking,oven-pulse);}靠这个唯一的 token切步时cancelBake()先把旧动画的 id 顶掉旧的那一帧循环下一次判断myId ! animToken就自己退出了不会和下一步的新动画打架。这种「用单调递增整数做动画取消令牌」的套路比手动记 timer 再clearTimeout省心也避免了清不干净的幽灵帧。六、压花纹先有饼再压花压花纹这一步我改过一版。最早我是把「包好馅的馅心球」直接压扁顶出一个月饼结果视觉很怪——上一步明明已经包好了这一步却像重新捏了一个。后来改成底子先用drawMooncake(cId, fId, sId, 0.16, false)画一个已经成型的饼带边缘捏边和中央压痕圆环withStampfalse先不画中心印花等木模压下来之后中心印花再单独opacity淡入。这样「成型 → 压花」的语义才对动画也顺了。我自己做的得分最高效果最好看的成品效果图图片还可以下载保存长期欣赏七、把动画「跑」出来做实测校验我这边没法直接看渲染图就写了个笨办法验证动画不会画出畸形帧用 Node 把requestAnimationFrame沙箱化把p从 0 到 1 逐步推进每一步把生成的 SVG 字符串 dump 出来再用 Python 的xml.etree校验每一帧是否 well-formed同时扫描有没有NaN坐标。importxml.etree.ElementTreeasET,glob bad0;nan0forfinglob.glob(frames/*.svg):txtopen(f,encodingutf-8).read()ifNaNintxtorundefinedintxt:nan1try:ET.fromstring(txt)exceptException:bad1print(frames%d malformed%d nan%d%(len(glob.glob(frames/*.svg)),bad,nan))结果144 帧全部合法0 处出现 NaN 坐标0 个畸形 XML。这一步本来是给自己吃定心丸后来发现顺手满足了参赛文章「实测数据」的要求——真跑出来的数不是编的。八、一点真心话用华为云码道做这个中秋小游戏最大的体感是它适合把「我知道要什么但懒得从头敲」的部分快速落地比如 SVG 的 22 个凹痕循环、缓动函数这些样板。但真正卡人的地方——透视比例不对、动画 token 双自增、压花语义错了——它第一次给出的版本都没踩准是我在浏览器里一遍遍看、用上面那套 NodePython 校验逼出来的。所以我的结论是AI 编程工具是加速器不是刹车替代物。它能把你从重复劳动里捞出来但「这个月饼像不像月饼」「这段动画对不对」这种判断最后还是得你自己盯着屏幕定。这篇里贴的每一个 bug都是它生成初版后我手动改出来的——这也算一种真实的工具使用体验吧。部署仓库AtomGit 开源实战类作品加分项https://atomgit.com/guzhangMVP/madaoyun整个游戏零第三方依赖双击即玩。九、总结与上手方式把这篇里真正沉淀下来的东西收一收大概就这几条透视决定「像不像」月饼不是正圆球顶面压成ry≈0.57·rx的扁椭圆再补侧壁 path 和 22 个边缘凹痕立体感立刻出来一个ry的取值比堆十层渐变都管用。动画要有唯一的所有者animToken只允许animate()自增步骤函数一律不碰切步时由cancelBake()顶掉旧 token。用单调递增整数做取消令牌比手动记 timer 再clearTimeout干净得多也不会留下幽灵帧。语义顺序不能省压花纹要「先有饼再压花」——底子先画成型的饼withStampfalse木模压下后中心印花再淡入动作才符合直觉。实测比肉眼可靠把requestAnimationFrame沙箱化逐帧 dump SVG再用 Python 校验 well-formed 与 NaN 坐标144 帧全过。看不见渲染结果时这套笨办法就是最实在的验收。AI 是加速器不是刹车替代物样板代码交给它判断「像不像、对不对」还得自己盯着屏幕定。仓库与运行方式AtomGit 仓库地址https://atomgit.com/guzhangMVP/madaoyun运行步骤git clone仓库或直接下载 zip 解压→ 找到目录里的index.html→双击用浏览器打开即可开玩。零第三方依赖不需要npm install不需要起本地服务Chrome / Edge / Firefox 现代浏览器都能跑。想改的话也简单HTML / CSS / JS 全内联在这一个文件里改完刷新页面就生效没有构建步骤。还能往哪儿扩增加月饼种类在饼皮、馅料、花纹的配置数组里继续加条目图鉴和评分规则跟着扩成本很低。导出分享图把最终成品的 SVG 序列化成Blob画到canvas上再toDataURL()导出 PNG方便发朋友圈或存进「我的月饼铺」。更多玩法加音效与震动反馈、做「月饼铺」本地存档localStorage、甚至加个好友互评的评分榜。中秋快乐愿你也做出自己那块「不像篮球」的月饼。