Язык программирования для игр
Сейчас мне не известен язык, на котором было бы удобно разрабатывать игры. Поэтому я пишу Кедр.
Предыдущие статьи здесь и здесь, но они частично устарели, лучше смотреть документацию.
Новые возможности
Прежде чем перейти к теме игр, рассмотрим, что было добавлено из того, чего я не встречал в других языках.
Внедрение зависимостей
type TextBlock =
let get_font : String -> Font @auto
let main () =
let text_block = TextBlock.new
let get_font @publish = { path -> font_directory.get path }
main
Частая ситуация — необходимость передавать вспомогательные объекты. Мы не хотим делать это явно, но также не хотим создавать глобальные данные. С помощью атрибутов @publish
и @auto
значение привязки get_font
из корня файла будет передано в одноимённое поле объекта text_block
. Совпадать должны как имена привязок, так и их типы.
Дерево объектов
let start_button = Button.new
start_button.text = "start"
start_button.on_press = { start_calculation }
let stop_button = Button.new
stop_button.text = "stop"
stop_button.on_press = { stop_calculation }
let stack = Stack.new
stack.items.add start_button
stack.items.add stop_button
stack
Так могло бы выглядеть создание элементов управления на условном классическом языке. Подобный код, очевидно, не желателен, поэтому существуют системы разметки, например XAML для WPF. Но для большей гибкости и простоты желательно всё же ограничиться кодом.
Stack
Button
text = "start"
on_press = { start_calculation }
Button
text = "stop"
on_press = { stop_calculation }
При создании объекта можно не только передать аргументы в конструктор, но и задать значения любых полей.
Поле Stack.items
помечено атрибутом @dst
, поэтому в него добавляются кнопки. Если бы тип содержал два @dst
поля типа Option
— то соответствующие по порядку кнопки были бы присвоены им.
on_press
имеет тип Option
, т.е. может содержать либо функцию, которая ничего не принимает и не возвращает, либо значение None
. Во время присваивания замыкание типа Unit -> Unit
неявно приводится к типу Option::Some
.
let start_button = Button.new
let stop_button = Button.new
let stack = Stack.new
stack
start_button
text = "start"
on_press = { start_calculation }
stop_button
text = "stop"
on_press = { stop_calculation }
Нам могут понадобиться ссылки на объекты, которые создаются внутри выражения. Для этого мы создаём их снаружи, и используем имена привязок вместо типов внутри.
Разделённый конструктор
Ящик control
содержит реализацию элементов управления, ничего не знающих об отрисовщике, который отобразит их на экране.
type Control
var maybe_drawable : Option @mut = None
Мы добавляем поле к типу Control
в ящике drawer
, использующем конкретный отрисовщик, частью которого является Drawable
.
type Rectangle
let materials : Materials @auto
val drawable = Drawable
material = materials.ui
mesh = Mesh.from_memory memory@atom
maybe_drawable = drawable
Объект Drawable
создаётся в конструкторе типа Rectangle
, который неявно получает необходимый ему объект materials
.
let materials @publish = app.drawer.materials
let rectangle = Rectangle.new
После публикации materials
мы можем создавать объекты Rectangle
. В других ящиках может содержаться код, который также создаёт объекты Rectangle
, ничего не зная о его зависимости от materials
. Благодаря неявной передаче этот код продолжит работать, но сам теперь будет требовать materials
для выполнения.
Игры
Основные языки для разработки игр это C++ и C#. Кто-то ещё добавит JavaScript, но это совсем другие игры с другими требованиями, о них не будем.
C++ был создан давно, нужна замена, обладающая всеми его возможностями и учитывающая современные достижения в дизайне языков. Кедр является такой заменой.
C#, в отличие от C++, подходящим для игр языком никогда не был. В играх это во многом другой язык, без LINQ и даже без foreach
. Без возможности свободно создавать временные объекты. Он не был предназначен для такого сценария использования.
Создание временных объектов даже без сборщика мусора может быть нежелательным, поэтому в Кедре переиспользование памяти будет реализовано на уровне языка.
Концепция
Одна из особенностей Кедра — это единообразие кода в разных контекстах. Код внутри функции содержит привязки, выражения и вложенные функции. Код внутри типа — всё те же привязки, выражения и функции. При этом привязки становятся полями, а функции методами. В корне файла к ним добавляются типы и модули.
Код
Здесь есть код среды разработки. В ней пока можно редактировать код с подсветкой синтаксиса, запускать приложение, видеть ошибки компиляции. В соседнем репозитории привязка к Vulkan и отрисовщик. Всего вместе со стандартной библиотекой около 20к строк.