本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」
書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )
新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)
在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。
助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」
鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」
現在讓我們進入遊戲吧!首先當然是新增遊戲頁面!( ´ ▽ ` )ノ
新增 games 資料夾,用來集中未來所有遊戲的頁面組件並新增遊戲畫面組件。
src\games\the-first-penguin\game-scene.vue
<template>
<canvas
ref="canvas"
class="w-full h-full outline-none"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useLoading } from '../../composables/use-loading';
const loading = useLoading();
const canvas = ref<HTMLCanvasElement>();
function init() {
loading.hide();
}
init();
</script>
加入 Router 中。
src\router\router.ts
...
import enum RouteName {
...
GAME_CONSOLE_THE_FIRST_PENGUIN = 'game-console-the-first-penguin',
...
}
const routes: Array<RouteRecordRaw> = [
...
{
path: `/game-console`,
...
children: [
...
{
path: `the-first-penguin`,
name: RouteName.GAME_CONSOLE_THE_FIRST_PENGUIN,
component: () => import('../games/the-first-penguin/game-scene.vue')
},
]
},
...
]
...
現在讓我們完成 game-tab-panel 完整功能,使其可以跳轉至遊戲頁面吧。
首先新增遊戲清單資料定義。
export enum GameName {
THE_FIRST_PENGUIN = 'the-first-penguin',
}
export interface GameInfo {
name: string;
description: string;
gameName: `${GameName}`;
routeName: `${RouteName}`;
videoSrc: string;
}
接著新增遊戲清單資料與選擇資料用的 function。
const games: GameInfo[] = [
{
name: '第一隻企鵝',
description: '企鵝群下水前,會將最前頭的企鵝踢下水,確認水中沒有天敵後才會下水,努力不要被踢下水吧!',
gameName: 'the-first-penguin',
routeName: 'game-console-the-first-penguin',
videoSrc: '/game/the-first-penguin.mp4',
}
];
const currentIndex = ref(0);
const selectedGame = computed(() => games[currentIndex.value]);
function prev() {
currentIndex.value--;
if (currentIndex.value < 0) {
currentIndex.value += games.length;
}
}
function next() {
currentIndex.value++;
currentIndex.value %= games.length;
}
助教:「也才一個遊戲,真是做了個寂寞…('◉◞⊖◟◉` )」
鱈魚:「請課金解鎖更多遊戲 ᕕ( ゚ ∀。)ᕗ」
助教:( Bonk! (╬▔皿▔)╯)
鱈魚:「窩會努力慢慢更新的 … ( ´•̥̥̥ ω •̥̥̥` )」
目前內容為:
src\components\game-tab-panel.vue
...
<script lang="ts">
export enum GameName {
THE_FIRST_PENGUIN = 'the-first-penguin',
}
export interface GameInfo {
name: string;
description: string;
gameName: `${GameName}`;
routeName: `${RouteName}`;
videoSrc: string;
}
</script>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { RouteName } from '../router/router';
const games: GameInfo[] = [
{
name: '第一隻企鵝',
description: '企鵝群下水前,會將最前頭的企鵝踢下水,確認水中沒有天敵後才會下水,努力不要被踢下水吧!',
gameName: 'the-first-penguin',
routeName: 'game-console-the-first-penguin',
videoSrc: '',
}
];
const currentIndex = ref(0);
const selectedGame = computed(() => games[currentIndex.value]);
function prev() {
currentIndex.value--;
if (currentIndex.value < 0) {
currentIndex.value += games.length;
}
}
function next() {
currentIndex.value++;
currentIndex.value %= games.length;
}
</script>
...
將遊戲資源檔案放在 public 目錄下。(可至 GitLab 下載)
public\games\the-first-penguin\preview.mp4
更新一下 videoSrc 內容。
const games: GameInfo[] = [
{
name: '第一隻企鵝',
...
videoSrc: '/games/the-first-penguin/preview.mp4',
}
];
現在新增 template 內容,呈現遊戲影片。
<template>
<div class="panel relative rounded-[3rem]">
<video
:src="selectedGame.videoSrc"
autoplay
muted
loop
class="absolute w-full h-full"
/>
<div class="description">
<div class="name">
{{ selectedGame.name }}
</div>
</div>
</div>
</template>
...
<style scoped lang="sass">
.panel
width: 84vmin
height: 84vmin
background: rgba(white, 0.8)
overflow: hidden
border: 1rem solid white
.description
position: absolute
bottom: 0
left: 0
width: 100%
display: flex
justify-content: center
align-items: center
padding: 2rem
background: rgba(#67785d, 0.4)
.name
color: white
font-size: 2.4rem
text-shadow: 0 0 10px rgba(#67785d, 0.6)
</style>
現在會開始播放影片了!
最後讓我們完成開始遊戲的功能並將可用功能 defineExpose 出去。
<template>
...
</template>
...
<script setup lang="ts">
...
const start = debounce(() => {
gameConsole.setGameName(selectedGame.value.gameName);
gameConsole.setStatus('playing');
router.push({
name: selectedGame.value.routeName
});
}, 3000, {
leading: true,
trailing: false,
});
defineExpose({
prev, next, start
});
</script>
...
現在讓我們回到 game-console-lobby 建立開始派對的功能。
src\views\game-console-lobby.vue
<template>
...
<div class="absolute inset-0 flex">
<div class="flex w-full h-full">
<!-- 選單 -->
<div class="w-1/3 flex flex-col p-12">
<div class="flex flex-col flex-1 justify-center items-center gap-14">
...
<btn-base
:ref="mountElement"
label="開始遊戲"
...
@click="startParty"
>
...
</btn-base>
...
</div>
...
</div>
...
</div>
</div>
</template>
<script setup lang="ts">
...
async function startParty() {
}
...
</script>
...
使用 ref 綁定 game-tab-panel,並在 startParty() 中觸發。
<template>
...
<div class="absolute inset-0 flex">
<div class="flex w-full h-full">
...
<!-- 選擇遊戲 -->
<div class="w-2/3 flex flex-nowrap justify-between items-center flex-1 px-16">
...
<game-tab-panel ref="gameTabPanel" />
...
</div>
</div>
</div>
</template>
<script setup lang="ts">
...
const gameTabPanel = ref<InstanceType<typeof GameTabPanel>>();
async function startParty() {
await loading.show();
gameTabPanel.value?.start();
}
...
</script>
...
現在在大廳中按下「開始遊戲」後,就會跳轉至企鵝的遊戲畫面了!
接下來讓我們正式開始打造遊戲吧!ヽ(●`∀´●)ノ
以上程式碼已同步至 GitLab,大家可以前往下載: