本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」
書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )
新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)
在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。
助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」
鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」
阿德利企鵝在下水前,會將最前頭的企鵝踢下水,確認水中沒有天敵後才會下水,努力不要被踢下水吧!
現在讓我們完成所有的遊戲邏輯吧。( ´ ▽ ` )ノ
首先是冰塊會逐漸縮小,讓戰況更加刺激,加入動畫即可達成效果。
createIce() 增加動畫內容。
src\games\the-first-penguin\game-scene.vue
...
<script setup lang="ts">
...
function createIce(scene: Scene) {
const ice = MeshBuilder.CreateBox('ice', {
width: 30,
depth: 30,
height: 4,
});
ice.material = new StandardMaterial('iceMaterial', scene);
// mass 設為 0,就可以固定在原地不動
ice.physicsImpostor = new PhysicsImpostor(ice, PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 0, restitution: 0 }, scene
);
// 建立動畫
const frameRate = 10;
const melting = new Animation(
'melting',
'scaling',
frameRate / 50,
Animation.ANIMATIONTYPE_VECTOR3,
);
const keyFrames = [
{
frame: 0,
value: new Vector3(1, 1, 1)
},
{
frame: frameRate,
value: new Vector3(0.1, 1, 0.1)
}
];
melting.setKeys(keyFrames);
ice.animations.push(melting);
scene.beginAnimation(ice, 0, frameRate);
// 物理碰撞也要隨著尺寸更新
scene.registerBeforeRender(() => {
ice.physicsImpostor?.setScalingUpdated();
});
return ice;
}
...
</script>
...
得到看起來暖化很嚴重的浮冰!◝( •ω• )◟
接著讓我們偵測勝利企鵝的部分。
首先新增處理出界企鵝的 function。
/** 處理出界的企鵝
* y 軸低於 -3 判定為出界
*/
function detectOutOfBounds(penguins: Penguin[]) {
penguins.forEach((penguin) => {
if (!penguin.mesh) return;
if (penguin.mesh.position.y < -3) {
penguin.mesh.dispose();
}
});
}
接著新增處理獲勝玩家的部分。
const isGameOver = ref(false);
const winnerCodeName = ref('');
/** 偵測是否有贏家 */
function detectWinner(penguins: Penguin[]) {
const alivePenguins = penguins.filter(({ mesh }) => !mesh?.isDisposed());
if (alivePenguins.length !== 1) return;
engine.stopRenderLoop();
const winnerId = alivePenguins[0].getPlayerId();
winnerCodeName.value = gameConsole.getPlayerCodeName(winnerId);
isGameOver.value = true;
}
在來新增遊戲結束的 Dialog。
<template>
...
<q-dialog
v-model="isGameOver"
persistent
>
<div class="card gap-14">
<div class="flex items-center text-3xl text-gray-600">
<q-icon name="emoji_events" />
遊戲結束
</div>
<div class="text-3xl text-sky-700">
玩家 {{ winnerCodeName }} 獲勝!
</div>
<div class="text-xl text-gray-400">
按下 A 回到大廳
</div>
</div>
</q-dialog>
</template>
...
<style scoped lang="sass">
.card
width: 30rem
height: 24rem
background: white
border-radius: 2rem
display: flex
flex-direction: column
justify-content: center
align-items: center
</style>
最後整合程式。
...
<script setup lang="ts">
...
/** 處理出界的企鵝
* y 軸低於 -3 判定為出界
*/
function detectOutOfBounds(penguins: Penguin[]) {...}
/** 偵測是否有贏家 */
function detectWinner(penguins: Penguin[]) {...}
...
async function init() {
...
/** 持續運行指定事件 */
scene.registerAfterRender(() => {
detectCollideEvents(penguins);
detectOutOfBounds(penguins);
detectWinner(penguins);
});
...
}
...
</script>
現在讓我們實測看看,新增一個沙包企鵝,方便只有一個玩家也能測試。
...
<script setup lang="ts">
...
async function init() {
const result = await Promise.allSettled(...);
result.forEach(...);
// 沙包企鵝
const penguinNpc = await new Penguin('penguinNpc', scene, {
position: new Vector3(0, 5, 0),
color: new Color3(1, 1, 1),
ownerId: '',
}).init();
penguins.push(penguinNpc);
}
...
</script>
現在場上剩下一隻企鵝時,會出現以下畫面。
最後完成「按下 A 回到大廳」的功能就大功告成了!
...
<script setup lang="ts">
...
/** 控制指定企鵝 */
function ctrlPenguin(penguin: Penguin, data: GamepadData) {
...
// 攻擊按鍵
const attackData = findData('a');
if (attackData) {
if (isGameOver.value) {
backToLobby();
return;
}
penguin.attack();
return;
}
// 移動按鍵
...
}
...
async function backToLobby() {
isGameOver.value = false;
await loading.show();
router.push({
name: RouteName.GAME_CONSOLE_LOBBY
});
}
...
</script>
現在各位讀者可以聚集小夥伴們,大家一起互相傷害了!✧*。٩(ˊᗜˋ*)و✧*。
以上程式碼已同步至 GitLab,大家可以前往下載: