轉自: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
水果共有 11 種類型,數組
遊戲目標是合成最高級的水果:大西瓜!當堆積的水果超過頂部紅線時則遊戲結束整理出須要實現的核心邏輯服務器
整個項目使用cocos creator v2.4.3實現,建議初次瞭解的同窗能夠先過一下官方文檔,本文不會過多介紹creator的使用(主要是我也不太熟練hah)官方文檔連接:https://docs.cocos.com/creato...微信
首先須要準備美術資源,本位全部美術素材和音頻材料均來源於 www.wesane.com/game/654/。首先訪問遊戲網站,打開network面板,能夠看見遊戲依賴的全部美術資源,咱們下載本身所需的文件便可。app
所需的圖片資源包括編輯器
每種水果合成效果貼圖,均包含
音頻文件同理,能夠在Filter欄選擇.mp3
後綴的請求快速篩選對應資源。
打開cocos creator,新建一個項目(也能夠直接導入從github下載的項目源碼)。而後記得將剛纔下載的素材資源拖拽到右下角的資源管理器中。
項目初始化以後,在左下角資源管理器新建一個遊戲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 等。