Vue结合SVG开发一款可爱风射击游戏『ネコ🐱メザシ🐟アタック🌟』

0x01 前言

在日站看到这么一篇有点意思的帖子,在征得原作者的同意后进行翻译转载。说实话,日本的IT软件氛围远不如国内,但是与日本其它行业一样,日本总是在走一条与众不同的路,偶尔也能给人惊喜,希望这篇文章也能给您以启发。

作者许可证

0x02 成品效果


在线体验
源码(github)
这是一款简单的触屏射击游戏。操纵着角色边进行跳跃,边发射鱼干,击中靠近的猫即可得分(译者注:其实就是给猫喂鱼啦)。

特点
  • 不使用任何动画或游戏开发框架,单纯的使用vue来构建程序
  • 所有的图像都以SVG制作并内嵌在JS文件中(加上vue的本体也不到100KB)
  • iphone6也能顺畅的游玩

0x03 使用Vue进行游戏开发有意义吗?

Vue并不适合大型的游戏开发


就结论而言,使用vue开发复杂的动作游戏是一件吃力不讨好的事。
大量的vue组件进行响应式的刷新是相当耗性能的。目前vue在类和接口的继承及扩展方面并不容易,对于角色等高度相似的组件进行设计容易变得复杂失去控制。但是,随着Vue.js 3.0版本的不断逼近,这一现状也许会在未来得到改变。

开发迷你游戏游戏具有优势(大概)


一方面,只要是小游戏,即便是动作游戏,使用vue来开发也具有一定的优势。

  • 极其轻量

※22KB的app.js包含了所有的图像
这回vue本体加上另外两个用于碰撞检测和声音播放的库,即便再加上图像(svg)也不满100KB,根本就无需『游戏加载中。。。』的画面来过渡。

  • 普通的web知识可以轻松利用起来
    和一般使用了canvas/webgl的框架不同,在vue的世界里,不论是游戏角色还是背景都是用普通的HTML和CSS来实现的。换句话说,我们可以使用自己熟悉的技术来解决诸如响应性,Retina支持等麻烦的问题。这对于非游戏专业的工程师和设计师来说无异是非常方便的。
  • 可以进行声明式的游戏开发
    使用vue进行开发的时候,我们完全可以用【声明式】的方法进行开发。
    作为示例,以下是此次游戏开发的主要框架模版:
    • GameStage.vue
<template>
  <div class="stage-root">

    <cat v-for="cat in cats" ref="cat" :key="`cat-${cat.id}`"
      :x="cat.pos.x" :y="cat.pos.y" :s="cat.pos.s"
      @hitMezashi="(mezashiComp) => onCatHitMezashi(cat, mezashiComp)"
      @exit="removeCat(cat)"
    ></cat>

    <mezashi v-for="mezashi in mezashis" ref="mezashi" :key="`mezashi-${mezashi.id}`"
      :x="mezashi.pos.x" :y="mezashi.pos.y" :s="mezashi.pos.s"
      @hitCat="(catComp) => onMezashiHitCat(mezashi, catComp)"
    ></mezashi>

    <player ref="player"
      :x="playerPos.x" :y="playerPos.y" :s="playerPos.s"
      @hitCat="onPlayerHitCat"
    ></player>

  </div>
</template>

对vue稍有了解的话,我们就明白上述代码声明了:

  • stage组件里包含了player、mezashi(鱼干)、cat三个组件
  • player只有一个,mezashi和cat用循环指令通过mezashis和cats属性创建了多个
  • player的hitCat和cat的hitMezashi用于角色之间的碰撞事件回调

当然了,这取决于游戏类型和规模。

0x04 要点解说


下面我将简要介绍下开发这款游戏的具体要点。

SVG图像的制作和读取

这回的SVG我使用iPad应用Vectornator来制作。
这款应用简直就是iPad上便携版的illustrator,重要的它完全免费!天哪!
制作流程如下:用插画软件Procreate绘制草图→Vectornator进行修图并导出成SVG→最后用illustrator分解成各个部分


然后用vue来读取svg,使用的组件是svg-to-vue-component
使用此组件的优点是让你能够以vue组件而非url的方式使用SVG文件(它会在build的时候将SVG文件自动转换为Vue的组件)。由于是在build阶段进行转换的,所以你需要在vue.config.js里添加一些额外的配置(没有此文件的话请手动生成)。之后就可以和使用普通组件一般方便地用import关键字导入使用,就像下面这样:

<template>
  <mezashi-svg></mezashi-svg> <!-- 渲染导入的SVG -->
</template>
<script>
  import MezashiSvg from '@/assets/Mezashi.svg' // ※后缀一定要写
  export default {
    components: { MezashiSvg }
  }
</script>

使用之前制作(作者在另外一篇博客中介绍的)的ECont容器组件进行包裹,以此来控制图像的位置和角度。为了方便之后的碰撞检测,这边要事先设置好元素的大小和中心点。(这一点倒是有点麻烦啊)

<template>
  <e-cont :x="x - 66" :y="y - 16" :w="132" :h="32" :r="r" :s="s" :ox="66" :oy="16">
    <mezashi-svg></mezashi-svg>
  </e-cont>
</template>
<script>
import ECont from '@/components/core/ECont'
import MezashiSvg from '@/assets/Mezashi.svg'
export default {
  name: 'Mazashi',
  components: { ECont, MezashiSvg },
  props: {
    x: { type: [Number, String], default: 0 },
    y: { type: [Number, String], default: 0 },
    r: { type: [Number, String], default: 0 },
    s: { type: [Number, String], default: 1 }
  }
}
</script>

这样就定义好了mezashi(鱼干)组件,使用的时候一行就可以搞定。

  • 使用方.vue
<mezashi x="100" y="200" r="30"></mezashi>

接下来依样画葫芦定义好cat和player的组件。

Tween动画的组装


现在已经可以随意将角色放置在任何位置了,接下来我们来考虑动画的部分。

Tween类实现

为了更容易地实现具有高表现力的动画,我将实现Tween动画的功能。
Tween类的实现请参照/src/core/Tween.js。基本上就是在构造函数中指明目标对象,然后指定to(变化后的数值, 时间, easing函数)函数。此外,并无其他功能和公开方法。
由于许多库都已轻松地实现了Tween动画的功能,你也可以使用自己熟悉的库。我希望实现起来尽可能的轻量级,Createjs中的Tween.js那样的方法链使用起来有点麻烦,因此自己实现的了一个返回Promise对象的Tween类。

  • 使用CreateJS
createjs.Tween.get(target)
  .to({ x: 100, y: 100 }, 1000)
  .to({ x: 200, y: 50 }, 500)
  • 使用此次实现的Tween
const tw = new Tween(target)
await tw.to({ x: 100, y: 100 }, 1000)
await tw.to({ x: 200, y: 50 }, 500)

这样的话,不需要在Tween中实现特殊的功能,使用普通的js语句就能够随意地控制任何关键帧。

// 一边上下摇晃一边向左移动直到离开画面
const tw = new Tween(this.$data)
while (this.x > 100) {
  await tw.to({ x: this.x - 100, y: this.y + (Math.random() - 0.5) * 100 }, 1000)
}

碰撞检测


如果你决定用vue来制作一款动作游戏,恐怕碰到的第一个难题就是碰撞检测。对于面向游戏的动画框架来说,这个功能应该算是一个标配。但是在vue中就得靠我们自己实现了。
这回实现的碰撞检查实现类:/src/core/CollisionDetector.js
为了实现碰撞检测,我们需要准确地获取各个元素的坐标。通常HTMLElement.offsetTop的值并不考虑CSS的transform属性引起的变换。考虑到这种情况,我们利用Element.getBoundingClientRect()来获取元素的真实位置。

// this._comps数组存储着所有的vue组件,并以此取得真实矩形区域
const boxes = this._comps.map(c => {
  const el = c.$el
  if (!el) { return null }
  const box = el.getBoundingClientRect()
  return [ box.x, box.y, box.x + box.width, box.y + box.height ]
})

这个方法不受HTML的结构和滚动状态的影响,纯粹地获取元素在视口(ViewPort)中的外矩形位置。虽然不常用到,但是能够在包括IE在内的主流浏览器上运行
通过这种方法,使用定时器定期地获取Player・Cat・Mazashi的位置,并检查矩形的交集(碰撞)部分。由于此次最多只涉及几十个物体,因此如果简单地通过循环判定也应该能够平稳流畅地运行。但是我们还是决定使用主流的四叉树算法,为此引入了专门的库box-intersect

// 判定矩形是否冲突(重叠)
const result = boxIntersect(boxes).map(indexes => {
  // 由于boxIntersect返回的是冲突矩形的索引,这里转换成对应的组件
  const [i1, i2] = indexes
  return [this._comps[i1], this._comps[i2]] 
})

这样就能够获取到所有发生碰撞冲突的组件的组合。
最后,与上一次的判定结果进行比较,获取到此次新增的发生碰撞重叠的组件,并调用相应的collide方法。

const diffedRes = diffNewResults(this._lastResult, result) // 获取不同的部分,具体实现请看此文件的开头部分
diffedRes.forEach(pare => {
  const [c1, c2] = pare
  const c1Name = upperFirst(c1.$options._componentTag)
  const c2Name = upperFirst(c2.$options._componentTag)
  if (c1.collide) {
    c1.collide(c2, c2Name, 0)
  }
  if (c2.collide) {
    c2.collide(c1, c1Name, 1)
  }
})

顺便说下,被调用collide方法的组件会通过$emit()触发含有与之发生碰撞冲突对象名称的事件(如cat与mezashi发生了碰撞,会触发cat组件的hitMezashi事件及mezashi组件的hitCat事件),就像下面一般:

methods: {
  /* called by CollisionDetector */
  collide (targetComp, name) {
    this.$emit(`hit${name}`, targetComp)
  }
}

如此就和开头的<mezashi @hitCat="...">事件处理器部分连接起来了。

导入和播放声音


下一个难关就是声音的播放。如果是第一次接触的话,可能会有很多坑,如果了解的话就很简单了。
总的来说,你应该记住:

  • 播放声音大致有以下两种方法:Audio.play()或者WebAudioAPI相关的方法

这边将使用WebAudioAPI,但是呢,完全自己来写是一件非常麻烦的事情,还是偷点懒引入现成的第三方库吧,我认为audio-play就非常好,同时易于使用。

import loadSnd from 'audio-loader'
import playSnd from 'audio-play'

const snds = {}
const load = name => {
  loadSnd(`/snd/${name}.mp3`).then(a => { snds[name] = a })
}
load('btn')
load('catch')
load('jump')
load('gameover')
load('shot')

const playSound = name => {
  const audio = snds[name]
  if (!audio) {
    console.warn(`No sound for: ${name}`)
    return
  }
  playSnd(audio)
}

export default playSound

代码非常的短,这边就全部贴出来了。系统启动的时候调用load加载读取相关的音频文件,然后在需要的时候调用playSound进行播放即可。这次需要读取的文件并不多,因此上述代码足够满足我们的需求。

部署到Firebase


这次机会难得,总想用firebase做点什么,但是鉴于时间不多,最后只是用了托管的功能。
1.在Firebase控制台新建项目
2.使用firebase init命令进行项目的初始化,这边配置仅使用hosting功能
3.从Firebase控制台中启用hosting
4.firebase deploy进行部署
        这一部分其实很简单,甚至不写出来也没什么影响,但是为了体现出firebase的简单便捷,我还是保留了这一节。

0x05 性能评价


从结果来看是非常的快速的。


0x06 结语


Vue + SVG + Firebase作为超小型游戏的开发堆栈,你~值得拥有!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 前几日听到一句生猛与激励并存,可怕与尴尬同在,最无奈也无解的话:“90后,你的中年危机已经杀到”。这令我很受触动。...
    王钰峰阅读 4,383评论 1 22
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    柴东啊阅读 15,853评论 2 140
  • 简说Vue (组件库) https://github.com/ElemeFE/element" 饿了么出品的VUE...
    Estrus丶阅读 1,647评论 0 1
  • 时下正是杏子黄熟时节,看着满树橙黄的杏子,在绿叶的衬托下显得更加鲜艳剔透,不经意望一眼,忍不住流口水的记忆,...
    沐源工作室阅读 936评论 11 9
  • 2018立春过后是农历戊戌年。按中医五行来说,今年火运过旺。心属火,心脏不好的人要格外注意。可以看一下手腕血管,如...
    唔邪阅读 357评论 0 1