iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Modern Web

派對動物嗨起來!系列 第 20

D20 - I want to play a game

  • 分享至 

  • xImage
  •  

本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」

書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )

新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)

在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。

助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」

鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」

Yes


現在讓我們進入遊戲吧!首先當然是新增遊戲頁面!( ´ ▽ ` )ノ

新增 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>

現在會開始播放影片了!

Untitled

最後讓我們完成開始遊戲的功能並將可用功能 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>
...
  • start() 加上 debounce,是為了防止玩家按太多下,造成異常。

現在讓我們回到 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,大家可以前往下載:

GitLab - D20


上一篇
D19 - 是誰偷按確定?
下一篇
D21 - 大海、浮冰、企鵝勒?:使用 babylon.js 打造 3D 遊戲
系列文
派對動物嗨起來!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言