Stormworks: Build and Rescue

Stormworks: Build and Rescue

Not enough ratings
Lua с нуля
By lastgooh
В этом руководстве мы подробно рассмотрим основной функционал swLua, который в дальнейшем пригодится для создания мониторов и различных приколюх.
   
Award
Favorite
Favorited
Unfavorite
Введение
Ещё летом я хотел сделать гайды по stormworks, но в формате видео. Из-за последних событий с блокировкой ютуба это дело пришлось отложить, как и сам ютуб.
Зайдя в сообщество stormworks'a я увидел, что подробных гайдов по луа, для тех, кто его видит впервые, на русском языке вообще нет (на других языках не проверял). Есть 1 гайд, в котором подробно расписана большая часть функционала, но он направлен на людей, которые, по большей части, уже разобрались. У многих появляется только больше вопросов.

Здесь я постараюсь объяснить что и как работает.

Сильно не ругайте, моё первое руководство.
др 🎃

справочник
операции
  • = присвоение (задание переменной слева значения (значения переменной) справа)
  • == равно
  • ~= не равно
  • > больше
  • < меньше
  • >= больше или равно
  • <= меньше или равно
  • + сложение
  • - вычитание
  • * умножение
  • / деление
  • // деление без остатка (без знаков после точки и округления)
  • ^ возведение в степень
  • % остаток от деления (знаки после точки без знаков перед точкой)
      логические операции
    • and (если оба условия верны)
    • or (если одно условие из 2 верно или оба)
    • not (меняет значения условия на противоположное true-false/false-true)


основные понятия
•Символ-одна буква/цифра/специальный знак/пробел

•Переменная-набор символов без пробелов (имя ячейки), может хранить в себе какую либо информацию.

•Тип переменной-характер хранимой информации:
основные из них для sw Lua:
-bool ( true / false )
-number (3.14/-912...) десятичные дроби (числа с плавающей запятой) отделяются не запятой а точкой
-string ("hello the reader")
bool имеет 2 состояния true или false. Проще говоря true - разрешает false - запрещает.
number несёт в себе числовую информацию
string хранит в себе набор символов записанный в кавычках "...."

также переменная может быть локальной (перед названием переменной через пробел пишется "local") локальная переменная доступна только в своей области видимости (область видимости тело функции/цикла/ветвления) вне области видимости переменной как будто и не было
local a = 10


•Массив-таблица, хранящая информацию в ячейках, каждая со своим номером, их количество указывается при создании массива. Номер ячейки всегда начинается с 1, т.е. ячейка с индексом 1 будет первой.

•Функция-блок кода, выполняющий определённые действия

•Цикл-конструкция кода, выполняемая несколько раз за 1 проход

•Комментарий-письменная необязательная часть кода, не выполняющая какую-либо функцию. Комментарии необходимы для удобства работы с кодом пишутся в конце строки после двух тире --

•Присвоение-задание переменной значения, осуществляется с помощью =

•оператор ветвлений
1 вариант if (условие) then (действие) end (если условие не соблюдается то действие игнорируется) 2 вариант if (условие) then (действие если условие соблюдается) else (действие если условие не соблюдается) end 3 вариант if (условие 1) then (действие если условие соблюдается) elseif (условие 2) then (действие если условие 1 не соблюдается но соблюдается условие 2) elseif (условие 3) then (действие если условие 1 и 2 не соблюдается но соблюдается условие 3) end 4 вариант if (условие 1) then (действие если условие соблюдается) elseif (условие 2) then (действие если условие 1 не соблюдается но соблюдается условие 2) elseif (условие 3) then (действие если условие 1 и 2 не соблюдается но соблюдается условие 3) else (действие если ни одно из условий не соблюдается) end

напоминание
1-тип переменно bool
2-тип переменной string
3-тип переменной number
4-массив
5-третья ячейка массива (выведет hi)
мониторы
На данный момент в игре существуют 8 типов мониторов
размерами от 1х1 до 9х5

Часть монитора в один блок имеет разрешение 32х32 пикселя, т.е. монитор 1х1 блок будет иметь разрешение 32х32 пикселей, а монитор 9х5 разрешение 288х160 пикселей.

На мониторах есть стрелочка, которая указывает верхнюю часть.



Все мониторы имеют:
  • 1 вход on/off для включения и выключения
  • 1 вход видео для получения изображения из элемента луа или камеры
  • электрический вход питания
  • 1 композитный выход из которого передаются:
    • нажатие (1 канал тип bool )
    • координату нажатия по оси х (3 канал тип number)
    • координату нажатия по оси у (4 канал тип number)

создаём микроконтроллер
Переходим в раздел микроконтроллеров и создаём новый микроконтроллер

Для взаимодействия с монитором будет достаточно одного композитного входа (composite input) и одного выхода видео (video output)
Для вывода изображения будет достаточно одного video output

Далее переходим в раздел логики где уже и будем писать свой код в логическом элементе

В разделе поиска ищем "lua" и создаём этот элемент


Рассмотрим его

Этот элемент имеет 2 входа и выхода для композита и видео.

На входы композита мы подключаем все "внешние источники информации" с которыми будем взаимодействовать/получать значения, а также монитор.

На входе видео можно подключить камеру или другой блок Lua.

На выходе композита будет та информация, которую мы укажем внутри кода.

На выходе видео будет изображение, из функции рисования, совмещённое с изображением поданным на вход видео(если есть).

(пример возможного подключения)





Соответственно подключаем входы и выходы и открываем редактор кода
структура кода
В открывшемся окне мы видим это чудо:


Под цифрами 1 и 2 указаны функции в которых будут происходить все вычисления и рисование (их не трогаем).
Под цифрой 3 указано ограничение количества символов в коде. Поэтому при написании чего-то объёмного стоит сокращать свой код и следить за ними.
В случае если всё же символов не хватает можно будет добавить второй элемент lua и связать его вместе с первым.
4 кнопка проверки кода. Выдаст ошибку (если есть) с указанием строки и характером ошибки (не поставленный end в конец функций, циклов или операторов ветвлений / лишняя скобка)


Во вкладке script мы пишем сам код
Во вкладке help небольшой справочник по коду


1) function onTick()
Здесь пишутся различные операции с данными.
Всё что находится в этой функции (от её начала до последнего end) будет выполняться с каждым игровым кадром 1 фпс это один тик. Т.е. если у вас 60 фпс то частота выполнения кода 60 тиков (60 раз в секунду).

Считывание кода идёт построчно, оно может пригодиться.

2) function onDraw()
В этой функции мы "рисуем" (рассмотрим в дальнейшем). В отличии от function onTick() выполняется с каждой отрисовкой дисплея и зависит от количества подключённых мониторов.

А теперь рассмотрим визуально:



красное-function onTick()
жёлтое-function onDraw()

1-объявление переменных значение которым будет выдано только при запуске кода и в последствии может меняться
2-своя функция
3-объявление переменных значение которых меняется из вне, а также в этом же "блоке" можно назначать выходы
4-тело функции - то, что она будет выполнять
ввод/вывод переменных(данных)
Переменные можно объявлять как вне функции, так и в самой функции.


1-объявление вне функции, значения переменным присваиваются при запуске кода и может меняться в зависимости от вычислений в функциях.
2-объявление в функции, значения переменным присваиваются при каждой работе функции.

Т.е. если объявление в function onTick() будет иметь вид number=546, то с каждым тиком значение number будет снова присваиваться 546.

Функция получающее значение из определённого канала, номер которого мы пишем в скобках:
  • input.getBool(4) - получает значение true или false из 4 канала
  • input.getNumber(10) - получает число из 10 канала

Функция вывода знчения, в скобках пишется канал вывода а за ним через запятую название переменной, значение которой мы хотим вывести:
  • output.getBool(6, lock) - выведет в 6 канал значение переменной "lock" true или false
  • output.getNumber(3, x) - выведет в 3 канал число записанное в переменной "x"

рисование function onDraw()

w = screen.getWidth() - получение ширины монитора
h = screen.getHeight() - получение высоты монитора

Для задания цвета используем
screen.setColor(r, g, b)
r-красный (0-255) g-зелёный(0-255) b-синий(0-255)
Также можно добавить четвёртый параметр прозрачности
screen.setColor(r, g, b, a)
a-прозрачность(0-255) чем меньше значение прозрачности, тем более прозрачен объект и хуже виден на фоне других

setСolor() распространяется на элементы под ним, до конца кода или до нового setСolor()



Нижняя строчка имеет приоритет выше верхней







Рисуем мы по координатам пикселей монитора, отсчёт которых начинается с левого верхнего края

чтобы было более нагляднее в function onDraw() я написал цикл который рисует прямые шириной 1 пиксель с интервалом 1 пиксель по оси х

for i=1,97,2 do screen.drawLine(i, 0, i, 64) end


Добавлю прозрачность и прямые по у

Теперь пиксели(координаты) монитора более наглядны

screen.setColor(70,70,70,170) for i=1,97,2 do screen.drawLine(i, 0, i, 64) screen.drawLine(0, i, 96, i) end


Основное что нам пригодиться для рисования можно взять во кладке help, там же указаны какие параметры вписывать для создания фигуры/строки
например
screen.drawLine(x1, y1, x2, y2)
функция рисующая прямую
x1 y1 - координаты первой точки прямой
x2 y2 - координаты второй точки прямой

для вывода переменной используем
screen.drawText(x, y, text) или screen.drawTextBox(x, y, w, h, text, h_align, v_align)
drawText будет заполнять всю строку после координат записанных в функции

drawTextBox заполняет область указанную в первый четырёх параметрах (х и у соответственно координаты, а w и h ширина и высота "коробки" которую будет заполнять текст)
h_align и v_align выравнивание по краям

напишем такой код
screen.drawTextBox(20, 20, 30, 30, "hi", 0, 0) screen.drawRect(20, 20, 30, 30)
screen.drawRect(20, 20, 30, 30)-функция рисующая квадрат и в данном случае он дублирует невидимую область "коробки" текста

выравниение
h_align
v_align
без выравнивания
0
0
по правому краю
1
0
по левому краю
-1
0
по нижнему краю
0
1
по верхнему краю
0
-1

для выравнивания по углам комбинируем

для вывода информации из переменной пишем название переменно вместо text
для вывода статичного текста ставим кавычки "..."

screen.drawText(10, 20, number) - вывод переменной screen.drawText(29, 10, "hi") - вывод текста

Можно записать значения в одну строку, для этого нужно использовать .. между переменными / строками (без этого будет ошибка)




Вывод информации на монитор
Для того чтобы вывести информацию на монитор (например топливо/скорость/температура) нам нужно внести эту информацию в код.

Вводится информация с помощью присвоения переменной канала композитного сигнала
temperature=input.getNumber(1) speed=input.getNumber(2) fuel=input.getNumber(3)
в случай использования тачскрина на 3 и 4 числовых(getNumber) каналах будут координаты точки нажатия на экран, поэтому стоит начинать отсчёт каналов, при создании микроконтроллера, с 5.
на 1 булевом канале или же on/off true/false (getBool) нажатие на экран, при создании микроконтроллера сигнал on/off начинаем со 2 канала


Далее выводим эти переменные на экран в function onDraw()
screen.drawText(30,10,temperature) screen.drawText(30,20,speed) screen.drawText(30,30,fuel)


Для более привлекательного внешнего вида можно добавить надписи перед числами и также убрать знаки после точки, чтобы не было такого бесконечного значения

выводить информацию можно как текстом
screen.drawText(30,10,temperature) screen.drawText(30,20,speed) screen.drawText(30,30,fuel) screen.drawText(2,10,"temp") screen.drawText(2,20,"value") screen.drawText(2,30,"fuel")

так и с помощью текстбокса

При его использование текст полностью заполняет описанный в параметрах квадрат
Чтобы такого не было стоит использовать функцию
math.foolr(переменная) - выводит целое число перед точкой(не округляет) или math.ceil(переменная) - округляет


screen.drawTextBox(30,10,20,7,temperature,-1,0) screen.drawTextBox(30,20,20,7,math.ceil(speed),-1,0) screen.drawTextBox(30,30,20,7,fuel,-1,0) screen.drawText(2,10,"temp") screen.drawText(2,20,"value") screen.drawText(2,30,"fuel")

Если же нам нужно сохранить определённое количество знаков после запятой, то используем
string.format("%.nf", переменная) - вместо n указываем число, это число будет обозначать количество символов после запятой

На скриншотах видно как заполняется наш "бокс" 21.67

function onTick() temperature=input.getNumber(1) speed=input.getNumber(2) fuel=input.getNumber(3) end function onDraw() screen.drawTextBox(30,10,20,7,temperature,-1,0) screen.drawTextBox(30,20,20,7,string.format("%.2f",speed),-1,0) screen.drawTextBox(30,30,20,7,fuel,-1,0) screen.drawText(2,10,"temp") screen.drawText(2,20,"value") screen.drawText(2,30,"fuel") end

Также можно преобразовать переменную перед её выводом (например для перевода единиц измерения)
screen.drawText(2,10,battery*100) - получим перевод заряд батареи из 1 в 100% output.getNumber(2, battery*100)
function onTick()
Здесь мы будем работать с информацией.

Обработка информации

В зависимости от ваших целей может понадобиться обработка информации. Для этого используются: функции, циклы, операторы ветвлений


ветвление

Оператор ветвлений представляет собой структуру из ключевых слов, условия и действия

if a then b=true else b=false end
возможные варианты ветвления приведены во втором разделе "справочник"

if then else end - ключевые слова
a - условие при котором выполняется действие присвоения b=true
b=true/false - действие

if - переводится как "если" then - "тогда" else - "иначе" end - закрывает конструкцию

"если условие "а" (верно/true) тогда "б" присваивается значение правда (верно/true) иначе(если условие а не верно) "б" присваивается значение ложь (не верно/false)"

Если к условию добавить not то действие будет выполняться при неверном "а"

В качестве условия можно использовать функции и переменные, в качестве действий можно использовать циклы или ещё одно ветвление

Условий и действий может быть несколько

if a or b and k==1 then --если "а"-верно или "б"-верно и "к" равно 1 x=x+1 lock=false --тогда к "x" прибавляем 1 и "lock" присваивается false end

циклы

Цикл это конструкция кода которая выполняется полностью в течении 1 выполнения кода.

(i - переменная, может иметь любое название, просто я возьму i)

тело - то что выполняется в ходе цикла

1. while - имеет одно значение в условии и выполняется пока оно верно

while условие do тело цикла end

i=0 while i<10 do i=i+0.5 end i=0 if i<10 then i=i+0.5 end

Кому-то покажется что разницы нет, но цикл выдаст все значения и посчитает их за 1 тик (код выполнится полностью один раз), в то время как ветвления будет просчитывать всё с каждым тиком и выдавать одно значение.
Если рассмотреть этот код в промежутке времени 1 тик то:
цикл выдаст: 0.5; 1 ; 1.5 ; ... ; 10
ветвление: 0.5
все циклы выполняются за 1 тик

2.for - аналогично while выполняется пока условие верно, но имеет 3 значения и не требует заранее объявлять переменную

for i=начальное значение, конечное значение, шаг с которым i идёт от начального до конечного значения do тело цикла end
шаг можно не указывать он по умолчанию равен одному

out="" --присвоили тип string for i=0, 5, 0.5 do -- выполнение i от 0 до 5 с шагом 0.5 out=out .. i .. " " -- добавление к строке out по одному символу i и пробелу end

при выводе переменной out мы получим строку 0; 0.5; 1; 1.5; ... ; 5

3. repeat until - сначала делает 1 проход кода затем проверяет условие, если условие ложно то цикл повторяется

repeat тело цикла until условие (если ложно)

i=0 repeat i=i+2 until i<6 --выдаст 2 т.к. он санчала прибавил к i 2, а затем проверил условие, т.к. условие оказалось ложным (i меньше 6 т.к. 2 меньше 6) цикл прекратился -- i объявляем в функции, т.к. при объявлении за функцией i будет бесконечно прибавляться (из-за того что цикл проверяет условие после выполнения) i=0 repeat i=i+2 until i>6 --изменив знак равенства цикл посчитает не до 6, а до 8

Любой цикл можно прервать внутри тела добавив ветвление и break
if условие при котором прервётся цикл then break end

рассмотрим 3 ситуации поведения цикла при остановке:
1.цикла после преобразования i в строку
2.после объявления i за функции
3.после объявления в функцией

1.
2.
3.

в 1-ом случае цикл не останавливается из-за считывания кода построчно (упоминалось в начале)
т.е. i в строчке out=out .. i .. " " меняет тип на string и if i > 5 then break end не видит момента когда i больше 5 т.к. он проверяет числа, а не строки

во 2-ом цикл сработал корректно, т.к. преобразование в строку произошло после проверки условия

в 3-ем строка продолжается бесконечно, т.к. при последующем тике код начинает выполняться заново, i снова присваивается значение 0 и цикл идёт по новой, а из-за того что мы не очищали строку, то к ней прибавляются новые результаты вычислений
создание функций и локальные переменные
Для упрощения и сокращения кода создают функции.

Функция-фрагмент кода, выполняющий какие-либо действия, к которому можно обратиться из любого места кода.

Для создания функции используем "function" и после пробела даём ей название (аналогично с переменнными) и без пробела ставим скобки()
function image()
function название(параметры) тело функции return результат end
функции могут возвращать и не возвращать значение (return)
возврат значения - передача в основной код результата вычисления функции
function image() screen.drawRectF(20,20,20,20) end -- не возвращает значение, при вызове рисует квадрат

1-функция
2-вызов функции (поскольку функция "рисует" то вызываем её в function onDraw())

добавим параметры к функции (параметры - данные которые мы вводим в функцию, с которыми она работает и выдаёт какой-либо результат)

function image(x,y) --параметры которые мы перенесём в тело функции screen.drawRectF(x,y,20,20) -- рисует квадрат 20 на 20 по координатам которые мы введём в параметры end


создадим функцию с локальной переменной
function loc() local a=0 out="" while a<5 do a=a+1 out=out .. a end return out end
при попытке запуска код выдаст ошибку, т.к. в function onDraw() мы пытаемся вывести переменную "a", которая локальная и видна только в нашей функции

function loc() a=0 out="" while a<5 do a=a+1 out=out .. a end return out end
если мы сделаем "а" обычной переменной, то нам выдаст результат цикла

2 Comments
hostbanani Oct 23, 2024 @ 10:30am 
И не забываем правильно оформлять код. Пробелы и табуляция не просто для красоты а еще и для читаемости. Оформляя код для новичков особенно стоит уделять этому внимание. Они и так едва понимают написанное а ты еще и усложняешь не читаемым кодом.
hostbanani Oct 23, 2024 @ 10:29am 
Я понял притенению к моему гайду, но просто читая гайд нельзя ничему научиться. Только почерпнуть базу. Если хочется писать сложные программы нужно например знать что в функцию можно зашить скрытую переменную:

function createFunc()
local n = 0
return function()
n = n + 1
return n
end
end

func = createFunc()

func() --вернет 1
func() --вернет 2

для новичков сложновато но те кто шарят знают насколько это бывает полезно....