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,官方下載地址連結
簡單的使用方法如下:
- 開啟圖片目錄,把所有的圖片拖拽到 TexurePacker 中
- 儲存
製作完 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 功能:
- 首先我們建立一個動畫資源
- 將動畫資源繫結到
點選螢幕開始遊戲
所在的節點上 - 然後設定關鍵幀,給每個關鍵幀設定恰當的角度,比如說第一幀的角度為 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("點選了暫停按鈕")
}
}
然後編寫暫停頁面:
- 首先新增一個暫停的背景圖,設定透明度遮蓋原有的遊戲背景
- 新增三個按鈕,設定好按鈕的樣式以及顯示內容
- 為暫停背景設定一個 BlockInputEvents,遮蔽調下層節點
然後編寫這三個頁面的顯隱關係,大概的邏輯是:
- 玩家進入遊戲後顯示一個靜態的背景,當點選螢幕後隱藏調遊戲準備介面,進入遊戲開始介面
- 玩家可以在遊戲介面點選暫停按鈕,點選暫停按鈕後由遊戲開始介面進入遊戲暫停介面
- 在遊戲暫停介面有三個按鈕,點選繼續遊戲會回到遊戲開始介面,點選重新開始也會回到遊戲開始介面,點選回到主頁會回到遊戲準備介面
以上的邏輯可以通過統一的按鍵點選方法進行處理:
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))
}
然後編寫子彈消亡的邏輯,目前一共有三個場景可以回收子彈:
- 重新開始遊戲
- 回到主頁
- 子彈超出畫布範圍
// 回收單顆子彈
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. 新增敵機
新增敵機的邏輯和新增子彈的邏輯相似:
- 在節點樹中建立一個敵機節點
- 建立敵機的指令碼,並關聯給敵機節點
- 把敵機節點製作成 PerFab
- 在主邏輯中編寫敵機物件池、敵機建立、敵機銷燬的方法
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
學習參考:連結