微信飛機大戰小遊戲編寫分享(上)

阿菌的打工日記發表於2022-03-23

1. 下載遊戲引擎

遊戲引擎我選擇的是 cocos creator,官網地址:連結

您可以直接點選下載免費獲得這款引擎軟體,建議下載最新的穩定版本,寫這篇文章的時候,適合寫 2D 小遊戲的最新版本貌似是:V2.4.8

一般我們會先下載一個 DASHBOARD,可以理解成一個 cocos 的盒子,用於放各種遊戲工具。

然後從 DASHBOARD 中下載相應版本的 cocos creator。

2. 建立遊戲專案

安裝完畢後,我們進入大盤介面:

點選 new 建立一個新的 empty 遊戲工程:

看到上面這個介面後,就意味著我們們的開發環境已經搭建好了,可以開始開發小遊戲咯!

3. 建立遊戲畫布

首先我們把遊戲資源匯入到我們的遊戲工程中,遊戲資源地址:

然後檢視背景圖片的大小,資源裡的背景圖大小是:640 x 1136

於是我們設定畫布的大小為 640 x 1136

4. 設定遊戲背景

為了減少載入圖片所帶來的效能損耗,我們把所有的資源合到一張圖片中(cocos 能夠自動識別這些圖片),合圖工具用的是:TexturePacker,官方下載地址連結

簡單的使用方法如下:

  1. 開啟圖片目錄,把所有的圖片拖拽到 TexurePacker 中

  1. 儲存

製作完 plist 檔案後,我們新建一個叫 plist 的資料夾存放 plist 檔案,後續我們使用圖片都通過 plist 獲取。

然後我們把背景圖片 bg 拖拽到節點樹上:

為了製作一個輪播的背景,我們需要兩張背景圖,假設為 A 和 B(想讓背景圖片動起來,我們必須保證畫布中永遠有背景)。詳細解釋參見:連結

首先我們建立一個控制遊戲邏輯的 TypeScript 指令碼:

然後把指令碼檔案關聯到畫布中:

關聯到畫布後,我們們編寫指令碼,控制背景圖片輪播,首先需要定義兩個節點(我們只使用背景圖片的座標屬性,因此定義為節點型別就足夠了):

@property(cc.Node)
bg1: cc.Node = null;

@property(cc.Node)
bg2: cc.Node = null;

定義完成後記得到畫布中進行繫結:

繫結後我們編寫輪播的程式碼:

// 在遊戲載入的時候定義背景圖片的位置
protected onLoad() {
    this.bg1.y = 0
    this.bg2.y = this.bg1.y + this.bg2.height
}

update (dt) {
    // 背景圖片的移動速度
    this.bg1.y -= 10;
    this.bg2.y -= 10;
    // 背景圖片輪播邏輯(沒明白可以看視訊哦)
    if(this.bg1.y <= -this.bg1.height){
        // 當一張背景移動到螢幕外面後,立馬補到另一張背景圖片的後面
        this.bg1.y = this.bg2.y + this.bg1.height
    }
    if(this.bg2.y <= -this.bg2.height){
        this.bg2.y = this.bg1.y + this.bg2.height
    }
}

編寫完成後,我們們就有了輪播的背景圖。

5. 新增開始遊戲標語

首先我們把遊戲標語新增到節點樹中,選擇一個合適的位置:

為了提示玩家點選螢幕開始遊戲,我們在這個節點下新增一個 label 節點用於顯示文字:

為了模仿微信飛機大戰的展示效果,我們給這行 點選螢幕開始遊戲 的文字新增一個上下晃動的動畫效果,這裡用到了 cocos 的 animation 功能:

  1. 首先我們建立一個動畫資源
  2. 將動畫資源繫結到 點選螢幕開始遊戲 所在的節點上
  3. 然後設定關鍵幀,給每個關鍵幀設定恰當的角度,比如說第一幀的角度為 0,第二幀的角度為 15,第三幀的角度為 0,第四幀為 -15,第五幀為 0。

連起來播放後大概就成了這樣:

6. 編寫遊戲準備、遊戲中、遊戲暫停三個狀態

雖然我們編寫好了一個輪播的背景,有了動態的效果,但我們希望在遊戲剛開始的時候背景是不動的,等玩家進入遊戲後背景再動起來,讓小飛機有飛翔的效果。

首先我們定義一個判斷背景圖片是否在動的變數

isBgMove = false

然後把移動背景的程式碼封裝到一個方法裡,在 update 方法中通過判斷 isBgMove 變數控制背景是否移動:

update (dt) {
    if(this.isBgMove){
        this.moveBg()
    }
}

moveBg(){
    // 讓背景圖片動起來
    this.bg1.y -= 10;
    this.bg2.y -= 10;
    if(this.bg1.y <= -this.bg1.height){
        this.bg1.y = this.bg2.y + this.bg1.height
    }
    if(this.bg2.y <= -this.bg2.height){
        this.bg2.y = this.bg1.y + this.bg2.height
    }
}

有了控制背景輪播的開關後,我們編寫遊戲準備、遊戲中、遊戲暫停三個狀態。

首先我們把上面定義的 shoot_copyright 節點改個名字,改成遊戲準備節點 status_ready;

然後依次建立兩個空節點 status_playing 和 status_pause:

然後在遊戲開始頁面建立一個暫停按鈕:

為暫停按鈕建立一個點選事件,我們編寫一個通用處理點選事件的方法,通過控制檯進行除錯:

clickButton(sender, str){
    if(str == "pause"){
        console.log("點選了暫停按鈕")
    }
}

然後編寫暫停頁面:

  1. 首先新增一個暫停的背景圖,設定透明度遮蓋原有的遊戲背景
  2. 新增三個按鈕,設定好按鈕的樣式以及顯示內容
  3. 為暫停背景設定一個 BlockInputEvents,遮蔽調下層節點

然後編寫這三個頁面的顯隱關係,大概的邏輯是:

  1. 玩家進入遊戲後顯示一個靜態的背景,當點選螢幕後隱藏調遊戲準備介面,進入遊戲開始介面
  2. 玩家可以在遊戲介面點選暫停按鈕,點選暫停按鈕後由遊戲開始介面進入遊戲暫停介面
  3. 在遊戲暫停介面有三個按鈕,點選繼續遊戲會回到遊戲開始介面,點選重新開始也會回到遊戲開始介面,點選回到主頁會回到遊戲準備介面

以上的邏輯可以通過統一的按鍵點選方法進行處理:

clickButton(sender, str){
    if(str == "pause"){
        // 點選暫停後顯示暫停頁面
        this.pause.active = true
    }else if(str == "continue"){
        // 點選繼續遊戲後隱藏暫停頁面
        this.pause.active = false
    }else if(str == "restart"){
        // 點選重新開始後隱藏暫停頁面
        this.pause.active = false
    }else if(str == "backHome"){
        // 點選回到主頁隱藏暫停介面,停止遊戲,停止背景移動
        this.pause.active = false
        this.playing.active = false
        this.isBgMove = false
        this.ready.active = true
    }
}

實現效果大概是這樣的:

7. 編寫遊戲主角我方飛機

編寫好了遊戲場景切換功能後,我們開始編寫我們的遊戲主角。

首先新增一個節點,為它設定一張圖片(Sprite 屬性),然後製作一個小動畫:

然後編寫飛機跟隨手指(滑鼠)移動的邏輯,簡單來說就是要註冊一個觸控移動的監聽事件:

setTouch() {
    // ....
    this.node.on("touchmove", (event) => {
        // 獲取飛機的位置
        let hero_pos = this.hero.getPosition()
        // 獲取手指(滑鼠)距離上一次事件移動相對於左下角的距離物件
        let move_pos = event.getDelta()
        // 飛機的位置加上移動的相對位置得到飛機的最新位置
        this.hero.setPosition(cc.v2(hero_pos.x + move_pos.x, hero_pos.y + move_pos.y))
    }, this);
    //...
}

大概的效果是醬紫的:

8. 讓飛機可以發射子彈

由於子彈是會重複利用的資源,我們這裡採用預製體資源,首先我們在節點樹中建立一個子彈節點,然後給子彈配一個指令碼:

在每一幀中改變子彈的 y 值,讓子彈有發射的效果。

update(dt) {
    this.node.y += 10
}

配置好指令碼後,我們把子彈節點拖拽到資源管理器中,使其變成一個預製體,然後編寫主邏輯指令碼,先定義一個預製體:

// 子彈
@property(cc.Prefab)
pre_bullet: cc.Prefab

然後嘗試在每次滑鼠點選結束(觸控手指離開螢幕)的時候生成一顆子彈:

setTouch() {
    this.node.on("touchend", (event) => {
        //......
        // 生成一顆子彈
        let bullet = cc.instantiate(this.pre_bullet)
        // 把子彈掛在到節點樹上
        bullet.parent = this.node
        // 獲取飛機主角的位置
        let pos = this.hero.getPosition()
        // 設定子彈的初始位置為飛機頭
        bullet.setPosition(cc.v2(pos.x, pos.y + this.hero.height / 2))
    }, this);
}

這樣一來飛機就可以發射子彈了:

9. 物件池與單例

現在雖然能不停地發射子彈了,但是一直建立子彈例項不進行刪除可不行,如果遊戲時間久了遊戲會越來越卡。

我們使用 cocos 提供的物件池對子彈進行快取,先編寫一個生成子彈的方法:

createBullet() {
    // 建立子彈的方法
    let bullet = null
    // 生成子彈的時候先到物件池中取
    if (this.bulletPool.size() > 0) {
        // 如果物件池中有子彈物件則直接使用
        bullet = this.bulletPool.get()
    } else {
        // 如果物件池沒有子彈了,就建立一顆新的子彈
        bullet = cc.instantiate(this.pre_bullet)
    }
    // 獲取子彈後掛在跟節點下
    bullet.parent = this.node
    // 獲取飛機的位置
    let pos = this.hero.getPosition()
    // 設定子彈的初始位置
    bullet.setPosition(cc.v2(pos.x, pos.y + this.hero.height / 2))
}

然後編寫子彈消亡的邏輯,目前一共有三個場景可以回收子彈:

  1. 重新開始遊戲
  2. 回到主頁
  3. 子彈超出畫布範圍
// 回收單顆子彈
bulletKilled(bullet) {
    // 回收子彈的方法
    bullet.setPosition(cc.v2(0, 0))
    this.bulletPool.put(bullet)
}

// 回收全部子彈
removeBullets() {
    let children = this.node.children
    for (let i = children.length - 1; i >= 0; i--) {
        let bullet = children[i].getComponent("bullet")
        if (bullet) {
            this.bulletKilled(children[i])
        }
    }
}

阿菌在開發的時候比較困擾的問題是,我給子彈單獨建立一個指令碼後,怎麼在子彈指令碼中引用主邏輯類中的方法呢?

通過在 cocos 論壇搜尋,大佬給出的答案是使用單例,單例的簡單使用模版如下:

@ccclass
export default class Singleton extends cc.Component {

    // 單例
    public static instance: Singleton = null

    onLoad() {
        // 初始化單例
        if (Singleton.instance == null) {
            Singleton.instance = this
        } else {
            this.destroy()
            return
        }

通過上面的程式碼,把主邏輯物件匯出,在子彈指令碼中可以這麼使用:

const {ccclass, property} = cc._decorator;
// 匯入主邏輯類
import Singleton from "./main";

@ccclass
export default class NewClass extends cc.Component {

    update(dt) {
        this.node.y += 15
        if(this.node.y > 590){
            // 使用主邏輯單例物件 
            Singleton.instance.bulletKilled(this.node)
        }
    }
}

10. 新增敵機

新增敵機的邏輯和新增子彈的邏輯相似:

  1. 在節點樹中建立一個敵機節點
  2. 建立敵機的指令碼,並關聯給敵機節點
  3. 把敵機節點製作成 PerFab
  4. 在主邏輯中編寫敵機物件池、敵機建立、敵機銷燬的方法
createEnemy1() {
    // 建立敵機1的方法
    let enemy1 = null
    if (this.enemy1Pool.size() > 0) {
        enemy1 = this.enemy1Pool.get()
    } else {
        enemy1 = cc.instantiate(this.pre_enemy_1)
    }
    enemy1.parent = this.node
    enemy1.setPosition(cc.v2(0, 590))
}

enemy1Killed(enemy1){
    this.enemy1Pool.put(enemy1)
}

removeEnemy1s() {
    let children = this.node.children
    for (let i = children.length - 1; i >= 0; i--) {
        let enemy1 = children[i].getComponent("enemy1")
        if (enemy1) {
            this.bulletKilled(children[i])
        }
    }
}

在敵機指令碼中設定敵機移動後,得到的效果大概是這樣子的:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-jujgCh49-1647963420444)(https://s21.aconvert.com/convert/p3r68-cdx67/6d7lo-6fcx8.gif)]

11. 子彈與敵機碰撞

有了敵機之後,我們讓我方飛機發射的子彈可以擊中敵機。

首先我們給敵機和子彈新增碰撞元件(記得給子彈和敵機新增分組):

然後到專案設定中設定敵機和子彈可以碰撞:

接下來編輯敵機死亡的幀動畫(還要編輯一個敵機正常狀態的動畫):

然後在主邏輯中開啟碰撞:

// 開啟碰撞檢測系統,未開啟時無法檢測
cc.director.getCollisionManager().enabled = true;

開啟碰撞後,給子彈編寫處理碰撞的方法:

onCollisionEnter(other, self) {
    if (self.tag == 1) {
        // 普通子彈命中了普通敵機
        Singleton.instance.bulletKilled(this.node)
    }
    if (other.tag == 2){
        // 擊中的是普通敵機
        let enemy = other.getComponent("enemy_1")
        if(enemy && !enemy.isDie){
            enemy.hit()
        }
    }
}

給敵機新增被擊中後的處理邏輯:

hit(){
    // 擊中後狀態設定為死亡
    this.isDie = true
    // 播放幀動畫
    let anim = this.getComponent(cc.Animation)
    anim.play('enemy_1_die')
}

over(){
    // 幀動畫播放完後把敵機放回物件池中,等待下一次出現
    Singleton.instance.enemy1Killed(this.node)
}

記得給敵機的出生座標設定一個隨機值

//...
enemy1.parent = this.node
let randomX = 295 - 590 * Math.random()
enemy1.setPosition(cc.v2(randomX, 590))
//...

得到的效果是這樣的:

好了,上集就先到這,努力更新下集中......

遊戲資源地址:
連結:https://pan.baidu.com/s/1rL82cUYMnxgZQ3xkff5RGw
提取碼:r10d

學習參考:連結

相關文章