iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0

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

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

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

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

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

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

Yes


阿德利企鵝在下水前,會將最前頭的企鵝踢下水,確認水中沒有天敵後才會下水,努力不要被踢下水吧!

現在讓我們完成所有的遊戲邏輯吧。( ´ ▽ ` )ノ

首先是冰塊會逐漸縮小,讓戰況更加刺激,加入動畫即可達成效果。

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>
...

得到看起來暖化很嚴重的浮冰!◝( •ω• )◟

ezgif-2-4fbc4d3667.gif

接著讓我們偵測勝利企鵝的部分。

首先新增處理出界企鵝的 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>

現在場上剩下一隻企鵝時,會出現以下畫面。

Untitled

最後完成「按下 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,大家可以前往下載:

GitLab - D28


上一篇
D27 - 一人一隻才公平
下一篇
D29 - 結束是另一個開始
系列文
派對動物嗨起來!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
ShawnL
iT邦新手 1 級 ‧ 2022-10-12 14:58:27

google 阿德利企鵝之後陰影揮之不去⋯⋯

鱈魚 iT邦研究生 5 級 ‧ 2022-10-12 15:22:10 檢舉

阿德利企鵝真的是很破壞企鵝的形象 XD

我要留言

立即登入留言