简介:微信小程序“合成大西瓜”的云开发版源码,包含完整小游戏工程与流量主接入逻辑。游戏将经典合成玩法与俄罗斯方块式下落、消消乐元素结合,用户只需拖动同样的水果让其碰撞合成,逐步挑战最终的大西瓜,操作简单、反馈直接,适合新手快速上手体验,也适合微信小游戏开发者和站长借鉴学习。资源内置流量主功能,当玩家使用吸铁石或万能球道具时需观看激励视频广告,可帮助已开通流量主的账号获得广告收益。包体共91个文件,压缩包大小约2.93MB,主要包含png游戏素材、json配置与场景文件、js核心逻辑代码、mp3音效,以及安装教程、云服务器推荐等说明文档,结构清晰,导入微信开发者工具即可运行,无需额外服务器和域名。目前已有1242人学习下载,源码覆盖从界面资源、游戏逻辑到广告变现的完整链路,便于二次开发与学习研究。
1. 把“合成大西瓜”小游戏跑起来,最关键的不是玩法,而是工程结构
拿到这份synthesis_watermelon-main微信小游戏源码时,最容易看漏的是它并不是一个需要后端支撑的小程序,而是一套 Cocos Creator 导出的微信小游戏工程。它把“水果从上方掉落,相同水果碰撞后合成更高级水果”的玩法直接跑在客户端,game.js负责启动,assets存素材,project.config.json存 appid。也就是说,你的关注点其实要放在两处:一个是合成玩法在微信运行时里怎么被算出来,另一个是变现用的流量主激励视频挂在哪个交互点上。
这段源码适合两类人:一类是想快速上线“合成大西瓜”换皮产品的小程序开发者,另一类是学习微信小游戏资源结构、物理合成和广告接入的进阶选手。后面会先从文件骨架拆起,再给出可抄的代码实现。
2. Cocos 构建产物与微信小游戏运行时的文件骨架
解压后不要急着拖进微信开发者工具,先看一遍顶级目录。synthesis_watermelon-main这一层里的文件顺序很典型:game.js放在根目录作为小游戏入口,game.json声明窗口和设备方向,ccRequire.js是 Cocos Creator 构建时生成的模块加载器,adapter-min.js把 Canvas、WebAudio 这些浏览器 API 映射到微信运行时。这套东西不是wxml+js的页面小程序,而是“小游戏”类型,导入时选错类型会直接报“找不到 game.js”。
2.1 各文件的作用及可以手动改的位置
| 文件/目录 | 这个文件干了什么 | 需要手动改吗 |
|---|---|---|
game.js | 微信小游戏的启动入口,加载引擎和主逻辑 | 一般不改,最多加调试输出 |
game.json | 声明窗口尺寸、设备方向、子包列表 | 可改竖屏/横屏和分辨率策略 |
ccRequire.js | Cocos 构建产物的模块注册与依赖加载器 | 不碰 |
adapter-min.js | 把微信 API 适配成浏览器/Cocos 需要的环境 | 不碰 |
main.js | 引擎启动后的初始化流程,通常包含cc.game.run() | 可在run()后挂自定义脚本 |
project.config.json | 工程配置,包含 appid、编译类型、libVersion | 必须换成你自己的 appid |
project.private.config.json | 开发者本地私有配置,会覆盖公共配置 | 只在调试时改,别提交 |
assets | Cocos 的原始资源目录,场景、脚本、贴图、音频都在这里 | 替换素材和调整逻辑的地方 |
img | 直接放置在项目根目录的图片素材,早期版本常用 | 替换水果贴图时可用 |
这份文件关系看着简单,但有一个容易踩的坑:很多人直接改game.json里的appid,以为跟页面小程序一样。微信小游戏不是这样的,小游戏的项目 ID 在project.config.json的"appid"字段里,game.json只负责运行时表现。比如game.json里常见的deviceOrientation设置为portrait,表示强制竖屏,改成landscape后整个游戏就会横过来。
2.2 appid 与项目配置
微信小游戏工程的 appid 不在game.json,而在project.config.json。这份源码里它大约在第 50 行,默认值通常是"appid": "touristappid"。微信开发者工具可以用游客模式打开,但你一旦要使用流量主,就必须换成自己的小游戏 appid,否则激励视频广告无法调试。
project.config.json核心字段长这样:
{ "description": "合成大西瓜 synthesis_watermelon", "setting": { "urlCheck": false, "es6": true, "minified": true }, "appid": "wx1234567890abcdef", "compileType": "game", "libVersion": "2.30.0" }compileType固定是game,不要改成miniprogram,否则微信开发者工具不会走小游戏加载链路。libVersion表示基础库版本,如果广告 API 或物理 API 不起作用,先检查这里是不是被改成了一个太旧的版本。urlCheck是关掉合法域名校验的开关,本地方便,但如果以后接了自己的云函数或接口,记得按需打开。
如果你有project.private.config.json,它也会携带一份appid并覆盖公共配置。所以只改project.config.json无效时,别疑惑,检查一下project.private.config.json里有没有残留旧 appid。直接用命令批量替换也可以:
# 备份后把 touristappid 替换成你自己的 appid cp project.config.json project.config.json.bak sed -i 's/"appid": "touristappid"/"appid": "wx1234567890abcdef"/' project.config.json这个命令的含义是先把原配置备份成.bak,再在同一文件内把touristappid替换成真实 appid。执行后再打开微信开发者工具,在项目详情里看到 appid 已经是自己的,才说明替换成功。
2.3 game.js 启动链路与调试入口
微信小游戏的执行顺序跟浏览器页面完全不同:微信加载game.json后,先执行根目录的game.js。Cocos 导出项目里,game.js通常只有三条 require 语句:
// game.js 入口列表,顺序不能乱 require('./adapter-min.js'); // 先把微信 API 适配成浏览器环境 require('./ccRequire.js'); // 再注册 Cocos Creator 的模块依赖表 require('./main.js'); // 最后启动引擎和游戏场景第一条是为了让引擎里用到的window、document、canvas等对象在微信小游戏环境里能跑起来;第二条是把 Cocos 的所有脚本模块注册进 CommonJS 运行时,做到按需加载;第三条才是真正进入cc.game生命周期。理解这个顺序之后,你就知道微信开发者工具的控制台里报错时应该先看哪一层。
如果你要验证入口脚本有没有正常加载,最省事的做法是在main.js顶部加一句console.log('[synthesis] main loaded'),编译后看 vConsole 是否有输出。如果只有adapter-min.js的输出而没有main.js,说明问题出在模块注册阶段,优先检查ccRequire.js是否被破坏或assets/scripts里有没有语法错误。
3. 合成规则、拖拽落点与碰撞合并的防抖实现
合成大西瓜看似是物理游戏,核心只有三块:水果等级映射、投掷落点计算、碰撞后的合并判定。只要看懂这三块,换素材、换难度都是分钟级。
3.1 水果等级与合成目标映射
常见版本从低到高是葡萄、橘子、柠檬、猕猴桃、番茄、桃子、菠萝、椰子、半西瓜、大西瓜。在这份源码里,这些素材放在assets或img目录下,脚本中通常只存整数level与缩放倍数,不直接读图片名。这么做的好处是:合成逻辑只关心数字,不关心图片,后期换“苹果版”“动物版”素材时,脚本完全不用动。
| level | 水果 | 缩放/半径倍数 | 二次合成目标 |
|---|---|---|---|
| 1 | 葡萄 | 0.52 | 2 |
| 2 | 橘子 | 0.62 | 3 |
| 3 | 柠檬 | 0.72 | 4 |
| 4 | 猕猴桃 | 0.82 | 5 |
| 5 | 番茄 | 0.86 | 6 |
| 6 | 桃子 | 0.94 | 7 |
| 7 | 菠萝 | 1.00 | 8 |
| 8 | 椰子 | 1.08 | 9 |
| 9 | 半西瓜 | 1.15 | 10 |
| 10 | 大西瓜 | 1.25 | 无 |
表格里的缩放倍数会直接影响物理碰撞半径。比如level 1葡萄的半径只有世界基准值的 0.52,而level 9半西瓜已经到 1.15。合成新水果时用nextLevel = fruit.level + 1查下一级,并同时把碰撞半径乘上下一个缩放倍数。不需要为每种水果单独写一个“变大函数”,只维护这个映射表就够了。
这里有个容易被忽略的细节:level 10 的大西瓜不应该参与合成,所以代码里要给MAX_LEVEL一个上限判断。如果直接把level + 1写成无条件执行,大西瓜与另一个大西瓜相撞时会生成一个不存在的level 11,表现通常是生成一个全黑或透明的水果贴图,还会把物理世界里的碰撞半径撑得过大。
3.2 拖拽与落点的坐标换算
在 Cocos 加微信小游戏环境里,屏幕坐标和游戏世界坐标是两个坐标系。手指在手机屏幕上触摸到的clientX,不能直接当成游戏世界里的 x 坐标使用,需要做一次比例换算,同时限制左右边界,避免水果落到 UI 按钮下面或者掉出屏幕。
// 以引擎宽 GAME_WIDTH、屏幕宽 window.screenWidth 为例 function screenToGameX(clientX) { const ratio = GAME_WIDTH / window.screenWidth; let x = clientX * ratio; const halfRadius = CURRENT_FRUIT_RADIUS / 2; const minX = halfRadius; const maxX = GAME_WIDTH - halfRadius; return Math.min(Math.max(x, minX), maxX); }ratio解决的是不同手机屏幕宽度不一致的问题。iPhone 和 Android 的物理像素不同,但游戏世界坐标系固定是设计分辨率,比如 750,所以必须把真实触摸点映射到设计分辨率。halfRadius保证生成的水果不会半颗身子卡在屏幕外,否则水果一半悬空,一半卡墙,后续碰撞合并表现会非常怪。
手势监听一般放在wx.onTouchStart和wx.onTouchEnd里:touchstart记录一个瞄准横线,touchend时取当前clientX调用screenToGameX,然后在该 x 位置生成水果并给一个向下的加速度。很多新手直接监听touchmove生成水果,结果一次拖动生成了五六个水果,这里要区分“瞄准移动”和“松手落子”两个动作。
3.3 碰撞合并的防抖与替换顺序
物理引擎每帧都在做碰撞检测。如果不加防抖,两个水果碰撞时回调可能被触发两次,导致level+1被多算一遍、生成两个高等级水果。常见做法是给每个水果实体挂一个merged标记,并且在合并时先关掉两个旧 body,再延迟卸载节点。
// 碰撞回调里只做合并登记,下一帧再替换实体 function onPairCollide(a, b) { if (a.mergeTag !== b.mergeTag) return; // 等级 tag 不一致不合并 if (a.merged || b.merged) return; // 其中一个已参与合并就跳过 const nextLevel = a.level + 1; const midX = (a.pos.x + b.pos.x) * 0.5; const midY = (a.pos.y + b.pos.y) * 0.5; a.merged = true; b.merged = true; physics.removeBody(a.body); physics.removeBody(b.body); const newFruit = spawnFruit(nextLevel, midX, midY); newFruit.body.setVelocity(0, 60); // 给一点 Y 向速度,防止卡在底层 }mergeTag是判断两种水果是否同一品种的关键字段,它可以直接等于level,也可以用imageName。用level更简单,因为合成逻辑本来就是数字相加。merged标记是防抖的核心:一旦某个水果参与合并,即使碰撞回调再触发一次,也会因为merged = true被跳过。midX/midY取两个碰撞体的中点,新水果从这里生成,视觉上就像两个水果长成了一个更大的。
如果你用的是 Box2D 系列物理引擎,API 会变成SetLinearVelocity,思路完全一样:拿到目标刚体,设置一个 Y 方向小速度,防止新旧刚体重叠时发生瞬时爆炸。注意不要在碰撞回调里同步销毁节点,因为物理引擎内部还在遍历刚体列表,直接destroy可能触发野指针,正确做法是先removeBody,再在update里完成节点销毁。
另外,合成后的新水果最好做一个“短暂不可再合并”保护。比如给newFruit设置一个mergeCooldown = 0.5秒,在update中递减。否则新生成的水果如果在同帧又碰到另一个同等级水果,会瞬间产生连环合成,一把游戏不到五秒就合成大西瓜,难度直接崩掉。部分“合成版”源码里故意保留这种连锁,是为了爽快感,但如果你要做长线留存,建议加这个冷却。
4. 流量主激励视频接入:吸铁石与万能球的广告变现链路
这份资源最值钱的地方不是玩法,而是已经接好的流量主变现功能。用户使用吸铁石和万能球时,需要观看激励视频广告;广告完整播完后道具才解锁。这个链路看着简单,但背后有几个容易漏掉的点。
4.1 先确认流量主开关再说广告
接入广告前要明白流量主不是所有小程序默认就有的。没有开通流量主或没有广告位时,wx.createRewardedVideoAd会直接抛错,所以代码里不能裸调,必须先判断当前环境是否允许展示广告。
| 场景 | 表现 | 处理 |
|---|---|---|
| 未开通流量主 | createRewardedVideoAd报错 | 隐藏按钮或直接放道具 |
| 广告拉取超时 | 用户点击后无反应 | 走降级逻辑 |
| 用户中途关闭视频 | onClose回调res.isEnded = false | 不发放道具 |
| 用户完整观看 | res.isEnded = true | 发放道具 |
最稳妥的方式是在游戏配置里放一个isAdEnabled开关。你自己测试时关掉,上架前打开;也可以从后台接口下发。这样即使微信官方调整了流量主准入规则,客户端也不会因为广告接口崩溃导致整个游戏白屏。
4.2 一条广告实例给两个道具复用
微信小游戏里同时创建多个RewardedVideoAd实例不是不行,但没必要。吸铁石和万能球共用同一个实例,减少内存,也避免重复加载广告。关键是onClose回调要先用offClose清掉上次监听,再挂新的,防止同一个视频关闭后弹出两次奖励。
let rewardedVideoAd = null; const AD_UNIT_ID = 'adunit-xxxxxxxxxxxx'; function ensureRewardedVideo() { if (rewardedVideoAd) return rewardedVideoAd; rewardedVideoAd = wx.createRewardedVideoAd({ adUnitId: AD_UNIT_ID }); rewardedVideoAd.onError((err) => { console.warn('广告加载失败', err); }); return rewardedVideoAd; } function watchAdForItem(itemName) { const ad = ensureRewardedVideo(); ad.show() .catch(() => ad.load().then(() => ad.show())) .catch(() => { unlockItem(itemName); // 广告加载失败时降级,不能让用户卡死 }); ad.offClose(); // 先清掉上一次回调,避免重复发放 ad.onClose((res) => { if (res && res.isEnded) { unlockItem(itemName); } }); }adUnitId是你自己在微信公众平台流量主后台创建的广告单元 ID,不是 appid。很多第一次接流量主的人会把广告位 ID 填成小游戏 appid,结果控制台一直报adUnitId invalid。show()返回的是 Promise,如果广告还没加载完成,第一次show会 reject,所以要 catch 后手动调load()再show()。res.isEnded是判断“是否完整观看”的唯一依据,用户中途退出时不能发奖励。
提示:
offClose在onClose前面调用,顺序不能反过来。先 off 再 on,能保证回调列表里只有一个处理函数。
4.3 道具发放与本地次数存储
道具发放不能只加一个全局变量,因为小游戏在低内存手机上随时可能被微信回收,重新进入游戏后道具就没了。至少要落到本地缓存wx.setStorageSync。
function unlockItem(item) { const key = 'synthesis_watermelon_items'; const items = wx.getStorageSync(key) || {}; items[item] = (items[item] || 0) + 1; wx.setStorageSync(key, items); refreshItemUI(item, items[item]); }wx.getStorageSync读不到时返回空对象,所以用|| {}兜底。items[item]用|| 0防止第一次读取到 undefined。refreshItemUI负责更新游戏界面左上角的道具角标,这样用户看完广告后立刻能看到数量变化,不会产生“广告看了但道具没到账”的投诉。
吸铁石和万能球在玩法层的作用是错开的:吸铁石会把当前场上可合并的同等级水果配对并拉近,制造一次或多组碰撞,属于“提高合成效率”的辅助道具;万能球则是生成一个能匹配任何水果的高等级前置水果,碰到任意水果都能触发一次合成,属于“保底容错”道具。把这两个道具放在广告后面,就是典型的“用爽点换留存,用广告换收益”设计。
从收益角度看,建议给两个道具加每日使用次数上限。比如每天免费看广告次数不超过 10 次,超过后按钮置灰并提示“今日次数已用完”。否则单个重度用户会无限刷广告,虽然广告收入增加,但对用户体验损耗很大,次日留存可能反而下降。
5. 部署、参数调优与道具验收:从touristappid到可上线
5.1 用微信开发者工具导入小游戏
打开微信开发者工具,选择“小游戏”类型,点击“导入项目”,目录选中synthesis_watermelon-main。首次导入时如果提示 appid 无效,先按第 2 章的命令替换 appid,再重新导入。注意不要选择“小程序”类型,否则工具会去找app.json,这个工程里只有game.json,直接报错。
如果你确定要上线,还需要在game.json里确认设备方向和 UI 安全区:
{ "deviceOrientation": "portrait", "showStatusBar": false, "networkTimeout": { "request": 10000 } }deviceOrientation保持portrait就可以,合成大西瓜这类下落玩法竖屏操作最自然。showStatusBar在小游戏里控制是否显示系统状态栏,如果你的 canvas 没有适配刘海屏,建议保留false,用自己的 UI 顶部遮罩处理。
5.2 常见报错与排查方向
| 报错/现象 | 原因 | 解决 |
|---|---|---|
appid is not valid | project.config.json或私有配置文件里 appid 未替换 | 两处都改成自己的 appid |
Can not find module "cocos" | 导入路径不是game.js所在目录 | 选根目录,确保game.js在项目根层级 |
RewardedVideoAd is not defined | 广告 API 不可用或流量主未开通 | 加typeof wx.createRewardedVideoAd === 'function'判断 |
| 水果错位/贴图全黑 | 缓存库和实际资源 UUID 不匹配 | 删除根目录library和temp后重新编译 |
| 物理穿透严重 | 物理步长迭代次数太低或掉帧 | 调低水果下落间隔,检查是否有大数组频繁遍历 |
5.3 调参点与素材替换技巧
| 参数 | 通常位置 | 调整效果 |
|---|---|---|
| 水果生成间隔 | 生成器定时器setSeconds | 间隔越长越简单,越短越难 |
| 合成等级上限 | MAX_LEVEL | 控制是否能合成大西瓜 |
| 物理迭代次数 | 物理引擎初始化参数 | 提高精度但会吃性能 |
| 吸铁石吸附范围 | 道具脚本里的半径阈值 | 范围越大,一次广告带来的合成越多 |
替换水果素材时,不建议直接在微信开发者工具里删掉旧图拖入新图,因为 Cocos 的资源 UUID 变了会导致加载失败。正确做法是:新图片导出为 PNG,保持原文件名和原目录结构,覆盖assets里的对应图片文件,然后删除library和temp两个缓存目录,再重新编译。这个流程比重新打开工程快,也最不容易出现黑屏。改完参数后,花一局时间把所有道具完整触发一遍,确认吸铁石和万能球都在视频完整观看后发放、中途退出不发放,再提交审核。
本文还有配套的精品资源,点击获取