1. 程式人生 > >CocosCreator學習1:做一個簡單的遊戲

CocosCreator學習1:做一個簡單的遊戲

把計步器寫好了,到了寫遊戲場景、控制元件什麼的時候,傻眼了。想做一個簡單的地圖,可以在地圖上點選選擇城市,發現用Cocos2D-X程式碼碼出來好麻煩,尤其是城市位置問題,需要除錯去找對畫素區域做一個按鈕控制,整個人都不好了。本來還想嘗試用Tiled map來做,想法很逗比,應該不能用。今晚被長輩連番教育中,說到了撞牆走彎路才想起來,當初光顧著學引擎看程式碼了,竟然忘了還有CocosCreator,這麼簡單實用的工具我竟然沒有去用,太傻了。而且現在距離比較提交程式結果時間也比較緊了,直接用CocosCreator做遊戲時最明智的。下面將轉向CocosCreator的學習。

開啟專案

教程中提供了一個簡單遊戲的初始版本,我就從這個遊戲入手進行學習。從CocosCreator選擇開啟其他專案,開啟start_project資料夾,選中即可開啟。注意下載的start_project不能放在CocosCreator路徑下。

開啟後如圖:
這裡寫圖片描述

在資源管理器面板中,可以看到專案中的檔案。專案資源的根目錄叫asserts,對應於解壓初始版本得到的資料夾名。其中標註bf圖示的檔案是點陣圖字型,是一種字型資源,由fnt檔案和同名的png圖片檔案共同組成。

建立場景

下面來建立遊戲場景。遊戲場景一般包括:場景影象(Sprite)、文字(Label)、角色、以元件形式附加在場景節點上的遊戲邏輯指令碼。在資源管理器中點選asserts目錄前面的加號,選擇Scene,會建立一個New Scene的場景檔案。
這裡寫圖片描述
雙擊New Scene,就會在場景編輯器和層級管理器中開啟這個場景。在層級管理器中可以看到當前場景的所有節點和他們的層級關係。

Canvas畫布節點/渲染根節點

現在New Scene中只有一個Canvas節點,稱為畫布節點或渲染根節點,點選選中Canvas可以在屬性檢查器中看到他的屬性。
這裡寫圖片描述
這裡寫圖片描述
其中的Design Resolution屬性用來指定遊戲解析度,Fit Hight和Fit Width是解析度自適應匹配方法。只需要把影象放到Canvas下,就可以完成對不同解析度的自適應,非常簡單。

設定場景影象

增加背景影象

在資源管理器中將textures中background影象拖到
層級管理器的Canvas下即可增加背景影象,會看到Canvas下增加了一個background子節點。
這裡寫圖片描述
點選檔案->儲存場景或使用快捷鍵ctrl+s儲存場景後,在場景編輯器中便會顯示增加的背景影象。
這裡寫圖片描述


點選視窗左上角工具欄的第四格矩形變換工具,然後選中場景編輯器中的背景影象,可以實現對影象尺寸、位置、錨點的修改。
這裡寫圖片描述
當然也可以直接使用屬性檢查器來直接設定這些引數。
在實際的應用開發中背景影象一般都是要大於顯示範圍的,這樣可以讓背景圖覆蓋整個螢幕,不出現穿幫的情況。

增加地面
使用和增加背景影象相同的方法,將地面影象拖到層級管理器的Canvas上,將ground節點移到background節點下方。
這裡寫圖片描述
在層級管理器中,下方的節點的渲染順序在上方節點之後,也就是說下方節點會遮擋上方節點。
這裡寫圖片描述
使用相同的方法可以對地面影象的尺寸、位置、錨點進行修改。

增加主角
同樣的方法,將主角影象player拖到ground下方。在之後的動畫中,需要主角接觸地面並跳躍,因此將錨點改為最下方,即y值為0,然後將它拖到地面上。
這裡寫圖片描述

編寫指令碼

建立並新增指令碼
指令碼也就是遊戲的邏輯規則,需要程式碼實現。
首先在資源管理器中右鍵點選assets資料夾,新建->資料夾,建立一個名為New Folder的資料夾,然後對其右擊選擇新建->JavaScript,建立一個JavaScript指令碼NewScript,重新命名為Player,雙擊即可開啟編寫介面。Cocos Creator 中指令碼名稱就是元件的名稱。
在Player中的properties部分增加以下程式碼:

// Player.js
    //...
    properties: {
        // 主角跳躍高度
        jumpHeight: 0,
        // 主角跳躍持續時間
        jumpDuration: 0,
        // 最大移動速度
        maxMoveSpeed: 0,
        // 加速度
        accel: 0,
    },
    //...

這些屬性用於規定主角的移動方式,可以在屬性檢查器中直接設定屬性的數值。

然後在層級編輯器中選擇player,在屬性檢查器中選擇新增元件,選擇增加使用者指令碼元件->Player,即可為主角新增元件。

現在可以在屬性檢查器看到Player元件並修改屬性數值了。
這裡寫圖片描述

跳躍和移動程式碼
在properties程式碼塊下面定義setJumpAction方法:

// Player.js
    properties: {
        //...
    },

    setJumpAction: function () {
        // 跳躍上升
        var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
        // 下落
        var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
        // 不斷重複
        return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
    },

修改onLoad: function:

// Player.js
    onLoad: function () {
        // 初始化跳躍動作
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);
    },

onLoad方法會在場景載入完成後立即執行,所以在這裡進行初始化操作。
儲存指令碼執行一下試試,可以看到player不斷的在進行跳躍動作。

在setJumpAction下面定義setInputControl方法來實現用A和D控制主角的移動操作:

// Player.js
    setJumpAction: function () {
        //...
    },

    setInputControl: function () {
        var self = this;
        // 新增鍵盤事件監聽
        cc.eventManager.addListener({
            event: cc.EventListener.KEYBOARD,
            // 有按鍵按下時,判斷是否是我們指定的方向控制鍵,並設定向對應方向加速
            onKeyPressed: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = true;
                        self.accRight = false;
                        break;
                    case cc.KEY.d:
                        self.accLeft = false;
                        self.accRight = true;
                        break;
                }
            },
            // 鬆開按鍵時,停止向該方向的加速
            onKeyReleased: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = false;
                        break;
                    case cc.KEY.d:
                        self.accRight = false;
                        break;
                }
            }
        }, self.node);
    },

修改onLoad方法,在其中加入向左和向右加速的開關,以及主角當前在水平方向的速度,最後再呼叫我們剛新增的setInputControl方法,在場景載入後就開始監聽鍵盤輸入:

// Player.js
    onLoad: function () {
        // 初始化跳躍動作
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);

        // 加速度方向開關
        this.accLeft = false;
        this.accRight = false;
        // 主角當前水平方向速度
        this.xSpeed = 0;

        // 初始化鍵盤輸入監聽
        this.setInputControl();
    },

最後修改update方法,新增加速度、速度和主角當前位置的設定:

// Player.js
    update: function (dt) {
        // 根據當前加速度方向每幀更新速度
        if (this.accLeft) {
            this.xSpeed -= this.accel * dt;
        } else if (this.accRight) {
            this.xSpeed += this.accel * dt;
        }
        // 限制主角的速度不能超過最大值
        if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
            // if speed reach limit, use max speed with current direction
            this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
        }

        // 根據當前速度更新主角的位置
        this.node.x += this.xSpeed * dt;
    },

這樣就可以實現對主角移動的控制了。

製作Prefab指令碼-星星
這裡增加遊戲規則,從隨機位置生成星星,用主角接住星星即可得分。
對於重複生成的節點,可以儲存成Prefab(預置)資源,作為動態生成節點時的模板。
這裡直接將assets/textures/star資源到場景中,位置隨意,給星星增加一個指令碼,當主角碰到星星時,讓星星消失。

// Star.js
    properties: {
        // 星星和主角之間的距離小於這個數值時,就會完成收集
        pickRadius: 0
    },

給star節點增加指令碼,將pickRadius值設定為60。將層級管理器中的star節點拖到資源管理器的assets資料夾下,將場景中的star節點刪除。star現在即為Prefab資源。

新增遊戲控制指令碼
在assets/scripts資料夾下新增遊戲的主邏輯指令碼Game,新增生成星星需要的屬性:

// Game.js
    properties: {
        // 這個屬性引用了星星預製資源
        starPrefab: {
            default: null,
            type: cc.Prefab
        },
        // 星星產生後消失時間的隨機範圍
        maxStarDuration: 0,
        minStarDuration: 0,
        // 地面節點,用於確定星星生成的高度
        ground: {
            default: null,
            type: cc.Node
        },
        // player 節點,用於獲取主角彈跳的高度,和控制主角行動開關
        player: {
            default: null,
            type: cc.Node
        }
    },

然後將Game新增到Canvas節點上,然後將star這個Prefab資源拖到Canvas屬性檢查器Game元件中的Star Prefab屬性上。把層級管理器中的ground和Player節點也拖到元件中相應屬性處即可。設定Min Star Duration和Max Star Duration值為3和5,之後生成星星時,會在這兩個值之間隨機取值作為星星消失前經過的時間。

在隨機位置生成星星
修改Game指令碼,在onLoad方法後面新增星星的邏輯:

// Game.js
    onLoad: function () {
        // 獲取地平面的 y 軸座標
        this.groundY = this.ground.y + this.ground.height/2;
        // 生成一個新的星星
        this.spawnNewStar();
    },

    spawnNewStar: function() {
        // 使用給定的模板在場景中生成一個新節點
        var newStar = cc.instantiate(this.starPrefab);
        // 將新增的節點新增到 Canvas 節點下面
        this.node.addChild(newStar);
        // 為星星設定一個隨機位置
        newStar.setPosition(this.getNewStarPosition());
    },

    getNewStarPosition: function () {
        var randX = 0;
        // 根據地平面位置和主角跳躍高度,隨機得到一個星星的 y 座標
        var randY = this.groundY + cc.random0To1() * this.player.getComponent('Player').jumpHeight + 50;
        // 根據螢幕寬度,隨機得到一個星星 x 座標
        var maxX = this.node.width/2;
        randX = cc.randomMinus1To1() * maxX;
        // 返回星星座標
        return cc.p(randX, randY);
    }

新增主角碰觸收集星星的行為
重點在於星星要隨時獲取主角節點的位置,才能判斷與主角的距離是否小於可收集距離。因此將Game元件的例項傳給星星並儲存,然後可以隨時通過game.player來訪問主角節點。
在Game指令碼的spawnNewStar方法最後新增如下程式碼:

// Game.js
    spawnNewStar: function() {
        // ...
        // 將 Game 元件的例項傳入星星元件
        newStar.getComponent('Star').game = this;
    },

然後在star腳本里onLoad方法後面新增getPlayerDistance和onPicked方法:

// Star.js
    getPlayerDistance: function () {
        // 根據 player 節點位置判斷距離
        var playerPos = this.game.player.getPosition();
        // 根據兩點位置計算兩點之間距離
        var dist = cc.pDistance(this.node.position, playerPos);
        return dist;
    },

    onPicked: function() {
        // 當星星被收集時,呼叫 Game 指令碼中的介面,生成一個新的星星
        this.game.spawnNewStar();
        // 然後銷燬當前星星節點
        this.node.destroy();
    },

在update方法中新增每幀判斷距離,如果距離小於pickRadius屬性規定值,就執行收集行為:

// Star.js
    update: function (dt) {
        // 每幀判斷和主角之間的距離是否小於收集距離
        if (this.getPlayerDistance() < this.pickRadius) {
            // 呼叫收集行為
            this.onPicked();
            return;
        }
    },

現在可以看效果了。
這裡寫圖片描述

新增計分牌
在層級管理器的Canvas中右鍵建立新節點->建立渲染節點->Label(文字),命名為score。將position設為(0,180),string設為score:0,Font Size設為50。從資源管理器中將assets/mikado_outline_shadow點陣圖字型資源拖到Font屬性中。
這裡寫圖片描述

新增得分邏輯
在Game的properties裡新增score需要的屬性:

// Game.js
    properties: {
        // ...
        // score label 的引用
        scoreDisplay: {
            default: null,
            type: cc.Label
        }
    },

在onLoad方法裡新增計分用的變數初始化:

// Game.js
    onLoad: function () {
        // ...
        // 初始化計分
        this.score = 0;
    },

在update方法後面新增gainScore的新方法:

// Game.js
    gainScore: function () {
        this.score += 1;
        // 更新 scoreDisplay Label 的文字
        this.scoreDisplay.string = 'Score: ' + this.score.toString();
    },

將score節點拖到Canvas屬性的Game元件中的Score Display屬性中。
在Star指令碼的onPicked方法中新增gainScore的呼叫:

// Star.js
    onPicked: function() {
        // 當星星被收集時,呼叫 Game 指令碼中的介面,生成一個新的星星
        this.game.spawnNewStar();
        // 呼叫 Game 指令碼的得分方法
        this.game.gainScore();
        // 然後銷燬當前星星節點
        this.node.destroy();
    },

這樣收集到星星,分數就會變化了。
這裡寫圖片描述

失敗判斷和重新開始
加入給星星加入計時消失的邏輯,在Game指令碼的onLoad方法的spawNewStar呼叫之前加入需要的變數宣告:
// Game.js
onLoad: function () {
// …
// 初始化計時器
this.timer = 0;
this.starDuration = 0;
// 生成一個新的星星
this.spawnNewStar();
// 初始化計分
this.score = 0;
},
然後在spawnNewStar方法最後加入重置計時器的邏輯,其中this.minStarDuration和this.maxStarDuration是我們一開始宣告的Game元件屬性,用來規定星星消失時間的隨機範圍:

// Game.js
    spawnNewStar: function() {
        // ...
        // 重置計時器,根據消失時間範圍隨機取一個值
        this.starDuration = this.minStarDuration + cc.random0To1() * (this.maxStarDuration - this.minStarDuration);
        this.timer = 0;
    },

在update方法中加入計時器更新和判斷超過時限的邏輯:

// Game.js
    update: function (dt) {
        // 每幀更新計時器,超過限度還沒有生成新的星星
        // 就會呼叫遊戲失敗邏輯
        if (this.timer > this.starDuration) {
            this.gameOver();
            return;
        }
        this.timer += dt;
    },

最後加入gameOver方法,遊戲失敗重新載入場景。

// Game.js
    gameOver: function () {
        this.player.stopAllActions(); //停止 player 節點的跳躍動作
        cc.director.loadScene('game');
    }

然後修改Star指令碼,加入即將消失星星的提示效果,在update方法中加入以下程式碼:

// Star.js
    update: function() {
        // ...
        // 根據 Game 指令碼中的計時器更新星星的透明度
        var opacityRatio = 1 - this.game.timer/this.game.starDuration;
        var minOpacity = 50;
        this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
    }

這樣遊戲畫面和邏輯就全部完成了,可以說遊戲已經基本完成了。

加入音效
首先加入跳躍音效,在Player指令碼中引用聲音檔案資源jumpAudio屬性:

// Player.js
    properties: {
        // ...
        // 跳躍音效資源
        jumpAudio: {
            default: null,
            url: cc.AudioClip
        },
    },

修改setJumpAction方法,插入播放音效的回撥,並通過新增playJumpSound方法來播放聲音:

// Player.js
    setJumpAction: function () {
        // 跳躍上升
        var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
        // 下落
        var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
        // 新增一個回撥函式,用於在動作結束時呼叫我們定義的其他方法
        var callback = cc.callFunc(this.playJumpSound, this);
        // 不斷重複,而且每次完成落地動作後呼叫回撥來播放聲音
        return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
    },

    playJumpSound: function () {
        // 呼叫聲音引擎播放聲音
        cc.audioEngine.playEffect(this.jumpAudio, false);
    },

然後加入得分音效,在Game指令碼的properties新增引用聲音檔案:

// Game.js
    properties: {
        // ...
        // 得分音效資源
        scoreAudio: {
            default: null,
            url: cc.AudioClip
        }
    },

在gainScore方法新增播放聲音的程式碼:

// Game.js
    gainScore: function () {
        this.score += 1;
        // 更新 scoreDisplay Label 的文字
        this.scoreDisplay.string = 'Score: ' + this.score.toString();
        // 播放得分音效
        cc.audioEngine.playEffect(this.scoreAudio, false);
    },

在層級管理器中,將assets/audio/jump聲音資源拖到Player節點的元件Jump Audio屬性上。將assets/audio/score資源拖拽到Canvas節點Game元件的Score Audio屬性上。
大功告成,一個完整的遊戲就做好了。