Повідомлення про помилку в Roblox Studio майже завжди говорить про типи: очікувалося одне, прийшло інше. Дитина читає «attempt to perform arithmetic on a Vector3» і бачить набір латині, хоча насправді гра пояснила проблему досить точно — просто мовою, якої ще не знають.
Типів у Roblox небагато, і половина з них є в будь-якій мові. Решта — власні, придумані саме для тривимірного світу, і саме вони спочатку плутають. Нижче довідник по всіх, з якими дитина зустрінеться в перший рік, і розбір чотирьох місць, де вони підводять.
Звичайні типи
| Тип | Що це | Приклад |
|---|---|---|
| number | число, ціле або дробове | 12, 0.5 |
| string | текст у лапках | 'Olia' |
| boolean | так або ні | true, false |
| nil | нічого, порожнє місце | nil |
| table | набір значень | {1, 2, 3} |
Перша несподіванка — числа тут одні на всі випадки. Ділення не вимагає окремого типу, і 10 / 4 чесно дає 2.5. Для кількості життів або очок це іноді шкодить: після кількох операцій у змінній опиняється щось на кшталт 2.9999, і на екрані з'являється смішне число. Лікується округленням перед показом.
Типи, яких немає в інших мовах
| Тип | Для чого | Як створити |
|---|---|---|
| Vector3 | точка або розмір у просторі | Vector3.new(4, 1, 4) |
| CFrame | місце разом з поворотом | CFrame.new(0, 10, 0) |
| Color3 | колір | Color3.fromRGB(255, 0, 0) |
| UDim2 | розмір елемента інтерфейсу | UDim2.new(0.5, 0, 0.2, 0) |
| Enum | значення зі списку | Enum.Material.Neon |
| Instance | сам об'єкт сцени | Instance.new('Part') |
Ці шість рядків пояснюють більшість помилок у перших скриптах. Розмір деталі — не число, а Vector3 з трьох чисел, тому chastyna.Size = 4 не працює. Матеріал — не рядок, тому chastyna.Material = 'Neon' теж падає, потрібне значення з Enum.
Різниця між Vector3 і CFrame варта окремого слова. Перший описує тільки місце або розмір, другий — місце разом з поворотом. Поки об'єкт не крутиться, вистачає Vector3; щойно потрібно розвернути, беруть CFrame, і тоді переміщення з поворотом відбувається одним присвоєнням, без ривка.
Enum: список, з якого вибирають
Там, де значень небагато і вони заздалегідь відомі, Roblox не приймає ні числа, ні тексту — тільки значення зі свого переліку. Матеріали, клавіші, стани гравця, типи освітлення влаштовані саме так.
chastyna.Material = Enum.Material.Wood
if klavisha == Enum.KeyCode.E then
print('натиснуто E')
end
Виглядає довго, зате помилитися майже неможливо: Studio підказує продовження після кожної крапки, а неіснуюче ім'я одразу підсвічується. Текстовий рядок такої перевірки не дав би, і помилку в слові знайшли б уже під час гри.
Шукати потрібне значення зручніше не в довідниках, а прямо в панелі властивостей: там ці ж списки показані випадаючим меню, і назва в коді збігається з підписом у панелі.
Колір: два способи і головна плутанина
Колір створюється двома різними функціями, і вони приймають різні числа. Це найчастіша дрібна помилка в проєктах з інтерфейсом.
Color3.fromRGB(255, 0, 0) -- червоний, числа від 0 до 255
Color3.new(1, 0, 0) -- той самий червоний, числа від 0 до 1
Якщо переплутати і написати Color3.new(255, 0, 0), помилки не буде: гра прийме число й обріже його до максимуму. Результат виглядатиме просто занадто яскравим, а причина при цьому ніде не вказана. Порада проста: брати fromRGB завжди, бо саме в цих числах кольори записані в усіх палітрах та редакторах.
UDim2: чому інтерфейс їде на чужому екрані
Тип для інтерфейсу влаштований складніше за решту, і причина в тому, що екрани в гравців різні. Кожне з двох вимірювань задається парою чисел: частка від екрана і точна кількість пікселів зверху.
knopka.Size = UDim2.new(0.5, 0, 0.1, 0) -- половина ширини екрана
knopka.Size = UDim2.new(0, 200, 0, 50) -- рівно 200 на 50 пікселів
Перший варіант підлаштовується: на телефоні кнопка буде вужчою в пікселях, але так само займе половину ширини. Другий завжди однаковий, і на маленькому екрані може зайняти його весь.
Звідси проста порада: розміри й положення великих елементів задавати частками, а дрібні відступи — пікселями. Проєкт, зроблений цілком у пікселях, на комп'ютері автора виглядає бездоганно, а на телефоні розсипається, і зрозуміти причину без цього пояснення майже неможливо.
Арифметика з власними типами
Vector3 можна додавати і віднімати, і це найзручніший спосіб зсунути об'єкт відносно поточного місця:
chastyna.Position = chastyna.Position + Vector3.new(0, 5, 0)
А ось додати до Vector3 звичайне число не можна — саме про це говорить помилка з початку статті. Множити на число, навпаки, дозволено: так вектор подовжують або вкорочують, зберігаючи напрямок.
Корисна дрібниця, про яку рідко розповідають: у вектора є Magnitude — його довжина. Різниця двох позицій, узята за довжиною, дає відстань між об'єктами, і це найкоротший спосіб перевірити, чи близько гравець:
local vidstan = (gravec.Position - dveri.Position).Magnitude
if vidstan < 10 then
print('гравець поруч')
end
Таблиці нумеруються з одиниці
Ось місце, де Lua відрізняється майже від усіх мов, які дитина побачить потім. Перший елемент має номер один, а не нуль.
kolory = {'red', 'green', 'blue'}
print(kolory[1]) -- red
print(#kolory) -- 3
Значок решітки перед іменем дає довжину. Звикнути до нумерації з одиниці легко, складніше буде потім: у Python і більшості інших мов рахунок знову з нуля, і на переході діти плутаються вдруге. Ми на заняттях цю різницю проговорюємо заздалегідь, щоб вона не виглядала чиєюсь помилкою.
Таблиця вміє й другу роль — зберігати пари «ім'я і значення», як словник. Тоді звертаються не за номером, а за ключем: gravec['ochky'] або коротше gravec.ochky. Обидві ролі живуть в одному типі, і це одна з причин, чому таблиця в Lua зустрічається буквально всюди.
nil і найчастіша помилка в усьому Studio
Значення nil означає «тут нічого немає». Його повертає пошук, який нічого не знайшов, і воно ж лежить у змінній, якій нічого не присвоїли.
Повідомлення attempt to index nil value — найпоширеніше в дитячих проєктах. Читається воно так: «ти попросив щось у порожнечі». Зазвичай причина одна з трьох: ім'я об'єкта написане з помилкою, об'єкт лежить не там, де його шукають, або скрипт звернувся до нього раніше, ніж той з'явився.
local dveri = workspace:FindFirstChild('Dveri')
if dveri then
dveri.Transparency = 0.5
end
Перевірка перед використанням знімає цю помилку повністю. Звичка писати її одразу коштує одного рядка, а економить години пошуку — і далі точно так само працює в будь-якій мові.
Перетворення між текстом і числом
Текст і число в Lua не змішуються самі, і спроба скласти їх дає помилку. Для перекладу є дві короткі функції.
local ochky = 120
print('Балів: ' .. tostring(ochky))
local vvedeno = '42'
print(tonumber(vvedeno) + 8) -- 50
Дві крапки підряд — це склеювання тексту, аналог плюса в інших мовах. Запам'ятовується не одразу, зате потім впізнається миттєво: якщо в рядку видно дві крапки, значить збирається повідомлення.
Функція tonumber на тексті, який числом не є, повертає nil, а не падає. Це зручно для перевірки введеного: якщо результат порожній, користувач написав не те. Таку перевірку варто ставити скрізь, де гравець може щось набрати сам.
Як дізнатися, що саме прийшло
Коли незрозуміло, з чим скрипт має справу, є функція typeof. Вона знає й звичайні типи, і власні типи Roblox:
print(typeof(5)) -- number
print(typeof(Vector3.new(1, 2, 3))) -- Vector3
print(typeof(workspace.Part)) -- Instance
Поруч живе схожа функція type без закінчення, але вона простіша і на Vector3 відповість просто userdata, що нічого не пояснює. Для Roblox завжди беруть typeof.
Для об'єктів сцени корисніша перевірка IsA: запис obj:IsA('BasePart') відповідає, чи є об'єкт деталлю, і саме її ставлять у циклах по вмісту моделі, щоб не намагатися закріпити звук.
Типи лежать в основі всього іншого: властивостей, подій, інтерфейсу. Синтаксис, у якому вони живуть, розібраний у довіднику Lua, список властивостей деталі — тут, а як змусити деталь реагувати, показано в довіднику подій.