Написання програм на Lua

Keira має вбудовану віртуальну машину мови програмування Lua. Це дозволяє писати та виконувати програми на Lua прямо з SD-картки, без необхідності компіляції чи перепрошивання Лілки.

Попередження

Якщо ви не знайомі з мовою Lua, рекомендуємо прочитати туторіал з Lua перед тим, як продовжувати: https://www.lua.org/pil/1.html

Приклад програми

Оскільки Lua сама по собі - це універсальна мова програмування, вона не має вбудованих функцій для роботи з дисплеєм, звуком тощо. Тому для роботи з цими пристроями на Лілці в Keira доступні спеціальні вбудовані модулі, які надають доступ до функцій пристроїв.

Модулі завантажуються автоматично - не потрібно писати жодних require(...). Ось приклад простої програми на Lua, яка виводить текст «Hello, world!» на екран:

 1function lilka::update()
 2    if controller.get_state().a.just_pressed then
 3        -- Завершуємо програму при натисканні кнопки "A"
 4        util.exit()
 5    end
 6end
 7
 8function lilka::draw()
 9    -- Заповнюємо екран чорним кольором:
10    display.fill_screen(display.color565(0, 0, 0))
11
12    -- Виводимо текст "Hello, world!" на екран:
13    display.set_cursor(0, 32)
14    display.print("Hello, world!")
15end

Ви можете зберегти цей код у файл з розширенням .lua на SD-картці, а потім виконати його, обравши його в браузері SD-картки.

Повний перелік доступних модулів та їх функцій можна знайти в розділі Lua API.

init, update, draw

В іграх важливо, щоб гра була плавною та виконувалася з певною стабільною кількістю кадрів на секунду.

Імовірно, ви знайомі з функцією delay(...) з фреймворка Arduino, яка затримує виконання програми на певну кількість мілісекунд. Це зручно для простих програм, що не використовують дисплей, але для ігор це не підходить, оскільки такі функції заблокують виконання програми на певну кількість мілісекунд, незалежно від швидкості виконання програми, а також не дозволяють вам взаємодіяти з грою під час затримки.

Для цього існує можливість визначити функції lilka.init(), lilka.update() і lilka.draw(). Якщо при запуску вашої програми Keira знайде цю функції, вона буде викликати їх автоматично.

  • lilka.init() викликається один раз при запуску програми.

  • lilka.update() викликається 30 разів на секунду, тому ви можете використовувати його для оновлення стану гри та обробки введення користувача.

  • lilka.draw() викликається після lilka.update() та використовується для малювання графіки на екрані.

Попередження

Програма на Lua буде виконуватись доти, доки не буде викликано функцію util.exit(). Інакше для виходу вам доведеться перезавантажити Лілку.

Попередження

Не варто використовувати util.sleep() всередині ваших функцій lilka.update() та lilka.draw(), оскільки це призведе до заповільнення виконання програми.

Намагайтесь писати код, який не блокує виконання програми, а використовує функцію lilka.update() для оновлення стану гри та обробки введення користувача.

lilka.update() також отримує необов’язковий аргумент delta, який вказує (в секундах), скільки часу пройшло з початку її попереднього виклику. Це дозволяє вам робити рухи та анімації, які будуть відбуватись з однаковою швидкістю незалежно від швидкості виконання програми.

За ідеальних обставин, delta буде дорівнювати 1/30, або приблизно 0.0333 секунди, але якщо код гри дуже складний і його виконання займає більше часу, ніж 1/30 мекунди, то значення delta буде більшим. Ваша програма може використовувати delta для того, щоб, наприклад, рухати об’єкти на екрані залежно від часу, а не від кількості кадрів на секунду.

Ці три функції повинні бути визначені у головному файлі програми, наприклад:

 1local ball_x
 2local ball_y
 3
 4-- Завантажуємо зображення м'яча, яке знаходиться в корені SD-картки:
 5local ball = resources.load_image("ball.bmp", display.color565(255, 255, 255))
 6
 7function lilka.init()
 8    -- Ця функція викликається один раз при запуску програми.
 9    -- Цей код можна було б виконати в глобальному контексті (поза цією функцією), як ми це зробили з "ball",
10    -- але ініціалізація гри буде очевиднішою, якщо вона відбувається тут.
11    ball_x = display.width / 2
12    ball_y = display.height / 2
13end
14
15function lilka.update(delta)
16    local dir_x = 0
17    local dir_y = 0
18
19    -- Обробляємо введення користувача:
20    local state = controller.get_state()
21    if state.up.pressed then
22        dir_y = -1
23    elseif state.down.pressed then
24        dir_y = 1
25    end
26    if state.left.pressed then
27        dir_x = -1
28    elseif state.right.pressed then
29        dir_x = 1
30    end
31    if state.a.pressed then
32        -- Вихід з програми:
33        util.exit()
34    end
35
36    -- Переміщуємо м'яч зі швидкістю 50 пікселів на секунду
37    ball_x = ball_x + dir_x * 50 * delta
38    ball_y = ball_y + dir_y * 50 * delta
39end
40
41function lilka.draw()
42    -- Малюємо графіку:
43    display.fill_screen(display.color565(0, 0, 0))
44    display.draw_image(ball, ball_x, ball_y)
45
46    -- Після виконання цієї функції Лілка автоматично відобразить все, що ми намалювали на екрані.
47    -- Не потрібно викликати display.queue_draw() чи щось подібне.
48end
49
50-- Інші функції:
51-- ...

Цей код створить просту програму, в якій ви можете керувати м’ячем за допомогою стрілок на контролері. Кожен кадр гри м’яч переміщується на певну відстань залежно від натиснутих кнопок, а потім малюється на екрані.

Завдяки аргументу delta м’яч завжди рухатиметься з однаковою швидкістю незалежно від того, як швидко виконується програма - чи це 30 кадрів на секунду, чи 10, чи 1000.

Попередження

Ваш код всередині функції lilka.draw() не повинен робити жодних припущень щодо того, що вже було намальовано раніше. Кожен кадр гри потрібно малювати повністю.

Це потрібно тому, що Keira використовує подвійну буферизацію екрану, тобто ваша гра малює на передньому буфері, а в цей час Keira відображає задній буфер на екрані.

Тому радимо починати кожен кадр гри з виклику display.fill_screen(display.color565(0, 0, 0)), щоб очистити передній буфер перед малюванням нового кадру, оскільки він може бути забруднений попереднім кадром.

Швидке тестування програм

Звісно, ви можете зберегти вашу програму на SD-картці, а потім вибрати її в браузері SD-картки, але це може бути доволі незручно, особливо якщо ви працюєте над програмою, яка вимагає багато ітерацій. Щоразу, коли ви зберігаєте програму на SD-картці, ви повинні виймати її з Лілки, вставляти в комп’ютер, зберігати файл, виймати з комп’ютера, вставляти в Лілку, вибирати файл в браузері SD-картки, запускати програму, перевіряти, в͟и̛п͜р͢а̵в̀л͟я̕т̴и͜ ̢п͟о̨м͘и̴л͢к͡и́,͝ ̕з́б͠е͞р͠і͞г͏а̢т͞и̧ ͘з̶н̛о̶в͢у̢,͡ ͡О НІ! Це - нестерпно і в нас немає часу на це!

Саме тому Keira має функцію, яка називається Live Lua. Вона дозволяє вам запускати Lua-код на Лілці через USB-кабель прямо з вашого комп’ютера, без необхідності зберігати його на SD-картці.

Попередження

Якщо ваша Lua-програма завантажує додаткові ресурси з SD-картки - наприклад, зображення, звуки тощо - ви спершу повинні вручну скопіювати ці ресурси на SD-картку, оскільки Live Lua не підтримує завантаження файлів на SD-картку через USB-кабель. Вона надсилає на Лілку лише код Lua.

Попередження

Live Lua підтримує запуск програм, які складаються лише з одного файлу. Якщо ваша програма складається з декількох файлів, які завантажуються через require(), вам потрібно спершу скопіювати всі модулі на SD-картку, а потім використовувати Live Lua для запуску головного файлу програми.

Щоб використовувати Live Lua, вам потрібно встановити розширення для Visual Studio Code, яке називається Serial Monitor:

../../../_images/serial_monitor.png

Після цього підключіть Лілку до комп’ютера за допомогою USB-кабеля та перейдіть в меню «Розробка» -> «Live Lua». Лілка перейде в режим «Live Lua» та очікуватиме код Lua через USB-порт.

Далі створіть новий файл з розширенням .lua - наприклад, test.lua. Напишіть в ньому якийсь код:

1display.fill_screen(display.color565(255, 0, 0))
2display.set_cursor(50, 50)
3display.print('Привіт, Лілка!')
4display.queue_draw() -- Потрібно викликати цю функцію, щоб відобразити зміни на екрані, оскільки цей код знаходиться поза функцією lilka.draw
5util.sleep(1)

Тепер перейдіть на вкладку «Serial Monitor» у VS Code, оберіть COM-порт, на якому підключена Лілка, та натисніть «Start Monitoring»:

../../../_images/select_com_port.png

Після цього натисніть правою кнопкою будь-де в коді на вкладці з вашим файлом test.lua та оберіть «Send to Serial Monitor» -> «Send Entire File»:

../../../_images/send_entire_file.png

Вжух - і код миттєво запуститься на Лілці! Якщо ви зробите зміни в коді, ви можете просто натиснути «Send to Serial Monitor» -> «Send Entire File» знову, і новий код запуститься на Лілці. Слід лише переконатись, що попередня версія вашої програми завершилася, перш ніж ви надсилати нову версію.

Інтерактивна консоль Lua (REPL)

REPL означає «Read-Eval-Print Loop». Це - інтерактивне середовище, яка дозволяє вам друкувати код на клавіатурі та виконувати його на Лілці прямо з вашого комп’ютера через USB-кабель, по одному рядку коду за раз.

Це зручно для тестування функцій, вивчення API, відлагодження тощо.

Для запуску Lua REPL вам потрібно відкрити на Лілці меню «Розробка» -> «Lua REPL». Лілка перейде в режим «Lua REPL» та очікуватиме команди через USB-порт.

Для цього відкрийте вкладку «Serial Monitor» у VS Code, оберіть COM-порт, на якому підключена Лілка, натисніть «Start Monitoring» і почніть вводити код Lua. Після кожної команди тисніть Enter. Вона виконається на Лілці, і ви побачите результат в консолі Visual Studio Code.

Спробуйте ввести, наприклад, ось такий код:

1print(3 + 4)

Після введення цього рядка ви маєте побачити в консолі результат виразу 3 + 4, тобто число 7.

Тепер спробуйте ввести цей код:

1display.fill_screen(display.color565(255, 0, 0))

Після введення другого рядка та натиску на клавішу Enter, екран Лілки має забарвитися червоним кольором.

Щоб вийти з режиму REPL, натисніть на Лілці кнопку A.