转自:shymeanwww.shymean.com/article/应用cocos实现一个合成大西瓜
最近微博上曝出了很多瓜,"合成大西瓜"这个游戏也很炽热,玩了一阵还挺有意思的。钻研了一下原理,发现目前流传的版本都是魔改编译后的版本,代码通过压缩不具备可读性,因而决定本人照着实现一个。
本我的项目次要用作 cocos creator 练手应用,所有美术素材和音频资料均来源于 www.wesane.com/game/654/感激原作者,向每一位游戏开发者致敬!
本文所有代码及素材都放在 Github上:
https://github.com/tangxiangm...
也能够通过在线预览地址体验:https://web-game-9gh6nrus14fe...
微信无奈点击外链,请给咱们公号发送大西瓜
获取入口
游戏逻辑
整个游戏逻辑比较简单,联合了俄罗斯方块与打消游戏的外围玩法
- 在生成一个水果
- 点击屏幕,水果挪动到对应x轴地位并自在着落
- 每个水果会与其余水果产生碰撞,两个雷同的水果碰撞时会产生合并,升级成更高一级的水果
水果共有 11 种类型,
游戏指标是合成最高级的水果:大西瓜!当沉积的水果超过顶部红线时则游戏完结整顿出须要实现的外围逻辑
- 生成水果
- 水果着落与碰撞
- 水果打消动画成果及降级逻辑
预备工作
cocos creator基本概念
整个我的项目应用cocos creator v2.4.3实现,倡议首次理解的同学能够先过一下官网文档,本文不会过多介绍creator的应用(次要是我也不太纯熟hah)官网文档链接:https://docs.cocos.com/creato...
游戏素材
首先须要筹备美术资源,本位所有美术素材和音频资料均来源于 www.wesane.com/game/654/。首先拜访游戏网站,关上network面板,能够看见游戏依赖的所有美术资源,咱们下载本人所需的文件即可。
所需的图片资源包含
- 11张水果贴图
每种水果合成成果贴图,均蕴含
- 一张果粒图片
- 一张圆形水珠图片
- 一张爆炸贴图
- 两个西瓜合成时有灯光和撒花的成果,工夫无限暂不实现
音频文件同理,能够在Filter栏抉择.mp3
后缀的申请疾速筛选对应资源。
- 水果打消时的爆炸声和水声
创立游戏场景和背景
关上cocos creator,新建一个我的项目(也能够间接导入从github下载的我的项目源码)。而后记得将方才下载的素材资源拖拽到右下角的资源管理器中。
创立scene和背景节点
我的项目初始化之后,在左下角资源管理器新建一个游戏Scene
,取名game作为游戏主场景。
创立结束后就能够在资源管理器的assets中看见方才创立的名为game的scene。抉择game场景,在左上角的层级管理器中能够看见场景的Canvas画布根节点,cocos默认画布是横屏的960*640
,能够抉择根节点而后再右侧属性查看器中调整宽高为640*960
接下来创立背景层,咱们在Canvas节点上面新建一个background节点,因为整个背景是纯色#FBE79D
的,因而应用一个单色Sprite填充即可
同样将background节点宽高调整为整个画布的大小,因为默认锚点均为0.5*0.5
,此时整个画布会被齐全填充。当初整个游戏场景大略是这个样子的
接下来设计游戏的逻辑脚本局部
场景脚本组件
在assets目录下新建一个js脚本,依照常规命令成Game.js
,creator会生成一个带根底cc.Class
的模板文件
先将脚本组件与节点关联起来,抉择Canvas根节点,在右侧属性查看器中增加组件,而后抉择方才创立的这个Game
组件
而后编写具体的代码逻辑,关上Game.js文件(倡议应用vscode或者webstrom关上整个我的项目的根目录进行编辑)外面的初始代码大略长这样
// Game.js cc.Class({ extends: cc.Component, properties: { }, onLoad(){ }, start(){ } })
咱们须要在这里保护整个游戏的逻辑,前面逐渐增加代码内容。
创立水果
水果是整个游戏的外围元素,在游戏中被频繁创立和销毁。
生成单个水果预制资源
这种动态创建的节点能够通过预制资源Prefab
来管制,制作prefab最简略的形式就是将资源从资源管理器拖动到场景编辑器中,而后再将层级管理器中的节点拖回资源管理器。这里以等级最低的水果“葡萄”为例
而后将层级管理器中的节点删除,这样咱们就失去了一个fruit的预制资源,在脚本组件中,就能够应用代码通过预制资源动静生成节点了。批改Game.js
,增加一个属性fruitPrefab
,其类型为cc.Prefab
,
// Game.js properties: { fruitPrefab: { default: null, type: cc.Prefab }, }
回到creator,。抉择Canvas节点,能够在属性查看器中的Game
组件栏目看见和批改该属性了。咱们将方才制作的prefab资源从资源管理器拖动到这里,在初始化的时候,有cocos负责初始化对应的属性数据
创立单个水果
回到Game.js,开始编写真正的逻辑:创立一个葡萄
// Game.js onLoad(){ let fruit = cc.instantiate(this.fruitPrefab); fruit.setPosition(cc.v2(0, 400)); this.node.addChild(fruit); }
预览模式下就能够看见屏幕正上方有一个葡萄了
nice,十分好的开始!
此外,因为水果还蕴含一些特定的逻辑,咱们能够向它增加一个Fruit
脚本组件,尽管目前看起来还没有什么用创立Fruit脚本组件与下面创立Game组件相似,而后抉择方才制作的prefab从新编辑,关联上Fruit用户脚本组件即可。
动静保护多种水果
整个游戏共11种水果(当然也能够增加或者改成其余的货色),如果每种水果都像下面去手动生成预制资源而后别离初始化,那也太繁琐了,咱们须要解决动静渲染多种水果的形式。咱们须要取得每种水果的贴图信息,而后在实例化水果时抉择对应贴图即可,最简略的形式就是保护一个配置表,每行的数据字段包含id
和iconSF
const FruitItem = cc.Class({ name: 'FruitItem', properties: { id: 0, // 水果的类型 iconSF: cc.SpriteFrame // 贴图资源 } });
而后为Game
脚本组件新增一个fruits
属性,用于保留每种水果的配置信息,其类型是数组,数组内元素类型为方才创立的FruitItem
// Game.js properties: { fruits: { default: [], type: FruitItem }, }
回到编辑器,这时候能够发现Game组件的属性上面多了一个Fruits
属性,将其长度批改为11,而后顺次编写每个水果的id,同时将其贴图资源从资源编辑器贴过来(体力活)
这样咱们只须要传入想要制作的水果id,就能够获取到对应的配置信息,并动静批改贴图了这种初始化的逻辑应该由水果本人保护,因而放在方才创立的Fruit
组件中,咱们裸露一个init接口进去
// Fruit.js properties: { id: 0, }, // 实例放在能够在其余组件中调用 init(data) { this.id = data.id // 依据传入的参数批改贴图资源 const sp = this.node.getComponent(cc.Sprite) sp.spriteFrame = data.iconSF },
而后批改一下下面的初始化水果的代码
// Game.js createOneFruit(num) { let fruit = cc.instantiate(this.fruitPrefab); // 获取到配置信息 const config = this.fruits[num - 1] // 获取到节点的Fruit组件并调用实例办法 fruit.getComponent('Fruit').init({ id: config.id, iconSF: config.iconSF }); }
这样就能够欢快的创立各种水果了
监听点击事件
cocos提供了各种事件监听,前端和客户端同学肯定不会生疏。整个游戏会在点击屏幕时创立一个水果,这只有监听一下全局点击事件即可,这个逻辑同样放在Game
脚本组件中
onLoad() { // 监听点击事件 this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this) }, onTouchStart(){ this.createOneFruit(1) // 生成水果 }
理论游戏中还须要解决随机生成水果、上一个水果在点击的x轴着落等细节逻辑,这里不再赘述。
物理零碎:自由落体与刚体碰撞
下面解决了水果创立的逻辑,在整个游戏中,水果是能够产生着落及弹性碰撞等物理成果的,利用cocos内置的物理引擎,能够很不便的实现对cocos引擎不相熟的同学能够先看看这个官网demo,外面展现的比拟具体(起码比文档要更容易了解)
开启物理引擎与碰撞检测
首先是开启物理引擎,以及设置重力大小
const instance = cc.director.getPhysicsManager() instance.enabled = true // instance.debugDrawFlags = 4 instance.gravity = cc.v2(0, -960);
而后须要开启碰撞检测,默认是敞开的
const collisionManager = cc.director.getCollisionManager(); collisionManager.enabled = true
而后设置周围的墙壁用于碰撞,这样水果就不会无限度往下面掉落了
// 设置周围的碰撞区域 let width = this.node.width; let height = this.node.height; let node = new cc.Node(); let body = node.addComponent(cc.RigidBody); body.type = cc.RigidBodyType.Static; const _addBound = (node, x, y, width, height) => { let collider = node.addComponent(cc.PhysicsBoxCollider); collider.offset.x = x; collider.offset.y = y; collider.size.width = width; collider.size.height = height; } _addBound(node, 0, -height / 2, width, 1); _addBound(node, 0, height / 2, width, 1); _addBound(node, -width / 2, 0, 1, height); _addBound(node, width / 2, 0, 1, height); node.parent = this.node;
当初咱们就开启了游戏世界的物理引擎,而后还须要配置须要受引擎影响的节点,也就是咱们的水果。
水果刚体组件与碰撞组件
回到creator,找到咱们的水果prefab,而后增加物理组件首先是Rigid Body(刚体)组件
而后是物理碰撞组件,因为咱们的水果全是圆形的,都抉择PhysicsCircleCollider组件就能够了,如果有个香蕉之类不规则多边形边的话,工作量就会减少不少\~
接下来能够看看整体成果,(记得把方才的点击事件加上,而后管制一下随机生成水果类型)
完满!!
水果碰撞回调
增加实现之后,还须要开启刚体组件的碰撞属性Enabled Contact Listener
,这样能够接管到碰撞之后的回调
这个碰撞回调同样写在Fruit脚本组件外面,
// Fruit.js onBeginContact(contact, self, other) { // 检测到是两个雷同水果的碰撞 if (self.node && other.node) { const s = self.node.getComponent('Fruit') const o = other.node.getComponent('Fruit') if (s && o && s.id === o.id) { self.node.emit('sameContact', {self, other}); } } },
为了保障Fruit组件性能的单一性,在两个雷同水果产生碰撞时,咱们通过事件告诉Game.js
,这样能够在初始化水果的时候注册sameContact
自定义事件的解决办法
// Game.js createOneFruit(num) { let fruit = cc.instantiate(this.fruitPrefab); // ...其余初始化逻辑 fruit.on('sameContact', ({self, other}) => { // 两个node都会触发,长期解决,看看有没有其余办法只展现一次的 other.node.off('sameContact') // 解决水果合并的逻辑,上面再解决 this.onSameFruitContact({self, other}) }) }
这样当水果产生碰撞时,咱们就可能监听并解决打消降级逻辑了。
打消水果动画
无动画版本
简略的打消逻辑就是将两个节点删除,而后在原水果地位生成高一级的水果即可,没有任何动画成果
self.node.removeFromParent(false) other.node.removeFromParent(false) const {x, y} = other.node // 获取合并的水果地位 const id = other.getComponent('Fruit').id const nextId = id + 1 const newFruit = this.createFruitOnPos(x, y, nextId) // 在指定地位生成新的水果
尽管看起来有点奇怪,但确实能够以玩了!
剖析动画
关上源站,通过Performance面板剖析一下动画成果(这里就不录gif了)
能够看见合成的时候动画成果包含
- 碰撞水果向原水果核心挪动
- 果粒爆炸的粒子成果
- 水珠爆炸的粒子成果
- 一滩果汁的缩放动画
此外还有爆炸声和水声的音效
治理爆炸素材资源
因为整个动画波及到的素材较多,每种水果均蕴含3种颜色不同的贴图,与下面FruitItem相似,咱们也采纳prefab加动静资源的做法来治理对应素材和动画逻辑。首先定义一个JuiceItem
,保留单种水果爆炸须要的素材
// Game.js const JuiceItem = cc.Class({ name: 'JuiceItem', properties: { particle: cc.SpriteFrame, // 果粒 circle: cc.SpriteFrame, // 水珠 slash: cc.SpriteFrame, // 果汁 } });
而后为Game组件新增一个juices
属性
// Game.js properties: { juices: { default: [], type: JuiceItem }, juicePrefab: { default: null, type: cc.Prefab }, }
接下来又是卖劳力的时候了,将贴图资源都拖放到juices
属性下
而后新增一个空的预制资源,次要是为了挂载脚本组件,也就是上面的Juice
脚本,而后记得将该预制资源挂载到Game的juicePrefab
上。最初,新建Juice
组件,用来实现爆炸的动画逻辑,同样须要裸露init接口
// Juice.js cc.Class({ extends: cc.Component, properties: { particle: { default: null, type: cc.SpriteFrame }, circle: { default: null, type: cc.SpriteFrame }, slash: { default: null, type: cc.SpriteFrame } }, // 同样裸露一个init接口 init(data) { this.particle = data.particle this.circle = data.particle this.slash = data.slash }, // 动画成果 showJuice(){ } }
这样,在合并的时候,咱们初始化一个Juice节点,同时展现爆炸成果即可
// Game.js let juice = cc.instantiate(this.juicePrefab); this.node.addChild(juice); const config = this.juices[id - 1] const instance = juice.getComponent('Juice') instance.init(config) instance.showJuice(pos, n) // 对应的爆炸逻辑
爆炸粒子动画
对于粒子动画,网上能查到不少材料,如果感兴趣,也能够移步我之前整顿的前端常见动画实现原理。粒子动画的次要的实现思路为:初始化N个粒子,管制他们的速度大小、方向和生命周期,而后管制每个粒子依照对应的参数执行动画,所有粒子会集在一起的成果就组成了粒子动画。话虽如此,要把动画成果调好还是挺麻烦的,须要管制各种随机参数。
showJuice(pos, width) { // 果粒 for (let i = 0; i < 10; ++i) { const node = new cc.Node('Sprite'); const sp = node.addComponent(cc.Sprite); sp.spriteFrame = this.particle; node.parent = this.node; // ... 一堆随机的参数 node.position = pos; node.runAction( cc.sequence( // ...各种action对应的动画逻辑 cc.callFunc(function () { // 动画完结后打消粒子 node.active = false }, this)) ) } // 水珠 for (let f = 0; f < 20; f++) { // 同果粒,应用的spriteFrame切换成 this.circle } // 果汁只有一张贴图,应用this.slash,展现惯例的action缩放和通明动画即可 },
源我的项目的代码中应用createFruitL
这个办法来解决爆炸动画,尽管通过了代码压缩,但依稀能看出对应的动画参数逻辑,如果不想调整动画参数,能够借鉴一下
这样,就实现了爆炸成果的展现,大略相似于这样,尽管有点丑
音效
通过cc.audioEngine
间接播放AudioClip
资源来实现音效在Game组件下新增两个类型为AudioClip的资源,不便脚本组件拜访
properties: { boomAudio: { default: null, type: cc.AudioClip }, waterAudio: { default: null, type: cc.AudioClip } }
同上,在属性查看器中将两个音频资源从资源管理器拖动到Game组件的属性下方
onSameFruitContact(){ cc.audioEngine.play(this.boomAudio, false, 1); cc.audioEngine.play(this.waterAudio, false, 1); }
这样就能够在碰撞的时候听到声音了。
构建打包
实现整个游戏的开发之后,能够抉择构建公布,打包成web-mobile版本,而后部署在服务器上,就能够给其他人高兴地游玩了
小结
不知不就就写到了最初,貌似!!曾经大工告成了!!尽管还有很多细节没有实现,比方增加得分、合成西瓜之后的撒花等性能,感兴趣的同学能够本人克隆去尝试批改一下。本文所有代码及素材都放在github下面了,也能够通过在线预览地址体验实现这个游戏花了这周六下午 + 一个早晨的工夫,因为对 cocos creator 并不是很相熟,因而花了一些工夫去看文档、查资料,甚至去B站上看了点教学视频。不过播种的成就感与满足感还是很大的,也算是正儿八经写了点游戏。最初,尤其要感激我媳妇,帮忙测试及提新需要。不说了,我还得再去加一个点击水果间接打消的性能!
微信无奈点击游戏链接,请给咱们公号发送大西瓜
获取入口开源前哨
日常分享热门、乏味和实用的开源我的项目。参加保护 10万+ Star 的开源技术资源库,包含:Python、Java、C/C++、Go、JS、CSS、Node.js、PHP、.NET 等。