В последнее время я работаю над SIMPLE Physics, набором образовательных тренажеров по физике, призванных помочь преподавать и изучать физику интуитивно без дорогостоящего лабораторного оборудования или личных занятий. Каждый симулятор позволяет пользователям импортировать и экспортировать сцены и потенциально добавлять некоторые дополнительные функции через Lua.
До недавнего времени сценарии Lua были весьма ограничены. Его можно было использовать для добавления/удаления объектов и изменения переменных, например, но, что особенно важно, он не мог влиять на объекты после их создания. Гравитация, а также была функция update()
, которая запускалась в каждом кадре.
-- this example instantiates a multicolored grid of circles for row = 1,HEIGHT do for col = 1,WIDTH do color = { r = (row * col) / (WIDTH * HEIGHT) * 255, g = col / WIDTH * 255, b = row / HEIGHT * 255 } add_shape{ shape = "circle", x = col * OFFSET + START_X_OFFSET, y = row * OFFSET, r = RAD, mass = 1, color = color } end end
Важно отметить, что не было возможности воздействовать на отдельные объекты напрямую после того, как они были созданы. К настоящему времени вы, вероятно, можете видеть, к чему все идет.
Я связался со своим профессором физики по поводу этого проекта, и он сказал мне, что хотел бы, чтобы студенты могли кодировать уравнения, которые мы изучаем в классе. Например, базовое интегрирование положения с помощью метода Эйлера или уравнений столкновения. При этом я решил реализовать способ, с помощью которого интерфейс Lua мог бы обновлять отдельные объекты.
К чему я пришел, так это к функциям обновления объекта. Указав функцию, которая будет вызываться для объекта в каждом кадре, пользователи теперь могут изменять характеристики объекта, включая положение, скорость, цвет и т. д. Вот MVP для лаборатории интеграции, которая позволяет студентам написать свой собственный метод интеграции:
-- the student should edit this function local function integrate(x, y, v_x, v_y, dt) return { new_x = x + v_x * dt, new_y = y + v_y * dt, } end X_VEL = 1 Y_VEL = -1 -- this function is called on the circle every frame function update_fn(obj) local old_x, old_y = obj.x, obj.y data = integrate(old_x, old_y, X_VEL, Y_VEL, DT / 100) obj.x, obj.y = data.new_x, data.new_y return obj end add_shape { shape="circle", x=SCREEN_X/2, y=SCREEN_Y/2, r=1, mass=1, update_function="update_fn" } GRAVITY = 0
Идея состоит в том, чтобы студенты написали свою собственную функцию integrate()
. В идеале должен быть еще один круг, использующий физический движок симулятора, и, сравнив поведение двух фигур, можно будет увидеть ошибку интегрирования по Эйлеру.
Когда я писал примеры, я понял, что функции обновления объекта очень похожи на то, как работает ECS Unity. Функции обновления, по сути, являются компонентом скрипта. Хотя к каждому объекту можно добавить только одну функцию обновления, это ограничение довольно легко обойти.
Осознав это, я решил написать Flappy Bird. Здесь я признаю, что название немного кликбейтно: по-прежнему нет способа обрабатывать пользовательский ввод из Lua-скриптов, поэтому программу все еще нельзя назвать игровым движком. Пользователи могут перетаскивать объекты с помощью мыши, поэтому можно было бы настроить физические кнопки в сцене, а также есть глобальные переменные MOUSE_X
и MOUSE_Y
Lua, но Lua не может видеть щелчки клавиатуры или мыши. Из-за этого я написал очень простой ИИ для игры во Flappy Bird.
Мой код на Lua немного дилетантский и слишком длинный для фрагмента кода, поэтому я рекомендую вам проверить его на GitHub, если вы хотите его прочитать.
Создавать Flappy Bird поверх такого физического движка довольно интересно. Я не удосужился написать условие конца игры, но если птица врежется в одну из труб, они обе улетят. Вместо того, чтобы вручную обновлять x-позицию труб, я просто инициализировал их отрицательную скорость. Я предполагаю, что это похоже на то, как это будет работать в Unity или Godot. Однако, несмотря на то, что на движке определенно можно писать игры, он не очень эргономичен. Хотя я не против.
Что еще меня удивило, так это скорость взаимодействия с Lua из Rust. В каждом кадре программа загружает многочисленные поля объектов Lua в физический движок и передает их обратно в Lua. В примере с Flappy Bird физический движок почти неактивен, поэтому я ожидал, что система Lua будет более интенсивной, чем физический движок, но она использует примерно десятую часть производительности, если я правильно читаю свой профайлер. У меня возник соблазн переписать Универсальную гравитационную часть SIMple Physics в сценарий Lua.