1
0
mirror of synced 2025-11-06 04:30:39 +08:00
2017-06-12 22:31:14 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 17:37:35 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:24:57 +08:00
2017-06-12 22:31:14 +08:00

用Vue、Vuex、Immutable做俄罗斯方块


本项目灵感来源于 React 版的俄罗斯方块,由于对其实现原理较感兴趣,而且相比于 React 更喜欢 Vue, 于是把 React 版的重构为了 Vue 版的,大致思路是把组件当成一个个函数,保证一个输入(props)能得到一个确定的输出(view),然后对不同方法也是做同样处理,对于 Redux 使用 Vuex 精简化

戳:http://binaryify.github.io/vue-tetris/ 玩一玩!


效果预览

效果预览

正常速度的录制,体验流畅。

响应式

响应式

不仅指屏幕的自适应,而是在PC使用键盘、在手机使用手指的响应式操作

手机

数据持久化

数据持久化

玩单机游戏最怕什么?断电。通过订阅 store.subscribe将state储存在localStorage精确记录所有状态。网页关了刷新了、程序崩溃了、手机没电了重新打开连接都可以继续。

Vuex 状态预览(Vue DevTools extension

Vuex状态预览

Vuex 设计管理了所有应存的状态,这是上面持久化的保证。


游戏框架使用的是 Vue + Vuex其中再加入了 Immutable,确保性能和数据可靠性

1、什么是 Immutable

Immutable 是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

初识:

让我们看下面一段代码:

function keyLog(touchFn) {
  let data = { key: 'value' };
  f(data);
  console.log(data.key); // 猜猜会打印什么?
}

不查看f不知道它对 data 做了什么,无法确认会打印什么。但如果 data 是 Immutable你可以确定打印的是 value

function keyLog(touchFn) {
  let data = Immutable.Map({ key: 'value' });
  f(data);
  console.log(data.get('key'));  // value
}

JavaScript 中的ObjectArray等使用的是引用赋值,新的对象简单的引用了原始对象,改变新也将影响旧的:

foo = {a: 1};  bar = foo;  bar.a = 2;
foo.a // 2

虽然这样做可以节约内存,但当应用复杂后,造成了状态不可控,是很大的隐患,节约的内存优点变得得不偿失。

Immutable则不一样相应的

foo = Immutable.Map({ a: 1 });  bar = foo.set('a', 2);
foo.get('a') // 1

关于 “===”:

我们知道对于ObjectArray===比较,是对引用地址的比较而不是“值比较”,如:

{a:1, b:2, c:3} === {a:1, b:2, c:3}; // false
[1, 2, [3, 4]] === [1, 2, [3, 4]]; // false

对于上面只能采用 deepCopydeepCompare来遍历比较,不仅麻烦且好性能。

我们感受来一下Immutable的做法!

map1 = Immutable.Map({a:1, b:2, c:3});
map2 = Immutable.Map({a:1, b:2, c:3});
Immutable.is(map1, map2); // true

// List1 = Immutable.List([1, 2, Immutable.List[3, 4]]);
List1 = Immutable.fromJS([1, 2, [3, 4]]);
List2 = Immutable.fromJS([1, 2, [3, 4]]);
Immutable.is(List1, List2); // true

Immutable学习资料

2、Web Audio Api

游戏里有很多不同的音效,而实际上只引用了一个音效文件:/build/music.mp3。借助Web Audio Api能够以毫秒级精确、高频率的播放音效,这是<audio>标签所做不到的。在游戏进行中按住方向键移动方块,便可以听到高频率的音效。

网页音效进阶

WAA 是一套全新的相对独立的接口系统对音频文件拥有更高的处理权限以及更专业的内置音频效果是W3C的推荐接口能专业处理“音速、音量、环境、音色可视化、高频、音向”等需求下图介绍了WAA的使用流程。

流程

其中Source代表一个音频源Destination代表最终的输出多个Source合成出了Destination。 源代码:/src/unit/music.js 实现了ajax加载mp3并转为WAA控制播放的过程。

WAA 在各个浏览器的最新2个版本下的支持情况CanIUse

浏览器兼容

可以看到IE阵营与大部分安卓机不能使用其他ok。

Web Audio Api 学习资料:


3、游戏在体验上的优化

  • 技术:
    • 按下方向键水平移动和竖直移动的触发频率是不同的,游戏可以定义触发频率,代替原生的事件频率,源代码:/src/unit/event.js
    • 左右移动可以 delay 掉落的速度,但在撞墙移动的时候 delay 的稍小在速度为6级时 通过delay 会保证在一行内水平完整移动一次;
    • 对按钮同时注册touchstartmousedown事件,以供响应式游戏。当touchstart发生时,不会触发mousedown,而当mousedown发生时,由于鼠标移开事件元素可以不触发mouseup,将同时监听mouseout 模拟 mouseup。源代码:/src/components/keyboard/index.js
    • 监听了 visibilitychange 事件,当页面被隐藏\切换的时候,游戏将不会进行,切换回来将继续,这个focus状态也被写进了 Vuex 中。所以当用手机玩来电话游戏进度将保存PC开着游戏干别的也不会听到gameover这有点像 ios 应用的切换。
    • 任意时刻刷新网页,(比如消除方块时、游戏结束时)也能还原当前状态;
    • 游戏中唯一用到的图片是image其他都是CSS
    • 游戏兼容 Chrome、Firefox、IE9+、Edge等
  • 玩法:
    • 可以在游戏未开始时制定初始的棋盘(十个级别)和速度(六个级别);
    • 一次消除1行得100分、2行得300分、3行得700分、4行得1500分
    • 方块掉落速度会随着消除的行数增加每20行增加一个级别

4、开发中的经验梳理

Vue 版本和 React 版本核心代码基本相同,但在编写组件的时候遇到了几个问题,比如:

  1. React 版的 store 使用了 immutable 结构的数据,vuex 上的 store 如果使用了 immutable 结构,不利用监听数据变化,故把store 的数据全部使用了普通的数据,在需要这些数据的地方通过 immutable 提供的 fromJS 转换,在需要普通数据的地方再通过 immutable 的 toJS 转换成普通数据

  2. Vue 没有 React 的componentWillReceiveProps 的生命周期,我的解决方法是使用 watch 配合 deep:true 来监听 props 的变化,如:

watch: {
  $props: {
    deep: true,
    handler(nextProps) {
      //xxx
    }
  }
}
  1. matrix 组件 的功能逻辑较复杂,使用 template 模版来渲染组件已经不合适了,通过自定义 render 方法再手动触发很繁琐,我的解决方法是通过 Vue 的 jsx 转换插件babel-plugin-transform-vue-jsx来使用 jsx 语法对页面进行渲染,当 props 或 state 变化了自动触发 render 方法,另外要注意的是 vue 的 jsx 和 React 的 jsx 书写上有一点的差异

5、架构差异

Redux 的数据流向是 把 store 的状态转化为 props 注入到 根组件,根组件再把这些 props 传入不同组件,当 store 的状态变化,根组件会重新 render, 更新子组件上的 props,子组件再 根据新 props重新 render 引用知乎一个答友的回答https://www.zhihu.com/question/47686258来说就是:

单例store的数据在react中可以通过view组件的属性props不断由父模块**“单向”**传递给子模块形成一个树状分流结构。如果我们把redux比作整个应用的“心肺” redux的flux功能像心脏reducer功能像肺部毛细血管那么这个过程可以比作心脏store将氧分子数据通过动脉毛细血管props送到各个器官组织view组件末端的view组件又可以通过flux机制将携带交互意图信息的action反馈给store。这个过程有点像将携带代谢产物的“红细胞”action通过静脉毛细血管又泵回心脏storeaction流回到store以后action以参数的形式又被分流到各个具体的reducer组件中这些reducer同样构成一个树状的hierarchy。这个过程像静脉血中的红细胞action被运输到肺部毛细血管reducer组件接收到action后各个child reducer以返回值的形式将最新的state返回给parent reducer最终确保整个单例store的所有数据是最新的。这个过程可以比作肺部毛细血管的血液充氧后又被重新泵回了心脏回到步骤1

而 vuex 的思路则不同,任何组件都随时可以通过 this.$store.state.xxx 获取 store 上的数据,更自由,只要 store 上的数据变了,组件都会自动重新渲染

6、开发

安装

npm install

运行

npm run dev

浏览自动打开 localhost:8080

多语言

i18n.json 配置多语言环境,使用"lan"参数匹配语言如:https://Binaryify.github.io/vue-tetris/?lan=en

打包编译

npm run build

dist 文件夹下生成结果。

Description
Use Vue, Vuex to code Tetris.使用 Vue, Vuex 做俄罗斯方块
https://binaryify.github.io/vue-tetris/ Readme MIT 1.7 MiB
Languages
JavaScript 71.8%
Less 14.3%
Vue 9.5%
CSS 3.4%
HTML 1%