{}const=>[]async()letfn</>var
РазработкаLua

Как работает скриптинг в играх на Lua

Почему персонаж получает урон от лавы, дверь открывается после нажатия кнопки, NPC внезапно решает напасть, а квест понимает, что вы наконец-то нашли тот самый ржавый ключ? Очень часто за такими механиками стоит скрипт.

К

Кодик

Автор

6 мин чтения

🎭 Lua — не игровой движок

Когда говорят, что в игре используется Lua, это не значит, что Lua сам рисует графику, считает столкновения и отправляет данные на видеокарту.

Обычно большая часть движка написана на C или C++, а Lua встраивается внутрь как язык для игровой логики.

Игровой движок
├── графика
├── физика
├── звук
├── сеть
├── анимации
└── Lua-скрипты
    ├── NPC
    ├── квесты
    ├── оружие
    ├── способности
    └── игровые события

Движок говорит: «Я умею создавать объекты, рассчитывать физику и показывать игроку мир».

Lua отвечает: «Отлично. Тогда когда игрок подойдёт к сундуку, открой его, выдай золото и включи подозрительно эпичную музыку».

🤝 Идеальное разделение обязанностей.

🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

🧠 Как Lua вообще получает доступ к игре

Сам по себе Lua не знает, что такое игрок, NPC, оружие или дверь. Эти возможности ему предоставляет игровой движок.

Допустим, внутри движка есть функция:

void SpawnEnemy(float x, float y) {
    // создание противника
}

Разработчики могут зарегистрировать её в Lua, после чего скрипт сможет написать:

spawnEnemy(100, 250)

Внутри произойдёт примерно следующее:

Lua-скрипт
↓
spawnEnemy(100, 250)
↓
API игрового движка
↓
код C/C++
↓
создание противника
↓
игровой мир

Именно поэтому Lua в разных играх выглядит немного по-разному. Сам язык один, а API движков отличается.

Где-то здоровье меняют так:

player:SetHealth(100)

Где-то так:

player.health = 100

А в другой игре вообще:

Game.SetPlayerHealth(playerId, 100)

Lua тот же. Возможности, которые ему дал движок, разные.

🔥 Скрипты чаще всего реагируют на события

Игровой код постоянно реагирует на происходящее:

  • игрок нажал кнопку;

  • персонаж вошёл в зону;

  • пуля попала во врага;

  • NPC заметил игрока;

  • таймер закончился;

  • предмет подобрали;

  • квест завершился.

Например, у нас есть лава:

function onCollision(object)
    if object.type == "player" then
        object.health = object.health - 20
    end
end

Важно: Lua сам не вычисляет столкновение.

Физический движок замечает контакт и вызывает нужный Lua-скрипт:

Физический движок
↓
обнаружил столкновение
↓
вызвал onCollision()
↓
Lua уменьшил здоровье
↓
игра обновила состояние игрока

Такой подход называют event-driven — событийным программированием.

⚡ Что происходит каждый кадр

У игры есть главный цикл. Очень сильно упрощённо он выглядит так:

while игра запущена:
    прочитать ввод
    обновить физику
    обновить игровую логику
    выполнить нужные скрипты
    обновить анимации
    нарисовать кадр

Если игра работает в 60 FPS, этот цикл выполняется примерно 60 раз в секунду.

Некоторые Lua-скрипты тоже могут получать обновление каждый кадр:

function update(dt)
    enemy.x = enemy.x + speed * dt
end

dt — время между текущим и предыдущим кадром.

Благодаря ему скорость объекта не должна внезапно зависеть от мощности компьютера.

Иначе покупка новой видеокарты могла бы случайно разблокировать персонажу турборежим. 🏎️

👾 Как на Lua делают поведение NPC

Даже простой противник уже может управляться скриптом:

function update()
    local distance = getDistance(enemy, player)
    if distance < 10 then
        enemy:attack(player)
    else
        enemy:patrol()
    end
end

Если игрок близко — атакуем. Если далеко — продолжаем патруль.

Более сложный NPC может иметь состояния:

PATROL
↓
PLAYER_DETECTED
↓
CHASE
↓
ATTACK
↓
SEARCH
↓
PATROL

На Lua это можно представить примерно так:

state = "patrol"
function update()
    if state == "patrol" and canSeePlayer() then
        state = "chase"
    elseif state == "chase" and closeToPlayer() then
        state = "attack"
    end
end

Поэтому, когда NPC полкарты преследует именно вас, не принимайте близко к сердцу.

У него просто:

state = "attack"

😔

📦 Почему таблицы Lua так удобны для игр

Одна из главных структур данных Lua — table.

Она может работать как массив:

inventory = {
    "sword",
    "potion",
    "key"
}

Как словарь:

player = {
    health = 100,
    mana = 50,
    level = 7
}

И даже как что-то похожее на объект:

enemy = {
    health = 100,
    damage = 15
}
function enemy:attack()
    print("Enemy attacks!")
end

Поэтому на Lua удобно хранить игровые параметры.

weapons = {
    pistol = {
        damage = 20,
        fireRate = 0.4
    },
    shotgun = {
        damage = 70,
        fireRate = 1.2
    },
    sniper = {
        damage = 150,
        fireRate = 2
    }
}

Нужно ослабить дробовик? Поменяли число.

Нужно усилить снайперку? Поменяли ещё одно.

Не обязательно лезть глубоко в код движка.

🪄 Почему скрипты удобно менять во время разработки

Одно из преимуществ Lua — возможность быстро менять игровую логику.

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

damage = 20

Меняем на:

damage = 50

Сохраняем — проверяем.

Разработчик через два часа:

damage = 999999

«Это не чит. Я баланс тестирую».

⏳ Что делать с задержками, диалогами и кат-сценами

Представьте NPC, который должен сказать фразу, немного подождать, открыть дверь и уйти.

В игровом коде нельзя просто остановить всю программу на две секунды. Иначе вместе с NPC замрёт весь игровой мир.

Для таких сценариев используют:

  • таймеры;

  • callbacks;

  • планировщики задач;

  • асинхронные механики движка;

  • coroutines.

В Lua есть корутины — функции, выполнение которых можно временно приостановить, а затем продолжить.

Это удобно для:

  • диалогов;

  • кат-сцен;

  • анимационных последовательностей;

  • миссий;

  • скриптовых событий.

🛡️ Может ли Lua-скрипт получить доступ ко всему компьютеру

Если игра поддерживает пользовательские скрипты или моды, давать им полный доступ к системе было бы очень плохой идеей.

Поэтому разработчики часто запускают Lua внутри sandbox — ограниченной среды.

Опасные функции могут быть отключены, а вместо них скрипт получает только игровое API:

Game.Spawn()
Game.PlaySound()
Player.GetPosition()

Так Lua может управлять игровым миром, но не получает полный доступ к операционной системе.

🌐 А что происходит в мультиплеере

Вот тут появляется легендарный вопрос:

«А на сервере ты это проверил?»

Допустим, клиентский скрипт отправляет:

player.money = 999999999

Если сервер просто поверит клиенту, внутриигровая экономика закончится ещё до открытия магазина.

Поэтому важные действия должны подтверждаться сервером.

Игрок может сказать:

«Я хочу купить меч».

Но сервер проверяет:

if player.money >= sword.price then
    buySword(player)
end

То есть клиент просит, а сервер решает, можно ли выполнять действие.

🎲 Где находятся Lua-скрипты в игре

Зависит от конкретного движка.

Это могут быть обычные файлы:

scripts/enemies/zombie.lua
scripts/quests/intro.lua
scripts/weapons/rifle.lua

А могут быть скрипты, привязанные прямо к объектам внутри редактора.

Zombie
├── model
├── animations
├── sounds
└── zombie.lua

Когда движок создаёт зомби, он подключает его скрипт поведения.

Без него это была бы просто красивая модель, которая стоит посреди карты и размышляет о смысле существования.

🧩 Lua связывает игровые системы между собой

Это, пожалуй, главное.

Представьте способность «огненный шар»:

function castFireball(player)
    playAnimation(player, "cast")
    local fireball = spawnProjectile()
    fireball:onHit(function(enemy)
        enemy:takeDamage(50)
        playEffect("explosion")
        playSound("boom")
    end)
end

Один небольшой скрипт связывает:

  • анимацию;

  • физику;

  • урон;

  • частицы;

  • звук;

  • игровую механику.

Lua не обязан самостоятельно реализовывать все эти системы.

Он управляет ими через API игрового движка.

🎼 Движок — оркестр.

🎩 Lua — дирижёр, который решает, когда должен произойти BOOM.

🧪 Мини-проект: комната с противниками

Представим комнату:

  • игрок входит;

  • дверь закрывается;

  • появляются три противника;

  • игрок побеждает их;

  • кнопка становится активной;

  • нажатие кнопки открывает дверь.

На Lua логика могла бы выглядеть так:

local enemiesAlive = 3
function onPlayerEnter()
    door:close()
    spawnEnemy()
    spawnEnemy()
    spawnEnemy()
end
function onEnemyDeath()
    enemiesAlive = enemiesAlive - 1
    if enemiesAlive == 0 then
        button:enable()
    end
end
function onButtonPressed()
    door:open()
end

Вот это и есть игровой скриптинг в чистом виде.

Вы не пишете физический движок двери. Не программируете рендер врага. Не рассчитываете каждый пиксель.

Вы описываете что должно произойти и при каких условиях.

🚀 Где попробовать Lua самому

Есть классическая ловушка любого новичка:

Читаешь статью: «Да, всё понятно».
Открываешь редактор: |
Мозг: «Мы вообще знакомы?»

Поэтому программирование лучше всего закреплять практикой.

В Кодике можно изучать программирование через короткие уроки и сразу решать задачи в редакторе. Это помогает не просто читать про переменные, условия, функции и таблицы, а использовать их в коде своими руками.

Если хочется разобраться в Lua, игровой логике или просто прокачать базовые навыки программирования, такой формат намного полезнее бесконечного просмотра теории.

А ещё у Кодика есть Telegram-сообщество, где выходят полезные посты про программирование, технологии, языки и разработку. Удобный способ регулярно повторять материал: увидел пример, вспомнил концепцию, сохранил что-то полезное и пошёл дальше. 📱

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты