开源了!从 0 到 1 实现“合成大西瓜”

转自:shymeanwww.shymean.com/article/使用cocos实现一个合成大西瓜

最近微博上曝出了不少瓜,"合成大西瓜"这个游戏也很火热,玩了一阵还挺有意思的。研究了一下原理,发现目前流传的版本都是魔改编译后的版本,代码通过压缩不具有可读性,所以决定本身照着实现一个。前端

本项目主要用做 cocos creator 练手使用,全部美术素材和音频材料均来源于 www.wesane.com/game/654/感谢原做者,向每一位游戏开发者致敬!node

本文全部代码及素材都放在 Github上:
https://github.com/tangxiangm...git

也能够经过在线预览地址体验:https://web-game-9gh6nrus14fe...github

微信没法点击外链,请给咱们公号发送 大西瓜获取入口

游戏逻辑

整个游戏逻辑比较简单,结合了俄罗斯方块与消除游戏的核心玩法web

  • 在生成一个水果
  • 点击屏幕,水果移动到对应x轴位置并自由下落
  • 每一个水果会与其余水果发生碰撞,两个相同的水果碰撞时会发生合并,升级成更高一级的水果

水果共有 11 种类型,数组

游戏目标是合成最高级的水果:大西瓜!当堆积的水果超过顶部红线时则游戏结束整理出须要实现的核心逻辑服务器

  • 生成水果
  • 水果下落与碰撞
  • 水果消除动画效果及升级逻辑

预备工做

cocos creator基本概念

整个项目使用cocos creator v2.4.3实现,建议初次了解的同窗能够先过一下官方文档,本文不会过多介绍creator的使用(主要是我也不太熟练hah)官方文档连接:https://docs.cocos.com/creato...微信

游戏素材

首先须要准备美术资源,本位全部美术素材和音频材料均来源于 www.wesane.com/game/654/。首先访问游戏网站,打开network面板,能够看见游戏依赖的全部美术资源,咱们下载本身所需的文件便可。app

所需的图片资源包括编辑器

  • 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种水果(固然也能够添加或者改为其余的东西),若是每种水果都像上面去手动生成预制资源而后分别初始化,那也太繁琐了,咱们须要解决动态渲染多种水果的方式。咱们须要得到每种水果的贴图信息,而后在实例化水果时选择对应贴图便可,最简单的方式就是维护一个配置表,每行的数据字段包括idiconSF

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 等。
相关文章
相关标签/搜索