[Из песочницы] Как за 3 дня создать игровой движок для новелл

Одним дождливым питерским днем мой проект в UE4 перестал загружаться, и из-за этого я захотел сделать свой собственный движок. И одна новелла подтолкнула меня сделать движок именно для новелл. Если вам хочется узнать побольше и вы не боитесь goto, gosub и других ужасов, добро пожаловать под кат.
image

image
Все началось с того, как после прохождения N-ой новеллы друг порекомендовал мне Katawa Shoujo. Как оказалось официальной версии в App Store нет и не предвидится, только .ipa на 4pda.

Этот же друг зная, что я пытаюсь создать игру уже третий месяц предложил мне портировать Катаву на iPad без Jailbreak. Выбор пал на smart BASIC, так как я пользуюсь этой программой уже полтора года и знаю синтаксис её внутреннего языка программирования (также у smart BASIC есть SDK для XCode).

Сразу же был скачан архив со всеми файлами игры кроме текста. Но ни он не я не знали, что это приведет к созданию своего движка для удобства разработки.


image

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

render.txt
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



speak.txt
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
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
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



Результат:

image

«Но ведь в Катаве может говорить два персонажа одновременно!» Для этого есть отдельная команда:»2|перс1&перс2|текст1&текст2»:
2|Хисао&Лилли|«Привет!»&«Здравствуйте!»

Добавим в render.txt строки:

render.txt
...
show:
...
if info$(y,0) = 2 then gosub together
...
together:
{Scripts/Ren_sB/Functions/Double_Speak.txt}
return



Double_Speak.txt выполняет данные команды:

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


Результат:

image

На этом первый день разработки окончается, а значит оканчается и первая часть моей статьи. Во второй части я опишу второй день, а именно добавлю графику (спрайты, фоны и ивенты) и музыку.

Спасибо всем тем, кто дочитал до конца статьи. До встречи в следующей части!

© Habrahabr.ru