Войти
ПрограммированиеФорумОбщее

Lua + C++. Правильное разделение обязанностей

Страницы: 1 2 3 4 Следующая »
#0
20:42, 21 дек. 2017

Написав почти 40к строк кода в своей новой игре на C++, решил часть логики вынести на Lua-скрипты, потому что стало невыносимо ждать перекомпиляцию при малейшем изменении логики. В связи с этим переходом возникло несколько вопросов:

1) Какой функционал должен быть на стороне скриптов?
2) Должны ли скрипты создавать движковые объекты? Если должны, то как быть с сохранением\загрузкой игры. Как восстанавливать состояние скрипта при загрузке сейва?


#1
21:40, 21 дек. 2017

От игры зависит, и того, для чего нужны эти скрипты.

#2
22:19, 21 дек. 2017

mr.DIMAS
> потому что стало невыносимо ждать перекомпиляцию при малейшем изменении логики.

Edit&Continue и ваши луа-скрипты мягкие и пустые.

#3
22:50, 21 дек. 2017

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

#4
3:42, 22 дек. 2017

mr.DIMAS
Подходов очень много. Начинающие обычно стартуют с "Lua - это такие скриптовые колбеки".
А вообще самое первое и самое мощное решение - это выбрать сторону push/pull модели. C++/Lua - кто сервер, кто клиент.
LuaCSP - пример, где main в lua и где lua "тянет" C++ (и вся асинхронность и колбеки спрятаны внутри "длящихся" операций)

Сериализация - опять зависит от твоего подхода к object system и lifetime (GC из lua, LuaPlus, C++-managed и т.д.)
Обходить граф объектов в lua - легко. Была даже библиотека pluto/eris (на lua), но там есть минусы.
Также легко организуются scope'ы для зачисток при "перезагрузке уровня".

#5
5:59, 22 дек. 2017

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

#6
15:48, 22 дек. 2017

mr.DIMAS
> 1) Какой функционал должен быть на стороне скриптов?
Логика подсказывает - тот самый, при малейшем изменении которого стало невыносимо ждать.

#7
16:01, 22 дек. 2017

loyso
> Сериализация - опять зависит от твоего подхода к object system и lifetime
Все игровые объекты у меня хранятся в shared_ptr.

Для биндингов использую lua-intf. Он умеет расшаривать владение shared_ptr между Lua и C++.

Zab
> Выноси в lua-скрипты только то, что не создаст глобальных повреждений если его
> совсем криво запрограммировать.
Вот сейчас пробую вынести создание квестов на скрипты. Что-то да выходит, однако я до сих плохое себе представляю как сохранять состояние скрипта.


Для примера есть простой квест на обучение управлению. Структура квестов задается JSON-файликом:

{
  "name": "Tutorial",  
  "script": "data/scripts/quest_tutorial.lua",
  "scriptIdentifier": "tutorial",
  "stages": {    
    "Controls": {
      "title": "Controls",
      "description": "To move - use[A][D].To jump - use[Space].Try it"    
    },
    "Combat": {
      "title": "Combat",
      "description": "Grab a weapon and find some infected creature to kill."    
    }
  }
}
При загрузке конфига, игра создает объект Quest c QuestStages перечисленными в "stages". Регистрирует в таблице "scriptIdentifier" указатель this текущего Quest в виде "scriptIdentifier.questObject".

В этом квесте требуется нажать на определенные клавиши, соответственно приходится сохранять состояние "нажато\не нажато" в переменных.

tutorial = {}

function tutorial.onInit()
  print("Tutorial quest loaded!")
  tutorial.questObject:SetCurrentStage("Controls")
end

function tutorial.onUpdate() 
  local controls = Game.Instance():GetControls()
  local quest = tutorial.questObject
  
  if(quest:IsCurrentStage("Controls")) then
    if(controls:IsKeyHit(Controls.KeyMoveRight)) then
      tutorial.movedRight = true
    elseif(controls:IsKeyHit(Controls.KeyMoveLeft)) then
      tutorial.movedLeft = true
    end
    if(tutorial.movedRight ~= nil and tutorial.movedLeft ~= nil) then
      quest:CompleteStage("Controls", "Combat")
    end
  elseif(quest:IsCurrentStage("Combat")) then
    -- пока не доделал
  end
end

Вот как сохранить состояние tutorial.movedRight и tutorial.movedLeft? У меня в игре есть штатный сериализатор, в котором есть перегруженный шаблонный оператор & и для сериализации я просто делаю:

serializer & foo;
serializer & bar;
Сериализатор может сериализовать все что угодно, начиная от int и заканчивая shared_ptr\weak_ptr (если у сериализуемого объекта есть метод Serialize). Компилятор на стороне C++ сам определяет какую перегрузку использовать. А как в луа экспортировать эту пачку перегруженных операторов? Единственная мысль это зарегистрировать функции SerializeNumber, SerializeBoolean, SerializeString, SerializeObject и в скрипте сделать функцию-член onSerialize

function tutorial.onSerialize(serializer)
  tutorial.movedRight = SerializeBoolean(serializer, tutorial.movedRight)
  tutorial.movedLeft = SerializeBoolean(serializer, tutorial.movedLeft)
  -- сериализуем указатель на квест
  tutorial.questObject = SerializeObject (serializer, tutorial.questObject)
end

И уже на стороне C++ в Quest::Serialize в конце метода вызывать tutorial.onSerialize

Я почти уверен, что это оверкилл и что я все неправильно делаю.

#8
16:31, 22 дек. 2017

Храни все что нужно в сишных объектах, протянутых в lua, а не наоборот. И сериализацией тогда не придется в скриптах заниматься. Imho, это слишком много для мозгов того, кто будет со скриптами возиться. Не будешь же ты сам все писать? А потом контролировать, чтобы не попортили. Если найдешь толкового напарника, так он на такой работе загрустит и сбежит быстро. Скрипты поручают кому попроще, кто ничего другого делать не смог бы. Минимум взаимоувязок в коде должно быть. Написал - оно сработало. Безусловно. Протестировал - то что надо получилось - оставил. Не понравилось - написал иначе. В таком режиме сможет и школьник работать, едва освоивший программирование.

#9
17:14, 22 дек. 2017

Zab
> Храни все что нужно в сишных объектах, протянутых в lua, а не наоборот
В общем да, так и надо делать.

Специально посмотрел как сделано в Starbound: https://starbounder.org/Modding:Lua/Tables/Entity
"Every function refers to the entity the script context is running on." - получается что при вызове entity.id будет возвращен id текущей сущности.

А вот список всех таблиц: https://starbounder.org/Modding:Lua
Например Monster дает весь набор методов для переопределения поведения монстра.

Буду дальше разбираться что и как там реализовано. Думаю на примерах будет проще разобраться.

#10
19:24, 22 дек. 2017

40к кода можно и быстро скомпилировать. В проекте над которым я работаю в данный момент ~100000 строк (*включая заголовки и комментарии) - собирается за 2 секунды без оптимизации и за 4 с O2. И это на старом i3. Для начала создай один файл и включи туда все исполнительные файлы - и собирай только его.

#11
20:03, 22 дек. 2017

RostislavP
Ты про Single Compilation Unit? Читал про него, но так и не попробовал, видимо настало время.

#12
20:17, 22 дек. 2017

mr.DIMAS
Ага. Или Unity Build. И вообще, нужны ли Lua-скрипты? Если вся проблема только во времени сборки, может стоит подумать еще? Какие преимущества Lua даст? За исключением (и то спорно) возможности для модификаций?

#13
20:28, 22 дек. 2017

RostislavP
Проблема во времени сборки, да. Сам я скрипты не особо люблю, как и все языки с динамической типизацией. Вот сейчас пробую SCU.

Lua все же нужен скорее для модификаций - так как я не хочу исходники открывать.

#14
20:38, 22 дек. 2017

RostislavP
В общем SCU уменьшил время полной компиляции с 1:30 до 0:47. Но теперь на каждый чих полностью перекомпилируется SCU.cpp и довольно долго - 0:36. Как у тебя вышло такое маленькое время компиляции?

Страницы: 1 2 3 4 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.