安安今天想來分享一下我為了最近開發的一款遊戲做的一個簡單的捏臉系統

稍微說一下這次專案的要求:

  • 4個不同的tab (臉,眼睛,頭髮和外觀)
  • 每個tab都有不同的捏臉選項
  • 每個選項都有不同的素材
  • 玩家可以在每個tab中切換不同的捏臉選項
  • 玩家可以在每個tab中切換不同的素材
  • 同樣的素材需要左邊大的 (主要給玩家預覽用) 和右邊小的 (給玩家選擇用)

以下是前置需要的一些代碼:

1
2
3
4
5
6
7
8
9
10
default mc_faceshape = 1
default mc_skintone = 1
default mc_eyes = 1
default mc_eyebrows = 1
default mc_backhair = 1
default mc_fronthair = 1
default mc_accessory = 0
default mc_mouth = "normal"
default customizer_tab = "body"

角色預覽畫面

角色預覽我獨立做成一個 mc_avatar screen
整個概念其實很單純

👉 每一個部位都是一張圖
👉 照固定順序一層一層疊上去

1
2
3
4
add Transform(
"gui/customizer/hair/hair/back/backhair-[mc_backhair].png",
zoom=0.327
)

檔名直接對應變數
Renpy 會自己換路徑

配件是唯一需要判斷的地方:

1
2
3
4
5
if mc_accessory != 0:
add Transform(
"gui/customizer/outfit/accessory-%i.png" % mc_accessory
)

只要你素材命名有規則
這種寫法其實非常穩定

按鈕裡的小預覽

捏臉麻煩的地方 除了換圖還有按鈕裡面那個小縮圖

一開始我也試過直接放 imagebutton
但會變成每一組選項都要重寫

所以做了一個 helper function:

1
2
3
4
5
def make_option_button(...):
return {
"idle": Composite(...),
"hover": Composite(...)
}

這個 function 做的事情只有一件:
👉 把「格子背景」+「預覽圖片」疊成一張圖

之後在 screen 裡用起來就會比較方便:

1
2
3
4
5
6
$ btn = make_option_button(
"option_grid_idle.png",
"option_grid_hover.png",
"mouth/%s.png" % m
)

這樣做的好處還有之後你想改 UI 風格,只要動一個地方

分頁切換

整個捏臉介面
其實只有一個主 screen chara_customizer
然後不同分類(身體、眼睛、頭髮)
只是切換不同的子畫面

1
2
if customizer_tab == "body":
use body_customizer_options

上方按鈕也只是改變變數:

1
SetVariable("customizer_tab", "hair")

頭髮和配件

當選項一多 畫面會不夠放

這個時候可以用 viewport

1
2
3
4
viewport:
mousewheel True
draggable True

裡面照樣用 vbox + hbox

滾動條用 YScrollValue 綁定

Reset / Confirm (重設/確定造型)

Reset 按鈕就是把所有變數設回初始值
所以變量全部設回 1

1
2
3
4
5
SetVariable("mc_faceshape", 1),
SetVariable("mc_skintone", 1),
...


Confirm 則是 Return() 所以會回到叫出這個 screen 的位置

以下是完整代碼:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

default mc_faceshape = 1
default mc_skintone = 1
default mc_eyes = 1
default mc_eyebrows = 1
default mc_backhair = 1
default mc_fronthair = 1
default mc_accessory = 0
default customizer_tab = "body"
default mc_mouth = "normal"


init python:
def make_option_button(
grid_idle,
grid_hover,
preview_img,
size=(250, 250),
preview_zoom=0.11,
preview_align=(0.5, 0.5),
preview_offset=(0, 0)
):
return {
"idle": Composite(
size,
(0, 0), Transform(grid_idle, zoom=1),
preview_offset, Transform(preview_img, zoom=preview_zoom, align=preview_align)
),
"hover": Composite(
size,
(0, 0), Transform(grid_hover, zoom=1),
preview_offset, Transform(preview_img, zoom=preview_zoom, align=preview_align)
)
}

screen mc_avatar():
fixed:
xpos 128
ypos 335
xsize 654
ysize 653

add Transform(
"gui/customizer/hair/hair/back/backhair-[mc_backhair].png",
zoom=0.327
)

add Transform(
"gui/customizer/body/face/faceshape-[mc_faceshape]/skintone-[mc_skintone].png",
zoom=0.327
)

add Transform(
"gui/customizer/eyes/eyes/eyes-[mc_eyes]/open-normal.png",
zoom=0.327
)

add Transform(
"gui/customizer/eyes/eyebrows/eyebrows-[mc_eyebrows]/relaxed.png",
zoom=0.327
)

add Transform(
"gui/customizer/body/face/mouth/[mc_mouth].png",
zoom=0.327
)

add Transform(
"gui/customizer/hair/hair/front/fronthair-base.png",
zoom=0.327
)

add Transform(
"gui/customizer/hair/hair/front/fronthair-[mc_fronthair].png",
zoom=0.327
)
add Transform(
"gui/customizer/outfit/clothe.png",
zoom=0.327
)

if mc_accessory != 0:
add Transform(
"gui/customizer/outfit/accessory-%i.png" % mc_accessory,
zoom=0.327
)



screen chara_customizer():
tag menu

add "gui/customizer/customize_bg.png"
add "gui/customizer/chara_bg.png" xpos 26 ypos 212
add "gui/customizer/rightbox.png" xpos 812 ypos 202

hbox:
xpos 798
ypos 80
spacing -20
imagebutton idle "gui/customizer/general_buttons/body_idle.png" hover "gui/customizer/general_buttons/body_hover.png" selected_idle "gui/customizer/general_buttons/body_hover.png" action SetVariable("customizer_tab", "body")
imagebutton idle "gui/customizer/general_buttons/eyes_idle.png" hover "gui/customizer/general_buttons/eyes_hover.png" selected_idle "gui/customizer/general_buttons/eyes_hover.png" action SetVariable("customizer_tab", "eyes")
imagebutton idle "gui/customizer/general_buttons/hair_idle.png" hover "gui/customizer/general_buttons/hair_hover.png" selected_idle "gui/customizer/general_buttons/hair_hover.png" action SetVariable("customizer_tab", "hair")
imagebutton idle "gui/customizer/general_buttons/outfit_idle.png" hover "gui/customizer/general_buttons/outfit_hover.png" selected_idle "gui/customizer/general_buttons/outfit_hover.png" action SetVariable("customizer_tab", "outfit")

if customizer_tab == "body":
use body_customizer_options
elif customizer_tab == "eyes":
use eyes_customizer_options
elif customizer_tab == "hair":
use hair_customizer_options
elif customizer_tab == "outfit":
use outfit_customizer_options

imagebutton idle "gui/customizer/general_buttons/reset_idle.png" hover "gui/customizer/general_buttons/reset_hover.png" xpos 1315 ypos 940 action [
SetVariable("mc_faceshape", 1),
SetVariable("mc_skintone", 1),
SetVariable("mc_eyes", 1),
SetVariable("mc_eyebrows", 1),
SetVariable("mc_mouth", "normal"),
SetVariable("mc_backhair", 1),
SetVariable("mc_fronthair", 1),
SetVariable("mc_outfit", 1),
SetVariable("mc_accessory", 0)
]

imagebutton idle "gui/customizer/general_buttons/confirm_idle.png" hover "gui/customizer/general_buttons/confirm_hover.png" xpos 1541 ypos 940 action Return()
use mc_avatar


screen body_customizer_options():
add "gui/customizer/body/skintone.png" xpos 842 ypos 257

hbox:
xpos 1166
ypos 247
spacing 10
for i in range(1, 7):
imagebutton:
idle "gui/customizer/body/skinton%i.png" % i
hover "gui/customizer/body/skinton%i_hover.png" % i
selected_idle "gui/customizer/body/skinton%i_hover.png" % i
action SetVariable("mc_skintone", i)

add "gui/customizer/body/faceshape.png" xpos 842 ypos 335
hbox:
xpos 842
ypos 382
spacing -90

for i in range(1, 4):
$ btn = make_option_button(
"gui/customizer/body/option_grid_idle.png",
"gui/customizer/body/option_grid_hover.png",
"gui/customizer/body/face/faceshape-%i/skintone-%i.png" % (i, mc_skintone),
preview_zoom=0.070,
preview_offset=(0, -20)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_faceshape", i)
focus_mask True

add "gui/customizer/body/mouth.png" xpos 842 ypos 557
hbox:
xpos 842
ypos 604
spacing -90

for m in ["normal", "smile", "open", "cute", "laugh"]:
$ btn = make_option_button(
"gui/customizer/body/option_grid_idle.png",
"gui/customizer/body/option_grid_hover.png",
"gui/customizer/body/face/mouth/%s.png" % m,
preview_zoom=0.12,
preview_offset=(-50, -70)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_mouth", m)
focus_mask True


screen eyes_customizer_options():
add "gui/customizer/eyes/eyes.png" xpos 839 ypos 235

hbox:
xpos 833
ypos 288
spacing -80

for i in range(1, 4):
$ btn = make_option_button(
"gui/customizer/eyes/option_grid_idle.png",
"gui/customizer/eyes/option_grid_hover.png",
"gui/customizer/eyes/eyes/eyes-%i/open-normal.png" % i,
preview_zoom=0.12,
preview_offset=(-45, -35)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_eyes", i)
focus_mask True



add "gui/customizer/eyes/eyebrows.png" xpos 839 ypos 477
hbox:
xpos 833
ypos 535
spacing -80

for i in range(1, 4):
$ btn = make_option_button(
"gui/customizer/eyes/option_grid_idle.png",
"gui/customizer/eyes/option_grid_hover.png",
"gui/customizer/eyes/eyebrows/eyebrows-%i/relaxed.png" % i,
preview_zoom=0.13,
preview_offset=(-52, -40)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_eyebrows", i)
focus_mask True

screen hair_customizer_options():
viewport:
yinitial 0.0
id "hair_vp"
xpos 842
ypos 245
ysize 700
xmaximum 880
mousewheel True
draggable True

vbox:
spacing 15

add "gui/customizer/hair/fronthair.png"

for row in range(0, 8, 4):
hbox:
spacing -25

for i in range(row + 1, min(row + 5, 9)):
$ btn = make_option_button(
"gui/customizer/hair/option_grid_idle.png",
"gui/customizer/hair/option_grid_hover.png",
"gui/customizer/hair/hair/front/fronthair-%i.png" % i,
preview_zoom=0.10,
preview_offset=(10, 10)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_fronthair", i)
focus_mask True



add "gui/customizer/hair/backhair.png"

for row in range(0, 8, 4):
hbox:
spacing -25

for i in range(row + 1, min(row + 5, 9)):
$ btn = make_option_button(
"gui/customizer/hair/option_grid_idle.png",
"gui/customizer/hair/option_grid_hover.png",
"gui/customizer/hair/hair/back/backhair-%i.png" % i,
preview_zoom=0.10,
preview_offset=(5, 5)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_backhair", i)
focus_mask True
vbar:
value YScrollValue("hair_vp")
xpos 1750
ypos 250
xsize 50
ysize 660
left_bar Frame("gui/customizer/hair/customize_scrollbar.png", 10, 0)
right_bar Frame("gui/customizer/hair/customize_scrollbar.png", 10, 0)
thumb "gui/customizer/hair/customize_scrollbar_thumb.png"
thumb_offset 25



screen outfit_customizer_options():
vbox:
xpos 842
ypos 240
spacing 15

add "gui/customizer/outfit/outfit.png"

$ btn = make_option_button(
"gui/customizer/outfit/option_grid_idle.png",
"gui/customizer/outfit/option_grid_hover.png",
"gui/customizer/outfit/clothe.png",
preview_zoom=0.087,
preview_offset=(0, -35)
)
imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_cloth", 1)
focus_mask True

add "gui/customizer/outfit/accessories.png" ypos -50

vbox:
ypos - 50
spacing -60

for row in range(0, 8, 4):
hbox:
spacing -35


for i in range(row + 1, row + 5):
$ btn = make_option_button(
"gui/customizer/outfit/option_grid_idle.png",
"gui/customizer/outfit/option_grid_hover.png",
"gui/customizer/outfit/accessory-%i.png" % i,
preview_zoom=0.15,
preview_offset=(-65, -50)
)

imagebutton:
idle btn["idle"]
hover btn["hover"]
selected_idle btn["hover"]
action SetVariable("mc_accessory", i)
focus_mask True


最後畫面大概會可以長成這樣: