Skip to content

Commit 0f4c468

Browse files
Update index.md
1 parent 48b2fdf commit 0f4c468

File tree

1 file changed

+51
-21
lines changed

1 file changed

+51
-21
lines changed

blog/posts/rfc2/index.md

+51-21
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ tags: ['RFC']
99

1010
第二次提交版本,2024年1月24日,更新了有关切换类名的指令、模板数据结构和具体的 CSS 替换实现方案的内容。
1111

12+
第三次提交版本,2024年1月31日,更新了有关应用 CSS IN JS 实现样式替换的方案。
13+
1214
## 总体方案
1315

1416
WebGAL 将使用模板思想进行 UI 自定义。在 WebGAL Terre 编辑器中,额外添加一个模板编辑器。在创建 WebGAL 游戏时和创建 WebGAL 游戏后,可以选择要使用的模板。
@@ -25,7 +27,7 @@ templateName/
2527
├── assets/
2628
└── UI/
2729
└── Title/
28-
└──title.css
30+
└──title.scss
2931
```
3032

3133
其中,`assets` 是资源目录。
@@ -42,16 +44,16 @@ templateName/
4244

4345
### 模板配置文件定义
4446

45-
模板配置文件将覆盖 WebGAL 默认样式的一些类,通过动态创建 CSS 来实现。
47+
模板配置文件将覆盖 WebGAL 默认样式的一些类,通过使用 CSS IN JS 动态应用 CSS 来实现。
4648

4749
实现原理大致表示如下:
4850

4951
```tsx
50-
// 第一个参数是在模板中的文件路径,第二个参数是 css 文件中的类名到要覆盖的 css module 类名的映射。这个映射用于字符串替换以适配经 css module 处理的类名
51-
useApplyStyle('UI/Title/title.css',{"Title_button":styles.Title_button});
52+
// 参数是在模板中的文件路径
53+
const applyStyle = useApplyStyle('UI/Title/title.scss');
5254
// ......
5355
<div
54-
className={styles.Title_button}
56+
className={applyStyle('Title_button',styles.Title_button)} // 第一个参数是要在 templateStyles 找的,第二个参数是如果没找到的缺省
5557
onClick={() => {
5658
startGame();
5759
playSeClick();
@@ -62,20 +64,48 @@ useApplyStyle('UI/Title/title.css',{"Title_button":styles.Title_button});
6264
</div>
6365
```
6466

65-
由于新的 CSS 是动态加载的,具有更高的优先级,所以默认样式就这样被覆盖。
66-
6767
而在配置文件中,自定义 UI 的配置是这样的:
6868

69-
`title.css`
69+
`title.scss`
7070

71-
```css
71+
```scss
7272
.Title_button {
7373
color: #005CAF;
7474
background: rgba(0, 0, 0, 0.1);
7575
font-size: 150%;
7676
}
7777
```
7878

79+
使用 SCSS 是因为 SCSS 可以写嵌套,进而支持伪类,比如
80+
81+
```scss
82+
.Title_button {
83+
color: #005CAF;
84+
background: rgba(0, 0, 0, 0.1);
85+
font-size: 150%;
86+
&:hover {
87+
color: #66CCFF;
88+
}
89+
}
90+
```
91+
92+
在找到模板中的类名时,就应用经过 CSS IN JS 处理过的类名,进而替换原有的缺省样式。
93+
94+
比如这里,`applyStyle` 试图应用 `Title_button` 这个类名,我们使用一定的解析方案,找到 scss 中的 `Title_button` 这个类名,然后将其转换为 CSS IN JS 的格式,形如:
95+
96+
```
97+
color: #005CAF;
98+
background: rgba(0, 0, 0, 0.1);
99+
font-size: 150%;
100+
&:hover {
101+
color: #66CCFF;
102+
}
103+
```
104+
105+
然后使用 CSS IN JS 框架中的 `css(cssInJsString)` 将其转换为 CSS IN JS 框架生成的类名。
106+
107+
如果没有找到,根据我们之前提到的`applyStyle('Title_button',styles.Title_button)`中的第二个参数,返回缺省类名。
108+
79109
这样就完成了对某个页面元素的 UI 自定义。
80110

81111
### 切换类名的指令
@@ -91,23 +121,23 @@ applyStyle:Title_button->Title_button_2, Dialog->Dialog_1;
91121

92122
### 实现方法概览
93123

94-
首先,在初始化模板时,WebGAL 维护一个从原始的模板中类名到 css module 生成的类名的映射
124+
首先,在初始化模板时,WebGAL 维护一个从原始的模板中类名到要应用的类名的映射
95125

96126
```
97127
const styleMap = new Map<string,{targetClass:string, currentApplyClass:string}>();
98128
```
99129

100-
`targetClass` 代表要替换到的目标类名,比如 `styles.Title_button`(这是 css module 生成的,运行时会替换为一个随机字符串),`currentApplyClass` 代表目前应用的类名,在初始化时与模板默认类名保持一致,但是可以被 `applyStyle` 指令切换。
130+
`targetClass` 代表在 `applyStyle` 时注册的某个组件的标识符,比如 `Title_button``currentApplyClass` 代表目前应用的类名,在初始化时与标识符保持一致,但是可以被 `applyStyle` 指令切换。
101131

102-
在注册时,就订阅类名切换的事件。如果某个插入的 css 段中的类名发生了“切换类名”,那么这个事件就会发出。指令会重新注册 `currentApplyClass`然后清除原有的 css 段,并重新字符串替换后插入新的 css 段
132+
在注册时,就订阅类名切换的事件。如果某个插入的 css 段中的类名发生了“切换类名”,那么这个事件就会发出。指令会重新注册 `currentApplyClass`然后重新在获取到的样式文件中寻找指定的类名,然后使用 CSS IN JS 框架应用
103133

104-
比如,原有的 `styleMap` 中有一个实体 `"Title_button"->{targetClass:styles.Title_button, currentApplyClass:"Title_button"}`
134+
比如,原有的 `styleMap` 中有一个实体 `"Title_button"->{targetClass:"Title_button", currentApplyClass:"Title_button"}`
105135

106136
运行了指令`applyStyle:Title_button->Title_button_2;`
107137

108-
此时更新 Map,注册为 `Title_button"->{targetClass:styles.Title_button, currentApplyClass:"Title_button_2"}`
138+
此时更新 Map,注册为 `Title_button"->{targetClass:"Title_button", currentApplyClass:"Title_button_2"}`
109139

110-
这时候,要替换到 `stytle.Title_button` 的类名就变为 `Title_button_2`,原有的 `Title_button` 由于不被替换,所以无法生效。
140+
这时候,要替换到标识符被注册为`Title_button` 的类名就变为 `Title_button_2`,原有的 `Title_button` 由于不被替换,所以无法生效。
111141

112142
由此可见,切换类名的脚本要发出事件
113143

@@ -119,7 +149,7 @@ eventBus.emit('classname-change',类名)
119149
`useApplyStyle` 要接受事件并判断是否要重新替换 CSS:
120150

121151
```ts
122-
const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
152+
const useApplyStyle = (url:string) => {
123153
useEffect(()=>{
124154
const applyStyle = ()=>{
125155
// ...... 其他代码
@@ -186,10 +216,10 @@ const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
186216
随着开发的进度,逐步解锁各个 UI 的自定义选项。尤其需要注意的是,**允许编辑的自定义块和类名是在编辑器配置中控制的**,比如
187217

188218
```typescript
189-
registerStyleEditor('UI/Title/title.css', "Title_button", t("标题按钮")) // 这里的 t 是国际化翻译
219+
registerStyleEditor('UI/Title/title.scss', "Title_button", t("标题按钮")) // 这里的 t 是国际化翻译
190220
```
191221

192-
这代表注册一个记录,这个记录表明 `UI/Title/title.css` 下,可以编辑的类名 `Title_button`,并且在“添加自定义块”的下拉菜单中展示为“标题按钮”。
222+
这代表注册一个记录,这个记录表明 `UI/Title/title.scss` 下,可以编辑的类名 `Title_button`,并且在“添加自定义块”的下拉菜单中展示为“标题按钮”。
193223

194224
在自定义块中,可以自定义常用的 CSS 属性,例如:
195225

@@ -214,7 +244,7 @@ registerStyleEditor('UI/Title/title.css', "Title_button", t("标题按钮")) //
214244
还记得我们定义的 `useApplyStyle` 吗?在这个函数中,我们在全局注册一个文件路径到指定的 `style` 块的映射,比如
215245

216246
```ts
217-
const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
247+
const useApplyStyle = (url:string) => {
218248
useEffect(()=>{
219249
const applyStyle = ()=>{
220250
// ...... 其他代码
@@ -224,10 +254,10 @@ const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
224254
}
225255
```
226256

227-
这样,当我们的编辑器后端向引擎发送一个 WebSocket 消息时,就可以让引擎重新请求对应的 css 文件,然后删除之前动态添加的 style 标签,重新转换 css ,然后动态添加标签。
257+
这样,当我们的编辑器后端向引擎发送一个 WebSocket 消息时,就可以让引擎重新请求对应的 scss 文件,然后删除之前动态添加的 style 标签,重新转换 css ,然后动态添加标签。
228258

229259
## 双模编辑
230260

231-
我们的游戏脚本都支持双模式编辑(图形化/代码编辑器),模板编辑没理由不支持,并且支持代码编辑器还可以覆盖到那些无法被图形编辑器编辑的 CSS 属性,以及 CSS 动画。甚至 LSP 都不要我们自己写,Monaco 内置了对 CSS 的语法检查。
261+
我们的游戏脚本都支持双模式编辑(图形化/代码编辑器),模板编辑没理由不支持,并且支持代码编辑器还可以覆盖到那些无法被图形编辑器编辑的 CSS 属性,以及 CSS 动画。甚至 LSP 都不要我们自己写,Monaco 内置了对 SCSS 的语法检查。
232262

233263
我们的图形化编辑器只编辑那些被支持的属性。至于那些不支持的,就直接不在图形化编辑器中展示,编辑的结果也保持不变。

0 commit comments

Comments
 (0)