Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

宠物项目总结 (vue/egret) #11

Open
yeojongki opened this issue Jun 26, 2019 · 0 comments
Open

宠物项目总结 (vue/egret) #11

yeojongki opened this issue Jun 26, 2019 · 0 comments

Comments

@yeojongki
Copy link
Owner

yeojongki commented Jun 26, 2019

宠物项目总结 (vue/egret)

项目初期基于Vue 做了一版原型版本的,主要实现功能,ui也是临时资源。

后期用 egret 重构一版可以上线的,用它是因为 egret可以发布多平台,并且支持龙骨为宠物做一些骨骼动画

下面是项目开发过程中的一些思考以及解决方法

1. websocket 封装消息的接收以及发送

为什么封装?因为使用websocket 发/收消息时,要进一步处理:

// app.vue
const ws = new WebSocket(url)

// 发送消息时需要将数据转为字符串格式
const someMsg = {foo: 'bar'}
ws.send(JSON.stringify(someMsg))

// 接收消息时需要转成JSON数据
ws.onmessage = function onMessage(e) {
  const recieveMsg = JSON.parse(e.data)
}

1.1 最初的做法

websocket 收到消息时,存入 vuex

ws.onmessage = function onMessage(e) {
  // 将socket接收到的值存进vuex
  this.$store.commit(types.SET_SOCKET_MSG, JSON.parse(e.data))
}

然后组件中 watch 这条消息,就可以拿到对应的信息

// login.vue 
watch: {
  socketMsg(msg) {
    // 判断消息属于哪一条命令
    if (msg.cmd === 'login') {
      // do something...
    }
    if(msg.cmd === 'foo') {
      // do something...
    }
  }
}

可以看到这种方式的局限性,如果有 N 条命令的话,就是整屏的 if (msg.cmd === 'xxx')

1.2 采用发布订阅模式改进

vue 版本:

// app.vue
// 一个轻量级发布订阅库(可以自己写一个)
import EventEmitter from 'mitt'

class Socket {
  constructor() {
    // websocket 实例
    this._socket = null

    // 事件
    this.emmiter = new EventEmitter()
    this.on = this.emmiter.on
    this.emit = this.emmiter.emit
    this.off = this.emmiter.off
  }

  /**
   * 发送消息
   * @param {object} msg 消息内容
   */
  send(msg) {
    if (this._socket) {
      if (msg === 1) {
        this._socket.send(1)
      } else {
        this._socket.send(JSON.stringify(msg))
      }
    }
  }

  /**
   * websocket收到的数据
   * @param {MessageEvent} e
   */
  onmessage(e) {
    let message = JSON.parse(e.data)
    // fire an event
    this.emit(cmd, { data, msg })
  }
}

export default Socket

在组件中使用:

// login.vue
export default{
  created() {
    Socket.Instance.on('login', this.onLogin)
	Socket.Instance.on('foo', this.onFoo)
  },
  destroyed() {
    Socket.Instance.off('login', this.onLogin)
    Socket.Instance.on('foo', this.onFoo)
  },
  methods:{
	onLogin({ data }) {
  	  // do something...
    },
    onFoo({ data }) {
  	  // do something...
    }
  }
}

1.3 链式调用

Socketon 方法改成可以进行链式调用: 在 on 方法中 return this即可

Socket.Instance
  .on('login', this.onLogin)
  .on('foo', this.onFoo)
  .on('bar', this.onBar)

1.4 场景移除的时候自动移除事件监听

egret 版本:每个页面都继承 BaseView,在页面的基类 Baseview

// 这里实现 IViewName 接口 保证每个页面都有一个 name
class BaseView extends BaseScene implements IViewName {
  public readonly name: string
    
  /**
   * @param name 场景名
   */
  constructor(name) {
    super()
    this.name = name
    this.addEventListener(eui.UIEvent.CREATION_COMPLETE, this.onComplete, this)
    // 监听场景隐藏事件
    SceneManager.getInstance()
	  .on(`${SceneManager.VIEW_SCENE_HIDE + this.name}`, this.onViewHide, this)
  }

	
  /**
   * 页面隐藏事件
   */
  protected onViewHide(): void {
    // 移除当前页面的webSocket事件监听
    for (let i in SocketManager.handler) {
      let viewName = SocketManager.handler[i].target.name
      if (viewName === this.name) {
        delete SocketManager.handler[i]
      }
    }
  }
}

interface IViewName {
  name: string
}

2. 如何优雅地为所有按钮添加点击播放音效

需求是点击按钮都要播放一段音效,如何给所有按钮都加上这个效果?

vue 版本想到了三个方案:

  • 监听 windowclick 事件, 在 e.target 中判断是否点击了按钮
  • 建立一个 播放音效按钮组件,然后使用按钮的时候都用这个组件
  • 利用 vue 的指令: directive,给每个按钮加上音效指令

当时是因为还没用过指令,于是就用了这个方案。写完后在编辑器搜索 button ,加上指令 v-music

// directives.js
export default {
  music: {
    bind: function (el) {
      el.addEventListener('click', function (e) {
        e.stopPropagation()
        SoundManager.play('ui') // 播放音乐
      })
    }
  }
}

// usage
<button v-music @click="handleLogin"></button>

egret版本:创建基类 ui.Button 继承 eui.Button ,创建按钮的时候选择ui.Button即可

module ui {
  export class Button extends eui.Button {
  	constructor() {
      super()
      this.addEventListener(egret.TouchEvent.TOUCH_TAP, this.onTap, this)
    }
      
    protected onTap(event: egret.TouchEvent) {
      // 播放ui音效
      SoundManager.play(SoundTypes.ui)
    }
  }
}

3. nodejs 解决 egret 打包小程序的问题

3.1 文件中的变量覆盖问题

原因:打包后会有三个文件,这里方便说明假设为 index.js, a.jsb.js

// index.js
require('a.js')
require('b.js')
// a.js
var ui= {}
ui['foo']='bar'
// b.js
var ui = {}

于是导致 ui这个对象为空的

如何解决?肯定是要把 b.js 中的 var ui = {} 改成 var ui = window.ui || {}

利用 egret 的插件机制,利用正则表达式替换文件的内容

// 核心内容
let content: string = this.buffer.toString()
const reg = /^(window\.(.*=\{\};))/gm
content = content.replace(reg, (str, $1) =>
	$1.replace(/^(window\.(.*))=(\{\};)/g, (s, c) => `${c}=${c}||{};`)
)
commandContext.createFile(FixCoverBugPlugin.replaceFileName, new Buffer(content))

3.2 自定义组件不会自动添加到全局变量中

原因:在 eui 中使用自定义组件,发布到微信小程序的 default.thm.js 报错提示找不到自定义组件,需要将自定义组件暴露到全局

解决方法:主要也是遍历需要打包的文件,读取到文件的内容,再写入

// 核心内容
private generateGlobalReferenceCode(): string {
    let referenceCode: string = ''
    const { srcDir } = this.options
    // 遍历代码
    srcDir.forEach(p => {
      let fullDirPath = path.join(path.resolve('./'), p)
      this.loopTsFiles(fullDirPath)
    })
    // 添加到全局变量中
    if (this.needAddToGlobalNameSpaces.length) {
      this.needAddToGlobalNameSpaces.forEach(namespace => {
        referenceCode += `window['${namespace}'] = ${namespace};`
      })
    }
    return referenceCode
  }

4. nodejs 加快开发效率

vue 版本:

需求:预加载项目的所有图片。分析图片组成:

  1. 服务器返回的图片,json格式,这个好办,直接拿到地址拼接就行了。

  2. 本地的图片,会由 webpack 输出最终的图片。 这个地址怎么获取呢?我们不可能每次打包后手动把这些图片的地址写好。于是通过编写 webpack 插件代替我们做这些事:

    • 读取打包后的所有文件,用正则表达式取出所有图片

    • 将读取出来的图片地址生成一个json文件

然后将这个 json 文件上传到服务器上,在代码中获取这个 json 的内容,然后合并服务器端的图片,最终加载服务器和本地的图片。

egret 版本:

  • 每次打包的时候,因为压缩合并代码浪费了大部分时间。那么我们可以在第一次打包后生成的一些库文件(egret.min.js / socket.min.js等)保存下来。第二次后就不进行压缩合并的部分,只打包修改的业务部分。这样就大大减少了打包时间。

    • 优化前时间

      • 16:42:30 -> 16:43:03 = 33s

      • 16:47:18 -> 16:47:53 = 35s

      • 16:56:37 -> 16:57:10 = 33s

    • 优化后时间

      • 16:39:42 -> 16:40:11 = 29s

      • 16:49:38 -> 16:50:01 = 23s

      • 16:57:50 -> 16:58:14 = 24s

    平均减少了 (33+35+33)/3 - (29+23+24)/3 = 8s
    效率提高了 8 / ((33+35+33)/3) *100 = 23.76%

  • 自动合并图集,基于 TextureMerger

  • 合并图集之后需要压缩图片,利用 imagemin 代替我们手动压缩

  • 删除多余的文件,这个项目合并图集之后会有多余的图片,打包后需要删除

  • 编写 task文件, 在 vscode中图形化界面操作打包多平台版本

5. 其他

  • vue中利用 sassmixins 创建序列帧

    @mixin spriteAnime($url, $name, $width, $length, $time, $count: infinite) {
      animation: ani_ + $name $time steps($length) $count;
      background: url('~@/assets/images/#{$url}.png') 0 0 no-repeat;
      background-size: cover;
      @keyframes #{ani_+$name} {
        100% {
          /* prettier-ignore*/
          background-position: -($width * $length)*1PX 0; // $length帧 这里单位为大写的`PX`,避免px-to-rem转换单位导致一帧图片大小的精度不正确
        }
      }
    }
    
    // usage
    .egg {
    	/* prettier-ignore */
      width: 135PX;
      /* prettier-ignore */
      height: 163PX;
      @include AnimalSpr('home/cat-birth-spr', 'birth', 135, 6, 0.8s, 3);
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant