用 vue3 + phaser 實現經典小游戲:飛機大戰

cb8a11e8bd3bbef243d0797ea0bbb72d.jpeg

a658413058f122948eb0676cdd217980.gif

本文字數:7539

預計閱讀時間:30分鐘

01

前言

說起小游戲,最經典的莫過于飛機大戰了,相信很多同學都玩過。今天我們也來試試開發個有趣的小游戲吧!我們將從零開始,看看怎樣一步步實現一個H5版的飛機大戰!

首先我們定好目標,要做一個怎樣的飛機大戰,以及去哪整游戲素材?

剛好微信小程序官方提供了一個飛機大戰小游戲的模板,打開【微信開發者工具】,選擇【新建項目】-【小游戲】,選擇飛機大戰的模板,創建后就是一個小程序版飛機大戰。

1704ad87855556aa4882da88d03193a5.png

運行小程序之后可以看到下面的效果:

115a965de65c1fe3611d37ef8d57dca3.gif

從運行效果上看,這個飛機大戰已經比較完整,包含了以下內容:

1.地圖滾動,播放背景音效;

2.玩家控制飛機移動;

3.飛機持續發射子彈,播放發射音效;

4.隨機出現向下移動的敵軍;

5.子彈碰撞敵軍時,播放爆炸動畫和爆炸音效,同時子彈和敵軍都銷毀,并增加1個得分;

6.飛機碰撞敵軍時,游戲結束,彈出結束面板。

接下來我們以這個效果為參考,并拷貝這個項目中的圖片和音效素材,從頭做一個H5版飛機大戰吧!

02

選擇游戲框架

你可能會好奇,既然微信小程序官方已經生成好了完整代碼,直接參考那套代碼不就好嗎?

這里就涉及到游戲框架的問題,小程序那套代碼是沒有使用游戲框架的,所以很多基礎的地方都需要自己實現,比如說子彈移動,子彈與敵軍碰撞檢測等。

我們以碰撞為例,在小程序項目中是這樣實現的:

1.先定義好碰撞檢測的方法isCollideWith(),通過兩個物體的坐標和寬高進行碰撞檢測計算:

isCollideWith(sp)?{let?spX?=?sp.x?+?sp.width?/?2;let?spY?=?sp.y?+?sp.height?/?2;if?(!this.visible?||?!sp.visible)?return?false;return?!!(spX?>=?this.x?&&?spX?<=?this.x?+?this.width?&&?spY?>=?this.y?&&?spY?<=?this.y?+?this.height);
},

2.然后在每一幀的回調中,遍歷所有子彈和所有敵軍,依次調用isCollideWith()進行碰撞檢測:

update()?{bullets.forEach((bullet)?=>?{for?(let?i?=?0,?il?=?enemys.length;?i?<?il;?i++)?{if?(enemys[i].isCollideWith(bullet))?{//?Do?Something}}});
}

3.而通過游戲框架,可能只需要一行代碼。我們以Phaser為例:

this.physics.add.overlap(bullets,?enemys,?()?=>?{?//?Do?Something
},?null,?this);

上面代碼的含義是:bullets(子彈組)和enemys(敵軍組)發生overlap(重疊)則觸發回調。

從上面的例子可以看出,選擇一個游戲框架來開發游戲,可以大大降低開發難度,減少代碼量。

當開發一個專業的游戲時,我們一般會選擇專門的游戲引擎,比如Cocos,Egret,LayaBox,Unity等。但是如果只是做一個簡單的H5小游戲,嵌入我們的前端項目中,使用Phaser就可以了。

引用Phaser官網上的介紹:

【Phaser是一個快速、免費且有趣的開源HTML5游戲框架,可在桌面和移動Web瀏覽器上提供WebGL和Canvas渲染。可以使用第三方工具將游戲編譯為iOS、Android和本機應用程序。您可以使用JavaScript或TypeScript進行開發。】

同時Phaser在社區也非常受歡迎,Github上收獲35.5k的Star,Npm上最近一周下載量19k。

因此我們采用Phaser作為游戲框架。接下來,開始正式我們的飛機大戰之旅啦!

03

準備工作

3.1 創建項目

項目采用的技術棧是:Phaser + Vue3 + TypeScript + Vite。

當然對于這個游戲來說,核心的框架是Phaser,其他都是可選的。只使用Phaser + Html也是可以開發的,只是我們希望采用目前更主流的開發方式。

進行工作目錄,直接使用vue手腳架創建名為plane-war的項目。

npm?create?vue

項目創建完成,安裝依賴,檢查是否運行正常。

cd?plane-war
npm?install
npm?run?dev

接下來再安裝phaser。

npm?install?phaser

3.2 整理素材

接下來我們重新整理下項目,清除不需要的文件,并把游戲素材拷貝到assets目錄,最終目錄結構如下:

plane-war
├──?src
│???├──?assets
│???│???├──?audio
│???│???│???├──?bgm.mp3
│???│???│???├──?boom.mp3
│???│???│???└──?bullet.mp3
│???│???├──?images
│???│???│???├──?background.jpg
│???│???│???├──?boom.png
│???│???│???├──?bullet.png
│???│???│???├──?enemy.png
│???│???│???├──?player.png
│???│???│???└──?sprites.png
│???│???└──?json
│???│???????└──?sprites.json
│???├──?App.vue
│???└──?main.ts

素材處理1:

原本游戲素材中,爆炸動畫是由19張獨立圖片組成,在Phaser中需要合成一張雪碧圖,可以通過雪碧圖合成工具合成,命名為boom.png,效果如下:

196d9eb820160c3513808605a3afae52.png

素材處理2:

原本游戲素材中,結束面板的圖片來源一張叫Common.png的雪碧圖,我們重命名為sprites.png。并且我們還需要為這個雪碧圖制作一份說明,起名為sprites.json。通過它來指定我們需要用到目標圖片及其在雪碧圖中的位置。

這里我們指定2個目標圖片,result是結束面板,button是按鈕。

{"textures":?[{"image":?"sprites.png","size":?{"w":?512,"h":?512},"frames":?[{"filename":?"result","frame":?{?"x":?0,?"y":?0,?"w":?119,?"h":?108?}},{"filename":?"button","frame":?{?"x":?120,?"y":?6,?"w":?39,?"h":?24?}}]}]
}

3.3 初步運行

我們重構App.vue,創建了一個游戲對象game,指定父容器為#container,創建成功后則會在父容器中生成一個canvas?元素,游戲的所有內容都通過這個canvas進行呈現和交互。

<template><div id="container"></div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
import { Game, AUTO, Scale } from "phaser";let game: Game;
onMounted(() => {game = new Game({parent: "container",type: AUTO,width: 375,// 高度依據屏幕寬高比計算height: (window.innerHeight / window.innerWidth) * 375,scale: {// 自動縮放至寬或高與父容器一致,類似css中的contain// 由于寬高比與屏幕寬高比一致,最終就是剛好全屏效果mode: Scale.FIT,},physics: {default: "arcade",arcade: {debug: false,},},});
});onUnmounted(() => {game.destroy(true);
});
</script>
<style>
body {margin: 0;
}
#app {height: 100%;
}
</style>

通過npm run dev再次運行項目,我們把瀏覽器展示區切換:為移動設備展示,此時可以看到canvas,并且其寬高應該正好全屏。

cb48c9979495fbddb6af06ad3224bcff.png

3.4 場景設計

可以看到現在畫布還是全黑的,這是因為創建game對象時還沒有接入任何場景。在Phaser中,一個游戲可以包含多個場景,而具體的游戲畫面和交互都是在各個場景中實現的。

接下來我們設計3個場景:

  • 預載場景 :加載整個游戲資源,創建動畫,展示等待開始畫面。

  • 主場景:游戲的主要畫面和交互。

  • 結束場景:展示游戲結束畫面。

2ac74f5fad2664e4c2f773412f80d92d.jpeg

在項目中我們新增3個自定義場景類:

plane-war
├──?src
│???├──?game
│???│???├──?Preloader.ts
│???│???├──?Main.ts
│???│???└──?End.ts

自定義場景類繼承Scene類,包含了以下基本結構:

import?{?Scene?}?from?"phaser";export?class?Preloader?extends?Scene?{constructor()?{//?場景命名,這個命名在后面場景切換使用super("Preloader");}//?加載游戲資源preload()?{}//?preload中的資源全部加載完成后執行create()?{}//?每一幀的回調update()?{}
}

按上面的基本結構分別實現好3個場景類,并導入到game對象的創建中:

import?{?onMounted,?onUnmounted?}?from?"vue";
import?{?Game,?AUTO,?Scale?}?from?"phaser";
import?{?Preloader?}?from?"./game/Preloader";
import?{?Main?}?from?"./game/Main";
import?{?End?}?from?"./game/End";let?game:?Game;
onMounted(()?=>?{game?=?new?Game({//?其他參數省略...//?定義場景,默認初始化數組中首個場景,即?Preloaderscene:?[Preloader,?Main,?End],});
});

04

預載場景

準備工作完成后,接下來我們開始真正開發第一個游戲場景:預載場景,對應Preloader.ts文件。

4.1 加載游戲資源

preload方法中加載整個游戲所需的資源。

import?{?Scene?}?from?"phaser";
import?backgroundImg?from?"../assets/images/background.jpg";
import?enemyImg?from?"../assets/images/enemy.png";
import?playerImg?from?"../assets/images/player.png";
import?bulletImg?from?"../assets/images/bullet.png";
import?boomImg?from?"../assets/images/boom.png";
import?bgmAudio?from?"../assets/audio/bgm.mp3";
import?boomAudio?from?"../assets/audio/boom.mp3";
import?bulletAudio?from?"../assets/audio/bullet.mp3";export?class?Preloader?extends?Scene?{constructor()?{super("Preloader");}preload()?{//?加載圖片this.load.image("background",?backgroundImg);this.load.image("enemy",?enemyImg);this.load.image("player",?playerImg);this.load.image("bullet",?bulletImg);this.load.spritesheet("boom",?boomImg,?{frameWidth:?64,frameHeight:?48,});//?加載音頻this.load.audio("bgm",?bgmAudio);this.load.audio("boom",?boomAudio);this.load.audio("bullet",?bulletAudio);}create()?{}
}

4.2 添加元素

接下來我們在create()方法中去添加背景,背景音樂,標題,開始按鈕,后續使用的動畫,并且為開始按鈕綁定了點擊事件。

const?{?width,?height?}?=?this.cameras.main;
//?背景
this.add.tileSprite(0,?0,?width,?height,?"background").setOrigin(0,?0);
//?背景音樂
this.sound.play("bgm");//?標題
this.add.text(width?/?2,?height?/?4,?"飛機大戰",?{fontFamily:?"Arial",fontSize:?60,color:?"#e3f2ed",stroke:?"#203c5b",strokeThickness:?6,}).setOrigin(0.5);//?開始按鈕
let?button?=?this.add.image(width?/?2,?(height?/?4)?*?3,?"sprites",?"button").setScale(3,?2).setInteractive().on("pointerdown",?()?=>?{//?點擊事件:關閉當前場景,打開Main場景this.scene.start("Main");});//?按鈕文案
this.add.text(button.x,?button.y,?"開始游戲",?{fontFamily:?"Arial",fontSize:?20,color:?"#e3f2ed",}).setOrigin(0.5);//?創建動畫,命名為?boom,后面使用
this.anims.create({key:?"boom",frames:?this.anims.generateFrameNumbers("boom",?{?start:?0,?end:?18?}),repeat:?0,
});

運行效果如下:

733389cc10aae37b13d15e0ad5663449.png

有個細節可以留意下,就是這個背景是怎樣鋪滿整個屏幕的?

上面的代碼是this.add.tileSprite()創建了一個瓦片精靈,素材中的背景圖就像一個一個瓦片一樣鋪滿屏幕,所以就要求素材中的背景圖是一張首尾能無縫相連的圖片,這樣就能無限平鋪。主場景中的背景移動也是基于此。

05

主場景

5.1 梳理場景元素

在預載場景中點擊“開始游戲”按鈕,可以看到畫面又變成黑色,此時預載場景被關閉,游戲打開主場景。

在主場景中,涉及到的場景元素一共有:背景、玩家、子彈、敵軍、爆炸,我們可以先嘗試把它們都渲染出來,并加一些簡單的動作,比如移動背景,子彈和敵軍添加垂直方向速度,播放爆炸動畫等。

import?{?Scene,?GameObjects,?type?Types?}?from?"phaser";//?場景元素
let?background:?GameObjects.TileSprite;
let?enemy:?Types.Physics.Arcade.SpriteWithDynamicBody;
let?player:?Types.Physics.Arcade.SpriteWithDynamicBody;
let?bullet:?Types.Physics.Arcade.SpriteWithDynamicBody;
let?boom:?GameObjects.Sprite;export?class?Main?extends?Scene?{constructor()?{super("Main");}create()?{const?{?width,?height?}?=?this.cameras.main;//?背景background?=?this.add.tileSprite(0,?0,?width,?height,?"background").setOrigin(0,?0);//?玩家this.physics.add.sprite(100,?600,?"player").setScale(0.5);//?子彈this.physics.add.sprite(100,?500,?"bullet").setScale(0.25).setVelocityY(-100);//?敵軍this.physics.add.sprite(100,?100,?"enemy").setScale(0.5).setVelocityY(100);//?爆炸this.add.sprite(200,?100,?"boom").play("boom");}update()?{//?設置背景瓦片不斷移動background.tilePositionY?-=?1;}
}

效果如下:

20fa8241e27a5e4d2c86c6581761d12d.gif

看起來似乎已經有了雛形,但是這里還需要優化一下代碼設計。我們不希望場景中的所有元素創建,交互都糅合Main.ts這個文件中,這樣就顯得有點臃腫,不好維護。

我們再設計出:玩家類、子彈類、敵軍類、炸彈類,讓每個元素它們自身的事件和行為都各自去實現,而主場景只負責創建它們,并且處理它們之間的交互事件,不需要去關心它們內部的實現。

雖然這個游戲的整體代碼也不多,但是通過這個設計思想,可以讓我們的代碼設計更加合理,當以后開發其他更復雜的小游戲時也可以套用這種模式。

6aff7204438698398b77449617cd0c71.jpeg

5.2 玩家類

回顧上面的創建玩家的代碼:

this.physics.add.sprite(100,?600,?"player").setScale(0.5);

原本的代碼是直接創建了一個“物理精靈對象“,我們現在改成新建一個Player類,這個類繼承Physics.Arcade.Sprite,然后在主場景中通過new Player()也同樣生成"物理精靈對象"。相當于Player類拓展了原本Physics.Arcade.Sprite,增加了對自身的一些事件處理和行為封裝。后續的子彈類,敵軍類等也是同樣的方式。

Player類主要拓展了"長按移動事件",具體實現如下:

import?{?Physics,?Scene?}?from?"phaser";export?class?Player?extends?Physics.Arcade.Sprite?{isDown:?boolean?=?false;downX:?number;downY:?number;constructor(scene:?Scene)?{//?創建對象let?{?width,?height?}?=?scene.cameras.main;super(scene,?width?/?2,?height?-?80,?"player");scene.add.existing(this);scene.physics.add.existing(this);//?設置屬性this.setInteractive();this.setScale(0.5);this.setCollideWorldBounds(true);//?注冊事件this.addEvent();}addEvent()?{//?手指按下我方飛機this.on("pointerdown",?()?=>?{this.isDown?=?true;//?記錄按下時的飛機坐標this.downX?=?this.x;this.downY?=?this.y;});//?手指抬起this.scene.input.on("pointerup",?()?=>?{this.isDown?=?false;});//?手指移動this.scene.input.on("pointermove",?(pointer)?=>?{if?(this.isDown)?{this.x?=?this.downX?+?pointer.x?-?pointer.downX;this.y?=?this.downY?+?pointer.y?-?pointer.downY;}});}
}

5.3 子彈類

Bullet類主要拓展了"發射子彈"和"子彈出界事件",具體實現如下:

import?{?Physics,?Scene?}?from?"phaser";export?class?Bullet?extends?Physics.Arcade.Sprite?{constructor(scene:?Scene,?x:?number,?y:?number,?texture:?string)?{//?創建對象super(scene,?x,?y,?texture);scene.add.existing(this);scene.physics.add.existing(this);//?設置屬性this.setScale(0.25);}//?發射子彈fire(x:?number,?y:?number)?{this.enableBody(true,?x,?y,?true,?true);this.setVelocityY(-300);this.scene.sound.play("bullet");}//?每一幀更新回調preUpdate(time:?number,?delta:?number)?{super.preUpdate(time,?delta);//?子彈出界事件(子彈走到頂部超出屏幕)if?(this.y?<=?-14)?{this.disableBody(true,?true);}}
}

5.4 敵軍類

Enemy類主要拓展了"生成敵軍"和"敵軍出界事件",具體實現如下:

import?{?Physics,?Math,?Scene?}?from?"phaser";export?class?Enemy?extends?Physics.Arcade.Sprite?{constructor(scene:?Scene,?x:?number,?y:?number,?texture:?string)?{//?創建對象super(scene,?x,?y,?texture);scene.add.existing(this);scene.physics.add.existing(this);//?設置屬性this.setScale(0.5);}//?生成敵軍born()?{let?x?=?Math.Between(30,?345);let?y?=?Math.Between(-20,?-40);this.enableBody(true,?x,?y,?true,?true);this.setVelocityY(Math.Between(150,?300));}//?每一幀更新回調preUpdate(time:?number,?delta:?number)?{super.preUpdate(time,?delta);let?{?height?}?=?this.scene.cameras.main;//?敵軍出界事件(敵軍走到底部超出屏幕)if?(this.y?>=?height?+?20)?{this.disableBody(true,?true)}}
}

5.5 爆炸類

Boom?類主要拓展了"顯示爆炸"和“隱藏爆炸”,具體實現如下:

import?{?GameObjects,?Scene?}?from?"phaser";export?class?Boom?extends?GameObjects.Sprite?{constructor(scene:?Scene,?x:?number,?y:?number,?texture:?string)?{super(scene,?x,?y,?texture);//?爆炸動畫播放結束事件this.on("animationcomplete-boom",?this.hide,?this);}//?顯示爆炸show(x:?number,?y:?number)?{this.x?=?x;this.y?=?y;this.setActive(true);this.setVisible(true);this.play("boom");this.scene.sound.play("boom");}//?隱藏爆炸hide()?{this.setActive(false);this.setVisible(false);}
}

5.6 重構主場景

上面我們實現了玩家類,子彈類,敵軍類,爆炸類,接下來我們在主場景中重新創建這些元素,并加入分數文本元素。

import?{?Scene,?Physics,?GameObjects?}?from?"phaser";
import?{?Player?}?from?"./Player";
import?{?Bullet?}?from?"./Bullet";
import?{?Enemy?}?from?"./Enemy";
import?{?Boom?}?from?"./Boom";//?場景元素
let?background:?GameObjects.TileSprite;
let?player:?Player;
let?enemys:?Physics.Arcade.Group;
let?bullets:?Physics.Arcade.Group;
let?booms:?GameObjects.Group;
let?scoreText:?GameObjects.Text;//?場景數據
let?score:?number;export?class?Main?extends?Scene?{constructor()?{super("Main");}create()?{let?{?width,?height?}?=?this.cameras.main;//?創建背景background?=?this.add.tileSprite(0,?0,?width,?height,?"background").setOrigin(0,?0);//?創建玩家player?=?new?Player(this);//?創建敵軍enemys?=?this.physics.add.group({frameQuantity:?30,key:?"enemy",enable:?false,active:?false,visible:?false,classType:?Enemy,});//?創建子彈bullets?=?this.physics.add.group({frameQuantity:?15,key:?"bullet",enable:?false,active:?false,visible:?false,classType:?Bullet,});//?創建爆炸booms?=?this.add.group({frameQuantity:?30,key:?"boom",active:?false,visible:?false,classType:?Boom,});//?分數score?=?0;scoreText?=?this.add.text(10,?10,?"0",?{fontFamily:?"Arial",fontSize:?20,});//?注冊事件this.addEvent();},update()?{//?背景移動background.tilePositionY?-=?1;}
}

需要注意的是,這里的子彈,敵軍,爆炸都是按組創建的,這樣我們可以直接監聽子彈組和敵軍組的碰撞,而不需要監聽每一個子彈和每一個敵軍的碰撞。另一方面,創建組時已經把組內的元素全部創建好了,比如創建敵軍時指定frameQuantity: 30,表示直接創建30個敵軍元素,后續敵軍不斷出現和銷毀其實就是這30個元素在循環使用而已,而并非源源不斷地創建新元素,以此減少性能損耗。

最后再把注冊事件實現,主場景就全部完成了。

//?注冊事件
addEvent()?{//?定時器this.time.addEvent({delay:?400,callback:?()?=>?{//?生成2個敵軍for?(let?i?=?0;?i?<?2;?i++)?{enemys.getFirstDead()?.born();}//?發射1顆子彈bullets.getFirstDead()?.fire(player.x,?player.y?-?32);},callbackScope:?this,repeat:?-1,});//?子彈和敵軍碰撞this.physics.add.overlap(bullets,?enemys,?this.hit,?null,?this);//?玩家和敵軍碰撞this.physics.add.overlap(player,?enemys,?this.gameOver,?null,?this);
}
//?子彈擊中敵軍
hit(bullet,?enemy)?{//?子彈和敵軍隱藏enemy.disableBody(true,?true);bullet.disableBody(true,?true);//?顯示爆炸booms.getFirstDead()?.show(enemy.x,?enemy.y);//?分數增加scoreText.text?=?String(++score);
}
//?游戲結束
gameOver()?{//?暫停當前場景,并沒有銷毀this.sys.pause();//?保存分數this.registry.set("score",?score);//?打開結束場景this.game.scene.start("End");
}

06

結束場景

最后再實現一下結束場景,很簡單,主要包含結束面板,得分,重新開始按鈕。

import?{?Scene?}?from?"phaser";export?class?End?extends?Scene?{constructor()?{super("End");}create()?{let?{?width,?height?}?=?this.cameras.main;//?結束面板this.add.image(width?/?2,?height?/?2,?"sprites",?"result").setScale(2.5);//?標題this.add.text(width?/?2,?height?/?2?-?85,?"游戲結束",?{fontFamily:?"Arial",fontSize:?24,}).setOrigin(0.5);//?當前得分let?score?=?this.registry.get("score");this.add.text(width?/?2,?height?/?2?-?10,?`當前得分:${score}`,?{fontFamily:?"Arial",fontSize:?20,}).setOrigin(0.5);//?重新開始按鈕let?button?=?this.add.image(width?/?2,?height?/?2?+?50,?"sprites",?"button").setScale(3,?2).setInteractive().on("pointerdown",?()?=>?{//?點擊事件:關閉當前場景,打開Main場景this.scene.start("Main");});//?按鈕文案this.add.text(button.x,?button.y,?"重新開始",?{fontFamily:?"Arial",fontSize:?20,}).setOrigin(0.5);}
}

07

優化

經過上面的代碼,整個游戲已經基本完成。不過在測試的時候,感覺玩家和敵軍還存在一定距離就觸發了碰撞事件。在創建game時,我們可以打開debug模式,這樣就可以看到Phaser為我們提供的一些調試信息。

game?=?new?Game({physics:?{default:?"arcade",arcade:?{debug:?true,},},//?...
});

測試一下碰撞:

659a49a34156e429b8f85b929e561111.png

可以看到兩個元素的邊框確實發生碰撞了,但是這并不符合我們的要求,我們希望兩個飛機看起來是真的挨到一起才觸發碰撞事件。所以我們可以再優化一下,飛機本身不變,但是邊框縮小。

Player.ts的構造函數中追加如下:

export?class?Player?extends?Physics.Arcade.Sprite?{constructor()?{//?...//?追加下面一行this.body.setSize(120,?120);}
}

Enemy.ts的構造函數中追加如下:

export?class?Enemy?extends?Physics.Arcade.Sprite?{constructor()?{//?...//?追加下面一行this.body.setSize(100,?60);}
}

最終可以看到邊框已經被縮小,效果如下:

bd0449c9774f541645e1c55f0881fae9.png

08

結語

至此,飛機大戰全部開發完成。

回顧一下開發過程,我們先搭建項目,創建游戲對象,接下來又設計了:預載場景、主場景、結束場景,并且為了減少主場景的復雜度,我們以場景元素的維度,將涉及到的場景元素進行封裝,形成:玩家類、子彈類、敵軍類、爆炸類,讓這些場景元素各自實現自身的事件和行為。

在Phaser中的場景元素又可以分為普通元素和物理元素,物理元素是來自Physics,其中玩家類,子彈類,敵軍類都是物理元素,物理元素具有物理屬性,比如重力,速度,加速度,彈性,碰撞等。

在本文代碼中涉及到了很多Phaser的API,介于篇幅沒有一一解釋,但是很多通過字面意思也可以理解,比如說disableBody表示禁用元素,setVelocityY表示設置Y?軸方向速度。并且我們也可以通過編譯器的代碼提示功能去了解這些方法的說明和參數含義:

66091e0ad6ef2fe1bd9084fe31ea805f.png

最后,本文的所有代碼都已上傳gitee,有興趣的同學可以拉取代碼看下。

演示效果:https://yuhuo.online/plane-war/(點擊"閱讀原文"訪問鏈接)

源碼地址:https://gitee.com/yuhuo520/plane-war

2e26c0512a6576830f535367bf07f682.jpeg

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15113.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15113.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# 串口通訊之艱難排錯之路 —— system.ObjectDisposedException已關閉 Safe handle

今天寫了一個串口通訊掃碼槍驅動&#xff0c;程序運行后&#xff0c;不出意外的全線崩潰&#xff0c;開始了漫長的排查之旅&#xff0c;具體情況報錯如下&#xff1a; 解決未處理 System.ObjectDisposedException Message已關閉 Safe handle Sourcemscorlib ObjectName"&…

【pyspark速成專家】4_Spark之RDD編程2

目錄 四&#xff0c;常用PairRDD的轉換操作 五&#xff0c;緩存操作 四&#xff0c;常用PairRDD的轉換操作 PairRDD指的是數據為長度為2的tuple類似(k,v)結構的數據類型的RDD,其每個數據的第一個元素被當做key&#xff0c;第二個元素被當做value. reduceByKey #reduceByKey…

層次式架構設計理論與實踐

層次式體系結構概述 軟件體系結構為軟件系統提供了結構、行為和屬性的高級抽象&#xff0c;由構成系統的元素描述這些元素的相互作用、指導元素集成的模式以及這些模式的約束組成。 層次式體系結構的每一層最多只影響兩層&#xff0c;同時只要給相鄰層提供相同的接口&#xff…

禁用win10自動更新

services.msc——Windows Update——常規——啟動類型——禁用 services.msc——Windows Update——恢復——三個無操作&#xff0c;9999天。 gpedit.msc——計算機配置——管理模板——Windows組件——Windows更新——配置自動更新——已啟用——2-通知下載和自動更新 Windows…

如何參與github開源項目并提交PR

&#x1f47d;System.out.println(“&#x1f44b;&#x1f3fc;嗨&#xff0c;大家好&#xff0c;我是代碼不會敲的小符&#xff0c;目前工作于上海某電商服務公司…”); &#x1f4da;System.out.println(“&#x1f388;如果文章中有錯誤的地方&#xff0c;懇請大家指正&…

高速公路定向廣播(聲光一體) HT-600D

1、產品概述&#xff1a; HT-600D聲光一體平面波IP定向廣播是北京恒星科通創新性研發產品&#xff0c;采用公司自主研發的平面波傳聲技術&#xff0c;該產品具有高聲壓、強指向性、高清晰度等特點&#xff0c;采用定向聲傳聲技術將聲音聚集到正前方定向傳輸,周邊聲壓級明顯降低…

BTC系列-系統學習銘文(二)-序數理論

Ordinals的BIP: https://github.com/ordinals/ord/blob/master/bip.mediawiki 序數理論概述 序數是一種比特幣的編號方案&#xff0c;允許跟蹤和轉移單個聰。這些數字被稱作序號。比特幣是按照它們被挖掘的順序編號的&#xff0c;并從交易輸入轉移到交易輸出&#xff08;遵循先…

面試題:對已經關閉的channel進行讀寫

在Go語言中對已經關閉的channel進行讀寫&#xff0c;結果會有所不同。 讀操作 我們可以安全地從一個已經關閉的channel中進行讀取數據。如果channel中還有未讀取的數據&#xff0c;讀操作將成功并返回數據以及一個用于表示數據是否有效的標記(如果channel已經關閉并且該數據有…

YOLOV10實時端到端目標檢測

代碼地址&#xff1a;GitHub - THU-MIG/yolov10: YOLOv10: Real-Time End-to-End Object Detection 論文地址&#xff1a;https://arxiv.org/pdf/2405.14458 本文介紹了YOLO系列目標檢測器在實時和高效方面的優勢&#xff0c;但是仍然存在一些缺陷&#xff0c;包括依賴非極大值…

[240525] VMware Pro 個人可免費使用 | 人機交互角度 解釋 AI 同事出錯雖多但深得青睞之奧義

目錄 VMware Workstation Pro 個人可免費使用人機交互研究 ChatGPT 52%回答失實&#xff0c;78%邏輯不一致然卻備受青睞之奧義 VMware Workstation Pro 個人可免費使用 VMware 宣布 Fusion Pro&#xff08;Mac&#xff09;和 Workstation Pro&#xff08;Windows 和 Linux&…

純度高的安卓和混血安卓

安卓陣營純安卓和改裝安卓&#xff0c;純安卓好用&#xff0c;權限控制力度做到很小&#xff0c;每相權限都交用戶控制&#xff0c;權限控制層面可以精確到文件夾和文件&#xff0c;剪切板讀和寫&#xff0c;而且有精確權限追蹤功能&#xff0c;國產高度定制安卓系統只有粗糙訪…

React useState修改對象

在 React 中&#xff0c;useState 是一個 Hook&#xff0c;它可以讓函數組件擁有狀態。當想要改變一個對象類型的狀態時&#xff0c;我們需要使用展開運算符&#xff08;...&#xff09;或者 Object.assign 來確保狀態是正確地更新。 以下是一個使用 useState 來更新對象的例子…

webstorm新建vue項目相關問題

前言 這個迭代后端需求偏少&#xff0c;前端code的鍵盤都起火星子了。來了4個外包支持&#xff0c;1個后端3個前端&#xff0c;還是不夠用啊。剛好趁這個機會稍微學習下vue&#xff0c;其實之前環境也配置過了&#xff0c;所以這里就不分享環境配置了&#xff0c;主要分享下新建…

基于單片機電梯控制系統設計與實現

摘 要: 介紹了電梯控制系統架構 &#xff0c; 指出了該系統的硬件設計和控制系統的軟件設計以及系統調試 &#xff0c; 使系統可根據按鍵 要求完成載客任務&#xff0c;為電梯控制系統的優化提供了參考 。 關鍵詞 : 電梯控制 ; 單片機 ; 系統設計 0 引言 在高層建筑中發揮…

Java開發大廠面試第22講:Redis 是如何保證系統高可用的?它的實現方式有哪些?

高可用是通過設計&#xff0c;減少系統不能提供服務的時間&#xff0c;是分布式系統的基礎也是保障系統可靠性的重要手段。而 Redis 作為一款普及率最高的內存型中間件&#xff0c;它的高可用技術也非常的成熟。 我們今天分享的面試題是&#xff0c;Redis 是如何保證系統高可用…

GPT-4o之多模態

前言 想必&#xff0c;很多小伙伴都知道GPT-4o已經發布了&#xff0c;一手基于多模態的問答顯示&#xff0c;看起來挺厲害的&#xff08;也就是看起來&#xff0c; &#xff09;。然后&#xff0c;我就順手看了看什么是多模態。 簡介 多模態&#xff08;Multimodal&#xff…

什么是組態?什么是工業控制中的組態軟件?

隨著工業4.0和智能制造的發展&#xff0c;工控軟件的應用越來越廣泛&#xff0c;它們在提高生產效率、降低能耗和減少人力成本等方面發揮著越來越重要的作用。 什么是工控軟件&#xff1f; 工控軟件是指用于工業控制系統的軟件&#xff0c;主要應用于各種生產過程控制、自動化…

標準庫算法

歡迎訪問我的博客首頁。 標準庫算法 1. 查找對象的算法2. 其它只讀算法3. 二分搜索算法4. 寫容器元素的算法5. 劃分與排序算法6. 通用重排操作7. 排列算法8. 有序序 列的 集合算法9. 最 小值和 最大值10. 數值算法11. 參考 Pred 表示返回值為布爾類型的可調用對象。 1. 查找對…

Python序列的概念與使用-課后作業[python123題庫]

序列的概念與使用-課后作業 一、單項選擇題 1、關于Python組合數據類型&#xff0c;以下描述錯誤的是&#xff1a;??????????????????????????????????????????????????????????????????????????…

Flutter 中的 DecoratedBox 小部件:全面指南

Flutter 中的 DecoratedBox 小部件&#xff1a;全面指南 在Flutter中&#xff0c;DecoratedBox是一個功能豐富的小部件&#xff0c;它為子組件提供了多種裝飾效果&#xff0c;如背景色、邊框和陰影。通過DecoratedBox&#xff0c;你可以輕松地為任何小部件添加裝飾&#xff0c…