[Из песочницы] Как за 3 дня создать игровой движок для новелл
Одним дождливым питерским днем мой проект в UE4 перестал загружаться, и из-за этого я захотел сделать свой собственный движок. И одна новелла подтолкнула меня сделать движок именно для новелл. Если вам хочется узнать побольше и вы не боитесь goto, gosub и других ужасов, добро пожаловать под кат.
Все началось с того, как после прохождения N-ой новеллы друг порекомендовал мне Katawa Shoujo. Как оказалось официальной версии в App Store нет и не предвидится, только .ipa на 4pda.
Этот же друг зная, что я пытаюсь создать игру уже третий месяц предложил мне портировать Катаву на iPad без Jailbreak. Выбор пал на smart BASIC, так как я пользуюсь этой программой уже полтора года и знаю синтаксис её внутреннего языка программирования (также у smart BASIC есть SDK для XCode).
Сразу же был скачан архив со всеми файлами игры кроме текста. Но ни он не я не знали, что это приведет к созданию своего движка для удобства разработки.
1. Текст
Для начала я создал папки (спойлер).
/Sprites
/Event
/BGs
/Music
/Scenario
/Scripts
/Scripts/Ren_sB
/Scripts/Ren_sB/Functions
/UI
/UI/main
/VFX
Файл Katawa Shoujo Port.txt будет подгружать все скрипты.
scenario$ = "Habr”
{Scripts/colorcodes.txt}
{Scripts/Ren_sB/render.txt}
Файл colorcodes.txt будет содержать RGB цвета имен для рендера, для примера возьму Сидзунэ и ее синее выделение:
data "Сидзунэ","107","174","239"
dim colors$(1,2)
read colors$(color,0)
Основные приготовления сделаны, теперь можно писать скрипт для показа текста. В Катаве, как и в любой визуальной новелле, текст показывается в поле, и над текстом пишется имя персонажа, от чьего лица этот текст.
За это будет отвечать скрипт speak.txt.
Для начала в файл load.txt я прописал загрузку спрайта bg-say.png (поле для реплик) и шрифта playtime_cyrillic:
font "Font/playtime_cyrillic2.ttf" load a$
sprite "bg-say" load "UI/bg-say.png"
sprite "bg-say" resize 1024,205
sprite "bg-say" at 0,screen_height()-205
sprite "bg-doublespeak" load "UI/bg-doublespeak.png"
sprite "bg-doublespeak" resize 1024,205
sprite "bg-doublespeak" at 0,screen_height()-205
sprite "ctc-strip" load "UI/ctc-strip.png"
get sprite "ctc_strip" size w,h
sprite "ctc_strip" resize w*1.28,h*1.28
sprite "ctc_strip" at screen_width()-w*1.28-15,screen_height()-52
sprite "ctc_strip" delay 0.05
После я решил использовать в скрипте сценария знак »|» как разделитель команды, цели команды, вторичной цели, действия и аттрибута.
Показ текста:»1|перс|текст»
1|Сейдзунэ|[Привет, Хабр]
Файл render.txt считывает эту команду и перенаправляет на метку alone
graphics
set toolbar off
set orientation landscape
file "Scenario/"&scenario$&".txt" readline hmm$
count = count+1
end = file_end("Scenario/"&scenario$&".txt")
if end = 0 then goto count
file "Scenario/"&scenario$&".txt" setpos 0
dim info$(count,5)
for l=0 to count-1
file "Scenario/"&scenario$&".txt" readline line$
splite line$ to temp$,n with "|"
for m = 0 to n-1
info$(l,m) = temp$(m)
next m
next l
'Разбор массива текста, перенаправления на подпрограммы
'
show:
for y = 0 to count-1
if info$(y,0) = "1" then gosub alone
...
alone:
{Scripts/Ren_sB/Functions/Speak.txt}
return
sprite "bg-say" show
name$ = info$(y,1)
text$ = info$(y,2)
'Поиск кода цвета данного персонажа
'
for color = 0 to 6
if name$ = colors$(color,0) then
r = colors$(color,1)
g = colors$(color,2)
b = colors$(color,3)
endif
next color
'Создание двух полей
'
field 1 text name$ at 20,575 size 600,40 ro
field 1 back alpha 0
field 1 font color r/255,g/255,b/255
field 1 font name a$
field 1 font size 28
field 2 text render$ at 20,615 size screen_width()-20,130 ro ml
field 2 back alpha 0
field 2 font color 1,1,1
field 2 font size 28
field 2 font name a$
'Анимация текста
'
for k = 0 to len(text$)-1
'Использование касания для пропуска анимации
'
gosub 3
render$ = substr$(text$,0,k)
field 2 text render$
pause 0.025
next k
'Ожидание касания для перехода
'
sprite "ctc_strip" show ! sprite "ctc_strip" loop
gosub 1
sprite "ctc_strip" hide
1
x1 = touch_x(0)
y1 = touch_y(0)
if x1 > -1 or y1 > -1 then goto 2
slowdown
goto 1
2
x1 = touch_x(0)
y1 = touch_y(0)
if x1 = -1 or y1 = -1 then
return
endif
slowdown
goto 2
3
x1 = touch_x(0)
y1 = touch_y(0)
if x1 > -1 or y1 > -1 then
tapped = 1
endif
if tapped = 1 then
if x1 = -1 or y1 = -1 then
tapped = 0
if len(text$) = 0 then
k = lengthmax - 1
else
k = len(text$) - 1
endif
return
else
return
endif
endif
return
Результат:
«Но ведь в Катаве может говорить два персонажа одновременно!» Для этого есть отдельная команда:»2|перс1&перс2|текст1&текст2»:
2|Хисао&Лилли|«Привет!»&«Здравствуйте!»
Добавим в render.txt строки:
...
show:
...
if info$(y,0) = 2 then gosub together
...
together:
{Scripts/Ren_sB/Functions/Double_Speak.txt}
return
Double_Speak.txt выполняет данные команды:
sprite "bg-doublespeak" show
names$ = info$(y,1)
'Имена и тексты
'
nd = instr(names$,"&")
name1$ = substr$(names$,0,nd-1)
name2$ = substr$(names$,nd+1,len(names$)-1)
texts$ = info$(y,2)
td = instr(texts$,"&")
text1$ = substr$(texts$,0,td-1)
text2$ = substr$(texts$,td+1,len(texts$)-1)
'Определение цвета для каждого персонажа
'
for color = 0 to 6
if name1$ = colors$(color,0) then
r1 = colors$(color,1)
g1 = colors$(color,2)
b1 = colors$(color,3)
endif
next color
for color = 0 to 6
if name2$ = colors$(color,0) then
r2 = colors$(color,1)
g2 = colors$(color,2)
b2 = colors$(color,3)
endif
next color
'Создание полей
'
field 11 text name1$ at 20,575 size 300,40 ro
field 11 back alpha 0
field 11 font color r1/255,g1/255,b1/255
field 11 font name a$
field 11 font size 28
field 12 text name2$ at 535,575 size 300,40 ro
field 12 back alpha 0
field 12 font color r2/255,g2/255,b2/255
field 12 font name a$
field 12 font size 28
field 21 text render1$ at 20,615 size screen_width()/2-20,130 ro ml
field 21 back alpha 0
field 21 font color 1,1,1
field 21 font size 28
field 21 font name a$
field 22 text render2$ at 535,615 size screen_width()/2-20,130 ro ml
field 22 back alpha 0
field 22 font color 1,1,1
field 22 font size 28
field 22 font name a$
'Определение максимальной и минимальной длинны
'
lengthmax = max(len(text1$),len(text2$))
lengthmin = min(len(text1$),len(text2$))
'Анимация текста
'
if len(text1$) = len(text2$) then
for k = 0 to lengthmax-1
gosub 3
render1$ = substr$(text1$,0,k)
field 21 text render1$
render2$ = substr$(text2$,0,k)
field 22 text render2$
pause 0.025
next k
endif
if len(text1$) > len(text2$) then
for k = 0 to lengthmax-1
gosub 3
render1$ = substr$(text1$,0,k)
field 21 text render1$
if k < lengthmin then
render2$ = substr$(text2$,0,k)
field 22 text render2$
endif
pause 0.025
next k
endif
if len(text2$) > len(text1$) then
for k = 0 to lengthmax-1
gosub 3
render2$ = substr$(text2$,0,k)
field 22 text render2$
if k < lengthmin then
render1$ = substr$(text1$,0,k)
field 21 text render1$
endif
pause 0.025
next k
endif
'Ожидание касания
'
sprite "ctc_strip" show ! sprite "ctc_strip" loop
gosub 1
sprite "ctc_strip" hide
Результат:
На этом первый день разработки окончается, а значит оканчается и первая часть моей статьи. Во второй части я опишу второй день, а именно добавлю графику (спрайты, фоны и ивенты) и музыку.
Спасибо всем тем, кто дочитал до конца статьи. До встречи в следующей части!