头图

Vue3 也能做游戏:飞翔的思否猫(挖坑警告

大家知道,作为一个开发者社区,10 月 24 日不搞点事情有点说不过去。于是思否运营同学找到我,邀请我出一篇文章,以思否猫为主题进行开发。我当即就答应了,写代码写文章嘛,小事一桩。

我不想走寻常路,看了思否猫的造型后,当即决定用 Web 技术做一个横版飞行游戏。而且要用 OpenCV + 面部识别来控制,还要用语音控制发炮。可以说,当时想的很好。然而,理想很丰满,现实很骨感,现在看着只写了一半的代码,悔不当初啊……

0. 技术预研

先说结论:

  • Web 语音识别暂时难堪大用
  • OpenCV 在现代化浏览器的性能足够

具体来说,我不太确定 Web 语音识别(STT)的实现机制,比如:是本地集成模型,还是要发给服务器,识别了再返回给客户端?实测的效果不太理想,响应速度太慢,也许做一般的应用尚可,搞游戏就完全不可行,从发声到得到结果至少需要 10s。不过兼容性还可以,Android、iOS、桌面都能用。

OpenCV 我找到了 这个项目,虽然没有文档,但是有 demo 页,效果不错,响应速度很快,基本上可以满足操控所需。

所以我决定:

  1. 如果时间足够,那么我就用 Web Audio 中的音量 API 制作发炮功能。即只要声音够大就开炮,而不是最初设想的要念特定单词(我甚至 YY 念不同的单词发不同的炮)。
  2. OpenCV 一定要用,不然不够酷。

可以说,我这会儿都还太过自信。

1. 其它技术选型

做游戏,有些同学可能会想到 Cocos、Unity 等技术,至少也要在 <canvas> 里画图。不过,对于一款简单的横版飞行游戏,我觉得 DOM 就足够了。没有很复杂的图层关系、没有很多元件需要做判定,固定思否猫在屏幕中线上下飞,撞到墙就失败,我甚至可以利用 InsersectionObserver 帮忙做碰撞检测。

于是我决定:

  1. 还是基于 Vue3 + Vite 来做,至少非游戏的交互部分(开始游戏、显示成绩、显示奖励等)就很简单
  2. 游戏内容用 DOM 来实现,基本 position: relative + absolute 就足够
  3. 碰撞检测用 IntersectionObserver 来实现

2. 创建 Vite 项目

谋定就可以开始动了。比较意外的是,我厂最近突然特别忙,加上国庆节亲戚来广州玩时攒了不少事情(“国庆后国庆后……”),所以空余时间很少,一直拖到本周一才开始动手。

(嗯,先铺垫着,我们来看代码。)

创建 Vite 项目很简单,我们参考官方文档即可:

pnpm create vite sf-cat-flying --template vue

接着,安装我的常用依赖,因为游戏比较简单,就不需要 vue-router 那些了:

pnpm i tailwindcss postcss autoprefixer daisyui -D

# 配置 tailwindcss
pnpx tailwindcess init -p

修改 tailwind.config.js,加入需要的配置:

module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,vue,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require('daisyui')],
}

修改 index.css,删掉 vue 模版里的样式,引入 tailwind:

@tailwind base;
@tailwind components;
@tailwind utilities;

好,基础脚手架就绪,接着可以开始写代码了。

3. 布局游戏舞台

直接上代码吧:

<div id="stage">
  <div class="stage-bg"></div>
  <video />
  <img class="sf-cat" src="sf-cat.png" />
</div>
// 起初目标是移动端,所以启动时默认全宽全高
html, body, #app, #stage, .stage-bg
  height 100%

#stage
  width 100vw
  overflow: hidden

.stage-bg
  background url(bg.png) repeat-x
  background auto 100%
  width 1000vw
  position relative
  transition 30s linear

  &.play
    transform translateX(-900vw)

#sf-cat
  transition .5s ease-in-out
  position fixed
  left 50%
  top 50%
  width 4rem
  margin -2rem 0 0 -2rem

首先我布置了一个 <div id="stage"> 作为舞台,它的高宽就是我们能看到的游戏区域。超出它的部分都要隐藏。

接下来,我拿一个 <div class="stage-bg"> 作为背景,它的宽度是 1000vw,10倍于 stage,也就是总共可以滚动 9 屏。30s 意味着滚动 30s,linear 表示线性滚动,速度不变。

这里的 <video> 用来显示摄像头捕获的视频流,方便用户操作思否猫,后面再细说。

思否猫就是个普通的 <img>,我使用 position: fixed 和定位属性让它固定在屏幕正中。为了实现加速度效果,我让它的动画效果是 ease-in-out,即启动和停止都有加速度。

4. 刷新柱子和碰撞检测

柱子的添加很简单,setInterval 之后插入一些节点,把柱子的背景图填入即可。难点在于碰撞检测。

传统方式我们一般要计算两个物件之间的距离,然后利用一些近似计算来模拟碰撞检测。但是在现代化浏览器里,我们有 IntersectionObserver,它可以用来检测 DOM 节点的显示状态,返回的信息里甚至包括被遮挡的比例,目测十分适合用来作碰撞检测。

这游戏里会出现变化的情况只有两种:

  1. 被思否猫碰到
  2. 随着滚屏消失在屏幕左侧

已知我们会把思否猫固定在屏幕正中,所以根据当前滚屏的位置,可以很容易判定柱子是否是被撞到。于是就得到以下代码:

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      const { target, isIntersecting, intersectionRatio } = entry;
      if (intersectionRatio < 0.9) {
        // 目标元素被遮挡超过 10% 就报告
        const event = new CustomEvent('hit', {
          bubbles: true, // 允许事件冒泡,就可以在父元素统一捕获事件,节省绑定的代码
          detail: {
            ratio: intersectionRatio,
          },
        });
        target.dispatchEvent(event);
      }
    });
  },
  {
    threshold: 0.05,
  }
);

function addPipe() {
  const pipe = document.createElement("div");
  pipe.className = ["pipe", 'pipe-' + position].join(' ');
  pipe.style.left = 150 + 30 * pipeCount + 'vw';
  stageBackground.value.appendChild(pipe);

  // 侦测显示状态
  observer.observe(pipe);
}

最后配合事件侦听器即可:

<div class="stage-bg" @hit="onHit">

5. 添加 OpenCV 控制思否猫

OpenCV 部分的代码是直接从别人的项目里抄来的,也就不详细解释了。简单说下流程吧:

  1. 使用 navigator.mediaDevices.getUserMedia({video: resolution, audio: false}) 申请使用用户的摄像头。用户许可之后,可以捕获到视频流 stream
  2. 把视频流赋给 <video>,播放,让用户能够看到自己
  3. 创建一个 <canvas>,用来从 <video> 捕获画面
  4. 将画面信息交给 opencv 进行识别,如果找到面孔,则传回面孔中间的位置,作为思否猫的目标高度

详细代码可以阅读:https://github.com/meathill/s...

6. 提前总结

装不下去了,开始摆烂 😭😭。以上,就是我目前完成的部分代码。咳咳,非常惭愧,我没能把整个游戏做到能玩。

首先这个游戏的开发工作量比我想象的要大;其次我这周比想象中要忙。最后游戏并没有完成,我只是把主要门槛都迈过去了。

我把代码放在 GitHub 上,感兴趣的同学可以看看:飞翔的思否猫 GitHub 仓库,欢迎大家贡献代码。我也会抽时间(下周以后吧),继续把它完成,所以一样欢迎关注。

打包后的代码放在 我的服务器上,目前可以在桌面浏览器开启响应式后尝试。(你看这条件……


7.(未完待续)

本文参与了1024程序员节,欢迎正在阅读的你也加入。

翟路佳
前端为主,包括但不限于 HTML,CSS,JavaScript,移动端,微信开发。偶尔会有 PHP 和服务器相关内容。 ...

爱编程,爱旅游,爱吐槽。

18.2k 声望
5.7k 粉丝
0 条评论
推荐阅读
一起努力吧,2023
2019 年年底的时候,一毛不拔大师说,未来你们会怀念 2019 年,因为这会是未来十年中最好的一年。从后往前看,我觉得他说的有道理;同时,我也希望 2022 年,会是前后十年里,最惨的一年,实在实在很难顶。

Meathill11阅读 1.7k评论 2

封面图
Vue3源码-整体渲染流程浅析
本文基于Vue 3.2.30版本源码进行分析为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js...

边城3阅读 436

感受 Vue3 的魔法力量
• setup语法糖&lt;script setup lang=&quot;ts&quot;&gt;摆脱了书写声明式的代码,用起来很流畅,提升不少效率

京东云开发者3阅读 623

封面图
基于vue3.x+qiankun微前端项目搭建

前端扫地僧阅读 2.8k

vue3公司自用项目实战入门(vue3+router4+antdv+pinia实现)
一.为什么学typescriptvar三大框架都是用ts写的,做个类比,如果你不知道阿拉伯数字和加减法,怎么解应用题?同理不会ts如何使用vue开发工作项目?二.开发环境准备1.安装node,[链接],下载安装,一路默认下一步 ,...

彬哥头发多2阅读 606评论 1

封面图
【vue3 + ts + echarts】封装一个基础echarts组件
话不多说,直接上代码吧新增一个MyCharts.vue文件,文件内容如下 {代码...} 页面下引用组件及使用方法 {代码...} 预览效果如下附echarts安装指令 {代码...}

前端小高阅读 1.5k

封面图
Python写个“点球大战”小游戏
看过我Python入门教程的朋友应该会看到其中有提到一个点球小游戏的作业。在世界杯决赛即将到来之际,我们再来回顾一下这个小游戏。对于刚刚学习编程不久的同学,这是个不错的练手习题,可以尝试自己写一写。

Crossin先生阅读 1.4k

封面图

爱编程,爱旅游,爱吐槽。

18.2k 声望
5.7k 粉丝
宣传栏