From Zero to Visual Novel 2: Excel-Script and Automated
5 min readMay 29, 2025
Introduce
- จากบทความที่แล้ว (From Zero to Visual Novel) ถ้าเพื่อนๆ Follow ตามบทความจนจบ เพื่อนๆ น่าจะได้พื้นฐานพอในการสร้าง Visual Novel ของตัวเองแล้วนะครับ
- เพื่อนๆ น่าจะสังเกตได้ว่า โค๊ต Renpy นั้นมี “ความซ้ำซ้อน” มากครับ
- บทความนี้จะเป็นการแนะนำให้เพื่อนๆ ลดงาน boilerplate เหล่านี้ ด้วยการทำงานผ่าน Excel แล้วใช้ Script เพื่อแปลงเป็น Renpy แทน
คนที่เขียน Automated จะต้องเป็น “คนที่เข้าใจสิ่งนั้นๆอย่างลึกซึ้งเท่านั้น” ถ้าเพื่อนๆยังใช้ Renpy ไม่คล่อง อยากจะให้ฝึกเขียนแบบปกติให้คล่องก่อนนะครับ
Advantages of using Excel
- ก่อนที่เราจะพูดถึง “การใช้ Script แปลง Excel ให้กลายเป็น Renpy Code” เราควรจะเข้าใจตรงกันก่อนว่า Excel ที่ว่าคืออะไร
- ถ้าเพื่อนๆ มอบบทข้างบนให้ Programmer เพื่อนๆคิดว่า Programmer ในทีมเพื่อนๆ จำเป็นต้องเดาอะไรบ้างครับ เพื่อให้เขียน Renpy ขึ้นมาได้
- Background ใช้เป็นอะไร?, มี SFX ไหม?, มี Effect พิเศษอะไรไหม?, แล้วแมวที่เจอนี่ มันต้องทำหน้ายังไง?, เสียงแมวใช้ Voice file ไหน?
- ซึ่งมีโอกาสไม่น้อยเลย ที่ภาพในหัว Programmer, Director, Writer จะออกมาเป็นคนละภาพกันครับ
- ทางกลับกัน ถ้าเป็น Excel นี้, Programmer ในทีมเพื่อนๆ จะทำงานได้ง่ายกว่าใช่ไหมครับ
- Excel ที่เรากำลังพูดถึงในบทความนี้จะเป็นตัวแทนของ “ภาพในหัวของผู้กำกับ” ที่ต้องการให้ Programmer ทำตามครับ
- ข้อดีที่สุดของ Excel นี้คือ “ภาพของทั้งทีมจะเป็นภาพเดียวครับ” ซึ่งทำให้การสื่อสารกันในทีมนั้นทำได้ง่ายกว่าครับ
- ถือเป็นการรวมงานของทุกฝ่ายด้วยครับ ทั้ง ภาพของ Illustrator, บทของ Writer, Voice ของฝั่งนักพากย์ และอาจจะรวมถึงการแปลภาษา ไว้ในที่เดียวครับ
- อาจมีข้อเสียคือ เรากำลัง Double Work อยู่ (คิดภาพว่า แทนจะเขียน Code ไปเลย เราสร้าง Excel มาก่อน -> เอา Excel ไปเขียนเป็น Code ) จึงเป็นเหตุผลที่หลายๆทีม อาจจะข้ามขั้นตอนการสร้าง Excel นี้ครับ
Advantages of using Automated Scripts
- ลดงาน boilerplate ที่เราต้องเขียนไปได้เยอะมาก
- ถ้าทีมเพื่อนๆ มีการสร้าง Excel อยู่แล้ว เราแทบจะลดงาน Scripting ได้แบบ 90%+ เลยครับ เนื่องจากผ่าน Automated หมดครับ
- ลดความผิดพลาดที่เกิดขึ้นจากการเขียน Code ของเราได้มากครับ ถ้าผิด = Excel เขียนผิดเลย ซึ่งจริงๆ หาความผิดพลาดใน Excel มันง่ายกว่าใน Code มากครับ
- แบ่งงานที่ Programmer ถือให้เพื่อนในทีมช่วยได้ สมมุติถ้า Illustrator ทีมเพื่อนๆ วาดรูปไม่ทัน เพื่อนในทีมช่วยไม่ได้แน่นอนครับ
- แต่ถ้า Programmer ทำงาน Script ไม่ทัน ยังอาจจะแบ่งให้ Director, Writer ช่วยสร้าง Excel ขึ้นมาได้ครับ (เผลอๆ 2 Role นี้ ภาพอาจชัดกว่า Programmer ด้วยซ้ำไปครับ)
Disadvantages of using Automated Scripts
- “ไม่มี Automated ใดที่ทำงานได้อย่างสมบูรณ์แบบ” บางครั้งจะ Director อาจจะเห็นภาพที่ Automated ไม่ Support ครับ; Case นี้ Programmer เองจำเป็นต้องเขียนเองอยู่ดีครับ
- Automated บัคเยอะมากในการพัฒนาครับ เพราะเราต้องเขียน Automated ให้ครอบคลุมที่สุดครับ (ทางผู้เขียนบทความใช้จนเกมส์จะจบแล้ว ยังเจอบัคในบทท้ายๆ ได้อยู่เลยครับ)
- เขียนยากระดับหนึ่งครับ รอบนี้สาวบ้านที่เพื่อนๆ Assign เป็น Programmer อาจจะทำไม่ได้แล้วครับ
- ต่ำๆคือต้องเข้าใจ Renpy ระดับหนึ่งจนเห็นรูปแบบซ้ำๆ ได้ครับ
- แต่ที่ยากที่สุดคือ ต้องรู้ว่า การ Automated feature นั้นๆ มันเขียนยากแค่ไหน และคุ้มค่าการเขียนไหมนะครับ
- ยกตัวอย่างง่ายๆ Automated Voice file เป็นสิ่งที่เขียนได้ง่ายครับ ใช้แค่ 1–2 line น่าจะทำงานได้ครับ แถมทั้งเกมส์ถ้าเรา Full voice เราใช้ Feature นี้หลายร้อยครั้งครับ ไม่มีเหตุผลอะไรที่จะไม่ Implement ครับ
- อีกตัวอย่างคือ Choice ครับ อันนี้ถือว่า implement ได้ค่อนข้างยาก แถมทั้งเกมส์ เราน่าจะใช้ Feature นี้เพียงไม่กี่ครั้งครับ
- แต่ถ้า Visual Novel ของเพื่อนๆ เป็นแบบ Gal game ยุคเก่าๆเลย การ Implement Feature นี้ อาจจะคุ้มค่าขึ้นมาครับ
File Structure
game/
│
├── audio/
│ ├── bgm/ # เพลงประกอบหรือเพลงพื้นหลัง
│ └── sfx/ # เอฟเฟคเสียงต่างๆ
│
├── gui/ # GUI
│
├── images/
│ ├── bg/ # ภาพพื้นหลัง
│ ├── character/ # Sprite ตัวละคร
│ └── sd/ # ภาพ SD
│
├── saves/ # ไฟล์บันทึกเกม
│
├── tl/ # ไฟล์แปลภาษา (Translation)
│
├── gen.py # Automated Script ที่เราใช้ในบทความนี้ครับ
├── char.rpy # การกำหนดตัวละคร
├── gui.rpy # การกำหนดค่า GUI
├── LayijiMahaniyomV1_61.ttf # ไฟล์ฟอนต์
├── options.rpy # การตั้งค่าต่างๆ ของเกม
├── screens.rpy # การกำหนดหน้าจอต่างๆ
└── script.rpy # สคริปต์หลักของเกม
#จะเห็นว่า Sturcture ใกล้เคียงกับเดิมเลย แค่เพิ่ม gen.py เข้ามาครับ
Architecture
- ถ้าเพื่อนๆใช้ Renpy แสดงว่า เพื่อนๆน่าจะมีความคุ้นเคยกับภาษา Python ในระดับหนึ่ง คืออย่างน้อยก็ต้องมีในเครื่องแน่ๆ ล่ะครับ Automated ของเราจึงจะใช้ภาษานี้มา Implement นะครับ
- อีกทั้งเป็นภาษา Script ทำให้รันได้ง่าย จัดการ Environment ได้ง่ายครับ
- ในเมื่อเราทำงานกับ Table Data ผู้เขียนจึงใช้ Pandas เป็น Reader นะครับ เนื่องจากใช้งานได้ง่ายมาก แล้วโค๊ตค่อนข้าง Clean ครับ
Initialization
#gen.py
import pandas as pd
FILE_NAME = "renpy_script_tutorial1.csv"
VOICE_BASE_PATH="audio/voice"
VOICE_PATH = ""
SFX_BASE_PATH="audio/sfx"
data = pd.read_csv(FILE_NAME,encoding="utf-8")
data = data.fillna("")
#เป็นการประกาศ Constant ของ Project ทั่วไปครับ
#encoding="utf-8" ใส่เพื่อให้มันอ่านภาษาไทยได้ครับ ไม่งั้นจะเป็นภาษาอะไรแปลกๆเลย
#fillna("") คือการเติม NaN (ค่าว่าง) ด้วย "" (String เปล่า) ครับ
#จะไม่ต้องลำบาก check เป็น NaN ไหม ตลอดครับ
Main Loop & Assign
for i,c in data.iterrows():
### Assign ##############################
bg = c['bg']
bg_effect = c['bg_effect']
bgm = c['bgm']
character1 = c['character1']
character2 = c['character2']
face = c['face']
who_talk = c['who_talk']
talk = c['talk']
voice = c['voice']
sfx = c['sfx']
#เป็น Main Loop ที่เราจะวิ่งไปในทุก Row ใน Table ครับ
#ในแต่ละ C (คือ 1 Row) จะประกอบด้วยหลายๆ Column ถูกไหมครับ
#ให้เราเก็บ Value ต่างๆใน Excel ตามชื่อ Column เลยครับ
Action
### Action ###############################
if(bg):
print(f'scene {bg} with Dissolve(1.0)')
if(bgm and bgm != 'stop'):
print(f"stop music")
print(f'play music "audio/bgm/{bgm}.mp3" volume 0.5')
if(bgm =='stop'):
print(f"stop music")
show_charector(character1, character2)
if(sfx):
print(f'play sound "{SFX_BASE_PATH}/{sfx}.mp3"')
if(voice):
print(f'play sound "{VOICE_BASE_PATH}/{who_talk}/{VOICE_PATH}/{voice}.mp3"')
if(bg_effect):
print(f'{bg_effect}')
if(talk):
if(who_talk):
print(f'{who_talk} {(face)} "{talk}" with dissolve')
else:
print(f'th "{talk}" with dissolve')
print(f"return")
# ล้อจากบทที่แล้วมาครับ สังเกตว่า แค่ครอบคำสั่ง renpy ที่เราใช้ปกติใน Method print ครับ
# print(f'') ทำให้เราสามารถใช้ Variable ใน คำสั่ง print ได้ครับ
# โดยเราจะใช้ Variable จากหัวข้อข้างบนครับ
# น่าสนใจคือ show_charector เป็น Method ที่เรายังไม่ได้ define ครับ โดยจะอธิบายเพิ่มเติมในหัวข้อถัดไป
# อย่าลืมปิดด้วย return นะครับ
Show Character Method
def show_charector(charector1,charector2):
# ถ้ามี Character ใหม่ จะเอาตัวเก่าออกหมดก่อนครับ
if (character1 != "" or character2 != ""):
print(f'hide eri')
print(f'hide ayase')
print(f'hide mikan')
# ถ้าไม่มี Character เลย จะไม่ทำ Method นี้ครับ
if ( charector1 == "" ):
return
#2 Charector Case
# ถ้ามี Chartertor2 show Character ทั้งสอง ซ้ายและขวาครับ
if ( charector2 != ""):
print(f'show {character1} normal at left')
print(f'show {character2} normal at right')
return
#1 Character Case
# ถ้ามี 1 Character จะ Show Character1 ครับ
print(f'show {character1} normal at center')
return
# Method นี้จะซับซ้อนหน่อย เลยแยกมาเป็น Method ครับ
# ใช้สำหรับการเปลี่ยน Character ครับ
How to Run
set PYTHONIOENCODING=utf-8
set PYTHONLEGACYWINDOWSSTDIO=utf-8
python gen.py > chap1.rpy
# Automated Script นี้ต้องรันผ่าน Cmd(Windows) or Terminal(Osx, Linux) นะครับ
# 2 Line แรกเป็นการแก้ปัญหาที่ Windows ไม่ทราบว่าเรากำลังจะ Print ภาษาไทยอยู่ครับ
# print gen.py เป็นการ run python แบบปกติครับ
# > chap1.rpy เป็นคำสั่ง pipeline ของ CMD
# ปกติแล้ว print จะแสดงผลลัพท์ของโปรแกรมผ่านทาง CMD ใช่ไหมครับ
# การใช้ > chap1.rpy ทำให้นำผลลัพท์ไปเก็บใน File chap1.rpy แทนครับ
# Run เสร็จแล้ว อย่าลืมจัด indent + ครอบด้วย Label ด้วยนะครับ
It’s your turn
- ถ้าเพื่อนๆ Follow ตาม Medium นี้; น่าจะพอเห็นภาพของการใช้ Automated มาแปลง Excel เป็น Program แล้วใช่ไหมครับ
- แต่จริงๆแล้ว ตัวอย่างโค๊ตที่ผู้เขียนยกมา ยังมีจุดให้ปรับปรุงอีกเยอะมากครับ
- จากโค๊ตนี้ เราไม่สามารถกำหนดการแสดงผลให้ในจอไม่มี Sprite เลยได้ครับ
- การ Hide ทุก Character ตอนที่มีตัวละครใหม่เข้ามา นั้นทำให้โค๊ตอ่านยากโดยไม่จำเป็นครับ สมมุติเพื่อนๆ มี 5–6 ตัวละคร ตัวละครละ 2 ชุดนี่ เราได้ Hide กัน 12 Line++ ครับ
- เราควรจะ Hide แค่ Character ที่อยู่ในจอเท่านั้นครับ
- ยังไม่ Support 2 ภาษาครับ
- หลาย Feature อย่าง Assign Variable, Choice ยังไม่ Support ครับ
Extra
- cha.rpy เป็นการ Define Sprite Image ของ Character ทั้งหมดครับ
- ซึ่งรวมเป็น 1,000 Line เลย Case แบบนี้ไม่ใช่ Scale ที่ควรจะเขียนเองครับ (ขนาดมีแค่ 3 ตัวละคร ตัวละครละชุด)
- อย่าง Re:Fragment ~Absolute Ambition~ File นี้มีขนาดราวๆ 12,000 Line ครับ
- ซึ่งเราจะไม่เขียนเองแน่นอนครับ เราจะใช้ gencharacter.py ในการ Gen ขึ้นมาครับ
- ซึ่งผู้เขียนสัญญาว่าจะเอามาเล่าให้ฟังในบทหน้าๆ แน่นอนครับ แต่ถ้าแกะ Code ดูเองเลย จะเข้าใจได้ว่าไม่มีอะไรมากครับ LOL
Epilogue
- บทความนี้ถือว่าเป็นความท้าทายของทั้งผู้เขียน และเพื่อนๆที่ ติดตามมากๆ ครับ เนื่องจากความยากเพิ่มจากบทแรกอย่างก้าวกระโดดเลยครับ
- นอกจากที่เพื่อนๆ ต้องเขียน Renpy เป็นแล้ว ยังต้องเข้าใจ Python ในระดับหนึ่ง, ต้องเข้าใจ Basic Terminal ด้วยครับ
- และที่ยากจริงๆคือ “เราไม่สามารถ Automated ทุกอย่างได้ครับ” การจะเลือกว่า จะ Automated ใน Feature ไหนนั้น ขึ้นอยู่กับ ลักษณะ Visual Novel ของเพื่อนๆ เลยครับ
- แถม Debug ได้ยากมากด้วยครับ ถ้าเพื่อนๆ คิด Case ได้ไม่ครอบคลุมพอครับ
- แน่นอนว่า ทีมเพื่อนๆ อาจจะไม่ได้ใช้ Excel Format เดียวกับทีมของผู้เขียน (หรืออาจจะไม่มีเลยก็ได้ lol)
- ผู้เขียนจึงอยากให้มองว่า บทความนี้เป็นเพียง Inspire ให้เพื่อนๆ เท่านั้นครับ การจะนำไปใช้จริงนั้น ต้องพัฒนาอีกระดับนึงเลยครับ (แต่จริงๆแก้ให้ Hide Character ได้ มันก็ใช้จริงได้ 80–90% แล้วนะครับ)
- จากประสบการณ์ตรงเลยคือ Memary: Memory of the nameless one นั้น ผู้เขียนไม่ได้ทำผ่าน Automated ครับ
- จริงๆ เกมส์นั้นถือว่าไม่เหมาะกับการ Automated เท่าไหร่ เนื่องจาก Condition, if else Case เยอะแบบมากๆ ครับ
- แต่จริงๆแล้ว จังหวะที่ใส่ Voice , English Translate นี่ ทำเองโดยไม่ผ่าน Script นี่ เสียเวลามากๆ ครับ
- ท้ายที่สุด ก็ขอขอบคุณทาง Rayedge จากทีม AstralSeal สำหรับ Asset ที่ใช้ในบทความ รวมถึงเพื่อนๆที่ตามกันมาถึงบทความที่ 3 ของ Series Visual Novel นะครับ