D22 - 「不断线的侏罗纪」:很久很久以前的侏罗纪

本篇来实际建立游戏场景!

建立游戏场景

建立游戏场景组件 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


<<:  Day21 黄金砖-金沙豆腐

>>:  Day 23 | 在Flutter里串接restful api - 我不使用HttpClient了 jojo

[LeetCode30] Day28 - 65. Valid Number

题目 给定一个string s, 判断能不能解释成十进制数字。 题目很好心得列了用来作有效的十进制的...

[从0到1] C#小乳牛 练成基础程序逻辑 Day 19 - for loop 中断点 + watch

已知次数用for loop | 您最好的程序码侦错社 | watch全面追踪 ...

Day 27 实测透过隧道广播BGP

上篇有讲到许多种广播BGP的方式,那这篇我们就来用"隧道"广播BGP! 那这篇使...

JavaScript Day27 - IIFE (立即函式)

IIFE IIFE (立即函式):IIFE (Immediately Invoked Functio...

Day_30 RPI GPIO

openwrt虽然主力是在网路服务,但如果硬体与韧体的支援上有GPIO(通用型之输入输出的简称),也...