@@ -9,6 +9,8 @@ tags: ['RFC']
9
9
10
10
第二次提交版本,2024年1月24日,更新了有关切换类名的指令、模板数据结构和具体的 CSS 替换实现方案的内容。
11
11
12
+ 第三次提交版本,2024年1月31日,更新了有关应用 CSS IN JS 实现样式替换的方案。
13
+
12
14
## 总体方案
13
15
14
16
WebGAL 将使用模板思想进行 UI 自定义。在 WebGAL Terre 编辑器中,额外添加一个模板编辑器。在创建 WebGAL 游戏时和创建 WebGAL 游戏后,可以选择要使用的模板。
@@ -25,7 +27,7 @@ templateName/
25
27
├── assets/
26
28
└── UI/
27
29
└── Title/
28
- └──title.css
30
+ └──title.scss
29
31
```
30
32
31
33
其中,` assets ` 是资源目录。
@@ -42,16 +44,16 @@ templateName/
42
44
43
45
### 模板配置文件定义
44
46
45
- 模板配置文件将覆盖 WebGAL 默认样式的一些类,通过动态创建 CSS 来实现。
47
+ 模板配置文件将覆盖 WebGAL 默认样式的一些类,通过使用 CSS IN JS 动态应用 CSS 来实现。
46
48
47
49
实现原理大致表示如下:
48
50
49
51
``` 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 ' );
52
54
// ......
53
55
<div
54
- className = { styles .Title_button }
56
+ className = { applyStyle ( ' Title_button ' , styles .Title_button ) } // 第一个参数是要在 templateStyles 找的,第二个参数是如果没找到的缺省
55
57
onClick = { () => {
56
58
startGame ();
57
59
playSeClick ();
@@ -62,20 +64,48 @@ useApplyStyle('UI/Title/title.css',{"Title_button":styles.Title_button});
62
64
</div >
63
65
```
64
66
65
- 由于新的 CSS 是动态加载的,具有更高的优先级,所以默认样式就这样被覆盖。
66
-
67
67
而在配置文件中,自定义 UI 的配置是这样的:
68
68
69
- ` title.css `
69
+ ` title.scss `
70
70
71
- ``` css
71
+ ``` scss
72
72
.Title_button {
73
73
color : #005CAF ;
74
74
background : rgba (0 , 0 , 0 , 0.1 );
75
75
font-size : 150% ;
76
76
}
77
77
```
78
78
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
+
79
109
这样就完成了对某个页面元素的 UI 自定义。
80
110
81
111
### 切换类名的指令
@@ -91,23 +121,23 @@ applyStyle:Title_button->Title_button_2, Dialog->Dialog_1;
91
121
92
122
### 实现方法概览
93
123
94
- 首先,在初始化模板时,WebGAL 维护一个从原始的模板中类名到 css module 生成的类名的映射 :
124
+ 首先,在初始化模板时,WebGAL 维护一个从原始的模板中类名到要应用的类名的映射 :
95
125
96
126
```
97
127
const styleMap = new Map<string,{targetClass:string, currentApplyClass:string}>();
98
128
```
99
129
100
- ` targetClass ` 代表要替换到的目标类名 ,比如 ` styles. Title_button` (这是 css module 生成的,运行时会替换为一个随机字符串), ` currentApplyClass ` 代表目前应用的类名,在初始化时与模板默认类名保持一致 ,但是可以被 ` applyStyle ` 指令切换。
130
+ ` targetClass ` 代表在 ` applyStyle ` 时注册的某个组件的标识符 ,比如 ` Title_button ` , ` currentApplyClass ` 代表目前应用的类名,在初始化时与标识符保持一致 ,但是可以被 ` applyStyle ` 指令切换。
101
131
102
- 在注册时,就订阅类名切换的事件。如果某个插入的 css 段中的类名发生了“切换类名”,那么这个事件就会发出。指令会重新注册 ` currentApplyClass ` ,然后清除原有的 css 段,并重新字符串替换后插入新的 css 段 。
132
+ 在注册时,就订阅类名切换的事件。如果某个插入的 css 段中的类名发生了“切换类名”,那么这个事件就会发出。指令会重新注册 ` currentApplyClass ` ,然后重新在获取到的样式文件中寻找指定的类名,然后使用 CSS IN JS 框架应用 。
103
133
104
- 比如,原有的 ` styleMap ` 中有一个实体 ` "Title_button"->{targetClass:styles. Title_button, currentApplyClass:"Title_button"} `
134
+ 比如,原有的 ` styleMap ` 中有一个实体 ` "Title_button"->{targetClass:" Title_button" , currentApplyClass:"Title_button"} `
105
135
106
136
运行了指令` applyStyle:Title_button->Title_button_2; `
107
137
108
- 此时更新 Map,注册为 ` Title_button"->{targetClass:styles. Title_button, currentApplyClass:"Title_button_2"} `
138
+ 此时更新 Map,注册为 ` Title_button"->{targetClass:" Title_button" , currentApplyClass:"Title_button_2"} `
109
139
110
- 这时候,要替换到 ` stytle. Title_button` 的类名就变为 ` Title_button_2 ` ,原有的 ` Title_button ` 由于不被替换,所以无法生效。
140
+ 这时候,要替换到标识符被注册为 ` Title_button ` 的类名就变为 ` Title_button_2 ` ,原有的 ` Title_button ` 由于不被替换,所以无法生效。
111
141
112
142
由此可见,切换类名的脚本要发出事件
113
143
@@ -119,7 +149,7 @@ eventBus.emit('classname-change',类名)
119
149
` useApplyStyle ` 要接受事件并判断是否要重新替换 CSS:
120
150
121
151
``` ts
122
- const useApplyStyle = (url : string , classNameMap : Record < string , string > ) => {
152
+ const useApplyStyle = (url : string ) => {
123
153
useEffect (()=> {
124
154
const applyStyle = ()=> {
125
155
// ...... 其他代码
@@ -186,10 +216,10 @@ const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
186
216
随着开发的进度,逐步解锁各个 UI 的自定义选项。尤其需要注意的是,** 允许编辑的自定义块和类名是在编辑器配置中控制的** ,比如
187
217
188
218
``` typescript
189
- registerStyleEditor (' UI/Title/title.css ' , " Title_button" , t (" 标题按钮" )) // 这里的 t 是国际化翻译
219
+ registerStyleEditor (' UI/Title/title.scss ' , " Title_button" , t (" 标题按钮" )) // 这里的 t 是国际化翻译
190
220
```
191
221
192
- 这代表注册一个记录,这个记录表明 ` UI/Title/title.css ` 下,可以编辑的类名 ` Title_button ` ,并且在“添加自定义块”的下拉菜单中展示为“标题按钮”。
222
+ 这代表注册一个记录,这个记录表明 ` UI/Title/title.scss ` 下,可以编辑的类名 ` Title_button ` ,并且在“添加自定义块”的下拉菜单中展示为“标题按钮”。
193
223
194
224
在自定义块中,可以自定义常用的 CSS 属性,例如:
195
225
@@ -214,7 +244,7 @@ registerStyleEditor('UI/Title/title.css', "Title_button", t("标题按钮")) //
214
244
还记得我们定义的 ` useApplyStyle ` 吗?在这个函数中,我们在全局注册一个文件路径到指定的 ` style ` 块的映射,比如
215
245
216
246
``` ts
217
- const useApplyStyle = (url : string , classNameMap : Record < string , string > ) => {
247
+ const useApplyStyle = (url : string ) => {
218
248
useEffect (()=> {
219
249
const applyStyle = ()=> {
220
250
// ...... 其他代码
@@ -224,10 +254,10 @@ const useApplyStyle = (url:string,classNameMap:Record<string,string>) => {
224
254
}
225
255
```
226
256
227
- 这样,当我们的编辑器后端向引擎发送一个 WebSocket 消息时,就可以让引擎重新请求对应的 css 文件,然后删除之前动态添加的 style 标签,重新转换 css ,然后动态添加标签。
257
+ 这样,当我们的编辑器后端向引擎发送一个 WebSocket 消息时,就可以让引擎重新请求对应的 scss 文件,然后删除之前动态添加的 style 标签,重新转换 css ,然后动态添加标签。
228
258
229
259
## 双模编辑
230
260
231
- 我们的游戏脚本都支持双模式编辑(图形化/代码编辑器),模板编辑没理由不支持,并且支持代码编辑器还可以覆盖到那些无法被图形编辑器编辑的 CSS 属性,以及 CSS 动画。甚至 LSP 都不要我们自己写,Monaco 内置了对 CSS 的语法检查。
261
+ 我们的游戏脚本都支持双模式编辑(图形化/代码编辑器),模板编辑没理由不支持,并且支持代码编辑器还可以覆盖到那些无法被图形编辑器编辑的 CSS 属性,以及 CSS 动画。甚至 LSP 都不要我们自己写,Monaco 内置了对 SCSS 的语法检查。
232
262
233
263
我们的图形化编辑器只编辑那些被支持的属性。至于那些不支持的,就直接不在图形化编辑器中展示,编辑的结果也保持不变。
0 commit comments