VSCode 屏蔽 meta 文件

解决步骤:
从顶部菜单栏打开文件-> 首选项-> 设置(或者使用快捷键 Ctrl+,) 打开设置页面
在设置页面的输入框中输入 files:exclude
在设置页面查找 file: exclude.
点击添加模式,输入**/*.meta 即可

Cocos 的 node 与 component

今日开始入 Cocos 开发。
之前打开过 Unity 软件,一直不太明白 getChildgetComponent 到底有什么区别,万一有多个重名的组件怎么办呢?
做个 Demo 一步步分析,在 Canvas 中设置了一个 ProgressBar,名字也叫 ProgressBar,我想获取到这个 ProgressBar:

1
2
const bar = this.node.getComponent(ProgressBar);
console.log(bar); // output: null

通过断点发现,调用的 getComponent,获取到的是右侧属性面板中的“组件”,而不是“子节点”,在场景中的每个对象身上挂载的组件,都可以通过 getComponent 来获取,有点类似把所有的属性都封装成组件,由组件来实现对象的行为。
这其实就是 ecs 的东西了,所有的对象都是空的,至于对象表现成什么取决于它身上挂载的组件,比如对于所有的显示对象,身上都会挂载一个 Transform 的组件来表示位移、缩放等信息。
getChildByName 才是获取当前 node 下的子对象,也就是左边场景编辑器中的子节点对象。另外 getChildByName 是通过 for 循环来获取的,getChildByUuid 也是 for 循环。这也是为啥推荐“将子对象绑定”来实现快捷子对象访问了,即在组件上定义要绑定的其他组件,然后在场景编辑器中将被绑定的组件拖过去就可以了。示例:
在这个 demo 中,组件定义:

1
2
@property({ type: ProgressBar })
bar: ProgressBar = null;

在编辑器中:

这样组件定义中就可以通过 bar 直接访问到该 ProgressBar 了。

npm类库最佳实践

多年前我一直有一个困扰,那就是ts(js)的类库到底要怎么写?
习惯了古老语言AS3的人,会想当然的用命名空间来作为自己类库的标识,比如’com.adobe’之类,但是看了Laya和Egret的代码会发现也是一模一样的思路,那就是给自己设定一个全局的命名空间(Laya.Sprite or Egret.Sprite),虽然TypeScript本身就是支持命名空间的,但是实际上在一些标准类库中(指的是那些国外的开源软件,大家约定的一样),并不是这么写的。他们的类库用起来是这样导包的:

1
import { Scene } from 'THREE';

而用Laya和Egret的核心库,则是全局的不用导包,这到底有什么区别呢?(这里不讨论umd,module等js的历史,像裹脚布一样又臭又长)。
区别是Laya和Egret的核心代码,其实是挂载在window上的,所以可以直接全局的调用。而标准的js类库中并不是,是按需导入的。按需导入的好处就是可以在编译层面(比如利用webpack)来实现只编译自己需要的代码,而不是像Egret和Laya那样一股脑全包含进去。

标准库的写法里还有个区别,那就是导包的方式:

1
2
import { Scene } from 'THREE.JS';
import { Scene } from '@xxxlib/utils/scene';

看到区别没,第二种写法其实就是懒得在根目录写一个index.ts的导出文件,不写很正常,因为真的很繁琐,需要每新增一个ts文件就写一行。如果你的类库应用的场景比较少,可以这么做,但是如果你的类库用到好多个项目中,而且一旦改动涉及到的工程还蛮多的,建议还是采用第一个方式,我们目前的项目就遇到了这个问题,当架构师发现之前的一些包目录结构已经不满足现在的代码结构需要,需要重新组织目录的时候,各个项目都要跟着一起改动。如果用第一种方式就不会有这个问题。
所以谨慎、标准起见,写npm类库最好的姿势是(用typescript):

  • 在代码的每个文件夹下创建index.ts文件,并将当前目录需要导出的代码文件设置为导出。根目录的index.ts目录用来导出各个目录模块。
  • 使用ES6语法,不要namespace,不要module

简单的邮箱留言板

有个简单的需求,不需要展示用户的留言信息,只是收集用户反馈,不想消耗太多的资源在搭建服务器和后台页面上,网站很小,需求不多。那么用邮箱来管理留言是最简单快捷的方式了。
于是就有了这个项目,使用nextjs开发,支持一键部署到腾讯云,只需要提前准备好一个邮箱账号和腾讯云账号即可。
github地址:
https://github.com/daichangxin/mail_poster

截图:

Git工作流

啥是”工作流“?其实就是怎么使用一个东西,或者这个东西一般都是怎么被运用的。
说起Git的工作流就不得不提起非常出名的git-flow,这里有一个非常详细的说明:https://www.git-tower.com/learn/git/ebook/cn/command-line/advanced-topics/git-flow/

git-flow定义了完整的工作流程和规范,具体包含以下几点:

分支管理

常驻分支有两个:develop和master,为啥没有release分支?release在git-flow中是在发布release版本时创建的临时分支,创建后会自动打上版本号tag,随着发布成功,release分支会被立刻删除。而master分支永远保持着是软件的最终版本(也就是说永远是线上的最新发布版本)。

开发新功能

所有新功能都是基于develop分支开发,develop上的新功能会逐个逐个的增加,当开始发release版本的时候,就从develop分支创建release的临时分支,打上release的tag,然后将release合并到master中,再把release分支删除。

生产修复

当遇到生产(线上)问题,我们需要从生产上的分支开出新分支进行修复,所以会从master分支上开出分支,并修复问题,修复后执行git-flow指定,会自动将修复的分支合并到develop和master分支上。

这就是git-flow主要的流程,都包含在了git-flow的命令工具中,但是如果一个团队真的去应用git-flow会发现,这是一个非常繁琐复杂的流程,对于团队协作来说,多出的任何一个git指令,对于团队管理来说,就是成倍的教育成本。虽然我们可以选择招聘到对git非常擅长的人才,但是一个简洁的git工作流,可以减少开发人员在协作上遇到的冲突,也降低了使用git的上手难度。

这里介绍一个非常简单的git工作流:

分支管理

常驻分支有两个:master和release。这里的master相当于git-flow中的develop,而release相当于git-flow中的master,只是我们不会像git-flow中那样把release当做临时分支删除。

开发新功能

新功能基于master开发,并逐个合并进入master,待需要发版本时,将master合并到release

生产修复

从release开出分支修复问题,修复的问题直接合入release,通过git ci等集成工具,合入release的所有提交都会自动合并回master。

比较

看似并没有太大的改善,但是开发者在开发时不用关心自己当前是在什么分支流程上,git-flow过于严格,如果是生产修复(hotfix),那么就会自动发布release和合并到develop&master,但是对于日常开发来说,线上的问题可能不是那么紧急,也可能在修复的过程中发现必须要跟随着下个release版本发布才行,这就对hotfix有很大的灵活需求,我们完全可以在hotfix修复,合并到master即可。如果需要立刻上线,那么就把这个hotfix的分支cherrypick出来再合并到release上。

给Hexo添加百度统计

原理是在Hexo的header代码中,添加js统计脚本。

1、百度统计注册账号,获取统计脚本

https://tongji.baidu.com/

2、Hexo主题配置修改

修改主题配置文件:themes/{你使用的主题}/_config.yml,添加一个开关配置:

1
2
# baidu
baidu_analytics: true

3、新建模版

新建 themes/{你使用的主题}/layout/_partial/baidu_analytics.ejs

1
2
3
4
5
<% if (theme.baidu_analytics) { %>
<script type="text/javascript">
#申请的百度统计代码
</script>
<% } %>

然后编辑 themes/{你使用的主题}/layout/_partial/head.ejs 在最上方添加

1
<%- partial("baidu_analytics") %>

重新部署即可。

React与Layabox混合开发实现

1、图片加载的问题

使用import的地址,但是无法解决批量加载的问题,参考: https://blog.pawgame.com/2020/09/21/react-dynamic-assets-import

1
npm i -D @types/webpack-env

使用:

1
2
3
4
const assets = require.context('../../imgs', true);
Laya.URL.customFormat = (url: string) => {
return assets.keys().includes(`./${url}`) ? assets(`./${url}`).default : url;
};

2、canvas鼠标滑动问题

修改laya.core.js,注释部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
canvas.addEventListener("touchstart", function (e) {
if (MouseManager.enabled) {
// if (!MouseManager._isFirstTouch && !Input.isInputting)
// (e.cancelable) && (e.preventDefault());
_this.mouseDownTime = Browser.now();
_this.runEvent(e);
}
});
canvas.addEventListener("touchend", function (e) {
if (MouseManager.enabled) {
if (!MouseManager._isFirstTouch && !Input.isInputting)
(e.cancelable) && (e.preventDefault());
MouseManager._isFirstTouch = false;
_this.mouseDownTime = -Browser.now();
_this.runEvent(e);
}
else {
_this._curTouchID = NaN;
}
}, true);
canvas.addEventListener("touchmove", function (e) {
if (MouseManager.enabled) {
// (e.cancelable) && (e.preventDefault());
_this.runEvent(e);
}
}, true);

3、鼠标点击错位问题

修改laya.core.js,增加getBoundingClientRect检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
initEvent(e, nativeEvent = null) {
var _this = this;
_this._event._stoped = false;
_this._event.nativeEvent = nativeEvent || e;
_this._target = null;
const {left, top} = e.target.getBoundingClientRect();
this._point.setTo((e.pageX || e.clientX) - left, (e.pageY || e.clientY) - top);
if (this._stage._canvasTransform) {
this._stage._canvasTransform.invertTransformPoint(this._point);
_this.mouseX = this._point.x;
_this.mouseY = this._point.y;
}
_this._event.touchId = e.identifier || 0;
this._tTouchID = _this._event.touchId;
var evt;
evt = TouchManager.I._event;
evt._stoped = false;
evt.nativeEvent = _this._event.nativeEvent;
evt.touchId = _this._event.touchId;
}

React动态导入资源

React在导入资源的时候,只能指定资源的路径,如果要实现动态的路径,比如本地的任务icon列表,需要读取icon的地址是:

1
<img alt="" src={`../assets/images/taskIcons/task_${taskID}.png`}></img>

这样的写法会在webpack打包后,变成固定的字符串,而不会被webpack的资源hash处理,这就导致编译后资源地址读取错误。
解决的办法是利用webpack的require.context,原理是在编译阶段读取整个目录的文件,并将路径嵌套在代码中。
首先安装声明

1
npm install @types/webpack-env -D

指定require资源目录:

1
const assets = require.context('../assets');

提供一个动态获取资源地址的接口:

1
2
3
4
export const requirePath = (assets: __WebpackModuleApi.RequireContext, name: string) => {
const key = `./${name}`;
return assets.keys().includes(key) ? assets(key).default : name;
};

使用:

1
<img alt="" src={requirePath(`images/taskIcons/task_${taskID}.png`)}></img>

注意,在使用require.context时参数必须是字符串值,不可以用变量,因为是在编译阶段生成的路径映射,使用变量是无法生成的。

使用Github Action自动发布NPM包

准备NPM的部署token

官网 https://www.npmjs.com/ 注册账号,然后在Auth Tokens下创建Access Tokens,这个token是给Gitlab Action使用来实现NPM包发布用的。

Github绑定Access Token

个人用户:在Github中,打开Settings->Developer settings->Personal access tokens->Generate new token,名字将来作为env的一个变量可以读取的到。

组织账户:打开Settings->Secrets->New secret

部署脚本
在仓库根目录下,创建目录.github/workflows,然后在这个目录下创建配置文件:npm-publish.yml,内容如下:

使用Docker安装Gitlab

拉取镜像

1
docker pull gitlab/gitlab-ce:latest

创建容器脚本(docker_gitlab_run.sh

1
2
3
4
5
6
7
8
9
10
docker container run \
-d \
-p 6100:80 \
-it \
--name gitlab \
--restart always \
--volume ~/data/gitlab:/etc/gitlab \
--volume ~/data/log/gitlab:/var/log/gitlab \
--volume ~/data/opt/gitlab:/var/opt/gitlab \
gitlab/gitlab-ce:latest

这里对外端口为6100,查看下docker启动的状态:

1
docker ps -a

等启动完成,gitlab就创建好了。