1. 從0開始實(shí)現(xiàn)一個(gè)合成大西瓜

        共 15373字,需瀏覽 31分鐘

         ·

        2021-03-04 11:13


        作者:橙紅年代 (https://juejin.cn/post/6923803717808422925)

        最近微博上曝出了很多瓜,"合成大西瓜"這個(gè)游戲也很火熱,玩了一陣還挺有意思的。研究了一下原理,發(fā)現(xiàn)目前流傳的版本都是魔改編譯后的版本,代碼經(jīng)過壓縮不具備可讀性,因此決定自己照著實(shí)現(xiàn)一個(gè)。

        本項(xiàng)目主要用作cocos creator練手使用,所有美術(shù)素材和音頻材料均來源于 http://www.wesane.com/game/654/%E3%80%82

        感謝原作者,向每一位游戲開發(fā)者致敬!

        本文所有代碼及素材都放在github https://github.com/tangxiangmin/cocos-big-watermelon 上面了,也可以通過在線預(yù)覽地址 https://web-game-9gh6nrus14fec37e-1252170212.tcloudbaseapp.com/ 體驗(yàn)(點(diǎn)擊底部 閱讀原文 可直接體驗(yàn)

        游戲邏輯

        整個(gè)游戲邏輯比較簡(jiǎn)單,結(jié)合了俄羅斯方塊與消除游戲的核心玩法

        • 在生成一個(gè)水果
        • 點(diǎn)擊屏幕,水果移動(dòng)到對(duì)應(yīng)x軸位置并自由下落
        • 每個(gè)水果會(huì)與其他水果發(fā)生碰撞,兩個(gè)相同的水果碰撞時(shí)會(huì)發(fā)生合并,升級(jí)成更高一級(jí)的水果

        水果共有11種類型,

        游戲目標(biāo)是合成最高級(jí)的水果:大西瓜!當(dāng)堆積的水果超過頂部紅線時(shí)則游戲結(jié)束

        整理出需要實(shí)現(xiàn)的核心邏輯

        • 生成水果
        • 水果下落與碰撞
        • 水果消除動(dòng)畫效果及升級(jí)邏輯

        預(yù)備工作

        cocos creator基本概念

        整個(gè)項(xiàng)目使用cocos creator v2.4.3實(shí)現(xiàn),建議初次了解的同學(xué)可以先過一下官方文檔:https://docs.cocos.com/creator/2.3/manual/zh/,本文不會(huì)過多介紹creator的使用(主要是我也不太熟練hah)

        游戲素材

        首先需要準(zhǔn)備美術(shù)資源,本位所有美術(shù)素材和音頻材料均來源于 www.wesane.com/game/654/。

        首先訪問游戲網(wǎng)站,打開network面板,可以看見游戲依賴的所有美術(shù)資源,我們下載自己所需的文件即可

        所需的圖片資源包括

        • 11張水果貼圖
        • 每種水果合成效果貼圖,均包含
          • 一張果粒圖片
          • 一張圓形水珠圖片
          • 一張爆炸貼圖
        • 兩個(gè)西瓜合成時(shí)有燈光和撒花的效果,時(shí)間有限暫不實(shí)現(xiàn)

        音頻文件同理,可以在Filter欄選擇.mp3后綴的請(qǐng)求快速篩選對(duì)應(yīng)資源。

        • 水果消除時(shí)的爆炸聲和水聲

        創(chuàng)建游戲場(chǎng)景和背景

        打開cocos creator,新建一個(gè)項(xiàng)目(也可以直接導(dǎo)入從github下載的項(xiàng)目源碼)。

        然后記得將剛才下載的素材資源拖拽到右下角的資源管理器中。

        創(chuàng)建scene和背景節(jié)點(diǎn)

        項(xiàng)目初始化之后,在左下角資源管理器新建一個(gè)游戲Scene,取名game作為游戲主場(chǎng)景

        創(chuàng)建完畢后就可以在資源管理器的assets中看見剛才創(chuàng)建的名為game的scene。

        選擇game場(chǎng)景,在左上角的層級(jí)管理器中可以看見場(chǎng)景的Canvas畫布根節(jié)點(diǎn),cocos默認(rèn)畫布是橫屏的960*640,可以選擇根節(jié)點(diǎn)然后再右側(cè)屬性檢查器中調(diào)整寬高為640*960

        接下來創(chuàng)建背景層,我們?cè)贑anvas節(jié)點(diǎn)下面新建一個(gè)background節(jié)點(diǎn),由于整個(gè)背景是純色#FBE79D的,因此使用一個(gè)單色Sprite填充即可

        同樣將background節(jié)點(diǎn)寬高調(diào)整為整個(gè)畫布的大小,由于默認(rèn)錨點(diǎn)均為0.5*0.5,此時(shí)整個(gè)畫布會(huì)被完全填充。

        現(xiàn)在整個(gè)游戲場(chǎng)景大概是這個(gè)樣子的

        接下來設(shè)計(jì)游戲的邏輯腳本部分

        場(chǎng)景腳本組件

        在assets目錄下新建一個(gè)js腳本,按照慣例命令成Game.js,creator會(huì)生成一個(gè)帶基礎(chǔ)cc.Class的模板文件

        先將腳本組件與節(jié)點(diǎn)關(guān)聯(lián)起來,選擇Canvas根節(jié)點(diǎn),在右側(cè)屬性檢查器中添加組件,然后選擇剛才創(chuàng)建的這個(gè)Game組件

        然后編寫具體的代碼邏輯,打開Game.js文件(建議使用vscode或者webstrom打開整個(gè)項(xiàng)目的根目錄進(jìn)行編輯)

        里面的初始代碼大概長(zhǎng)這樣

        // Game.js
        cc.Class({
            extends: cc.Component,

            properties: {

            },
            onLoad(){

            },
            start(){ }
        })

        我們需要在這里維護(hù)整個(gè)游戲的邏輯,后面逐步添加代碼內(nèi)容。

        創(chuàng)建水果

        水果是整個(gè)游戲的核心元素,在游戲中被頻繁創(chuàng)建和銷毀。

        生成單個(gè)水果預(yù)制資源

        這種動(dòng)態(tài)創(chuàng)建的節(jié)點(diǎn)可以通過預(yù)制資源Prefab來控制,

        制作prefab最簡(jiǎn)單的方式就是將資源從資源管理器拖動(dòng)到場(chǎng)景編輯器中,然后再將層級(jí)管理器中的節(jié)點(diǎn)拖回資源管理器。

        這里以等級(jí)最低的水果“葡萄”為例

        然后將層級(jí)管理器中的節(jié)點(diǎn)刪除,這樣我們就得到了一個(gè)fruit的預(yù)制資源,在腳本組件中,就可以使用代碼通過預(yù)制資源動(dòng)態(tài)生成節(jié)點(diǎn)了。

        修改Game.js,添加一個(gè)屬性fruitPrefab,其類型為cc.Prefab,

        // Game.js
        properties: {
            fruitPrefab: {
                defaultnull,
                type: cc.Prefab
            },
        }

        回到creator,。選擇Canvas節(jié)點(diǎn),可以在屬性檢查器中的Game組件欄目看見和修改該屬性了。我們將剛才制作的prefab資源從資源管理器拖動(dòng)到這里,在初始化的時(shí)候,有cocos負(fù)責(zé)初始化對(duì)應(yīng)的屬性數(shù)據(jù)

        創(chuàng)建單個(gè)水果

        回到Game.js,開始編寫真正的邏輯:創(chuàng)建一個(gè)葡萄

        // Game.js
        onLoad(){
            let fruit = cc.instantiate(this.fruitPrefab);
            fruit.setPosition(cc.v2(0400));

            this.node.addChild(fruit);
        }

        預(yù)覽模式下就可以看見屏幕正上方有一個(gè)葡萄了

        nice,非常好的開始!

        此外,由于水果還包含一些特定的邏輯,我們可以向它添加一個(gè)Fruit腳本組件,雖然目前看起來還沒有什么用

        創(chuàng)建Fruit腳本組件與上面創(chuàng)建Game組件類似,然后選擇剛才制作的prefab重新編輯,關(guān)聯(lián)上Fruit用戶腳本組件即可。

        動(dòng)態(tài)維護(hù)多種水果

        整個(gè)游戲共11種水果(當(dāng)然也可以添加或者改成其他的東西),如果每種水果都像上面去手動(dòng)生成預(yù)制資源然后分別初始化,那也太繁瑣了,我們需要解決動(dòng)態(tài)渲染多種水果的方式。

        我們需要獲得每種水果的貼圖信息,然后在實(shí)例化水果時(shí)選擇對(duì)應(yīng)貼圖即可,最簡(jiǎn)單的方式就是維護(hù)一個(gè)配置表,每行的數(shù)據(jù)字段包括idiconSF

        const FruitItem = cc.Class({
            name'FruitItem',
            properties: {
                id0// 水果的類型
                iconSF: cc.SpriteFrame // 貼圖資源
            }
        });

        然后為Game腳本組件新增一個(gè)fruits屬性,用于保存每種水果的配置信息,其類型是數(shù)組,數(shù)組內(nèi)元素類型為剛才創(chuàng)建的FruitItem

        // Game.js
        properties: {
            fruits: {
                default: [],
                type: FruitItem
            },
        }

        回到編輯器,這時(shí)候可以發(fā)現(xiàn)Game組件的屬性下面多了一個(gè)Fruits屬性,將其長(zhǎng)度修改為11,然后依次編寫每個(gè)水果的id,同時(shí)將其貼圖資源從資源編輯器貼過來(體力活)

        這樣我們只需要傳入想要制作的水果id,就可以獲取到對(duì)應(yīng)的配置信息,并動(dòng)態(tài)修改貼圖了

        這種初始化的邏輯應(yīng)該由水果自己維護(hù),因此放在剛才創(chuàng)建的Fruit組件中,我們暴露一個(gè)init接口出來

        // Fruit.js
        properties: {
            id0,
        },
        // 實(shí)例放在可以在其他組件中調(diào)用
        init(data) {
            this.id = data.id
            // 根據(jù)傳入的參數(shù)修改貼圖資源
            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]

            // 獲取到節(jié)點(diǎn)的Fruit組件并調(diào)用實(shí)例方法
            fruit.getComponent('Fruit').init({
                id: config.id,
                iconSF: config.iconSF
            });
        }

        這樣就可以愉快的創(chuàng)建各種水果了

        監(jiān)聽點(diǎn)擊事件

        cocos提供了各種事件監(jiān)聽,前端和客戶端同學(xué)一定不會(huì)陌生。

        整個(gè)游戲會(huì)在點(diǎn)擊屏幕時(shí)創(chuàng)建一個(gè)水果,這只要監(jiān)聽一下全局點(diǎn)擊事件即可,這個(gè)邏輯同樣放在Game腳本組件中

        onLoad() {
            // 監(jiān)聽點(diǎn)擊事件
            this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this)
        },
        onTouchStart(){
            this.createOneFruit(1// 生成水果
        }

        實(shí)際游戲中還需要處理隨機(jī)生成水果、上一個(gè)水果在點(diǎn)擊的x軸下落等細(xì)節(jié)邏輯,這里不再贅述。

        物理系統(tǒng):自由落體與剛體碰撞

        上面處理了水果創(chuàng)建的邏輯,在整個(gè)游戲中,水果是可以產(chǎn)生下落及彈性碰撞等物理效果的,利用cocos內(nèi)置的物理引擎,可以很方便的實(shí)現(xiàn)

        對(duì)cocos引擎不熟悉的同學(xué)可以先看看這個(gè)官方demo,里面展示的比較詳細(xì)(起碼比文檔要更容易理解)

        開啟物理引擎與碰撞檢測(cè)

        首先是開啟物理引擎,以及設(shè)置重力大小

        const instance = cc.director.getPhysicsManager()
        instance.enabled = true
        // instance.debugDrawFlags = 4
        instance.gravity = cc.v2(0-960);

        然后需要開啟碰撞檢測(cè),默認(rèn)是關(guān)閉的

        const collisionManager = cc.director.getCollisionManager();
        collisionManager.enabled = true

        然后設(shè)置四周的墻壁用于碰撞,這樣水果就不會(huì)無限制往下面掉落了

         // 設(shè)置四周的碰撞區(qū)域
        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 / 201, height);
        _addBound(node, width / 201, height);

        node.parent = this.node;

        現(xiàn)在我們就開啟了游戲世界的物理引擎,然后還需要配置需要受引擎影響的節(jié)點(diǎn),也就是我們的水果。

        水果剛體組件與碰撞組件

        回到creator,找到我們的水果prefab,然后添加物理組件

        首先是Rigid Body(剛體)組件

        然后是物理碰撞組件,因?yàn)槲覀兊乃菆A形的,都選擇PhysicsCircleCollider組件就可以了,如果有個(gè)香蕉之類不規(guī)則多邊形邊的話,工作量就會(huì)增加不少~

        接下來可以看看整體效果,(記得把剛才的點(diǎn)擊事件加上,然后控制一下隨機(jī)生成水果類型)

        完美!!

        水果碰撞回調(diào)

        添加完成之后,還需要開啟剛體組件的碰撞屬性Enabled Contact Listener,這樣可以接收到碰撞之后的回調(diào)

        這個(gè)碰撞回調(diào)同樣寫在Fruit腳本組件里面,

        // Fruit.js
        onBeginContact(contact, self, other) {
            // 檢測(cè)到是兩個(gè)相同水果的碰撞
            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組件功能的單一性,在兩個(gè)相同水果發(fā)生碰撞時(shí),我們通過事件通知Game.js,這樣可以在初始化水果的時(shí)候注冊(cè)sameContact自定義事件的處理方法

        // Game.js
        createOneFruit(num) {
            let fruit = cc.instantiate(this.fruitPrefab);
            // ...其他初始化邏輯
             fruit.on('sameContact', ({self, other}) => {
                // 兩個(gè)node都會(huì)觸發(fā),臨時(shí)處理,看看有沒有其他方法只展示一次的
                other.node.off('sameContact'
                // 處理水果合并的邏輯,下面再處理
                this.onSameFruitContact({self, other})
             })
        }

        這樣當(dāng)水果發(fā)生碰撞時(shí),我們就能夠監(jiān)聽并處理消除升級(jí)邏輯了。

        消除水果動(dòng)畫

        無動(dòng)畫版本

        簡(jiǎn)單的消除邏輯就是將兩個(gè)節(jié)點(diǎn)刪除,然后在原水果位置生成高一級(jí)的水果即可,沒有任何動(dòng)畫效果

        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) // 在指定位置生成新的水果

        雖然看起來有點(diǎn)奇怪,但的確可以以玩了!

        分析動(dòng)畫

        打開源站,通過Performance面板分析一下動(dòng)畫效果(這里就不錄gif了)

        可以看見合成的時(shí)候動(dòng)畫效果包括

        • 碰撞水果向原水果中心移動(dòng)
        • 果粒爆炸的粒子效果
        • 水珠爆炸的粒子效果
        • 一灘果汁的縮放動(dòng)畫

        此外還有爆炸聲和水聲的音效

        管理爆炸素材資源

        由于整個(gè)動(dòng)畫涉及到的素材較多,每種水果均包含3種顏色不同的貼圖,與上面FruitItem類似,我們也采用prefab加動(dòng)態(tài)資源的做法來管理對(duì)應(yīng)素材和動(dòng)畫邏輯。

        首先定義一個(gè)JuiceItem,保存單種水果爆炸需要的素材

        // Game.js
        const JuiceItem = cc.Class({
            name'JuiceItem',
            properties: {
                particle: cc.SpriteFrame, // 果粒
                circle: cc.SpriteFrame, // 水珠
                slash: cc.SpriteFrame, // 果汁
            }
        });

        然后為Game組件新增一個(gè)juices屬性

        // Game.js
        properties: {
            juices: {
                default: [],
                type: JuiceItem
            },
            juicePrefab: {
                defaultnull,
                type: cc.Prefab
            },
        }

        接下來又是賣勞力的時(shí)候了,將貼圖資源都拖放到juices屬性下

        然后新增一個(gè)空的預(yù)制資源,主要是為了掛載腳本組件,也就是下面的Juice腳本,然后記得將該預(yù)制資源掛載到Game的juicePrefab上。

        最后,新建Juice組件,用來實(shí)現(xiàn)爆炸的動(dòng)畫邏輯,同樣需要暴露init接口

        // Juice.js
        cc.Class({
            extends: cc.Component,

            properties: {
                particle: {
                    defaultnull,
                    type: cc.SpriteFrame
                },
                circle: {
                    defaultnull,
                    type: cc.SpriteFrame
                },
                slash: {
                    defaultnull,
                    type: cc.SpriteFrame
                }
            },
            // 同樣暴露一個(gè)init接口
            init(data) {
                this.particle = data.particle
                this.circle = data.particle
                this.slash = data.slash
            },
            // 動(dòng)畫效果
            showJuice(){

            }
        }

        這樣,在合并的時(shí)候,我們初始化一個(gè)Juice節(jié)點(diǎn),同時(shí)展示爆炸效果即可

        // 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) // 對(duì)應(yīng)的爆炸邏輯

        爆炸粒子動(dòng)畫

        關(guān)于粒子動(dòng)畫,網(wǎng)上能查到不少資料,如果感興趣,也可以移步我之前整理的前端常見動(dòng)畫實(shí)現(xiàn)原理。

        粒子動(dòng)畫的主要的實(shí)現(xiàn)思路為:初始化N個(gè)粒子,控制他們的速度大小、方向和生命周期,然后控制每個(gè)粒子按照對(duì)應(yīng)的參數(shù)執(zhí)行動(dòng)畫,所有粒子匯集在一起的效果就組成了粒子動(dòng)畫。

        話雖如此,要把動(dòng)畫效果調(diào)好還是挺麻煩的,需要控制各種隨機(jī)參數(shù)。

        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;
                // ... 一堆隨機(jī)的參數(shù)

                node.position = pos;
                node.runAction(
                    cc.sequence(
                        // ...各種action對(duì)應(yīng)的動(dòng)畫邏輯
                        cc.callFunc(function ({
                            // 動(dòng)畫結(jié)束后消除粒子
                            node.active = false
                        }, this))
                )
            }

            // 水珠
            for (let f = 0; f < 20; f++) {
                // 同果粒,使用的spriteFrame切換成 this.circle
            }
            
            // 果汁只有一張貼圖,使用this.slash,展示常規(guī)的action縮放和透明動(dòng)畫即可
        },

        源項(xiàng)目的代碼中使用createFruitL這個(gè)方法來處理爆炸動(dòng)畫,雖然經(jīng)過了代碼壓縮,但依稀能看出對(duì)應(yīng)的動(dòng)畫參數(shù)邏輯,如果不想調(diào)整動(dòng)畫參數(shù),可以借鑒一下

        這樣,就完成了爆炸效果的展示,大概類似于這樣,雖然有點(diǎn)丑

        音效

        通過cc.audioEngine直接播放AudioClip資源來實(shí)現(xiàn)音效

        在Game組件下新增兩個(gè)類型為AudioClip的資源,方便腳本組件訪問

        properties: {
            boomAudio: {
                defaultnull,
                type: cc.AudioClip
            },
            waterAudio: {
                defaultnull,
                type: cc.AudioClip
            }
        }

        同上,在屬性檢查器中將兩個(gè)音頻資源從資源管理器拖動(dòng)到Game組件的屬性下方

        onSameFruitContact(){
            cc.audioEngine.play(this.boomAudio, false1);
            cc.audioEngine.play(this.waterAudio, false1);
        }

        這樣就可以在碰撞的時(shí)候聽到聲音了。

        構(gòu)建打包

        完成整個(gè)游戲的開發(fā)之后,可以選擇構(gòu)建發(fā)布,打包成web-mobile版本,然后部署在服務(wù)器上,就可以給其他人快樂地玩耍了

        小結(jié)

        不知不就就寫到了最后,貌似!!已經(jīng)大工告成了?。?/p>

        雖然還有很多細(xì)節(jié)沒有實(shí)現(xiàn),比如添加得分、合成西瓜之后的撒花等功能,感興趣的同學(xué)可以自己克隆去嘗試修改一下。本文所有代碼及素材都放在github上面了,也可以通過在線預(yù)覽地址體驗(yàn)

        完成這個(gè)游戲花了這周六下午 + 一個(gè)晚上的時(shí)間,由于對(duì)cocos creator并不是很熟悉,因此花了一些時(shí)間去看文檔、查資料,甚至去B站上看了點(diǎn)教學(xué)視頻。不過收獲的成就感與滿足感還是很大的,也算是正兒八經(jīng)寫了點(diǎn)游戲。

        最后,尤其要感謝我媳婦,幫忙測(cè)試及提新需求。不說了,我還得再去加一個(gè)點(diǎn)擊水果直接消除的功能!

        最后

        “在看和轉(zhuǎn)發(fā)”就是最大的支持
        瀏覽 54
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 教室呻吟用力湿冰山校草 | 国产熟妇 码AV | 青久娱乐| 免费欧美黄色 | 亚洲日韩一区 |