iT邦幫忙

2021 iThome 鐵人賽

DAY 23
0
Modern Web

你渴望連結嗎?將 Web 與硬體連上線吧!系列 第 23

D22 - 「不斷線的侏儸紀」:很久很久以前的侏儸紀

本系列文已改編成書「Arduino 自造趣:結合 JavaScript x Vue x Phaser 輕鬆打造個人遊戲機」,本書改用 Vue3 與 TypeScript 全面重構且加上更詳細的說明,

在此感謝 iT 邦幫忙、博碩文化與編輯小 p 的協助,歡迎大家前往購書,鱈魚在此感謝大家 (。・∀・)。

若想 DIY 卻不知道零件去哪裡買的讀者,可以參考此連結 。( •̀ ω •́ )✧


本篇來實際建立遊戲場景!

建立遊戲場景

建立遊戲場景組件 game-scene.vue,並提供「跳躍按鈕腳位」與「蹲下按鈕腳位」之 props

src\components\window-app-google-dino\game-scene.vue <script>

/**
 * @typedef {import('@/script/modules/port-transceiver').default} PortTransceiver
 *
 * @typedef {import('@/types/type').PinInfo} PinInfo
 * @typedef {import('@/types/type').PinCapability} PinCapability
 */

import { mapState } from 'vuex';

export default {
  name: 'GameScene',
  components: {},
  props: {
		/** 跳躍按鈕腳位 */
    jumpPin: {
      type: Object,
      default() {
        return null;
      },
    },
    /** 蹲下按鈕腳位 */
    squatPin: {
      type: Object,
      default() {
        return null;
      },
    },
  },
  data() {
    return {};
  },
  computed: {
    ...mapState({
      /** @type {PortTransceiver} */
      portTransceiver: (state) => state.core.transceiver,
    }),
  },
  watch: {},
  created() {},
  mounted() {},
  beforeDestroy() {},
  methods: {},
};

src\components\window-app-google-dino\game-scene.vue <template lang="pug">

.game-scene

src\components\window-app-google-dino\game-scene.vue <style scoped lang="sass">

@import '@/styles/quasar.variables.sass'

.game-scene
  width: 100%
  height: 100%
  user-select: none

接著在 window-app-google-dino.vue 引入 game-scene.vue

src\components\window-app-google-dino\window-app-google-dino.vue <script>

// ...

import GameScene from './game-scene.vue';

// ...

export default {
  name: 'WindowAppGoogleDino',
  components: {
    'base-window': BaseWindow,
    'base-select-pin': BaseSelectPin,

    'game-scene': GameScene,
  },
  // ...
};

src\components\window-app-google-dino\window-app-google-dino.vue <template lang="pug">

base-window.window-app-google-dino(
  // ...
)
  .h-full.overflow-hidden
    // 遊戲場景
    game-scene(:jump-pin='jumpPin', :squat-pin='squatPin')

    // 設定欄位
    // ...

加點細節吧

接下來讓我們依序加入更多內容吧!╰( *´ ︶ *` ** )╯

遊戲狀態

首先加入遊戲狀態列舉。

src\components\window-app-google-dino\game-scene.vue <script>

/**
 * @typedef {import('@/script/modules/port-transceiver').default} PortTransceiver
 *
 * @typedef {import('@/types/type').PinInfo} PinInfo
 * @typedef {import('@/types/type').PinCapability} PinCapability
 */

/**
 * @enum {string}
 */
export const GameStatus = {
  /** 等待開始 */
  STANDBY: 'standby',
  /** 遊戲開始 */
  START: 'start',
  /** 遊戲結束 */
  GAME_OVER: 'gameOver',
};

import { mapState } from 'vuex';

export default {
  name: 'GameScene',
  // ...
};

GameStatus export 出去,讓別的檔案也能夠使用。

遊戲變數

接著新增遊戲場景相關變數。

  • gameStatus:遊戲狀態。紀錄目前遊戲狀態。
  • timer:計時器。儲存計時器。
  • timeCounter:計數器。紀錄目前計數。
  • score:分數。紀錄目前分數。

src\components\window-app-google-dino\game-scene.vue <script>

// ...

export default {
  name: 'GameScene',
  // ...
	data() {
    return {
      gameStatus: GameStatus.STANDBY,
      timer: null,
      timeCounter: 0,

      score: 0,
    };
  },
	// ...
};

遊戲功能

再來新增遊戲基本功能,在 methods 新增以下 Method:

  • start():開始遊戲。
  • over():結束遊戲。
  • tick():更新遊戲內容,透過計時器呼叫。

畫面更新動畫最佳的實踐方式是使用 requestAnimationFrame(),不過為了簡單實現,在此使用 setInterval(),大家可以嘗試使用 requestAnimationFrame() 實作看看 ( ´ ▽ ` )ノ

最佳化 JavaScript 執行

src\components\window-app-google-dino\game-scene.vue <script>

// ...

export default {
  name: 'GameScene',
  // ...
	methods: {
		start() {
      if (this.gameStatus === GameStatus.START) {
        return;
      }

      // 初始化變數
      this.gameStatus = GameStatus.START;
      this.score = 0;
      this.timeCounter = 0;

      // 計時器啟動
      this.timer = setInterval(() => {
        this.tick();
      }, 10);
    },

    over() {
      this.gameStatus = GameStatus.GAME_OVER;
      clearInterval(this.timer);
    },

    tick() {
      this.timeCounter++;

      // score 每 0.1 秒增加一次
      if (this.timeCounter % 10 === 0) {
        this.score++;
      }
    },
  },
};

大家可能會有一個問題,為甚麼 if (this.timeCounter % 10 === 0) ,這段程式會是每 0.1 秒執行一次呢?

因為 timer 是 0.01 秒呼叫一次,而 this.timeCounter % 10 === 0 剛好就是每 10 次才成立一次。

結果就會是 0.01 * 10 = 0.1 秒執行一次了,以此類推:

  • 0.5 秒執行一次,就會是 this.timeCounter % 50 === 0
  • 1 秒執行一次,就會是 this.timeCounter % 100 === 0

增加場景內容

為了讓遊戲整體更 8 位元一點,我們加入 8 位元風格字體。

字體依舊來自 Google Font,讚嘆偉大的 Google ◝( •∀• )◟

src\components\window-app-google-dino\game-scene.vue <style scoped lang="sass">

@import url('https://fonts.googleapis.com/css2?family=DotGothic16&display=swap')
@import '@/styles/quasar.variables.sass'

.game-scene
  width: 100%
  height: 100%
  user-select: none
	font-family: 'DotGothic16', sans-serif

接著在場景中新增「地面」、「分數」與「提示文字」。

src\components\window-app-google-dino\game-scene.vue <template lang="pug">

.game-scene
	.ground

  .scoreboard
    | 00000

  .prompt-text
    template(v-if='gameStatus === "standby"')
      .text-30px
        | 按任意按鈕開始遊戲

    template(v-if='gameStatus === "gameOver"')
      .text-34px.mb-10px
        | GAME OVER
      .text-20px
        | 按任意按鈕重新開始

src\components\window-app-google-dino\game-scene.vue <style scoped lang="sass">

@import url('https://fonts.googleapis.com/css2?family=DotGothic16&display=swap')
@import '@/styles/quasar.variables.sass'

.game-scene
  width: 100%
  height: 100%
  user-select: none
  font-family: 'DotGothic16', sans-serif
  .ground
    position: absolute
    bottom: 0px
    left: 0px
    width: 100%
    height: 50px
    border-top: 2px solid $grey-4

  .scoreboard
    position: absolute
    top: 20px
    right: 26px
    font-size: 26px
    letter-spacing: 2px

  .prompt-text
    position: absolute
    top: 46%
    left: 50%
    transform: translate(-50%, -50%)
    letter-spacing: 4px
    font-size: 30px
    user-select: none
    text-align: center

目前應該長這樣。

Untitled

讓提示文字有閃爍特效,增加點電玩感,增加 .blink Class。

利用 animation step 達成補幀上跳躍感

src\components\window-app-google-dino\game-scene.vue <style scoped lang="sass">

@import url('https://fonts.googleapis.com/css2?family=DotGothic16&display=swap')
@import '@/styles/quasar.variables.sass'

.game-scene
  width: 100%
  height: 100%
  user-select: none
  font-family: 'DotGothic16', sans-serif
  // ...

	.blink
    animation: blink 2s infinite steps(3)

@keyframes blink
  0%, 100%
    opacity: 1
  50%
    opacity: 0

在要閃爍的文字加上 .blink

src\components\window-app-google-dino\game-scene.vue <template lang="pug">

.game-scene
  // ...

  .prompt-text
    template(v-if='gameStatus === "standby"')
      .text-30px.blink
        | 按任意按鈕開始遊戲

    template(v-if='gameStatus === "gameOver"')
      // ...

D22 - 提示文字加入電玩閃爍感.gif

有點大型電玩的感覺了。

最後是分數的部分,透過 computed 提供補 0 後的結果。

src\components\window-app-google-dino\game-scene.vue <script>

// ...

export default {
  name: 'GameScene',
  // ...
	computed: {
    // ...

    scoreboard() {
      return `${this.score}`.padStart(5, '0');
    },
  },
};

scoreboard 加入 Pug。

src\components\window-app-google-dino\game-scene.vue <template lang="pug">

.game-scene
	.ground

	.scoreboard
    | {{ scoreboard }}

  .prompt-text
    // ...

最後呼叫看看 start(),看看遊戲開始的感覺。

.game-scene 加上 @click,點擊看看遊戲有沒有開始。

src\components\window-app-google-dino\game-scene.vue <template lang="pug">

.game-scene(@click='start')
  // ...

D22 - 完成基本場景內容.gif

基本場景內容完成!再來就是加入角色了!

總結

  • 完成場景基本內容。

以上程式碼已同步至 GitLab,大家可以前往下載:

GitLab - D22


上一篇
D21 - 「不斷線的侏儸紀」:萃取 DNA
下一篇
D23 - 「不斷線的侏儸紀」:有一隻小恐龍在跑步(使用 GSAP 動畫)
系列文
你渴望連結嗎?將 Web 與硬體連上線吧!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言