Godot: Добавьте систему признаков GDScript.

Созданный на 18 окт. 2018  ·  93Комментарии  ·  Источник: godotengine/godot

(Редактировать:
Чтобы свести к минимуму дальнейшие проблемы XY:
Проблема, рассматриваемая здесь, заключается в том, что система/языки сценариев Godot Node-Scene еще не поддерживают создание многократно используемых сгруппированных реализаций, которые 1) специфичны для функций корневого узла и 2) которые можно менять местами и/или комбинировать. Сценарии со статическими методами или подузлы со сценариями могут использоваться для последнего бита, и во многих случаях это работает. Однако Godot обычно предпочитает, чтобы логика общего поведения вашей сцены хранилась в корневом узле, в то время как он использует данные, вычисленные дочерними узлами, или делегирует им значительно отличающиеся подзадачи, например, KinematicBody2D не управляет анимацией, поэтому он делегирует это AnimationPlayer.

Наличие более тонкого корневого узла, использующего «компонентные» дочерние узлы для управления своим поведением, является слабой системой по сравнению с ней. Наличие практически пустого корневого узла, который просто делегирует все свое поведение дочерним узлам, противоречит этой парадигме. Дочерние узлы становятся поведенческими расширениями корневого узла, а не самодостаточными объектами, которые выполняют задачу самостоятельно. Это очень неуклюже, и дизайн можно было бы упростить/улучшить, позволив нам объединить всю логику в корневом узле, но также позволив нам разбить логику на разные составные части.

Я полагаю, что тема этого выпуска больше о том, как справиться с вышеуказанной проблемой, чем конкретно о GDScript, но я считаю, что особенности GDScript были бы самым простым и прямым подходом к решению проблемы.
)

Для неосведомленных трейты — это, по сути, способ смешивания двух классов в один (в значительной степени механизм копирования/вставки), только вместо того, чтобы буквально копировать/вставлять текст файлов, все, что вы делаете, — это используете оператор ключевого слова для связать два файла. (Правка: хитрость в том, что, хотя скрипт может наследовать только один класс, он может включать в себя несколько трейтов)

Я представляю что-то, где любой файл GDScript может использоваться в качестве признака для другого файла GDScript, пока типизированный тип расширяет класс, унаследованный сценарием слияния, т. е. GDScript, расширяющий спрайт, не может использовать ресурсный GDScript. как трейт, но может использовать Node2D GDScript. Я бы представил синтаксис, подобный этому:

# move_right_trait.gd
extends Node2D
class_name MoveRightTrait # not necessary, but just for clarity
func move_right():
    position.x += 1

# my_sprite.gd
extends Sprite
is MoveRightTrait # maybe add a 'use' or 'trait' keyword for this instead?
is "res://move_right_trait.gd" # alternative if class_name isn't used
func _physics_process():
    move_right() # MoveRightTrait's content has been merged into this script
    if MoveRightTrait in self:
        print("I have a MoveRightTrait")

Я вижу два способа сделать это:

  1. Предварительно проанализируйте скрипт с помощью RegEx для "^trait \процесс перезагрузки). Нам пришлось бы не поддерживать вложение трейтов или постоянно пересматривать сгенерированный исходный код после каждой итерации, чтобы увидеть, было ли сделано больше вставок трейтов.
  2. Проанализируйте сценарий обычным образом, но научите синтаксический анализатор распознавать ключевое слово, загружать указанный сценарий, анализировать ЭТОТ сценарий, а затем добавлять его содержимое ClassNode к сгенерированному ClassNode текущего сценария (фактически беря проанализированные результаты одного сценария и добавляя его к проанализированным результатам другого скрипта). Это автоматически поддержит вложенность типизированных типов.

С другой стороны, люди могут захотеть , чтобы трейт GDScript имел имя, но может НЕ хотеть, чтобы имя класса GDScript отображалось в диалоговом окне CreateDialog (потому что оно не предназначено для создания само по себе). В этом случае на самом деле может НЕ быть хорошей идеей, чтобы какой -либо скрипт поддерживал его; только те, которые специально помечены (возможно, написав «признак» в начале файла?). Впрочем, есть над чем подумать.

Мысли?

Редактировать: после некоторых размышлений я считаю, что вариант 2 будет намного лучше, поскольку 1) мы будем знать, ИЗ КАКОГО сценария был получен сегмент сценария (для лучшего отчета об ошибках) и 2) мы сможем идентифицировать ошибки по мере их возникновения, поскольку включенные сценарии должны быть проанализированы последовательно, а не просто проанализированы все в конце. Это сократит время обработки, которое добавляется к процессу синтаксического анализа.

archived discussion feature proposal gdscript

Самый полезный комментарий

@aaronfranke Traits , в основном то же самое, что и Mixins , имеют совершенно другой вариант использования по сравнению с интерфейсами именно потому, что они включают реализации методов. Если бы интерфейс давал реализацию по умолчанию, то он больше не был бы интерфейсом.

Traits/Mixins присутствуют в PHP, Ruby, D, Rust, Haxe, Scala и многих других языках (как подробно описано в связанных вики), поэтому они уже должны быть хорошо знакомы людям, которые имеют широкий репертуар знакомства с языками программирования.

Если бы мы реализовали интерфейсы (против чего я тоже не против, особенно с появлением дополнительной статической типизации), это был бы фактически просто способ указать сигнатуры функций, а затем потребовать, чтобы соответствующие сценарии GDScript реализовывали эти сигнатуры функций с трейтами. включены (если таковые существовали к тому моменту).

Все 93 Комментарий

Какая польза вместо: extends "res://move_right_trait.gd"

@MrJustreborn Потому что у вас может быть несколько черт в классе, но вы можете наследовать только один сценарий.

Если я правильно понимаю, это в основном то, что С# называет « интерфейсами », но с неабстрактными методами? Возможно, было бы лучше называть интерфейсы функций, а не трейты, чтобы они были знакомы программистам.

@aaronfranke Traits , в основном то же самое, что и Mixins , имеют совершенно другой вариант использования по сравнению с интерфейсами именно потому, что они включают реализации методов. Если бы интерфейс давал реализацию по умолчанию, то он больше не был бы интерфейсом.

Traits/Mixins присутствуют в PHP, Ruby, D, Rust, Haxe, Scala и многих других языках (как подробно описано в связанных вики), поэтому они уже должны быть хорошо знакомы людям, которые имеют широкий репертуар знакомства с языками программирования.

Если бы мы реализовали интерфейсы (против чего я тоже не против, особенно с появлением дополнительной статической типизации), это был бы фактически просто способ указать сигнатуры функций, а затем потребовать, чтобы соответствующие сценарии GDScript реализовывали эти сигнатуры функций с трейтами. включены (если таковые существовали к тому моменту).

Может быть, ключевое слово вроде includes ?

extends Node2D
includes TraitClass

Хотя другие имена, такие как trait, mixin, has и т. д., безусловно, тоже подходят.

Мне тоже нравится идея иметь возможность исключить тип class_name из меню добавления. Он может быть очень загроможден небольшими типами, которые не функционируют сами по себе как узлы.

Это может быть даже отдельная тема.

(Случайно удалил мой комментарий, упс! Кроме того, обязательно "почему бы вам просто не разрешить несколько скриптов, это делает единство" )

Как это будет работать в VisualScript, если вообще будет?

Кроме того, было бы полезно включить интерфейс инспектора для трейтов, если бы трейты были реализованы? Я предполагаю, что некоторые варианты использования трейтов могут включать случаи использования, когда есть только трейты и нет скрипта (по крайней мере, нет скрипта, кроме того, который включает файлы трейтов). Хотя, подумав об этом подробнее, я задаюсь вопросом, могут ли усилия, затраченные на создание такого интерфейса, того стоить, по сравнению с простым созданием скрипта, включающего трейт-файлы.

@LikeLakers2

Как это будет работать в VisualScript, если вообще будет?

Если сделать так, как я предложил, для VisualScript этого вообще не произойдет. Только GDScript. Любая система признаков, реализованная для VisualScript, будет разработана совершенно по-другому, поскольку VisualScript не является языком синтаксического анализа. Однако это вовсе не исключает возможности (просто нужно было бы реализовать по-другому). Кроме того, может быть, нам следует сначала рассмотреть возможность поддержки наследования VisualScript? ржу не могу

Кроме того, было бы полезно включить интерфейс инспектора для трейтов, если бы трейты были реализованы?

В этом не было бы особого смысла. Трейты просто сообщают детали GDScript, передавая ему свойства, константы, сигналы и методы, определенные трейтом.

Я предполагаю, что некоторые варианты использования трейтов могут включать случаи использования, когда есть только трейты и нет скрипта (по крайней мере, нет скрипта, кроме того, который включает файлы трейтов).

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

Интересно, могут ли усилия, затраченные на создание такого интерфейса, того стоить?

Создание интерфейса Inspector каким-то образом не имело бы особого смысла только для GDScript. Добавление или удаление черты потребует непосредственного редактирования исходного кода свойства source_code ресурса Script, т.е. это не свойство самого Script. Поэтому либо...

  1. редактор должен быть обучен тому, как правильно обрабатывать исходный код для файлов GDScript, чтобы сделать это (подвержено ошибкам), или...
  2. все скрипты должны поддерживать трейты, чтобы GDScriptLanguage мог обеспечить свой собственный внутренний процесс для добавления и удаления трейтов (но не все языки ДЕЙСТВИТЕЛЬНО поддерживают трейты, поэтому свойство не будет иметь смысла во всех случаях).

Какая необходимость в такой функции? Есть ли что-то, что это позволяет, что вы не можете сделать сейчас? Или это значительно ускоряет выполнение некоторых задач?

Я бы предпочел оставить GDscript простым языком, чем добавлять сложные функции, которые почти никогда не используются.

Он решает проблему Child-Nodes-As-Script-Dependencies, с которой у этого парня была проблема , но не имеет того же багажа, что и MultiScript, потому что он ограничен одним языком. Модуль GDScript может изолировать логику того, как черты соотносятся друг с другом и с основным сценарием, в то время как устранение различий между разными языками было бы намного сложнее.

При отсутствии множественного импорта/множественного наследования зависимости дочерних узлов-как-скриптов являются единственным способом избежать МНОГО повторения кода, и это определенно решит проблему хорошим способом.

@groud @Zireael07 Я имею в виду, что более радикальным межъязыковым подходом было бы: 1) полностью перепроектировать Object для использования ScriptStack для объединения сложенных скриптов в единое представление скрипта, 2) заново ввести MultiScript и создать поддержку редактора, который автоматически преобразует добавление скриптов в мультискрипты (или просто создание мультискриптов для простоты, и в этом случае реализация MultiScript по существу будет нашим ScriptStack), или 3) реализовать своего рода межъязыковую систему признаков для типа объекта, которая может объединяться в Скрипты, расширяющие ссылки как черты, включая их содержимое, как обычный скрипт. Однако все эти варианты гораздо более агрессивны для двигателя. Это делает все проще.

Я не думаю, что черта необходима. что нам нужно больше всего, так это быстрый цикл выпуска двигателя. Я имею в виду, что нам нужно сделать движок более гибким, чтобы добавить новую функцию, так как просто добавьте новые dll или около того файлы, и движок автоматически интегрируется с самим собой, как стиль плагинов в большинстве IDE. например, я действительно отчаянно нуждаюсь в том, чтобы веб-сокет работал, мне не нужно ждать его до выпуска 3.1. 3.1 слишком сломана прямо сейчас с таким количеством ошибок. было бы здорово, если бы у нас была эта функция. новый класс может автоматически внедряться в GDScript из случайной .dll или .so по некоторому пути. я не знаю, сколько усилий это нужно сделать на С++, но я надеюсь, что это не слишком сложно 😁

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

ой, я не знаю, что GDNative существует 😂😂😂. Черта классная, но не проще ли создать поддельный класс черты и создать его экземпляр, а затем вызвать функцию, как базовый скрипт?

Если скрипт Godot является безымянным классом, почему бы не создать экземпляр «move_right_trait.gd» в «my_sprite.gd»?
Извините за невежество, если я не понимаю вопроса.

Я понимаю использование трейтов в более строго типизированных языках, таких как Rust или (интерфейсы) C++, но разве в языке с приглушенной типизацией это не нужно? Простая реализация одних и тех же функций должна позволить вам достичь единообразного интерфейса между вашими типами. Думаю, я немного не уверен, в чем именно заключается проблема с тем, как GDScript обрабатывает интерфейсы, или как система признаков могла бы вообще помочь.

Не могли бы вы также использовать preload("Some-other-behavior.gd") и сохранять результаты в переменной для достижения в основном того же эффекта?

@fian46 @DriNeo Ну и да и нет. Загрузка сценариев и использование классов сценариев уже позаботились об этом, но проблема выходит за рамки этого.

@Йокай

Реализация одних и тех же функций должна позволить вам добиться единого интерфейса между вашими типами.

Проблема не в достижении единого интерфейса, в котором вы правы, утиная типизация отлично решается, а в эффективной организации (объединении/замене) групп связанных реализаций.


В версии 3.1 с помощью классов скриптов вы можете определять статические функции в эталонном скрипте (или любом другом типе), а затем использовать этот скрипт в качестве пространства имен для глобального доступа к этим функциям в GDScript.

extends Reference
class_name Game
static func print_text(p_text):
    print(p_text)
# can even add inner classes for sub-namespaces

extends Node
func _ready():
    Game.print_text("Hello World!")

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

Например, что, если у меня есть KinematicBody2D, и я хочу иметь поведение «Прыжок» и поведение «Бег»? Для каждого из этих вариантов поведения потребуется доступ к обработке ввода и функциям move_and_slide KinematicBody2D. В идеале я мог бы независимо менять реализацию каждого поведения и хранить весь код для каждого поведения в отдельных сценариях.

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

  1. Если вы сохраните все реализации в одном сценарии и просто поменяете местами используемые функции...

    1. изменение «поведения» может включать замену нескольких функций в виде набора, поэтому вы не можете эффективно сгруппировать изменения реализации.

    2. Все функции для каждого поведения (X * Y) находятся в одном сценарии, поэтому он может очень быстро раздуться.

  2. Вы можете просто заменить весь сценарий, но тогда вам придется создавать новый сценарий для каждой комбинации вариантов поведения и любой логики, использующей эти варианты поведения.
  3. Если вы используете дочерние узлы в качестве зависимостей сценария, то это означает, что у вас будут эти странные «компонентные» узлы Node2D, которые захватывают своих родителей и вызывают метод move_and_slide ДЛЯ него, что относительно неестественно.

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

    • Вы также не получаете надлежащего завершения кода для предполагаемых операций узлов, поскольку они происходят от Node2D, и весь смысл в том, чтобы управлять поведением родителя KinematicBody2D.

Теперь я признаю, что вариант 3 является наиболее эффективным рабочим процессом в настоящее время, и что его проблемы могут быть в значительной степени решены с помощью удобной статической типизации для GDScript в версии 3.1. Однако существует более фундаментальная проблема.

Система узлов-сцен Godot обычно имеет форму пользователей, создающих узлы или сцены, которые выполняют определенную работу, в своей собственной закрытой системе. Вы можете поместить эти узлы/сцены в другую сцену и заставить их вычислять данные, которые затем используются родительской сценой (например, в случае отношений Area2D и CollisionShape2D).

Тем не менее, использование ванильного движка и общие рекомендации по передовой практике заключаются в том, чтобы поведение вашей сцены было привязано к корневому узлу и/или его сценарию. Вряд ли когда-либо у вас есть узлы «компонента поведения», которые на самом деле сообщают корню, что делать (и когда он есть, он кажется очень неуклюжим). Узлы AnimationPlayer/Tween — единственные спорные исключения, о которых я могу думать, но даже их операции управляются корнем (это фактически временно делегирует им управление). (Редактировать: даже в этом случае анимация и анимация не являются работой KinematicBody2D, поэтому имеет смысл делегировать эти задачи. Однако движение, такое как бег и прыжки , является его обязанностью). Проще и естественнее разрешить реализация трейта для организации кода, поскольку она сохраняет отношения между узлами строго по принципу «данные-вверх/поведение-вниз» и сохраняет код более изолированным в своих собственных файлах сценариев.

Эх, пометка себя как «реализующий интерфейс/черту» также должна выполнить тест * is * , который удобен для проверки функциональности чего-либо.

@OvermindDL1 Я имею в виду, что я привел пример выполнения такого теста, но вместо этого использовал in , так как хотел различать наследование и использование признаков.

Я думаю, я как бы столкнулся с проблемой XY здесь, мой плохой. Я только что вернулся из двух других выпусков (# 23052, # 15996), которые так или иначе касались этой темы, и решил, что я отправлю предложение, но на самом деле я не дал всего контекста.

@groud это решение решит одну из проблем, поднятых против # 19486.

@willnationsdev отличная идея, жду с нетерпением!

По моему ограниченному пониманию, эта система черт хочет добиться чего-то похожего на рабочий процесс, показанный в этом видео: https://www.youtube.com/watch?v=raQ3iHhE_Kk .
(Обратите внимание, я говорю о показанном рабочем процессе, а не об используемой функции)

В видео он сравнивается с другими видами рабочих процессов, с их преимуществами и недостатками.

По крайней мере, насколько мне известно, такой рабочий процесс сейчас невозможен в GDScript из-за того, как работает наследование.

@AfterRebelion Первые несколько минут этого видео, где он выделяет модульность, возможность редактирования и отладку кодовой базы (и связанные с ними детали этих атрибутов) , действительно являются стремлением к этой функции.

По крайней мере, насколько мне известно, такой рабочий процесс сейчас невозможен в GDScript из-за того, как работает наследование.

Это не совсем так, потому что Godot на самом деле очень хорошо справляется с иерархией узлов и проектированием сцен. Сцены являются модульными по своей природе, свойства можно экспортировать (и даже анимировать) прямо из редактора, даже не занимаясь кодом, и все можно тестировать/отлаживать изолированно, поскольку сцены могут выполняться независимо.

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

В Unity это не проблема, потому что у GameObject нет реального наследования, которым могли бы воспользоваться пользователи. В Unreal это может быть небольшой (?) проблемой, поскольку они имеют схожие внутренние иерархии на основе узлов/компонентов для Актеров.

Хорошо, давайте немного поиграем здесь в «Адвоката дьявола» ( @MysteryGM , возможно, вам это понравится). Потратил некоторое время на размышления о том, как я мог бы написать такую ​​систему в Unreal, и это дало мне новый взгляд на нее. Извините за людей, которые думали, что это будет хорошей идеей / были в восторге от этого:

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

Кроме того, трейты как функция затрудняют выделение переменных, констант, сигналов, методов и даже подклассов . Если сценарий вашего Node внезапно имеет 12 различных признаков, то вам не обязательно знать, откуда все берется. Если вы видите ссылку на что-то, вам придется просмотреть 12 разных файлов, чтобы даже узнать, где находится эта вещь в кодовой базе.

Это на самом деле снижает возможности отладки GDScript как языка, поскольку любая конкретная проблема может потребовать от вас выделить в среднем 2 или 3 разных места в кодовой базе. Если какое-то из этих мест трудно найти, потому что они говорят, что они расположены в одном скрипте, но на самом деле находятся где-то еще, и если читабельность кода не ясно указывает, какая вещь отвечает за данные/логику, тогда эти 2 или 3 шаги умножаются на произвольно большой, очень напряженный набор шагов.

Увеличивающийся размер и объем проекта еще больше усиливают эти негативные эффекты и делают использование трейтов довольно неприемлемым качеством.


Но что можно сделать, чтобы решить проблему? Мы не хотим, чтобы дочерние узлы «компонентной логики» сообщали корням сцены, что делать, но мы также не можем полагаться на наследование или изменение целых сценариев для решения нашей проблемы.

Ну и что в этой ситуации сделал бы любой двигатель без ECS? Композиция по-прежнему является ответом, но в этом случае полный узел неразумен, если его масштабировать / усложняет динамику иерархии владения. Вместо этого можно просто определить объекты реализации, отличные от узла, которые абстрагируются от конкретной реализации поведения, но все они по-прежнему принадлежат корневому узлу. Это можно сделать с помощью скрипта Reference .

# root.gd
extends KinematicBody2D

export(Script) var jump_impl_script = null setget set_jump_impl_script
var jump_impl
func set_jump_impl_script(p_script):
    jump_impl = p_script.new() if p_script else null

export(Script) var move_impl_script = null setget set_move_impl_script
var move_impl
func set_move_impl_script(p_script):
    move_impl = p_script.new() if p_script else null

func _physics_process():
    # use logic involving these...
    move_impl.move(...)
    jump_impl.jump(...)

Если бы экспорты работали таким образом, что мы могли бы редактировать их в Инспекторе как перечисления для классов, производных от определенного типа, как мы можем это делать для новых экземпляров Resource, тогда это было бы круто. Единственный способ сделать это сейчас — исправить экспорт скрипта ресурсов, а затем сделать так, чтобы ваш скрипт реализации расширял ресурс (ни по какой другой причине). Хотя расширение Resource было бы хорошей идеей, если для самой реализации требуются параметры, которые вы хотели бы определить в Инспекторе. :-)

Теперь, что бы сделать ЭТО проще, было бы наличие системы фрагментов или системы макросов, чтобы разработчикам было проще создавать эти повторно используемые разделы декларативного кода.

В любом случае, да, я думаю, что нашел вопиющие проблемы с системой черт и лучшим подходом к решению проблемы. Ура проблемам XY! /с

Редактировать:

Таким образом, рабочий процесс в приведенном выше примере будет включать настройку сценария реализации, а затем использование экземпляра сценария во время выполнения для определения поведения. Но что, если самой реализации требуются параметры, которые вы хотели бы установить статически из Инспектора? Вот версия, основанная на ресурсе.

# root.gd
extends KinematicBody2D

# if you use a Resource script AND had a way of specifying that the assigned Resource 
# must extend that script, then the editor would automatically assign an instance of 
# that resource script to the var. No separate instancing or setter necessary.

export(Resource) var jump_impl = null # set jump duration, max height, tween easing via Inspector
export(Resource) var move_impl = null # similarly customize movement from Inspector

# can then create different Resources as different implementations. Because they are resources,
# one can edit them even outside of a scene!
func _physics_process():
    move_impl.move(...)
    jump_impl.jump(...)

Похожие: #22660

@AfterRebelion

Учтите, я говорю о показанном рабочем процессе, а не об используемой функции.

Ирония в том, что после того, как вы разъясните это, и я соглашусь с оптимальным рабочим процессом, а затем не соглашусь с более поздними частями комментария, я затем продолжаю его, в основном говоря, что «функция, используемая» в этом видео, на самом деле является идеальным способом решения проблемы. эта проблема в Годо так или иначе . Ха-ха.

# root.gd
extends KinematicBody2D

export(Script) var jump_impl_script = null setget set_jump_impl_script
var jump_impl
func set_jump_impl_script(p_script):
jump_impl = p_script.new() if p_script else null
...

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

Это делает мой предыдущий комментарий недействительным.
Как вы сказали, если какой-то макрос реализован для упрощения его реализации, это будет лучший способ реализовать этот рабочий процесс без использования MultiScript. Может быть, не так универсально, как Unity, потому что вам все равно нужно заранее объявить все возможные сценарии, но все же хороший вариант.

@AfterRebelion

Как вы сказали, если какой-то макрос реализован для упрощения его реализации, это будет лучший способ реализовать этот рабочий процесс без использования MultiScript.

Что ж, подход на основе Resource , о котором я упоминал в том же комментарии, в сочетании с некоторой лучшей поддержкой редактора от #22660 сделает качество сопоставимым с тем, что может сделать Unity.

Может быть, не так универсально, как Unity, потому что вам все равно нужно заранее объявить все возможные сценарии, но все же хороший вариант.

Ну, если они исправят подсказки типа массива в 3.2, то вы сможете определить экспортируемый массив для путей к файлам, которые должны расширять скрипт и эффективно добавлять свои собственные. Это можно было сделать даже с помощью плагина в версии 3.1, используя класс EditorInspectorPlugin для добавления пользовательского содержимого в инспектор для определенных ресурсов или узлов.

Я имею в виду, что если вам нужна система, подобная «Unity», то у вас действительно ДОЛЖНЫ быть подузлы, которые сообщают корню, что делать, и вам нужно будет только получить их, обратившись к их имени, без их объявления или добавления вручную. из сценария корневого узла. Метод ресурсов, как правило, намного эффективнее и поддерживает более чистую кодовую базу.

Поскольку система признаков создаст чрезмерную нагрузку на удобство использования кода GDScript, исходя из причин, которые я изложил, я собираюсь закрыть этот вопрос. Подводные камни ее добавления намного перевешивают любые сравнительно скудные преимущества, которые мы могли бы получить от такой системы, и те же преимущества можно реализовать по-разному, с большей ясностью и удобством использования.

Ну, я пропустил это обсуждение, пока меня не было. Я еще не все прочитал, но у меня возникла идея добавить трейты в GDScript, чтобы решить проблему повторного использования кода, что я нахожу более элегантным и понятным, чем искусственное множественное наследование при присоединении скрипта к производному типу. Хотя то, что я хотел бы сделать, это создать определенные файлы свойств, не позволяя ни одному файлу класса быть свойством. Я не думаю, что это было бы слишком плохо.

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

@vnen решение, которое я придумал, и которое является последним пунктом, состоит в том, чтобы передать повторно используемые разделы сценариям ресурсов.

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

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

  • Они не добавляют излишней сложности GDScript как языку. Например, если бы они существовали, нам нужно было бы решить, как общие качества (свойства, константы, сигналы) будут объединены в основной сценарий (или, если они не объединены, заставлять пользователей справляться с конфликтами зависимостей).

  • Скрипты ресурсов лучше, поскольку их можно назначать и изменять из Инспектора. Дизайнеры, писатели и т. д. смогут изменять объекты реализации прямо из редактора.

@willnationsdev Понятно (хотя название «скрипты ресурсов» звучит странно, поскольку все сценарии являются ресурсами). Основная проблема с этим решением заключается в том, что оно не решает то, что люди ожидают от подхода наследования: добавление экспортируемых переменных и сигналов в корневой узел сцены (в частности, при создании экземпляра сцены в другом месте). Вы по-прежнему можете редактировать экспортированные переменные из подресурсов, но это становится менее практичным (вы не можете сразу сказать, какие свойства вы можете редактировать).

Другая проблема заключается в том, что для этого требуется многократное повторение стандартного кода. Вы также должны убедиться, что сценарии ресурсов действительно установлены перед вызовом функций.

Преимущество в том, что нам ничего не нужно, это уже доступно. Я думаю, это должно быть задокументировано, и люди, использующие текущий метод «прикрепить скрипт к производному узлу», могли бы прокомментировать решение, чтобы увидеть, насколько оно жизнеспособно.

@внен

но это становится менее практичным (вы не можете сразу сказать, какие свойства вы можете редактировать).

Можете ли вы уточнить, что вы имеете в виду здесь? Я не понимаю, как может быть какое-то существенное отсутствие ясности в отношении того, какие свойства доступны, особенно если что-то вроде # 22660 было объединено.

  1. Я создаю экземпляр сцены, хочу знать, как я могу ее редактировать, поэтому посмотрите на сценарий корневого узла этой сцены.
  2. В сценарии я вижу...

    • export(MoveImpl) var move_impl = FourWayMoveImpl.new()

    • use FourWayMoveTrait

  3. Предполагая, что у нас есть средства отслеживания кликов по идентификатору (который действительно должен быть функцией 3.1.1!) для открытия скрипта, мы открываем связанный скрипт и можем просматривать его свойства.

Мне кажется, что такое же количество шагов, если я что-то не упустил.

Кроме того, то, какие свойства доступны для редактирования, на самом деле еще более ясно с ресурсами, я бы сказал, поскольку, если реализация имеет свойства, специфичные для нее, то факт, что данные связаны с реализацией, становится более очевидным, если вам нужно предварить доступ к ним с помощью экземпляр реализации, т.е. move_impl.<property> .

Другая проблема заключается в том, что для этого требуется многократное повторение стандартного кода. Вы также должны убедиться, что сценарии ресурсов действительно установлены перед вызовом функций.

Это правда, однако я по-прежнему думаю, что преимущества экспорта с инициализацией перевешивают стоимость добавленной фразы:

(«Командный товарищ высокого уровня», HLT, например, дизайнеры, писатели, художники и т. д.)

  • Можно назначать значения непосредственно из Инспектора, вместо того, чтобы открывать скрипт, находить правильную строку для изменения, а затем изменять ее (уже упоминалось, но это ведет к...).

  • Можно указать, что экспортируемый контент имеет требование базового типа. Затем Инспектор может автоматически предоставить пронумерованный список допустимых реализаций. Затем HLT могут безопасно назначать производные только этого типа. Это помогает оградить их от альтернативы необходимости знать разветвления всех различных скриптов черт, плавающих вокруг. Нам также пришлось бы модифицировать автодополнение в GDScript, чтобы поддерживать поиск именованных и неименованных файлов трейтов в ответ на ключевое слово use .

  • Конфигурацию реализации можно сериализовать в виде файла *.tres. Затем HLT могут перетаскивать их из дока FileSystem или даже создавать свои собственные права в Инспекторе. Если бы кто-то хотел сделать то же самое с трейтами, им пришлось бы создать производный трейт, который предоставляет пользовательский конструктор для переопределения конструктора по умолчанию. Затем они использовали бы этот трейт как «предварительную настройку» через императивно закодированный конструктор.

    1. Слабее, потому что это императив, а не декларативный.
    2. Слабее потому, что конструктор должен быть явно определен в скрипте.
    3. Если трейт безымянный, то пользователю нужно будет знать, где он находится, чтобы правильно использовать его вместо базового трейта по умолчанию. Если трейт имеет имя, то он излишне засоряет глобальное пространство имен.
    4. Если они изменят сценарий на use FourWayMoveTrait вместо use MoveTrait , больше не будет никаких устойчивых указаний на то, что сценарий даже совместим с базовым MoveTrait . Это позволяет HLT запутаться в том, может ли FourWayMoveTrait даже измениться на другой MoveTrait без поломки.
    5. Если бы HLT создавал новую реализацию трейта таким образом, они не обязательно знали бы все свойства, которые можно/нужно установить из базового трейта. Это не проблема с ресурсами, созданными в Инспекторе.
  • Можно даже иметь несколько Ресурсов одного типа (если на то есть причина). Черта не поддерживает это, но вместо этого вызывает конфликты синтаксического анализа.

  • Можно изменять конфигурации и/или их отдельные значения, даже не выходя из окна просмотра 2D/3D. Это намного удобнее для HLT (я знаю многих, кто откровенно раздражается, когда им вообще приходится смотреть на код).

Со всем сказанным я согласен, что шаблон раздражает. Однако, чтобы справиться с этим, я бы предпочел добавить систему макросов или фрагментов, чтобы упростить ее. Система фрагментов была бы хорошей идеей, поскольку она могла бы поддерживать любой язык, который можно редактировать в ScriptEditor, а не только GDScript.

О:

но это становится менее практичным (вы не можете сразу сказать, какие свойства вы можете редактировать).

Я говорил об инспекторе, поэтому здесь я имею в виду "HLT". Люди, которые не будут смотреть в код. С трейтами мы могли бы добавить в скрипт новые экспортируемые свойства. С помощью сценариев ресурсов вы можете экспортировать переменные только в сам ресурс, поэтому они не будут отображаться в инспекторе, если вы не отредактируете подресурс.

Я понимаю ваш аргумент, но он выходит за рамки исходной проблемы: избегайте повторения кода (без наследования). Черты для программистов. Во многих случаях есть только одна реализация, которую вы хотите повторно использовать, и вы не хотите показывать ее в инспекторе. Конечно, вы по-прежнему можете использовать скрипты ресурсов без экспорта, просто назначая их прямо в коде, но это все равно не решает проблему повторного использования экспортированных переменных и сигналов из общей реализации, что является одной из основных причин, по которой люди пытаются использовать наследование.

То есть в настоящее время люди пытаются использовать наследование от универсального скрипта не только для функций, но и для экспортируемых свойств (которые обычно связаны с этими функциями) и сигналов (которые затем могут быть связаны с пользовательским интерфейсом).

Это трудно решить. Есть несколько способов сделать это только с помощью GDScript, но опять же они требуют копирования стандартного кода.

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

Было бы неплохо, если бы небо и земля не должны были сдвинуться с места, чтобы достичь этого. ИКС)

@vnen Я понимаю, что ты сейчас говоришь. Что ж, поскольку кажется, что в этом выпуске еще может быть какая-то жизнь, я открою его снова, когда у меня будет следующий шанс.

Есть новости о возобновлении работы? Теперь, когда объявлено о GodotCon 2019 и о Godot Sprint, возможно, стоит поговорить об этом.

@AfterRebelion Я просто забыл вернуться и снова открыть его. Спасибо за напоминание. XD

@willnationsdev Мне понравилось то, что я прочитал о EditorInspectorPlugin! быстрый вопрос, это означает, что я могу создать собственный инспектор для типа данных... например... добавить кнопку в инспектор.
(прошло довольно много времени с тех пор, как я хотел это сделать, чтобы иметь возможность запускать события для целей отладки с помощью кнопки в инспекторе, я мог заставить нажатие кнопки выполнить какой-то скрипт)

@xDGameStudios Да, это и многое другое возможно. Любой пользовательский элемент управления можно добавить в инспектор вверху, внизу, над свойством или под категорией.

@willnationsdev Я не знаю, могу ли я связаться с вами в личном сообщении!! Но я хотел бы узнать больше о EditorInspectorPlugin (например, о каком-то образце кода).. что-то в строках пользовательского типа ресурса (например) MyResource, у которого есть свойство экспорта «имя» и кнопка инспектора, которая печатает «имя» переменная, если я ее нажму (в редакторе или во время отладки)! Документации в этом вопросе не хватает... Я бы сам написал документацию, если бы знал, как это использовать! :Д спасибо

Я также хотел бы узнать больше об этом. ИКС)

Так это то же самое, что и скрипт автозагрузки с подклассами, содержащими статические функции?

например, ваш случай станет Traits.MoveRightTrait.move_right()

или даже проще, иметь разные скрипты автозагрузки для разных трейт-классов:

Movement.move_right()

Нет, черты — это особенность языка, эквивалентная копированию и вставке исходного кода одного сценария в другой сценарий (что-то вроде слияния файлов). Итак, если у меня есть трейт с move_right() , а затем я объявляю, что мой второй скрипт использует этот трейт, то он тоже может использовать move_right() , даже если он не статичен и даже если он обращается к свойства в другом месте класса. Это просто привело бы к ошибке синтаксического анализа, если бы свойство не существовало во втором скрипте.

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

Это было бы здорово, я обнаружил, что мне нужно сделать скрипт с точно такой же функциональностью, просто для использования на разных типах узлов, вызывающих разные extend s, поэтому в основном только для одной строки кода. Кстати, если кто-нибудь знает, как это сделать с текущей системой, дайте мне знать.

Если у меня есть функциональность для сценария с extends Node , есть ли способ привязать такое же поведение к другому типу узла без необходимости дублировать исходный файл и заменять его соответствующим extend ?

Есть прогресс в этом? Мне постоянно приходится дублировать код или добавлять узлы, как я уже говорил. Я знаю, что это не будет сделано в 3.1, но, может быть, стремиться к 3.2?

О, я вообще не работал над этим. На самом деле, я продвинулся дальше в реализации моего метода расширения, чем в этом. Мне нужно поговорить с vnen о них обоих, так как я хотел бы выработать с ним наилучшие детали синтаксиса/реализации.

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

"реализация метода расширения"?

@Zireael07 #15586
Это позволяет людям писать сценарии, которые могут добавлять новые «встроенные» функции для классов движка. Моя интерпретация синтаксиса будет примерно такой:

static Array func sum(p_self: Array):
    if not len(p_self):
        return 0
    var value = p_self[0]
    for i in range(1, len(p_self)):
        value += p_self[i]
    return value

Тогда где-нибудь еще я смогу просто сделать:

var arr = [1, 2, 3]
print(arr.sum()) # prints 6

Он будет тайно вызывать постоянно загружаемый скрипт расширения и вызывать статическую функцию с тем же именем, которая привязана к классу Array, передавая экземпляр в качестве первого параметра.

Я поговорил об этом с @vnen и @jahd2602 . Одна вещь, которая приходит на ум, — это решение Jai для полиморфизма: импорт пространства имен свойства.

Таким образом, вы можете сделать что-то вроде следующего:

class A:
    var a_prop: String = "Hello"
    func foo():
        print("A's a_prop: ", a_prop)
    func bar():
        print("A's bar()")

class B:
    using var a: A = A.new()
    var a_prop: String = "World" # Overriding A's a_prop

    func bar():  # Overriding A's bar()
        print("B's bar()")

func main():
    var b: B = B.new()
    b.foo() # output: "A's a_prop: World"
    b.bar() # output: "B's bar()"

Дело в том, что ключевое слово using импортирует пространство имен свойства, так что b.foo() на самом деле является лишь синтаксическим сахаром для b.a.foo() .

А затем убедиться, что b is A == true и B можно использовать в типизированных ситуациях, которые также принимают A.

Это также имеет то преимущество, что вещи не нужно объявлять как черты, это будет работать для всего, что не имеет общих имен качеств.

Одна из проблем заключается в том, что это плохо сочетается с текущей системой наследования. Если и A, и B являются Node2D, и мы создаем функцию в A: func baz(): print(self.position) , какая позиция будет напечатана, когда мы вызовем b.baz() ?
Одно из решений может заключаться в том, чтобы вызывающая сторона определяла self . Вызов b.foo() вызовет foo() с b как self, а bafoo() вызовет foo() с a как self.

Если бы у нас были автономные методы, такие как Python (где x.f(y) — это сахар для f(x,y) ), это было бы действительно легко реализовать.

Еще одна идея, не связанная с этим:

Сосредоточьтесь только на автономных функциях в стиле JavaScript.

Если мы примем соглашение x.f(y) == f(x,y) для статических функций, то легко получим следующее:

class Jumper:
    static func jump(_self: KinematicBody2D):
        # jump implementation

class Runner:
    static func run(_self: KinematicBody2D, direction: Vector2):
        # run implementation

class Character:
    extends KinematicBody2D
    func run = Runner.run       # Example syntax
    func jump = Jumper.jump

func main():
    var character = Character.new()
    character.jump()
    character.run(Vector2(1,0))

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

@jabcross Звучит хорошо, мне нравится концепция какого-то необязательного пространства имен, и идея функции интересна.

Что касается пространства имен, мне интересно, почему бы просто не using A , другие декларативные вещи кажутся посторонними.

Любопытно также, как это должно быть решено с множественным наследованием. Я полагаю, что вариант A заставляет оба сценария наследовать один и тот же тип, поэтому они оба просто расширяются поверх одного и того же класса без какого-либо специального слияния.

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

В верхней части A.gd:

extends Trait as Node2D
is Trait as Node2D
is Trait extends B
extends B as Trait

О, мне очень нравится концепция импорта пространства имен. Он решает не только проблему трейтов, но и, потенциально, концепцию «методов расширения» для добавления контента к типам движка.

class_name ArrayExt
static func sum(_self: Array) -> int:
    var sum: int = 0
    for a_value in _self:
        sum += a_value
    return sum

using ArrayExt
func _ready():
    var a = [1, 2, 3]
    print(a.sum())

@jabcross Если бы мы также добавили ламбы и/или разрешили объектам реализовывать оператор вызова (и имели бы тип callable для совместимых значений), то мы могли бы начать добавлять более функционально-ориентированный подход к коду GDScript (который Думаю будет отличная идея) Конечно, на тот момент я больше проникал на территорию @vnen #18698, но...

Мы должны учитывать, что GDScript по-прежнему является динамическим языком, и некоторые из этих предложений требуют проверки во время выполнения для правильной отправки вызовов, добавляя штраф к производительности (даже для людей, которые не используют функции, поскольку он должен проверять все вызовы функций). и, возможно, поиск недвижимости). Именно поэтому я не уверен, что добавление расширений является хорошей идеей (тем более, что они по сути являются синтаксическим сахаром).

Я предпочитаю чистую систему черт, где черты — это не классы, а самостоятельные вещи. Таким образом, они могут быть полностью разрешены во время компиляции и отображать сообщения об ошибках в случае конфликтов имен. Я считаю, что это решит проблему без дополнительных затрат времени выполнения.

@vnen Аааа, не знал о стоимости времени выполнения. И если это применимо к любой реализации метода расширения, то я думаю, что это тоже не было бы идеальным.

Если бы мы сделали чистую систему трейтов, не думали ли вы просто сделать trait TraitName вместо extends паре с using TraitName под расширением в других скриптах? И будете ли вы реализовывать это самостоятельно или это будет делегировано?

Если бы мы сделали чистую систему трейтов, не думали ли вы просто сделать trait TraitName вместо extends паре с using TraitName под расширением в других скриптах?

Это моя идея. Я считаю, что это достаточно просто и охватывает почти все случаи повторного использования кода. Я бы даже позволил классу, использующему трейт, переопределить методы трейта (если это возможно сделать во время компиляции). Черты также могут расширять другие черты.

И будете ли вы реализовывать это самостоятельно или это будет делегировано?

Я был бы не против дать задание кому-то другому, кто справится с этим. У меня все равно мало времени. Но мы должны согласовать дизайн заранее. Я довольно гибок в деталях, но он должен 1) не подвергаться проверкам во время выполнения (поверьте, что GDScript хорошо подходит для вещей, которые невозможно понять при компиляции), 2) быть относительно простым и 3) не добавлять слишком много к время компиляции.

@vnen Нравятся эти идеи. Мне было интересно, как вы представляете, что трейт сможет делать такие вещи, как автозаполнение для классов, которые его включают, или это было бы невозможно?

Мне было интересно, как вы представляете, что трейт сможет делать такие вещи, как автозаполнение для классов, которые его включают, или это было бы невозможно?

На мой взгляд, черта будет по сути «импортом». Должно быть тривиально показать завершение, предполагая, что завершение для участников работает.

@vnen Я полагаю, что вы, по сути, проанализируете ClassNode с установленным на нем флагом trait . И затем, если вы сделаете оператор using , он попытается объединить все свойства/методы/сигналы/константы/подклассы в текущий сценарий.

  1. Если метод конфликтует, то текущая реализация скрипта переопределяет базовый метод, как если бы он переопределял унаследованный метод.

    • Но что делать, если в базовом классе уже есть «объединенный» метод?

  2. Если свойство, сигнал и константа перекрываются, проверьте, имеют ли они один и тот же тип/сигнатуру сигнала. Если несовпадения нет, то просто считайте это "слиянием" свойства/сигнала/константы. В противном случае уведомите пользователя о конфликте типа/сигнатуры (ошибке синтаксического анализа).

Плохая идея? Или мы должны просто полностью запретить конфликты, не связанные с методами? Что насчет подклассов? У меня есть ощущение, что мы должны сделать это конфликтами.

@willnationsdev Звучит как «проблема алмаза» (также известная как «смертоносный алмаз смерти»), хорошо задокументированная двусмысленность с различными решениями, уже примененными в различных популярных языках программирования.

Что напоминает мне:
@vnen смогут ли черты расширять другие черты?

@jahd2602 jahd2602 Он уже предположил, что это возможно

Черты также могут расширять другие черты.

@ jahd2602 Основываясь на решениях Perl / Python, кажется, что они в основном образуют «стек» слоев, содержащих содержимое для каждого класса, так что конфликты из последнего использованного признака остаются поверх других версий и перезаписывают их. Это звучит как довольно хорошее решение для этого сценария. Если только у вас или у @vnen нет альтернативных мыслей. Спасибо за связанный обзор решений jahd.

Несколько вопросов.

Во-первых: как мы должны поддерживать оператор using?

Я думаю, что оператор using должен требовать GDScript с постоянным значением.

using preload("res://my_trait.gd") # a preloaded expression
using ScriptClass.MyTrait # a const resource
using Autoload.MyTrait # a const resource
using MyTrait # a regular script class

Я думаю обо всем вышеперечисленном.

Во-вторых: каким должен быть разрешенный синтаксис для определения признака и/или его имени?

Кто-то может не обязательно захотеть использовать класс сценария для своей черты, поэтому я не думаю, что мы должны навязывать требование trait TraitName . Я думаю, что trait должно быть на линии.

Итак, если это скрипт инструмента, он, конечно, должен иметь инструмент вверху. Затем, если это признак, он должен определить, является ли он свойством в следующей строке. При желании разрешите кому-либо указать имя класса сценария после объявления типажа в той же строке, и если они это сделают, не позволяйте им также использовать class_name . Если они опускают имя признака, тогда class_name <name> в порядке. Затем, при расширении другого типа, мы могли бы вставить extends после объявления типажа и/или в отдельной строке после объявления типажа. Итак, я бы посчитал каждое из них действительным:

# Global name from trait keyword.
trait MyTrait extends BaseTrait

# Global name from class_name keyword, but is still a trait and also happens to be a tool script.
tool
trait
extends BaseTrait
class_name MyTrait

# A trait with no global name associated with it. Does not extend anything.
trait

В-третьих: должны ли мы, в целях автодополнения и/или декларации намерений/требований, разрешать трейту определять базовый тип, который он должен расширять?

Мы уже обсуждали, что трейты должны поддерживать наследование других трейтов. Но если мы позволим TraitA использовать extend Node , разрешить сценарию TraitA получить автозаполнение Node, но затем также вызвать ошибку синтаксического анализа, если мы выполним оператор using TraitA , когда текущий класс не расширяет Node или любой из его производных типов?

В-четвертых: вместо того, чтобы трейты расширяли другие трейты, не можем ли мы вместо этого просто сохранить оператор extends , зарезервированный для расширений класса, разрешить трейту вообще не нуждаться в этом операторе, но вместо того, чтобы расширять базовый трейт, разрешить черты, чтобы просто иметь свои собственные операторы using , которые субимпортируют эти черты?

# base_trait.gd
trait
func my_method():
    print("Hello")

# derived_trait.gd
trait
using preload("base_trait.gd")
func my_method():
   print("World") # overrides previous method, will only print "World".

Конечно, преимущество здесь будет заключаться в том, что вы сможете группировать несколько трейтов под одним именем трейта, используя несколько операторов using , подобно включаемым файлам C++, которые включают несколько других классов.

Пятое: если у нас есть трейт, и он имеет using или extends для метода, а затем реализует свой собственный, что мы делаем, когда он вызывается, внутри этой функции .<method_name> для выполнения базовой реализации? Предполагаем ли мы, что эти вызовы всегда выполняются в контексте наследования класса, и иерархия трейтов здесь не имеет никакого влияния?

копия @vnen

Во-первых: как мы должны поддерживать оператор using?

Я думаю, что оператор using должен требовать GDScript с постоянным значением.

using preload("res://my_trait.gd") # a preloaded expression
using ScriptClass.MyTrait # a const resource
using Autoload.MyTrait # a const resource
using MyTrait # a regular script class

Я в порядке со всем этим. Но для пути я использую строку напрямую: using "res://my_trait.gd"

Во-вторых: каким должен быть разрешенный синтаксис для определения признака и/или его имени?

Кто-то может не обязательно захотеть использовать класс сценария для своей черты, поэтому я не думаю, что мы должны навязывать требование trait TraitName . Я думаю, что trait должно быть на линии.

Итак, если это скрипт инструмента, он, конечно, должен иметь инструмент вверху. Затем, если это признак, он должен определить, является ли он свойством в следующей строке. При желании разрешите кому-либо указать имя класса сценария после объявления типажа в той же строке, и если они это сделают, не позволяйте им также использовать class_name . Если они опускают имя признака, тогда class_name <name> в порядке. Затем, при расширении другого типа, мы могли бы вставить extends после объявления типажа и/или в отдельной строке после объявления типажа. Итак, я бы посчитал каждое из них действительным:

# Global name from trait keyword.
trait MyTrait extends BaseTrait

# Global name from class_name keyword, but is still a trait and also happens to be a tool script.
tool
trait
extends BaseTrait
class_name MyTrait

# A trait with no global name associated with it. Does not extend anything.
trait

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

Я согласен, что черта не обязательно имеет глобальное имя. Я бы использовал trait аналогично tool . Он должен быть первым в файле скрипта (кроме комментариев). За ключевым словом необязательно должно следовать имя признака. Я бы не стал использовать для них class_name , поскольку они не являются классами.

В-третьих: должны ли мы, в целях автодополнения и/или декларации намерений/требований, разрешать трейту определять базовый тип, который он должен расширять?

Честно говоря, мне не нравится добавлять функции в язык ради редактора. Вот где аннотации пригодятся.

Теперь, если мы хотим, чтобы черты применялись только к определенному типу (и его производным), тогда все в порядке. На самом деле, я думаю, что это лучше для статических проверок: это позволяет трейту использовать вещи из класса, в то время как компиляция может фактически проверить, используются ли они с правильными типами и так далее.

В-четвертых: вместо того, чтобы трейты расширяли другие трейты, не можем ли мы вместо этого просто сохранить оператор extends , зарезервированный для расширений класса, разрешить трейту вообще не нуждаться в этом операторе, но вместо того, чтобы расширять базовый трейт, разрешить черты, чтобы просто иметь свои собственные операторы using , которые субимпортируют _тех_ черт?

Ну, это в основном вопрос семантики. Когда я упомянул, что трейты могут расширять другие, я на самом деле не имел в виду ключевое слово extends . Основное отличие состоит в том, что с extends вы можете расширить только одну черту, а с using вы можете встроить в одну много других черт. Я согласен с using , пока нет циклов, это не проблема.

Пятое: если у нас есть трейт, и он имеет using или extends для метода, а затем реализует свой собственный, что мы делаем, когда он вызывается внутри этой функции .<method_name> для выполнения базовой реализации? Предполагаем ли мы, что эти вызовы всегда выполняются в контексте наследования класса, и иерархия трейтов здесь не имеет никакого влияния?

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

OTOH, трейт почти похож на «включение», поэтому он будет дословно применяться к классу, поэтому вызывается родительская реализация. Но, честно говоря, я бы просто запретил точечную нотацию, если метод не найден в родительском трейте.

Как насчет черты, которая требует, чтобы класс имел одну или несколько других черт? Например, трейт DoubleJumper требует как трейта Jumper , трейта Upgradable и класса, наследующего KinematicBody2D .

Rust, например, позволяет вам использовать такие сигнатуры типов. Что-то вроде KinematicBody2D: Jumper, Upgradable . Но поскольку мы используем : для аннотирования текста, мы могли бы просто использовать KinematicBody2D & Jumper & Upgradable или что-то в этом роде.

Есть еще проблема полиморфизма. Что, если реализация трейта различна для каждого класса, но предоставляет один и тот же интерфейс?

Например, нам нужен метод kill() в трейте Jumper , который используется как Enemy , так и Player . Нам нужны разные реализации для каждого случая, сохраняя при этом обе совместимые с одной и той же сигнатурой типа Jumper . Как это сделать?

Для полиморфизма вы просто создадите отдельный трейт, который включает трейт с kill() , а затем реализует свою собственную конкретную версию метода. Использование трейтов, которые переопределяют методы ранее включенных трейтов, — это то, как вы справитесь с этим.

Кроме того, я не думаю, что были какие-либо планы (пока) сделать подсказку типа требованиями к характеристикам. Это то, что мы хотели бы сделать?

создать отдельную черту

Разве это не сгенерирует кучу одноразовых трейт-файлов? Если бы мы могли делать объявления вложенных трейтов (аналогично ключевому слову class ), это было бы удобнее. Мы также могли бы просто переопределить методы непосредственно в классе, который использует трейт.

Я был бы очень признателен за надежную систему подписи типов (возможно, с логической композицией и необязательными/ненулевыми значениями). Черты бы подошли.

Я не уверен, что это обсуждалось, но я думаю, что должна быть возможность вызывать конкретную версию функции. Например:

trait A
func m():
  print("A")

trait B
func m():
  print("B")

class C
using A
using B

func c():
  A.m()
  B.m()
  m()

который печатает: A , B , B .


Также я не совсем уверен в «отсутствии затрат времени выполнения». Как будут обрабатываться динамически загружаемые сценарии (недоступные во время экспорта) с классами, использующими черты, определенные перед экспортом? Я что-то неправильно понимаю? Или тот случай не считается "временем исполнения"?

Я не уверен, что это обсуждалось, но я думаю, что должна быть возможность вызывать конкретную версию функции.

Я уже рассматривал это, но я не уверен, что класс может использовать конфликтующие черты (то есть черты, которые определяют метод с тем же именем). Порядок операторов using не должен иметь значения.

Также я не совсем уверен в «отсутствии затрат времени выполнения». Как будут обрабатываться динамически загружаемые сценарии (недоступные во время экспорта) с классами, использующими черты, определенные перед экспортом? Я что-то неправильно понимаю? Или тот случай не считается "временем исполнения"?

Дело не в экспорте. Это определенно повлияет на время загрузки, поскольку компиляция происходит при загрузке (хотя я не думаю, что это будет очень существенно), но это не должно влиять на выполнение скрипта. В идеале скрипты должны быть скомпилированы при экспорте, но это уже другой разговор.

Всем привет.

Я новичок в Godot и привык к нему за последние несколько дней. Когда я пытался выяснить, какие лучшие практики можно использовать для создания повторно используемых компонентов, я выбрал шаблон. Я бы всегда делал корневой узел подсцены, который предназначен для экземпляра в другой сцене, экспортировал бы все свойства, которые я намерен установить извне. Насколько это было возможно, я хотел сделать знание внутренней структуры инстанса ветки ненужным для остальной части сцены.

Чтобы это работало, корневой узел должен «экспортировать» свойства, а затем копировать значения в соответствующий дочерний узел в _ready. Так, например, представьте узел Bomb с дочерним Timer. Корневой узел Bomb в подсцене экспортирует «detonation_time», а затем делает $Timer.wait_time = detonation_time в _ready. Это позволяет нам красиво устанавливать его в пользовательском интерфейсе Godot всякий раз, когда мы его копируем, без необходимости делать дочерние элементы редактируемыми и переходить к таймеру.

тем не мение
1) Это очень механическое преобразование, поэтому кажется, что что-то подобное может поддерживаться системой.
2) Это, вероятно, добавляет небольшую неэффективность при установке соответствующего значения непосредственно в дочернем узле.

Прежде чем я продолжу, это может показаться второстепенным по отношению к тому, что обсуждается, потому что это не предполагает разрешения своего рода «частного» наследования (на языке C++). Тем не менее, мне на самом деле нравится система Godot построения поведения путем составления элементов сцены, а не более похожей на наследование инженерии. Эти «записанные» отношения неизменны и статичны. OTOH, структура сцены динамическая, вы даже можете изменить ее во время выполнения. Логика игры настолько подвержена изменениям во время разработки, что я думаю, что дизайн Годо очень хорошо подходит для этого варианта использования.

Это правда, что дочерние узлы используются как поведенческие расширения корневых узлов, но это не приводит к тому, что им не хватает самодостаточности, IMO. Таймер совершенно самодостаточен и предсказуем в поведении, независимо от того, к какому времени он привык. Используете ли вы ложку, чтобы пить суп или есть мороженое, она надлежащим образом выполняет свою функцию, даже если действует как продолжение вашей руки. Я рассматриваю корневые узлы как маэстро, которые координируют поведение дочерних узлов, поэтому им не нужно напрямую знать друг о друге и, ПОЭТОМУ, они могут оставаться автономными. Родительские/корневые узлы — это привязанные к рабочему столу менеджеры, которые делегируют обязанности, но не выполняют прямой работы. Поскольку они тонкие, легко создать новый для немного другого поведения.

Однако я думаю, что корневые узлы также должны выступать в качестве основного ИНТЕРФЕЙСА для функциональности всей инстансированной ветки. Все свойства, которые можно настроить в экземпляре, должны быть «устанавливаемыми» в корневом узле ветви, даже если конечным владельцем свойства является какой-либо дочерний узел. Если я что-то не упустил, это нужно настроить вручную в текущей версии Godot. Было бы неплохо, если бы это можно было как-то автоматизировать, чтобы объединить преимущества динамической системы с более простым написанием сценариев.

Одна вещь, о которой я думаю, это система «динамического наследования», если хотите, доступная для подклассов Node. В таком скрипте будет два источника свойств/методов: те, что из скрипта, который он расширяет, и те, которые «всплывают» из дочерних элементов в структуре сцены. Таким образом, мой пример с бомбой стал бы чем-то вроде export lifted var $Timer.wait_time [= value?] as detonation_time в разделе переменных-членов сценария bomb.gd. Система, по сути, сгенерирует $Timer.wait_time = detonation_time в обратном вызове _ready и сгенерирует геттер/сеттер, который позволит $Bomb.detonation_time = 5 из родительского узла Bomb привести к установке $Timer.wait_time = 5 .

В примере OP с MoveRightTrait у нас будет узел, к которому подключен mysprite.gd, с MoveRightTrait в качестве дочернего узла. Тогда в mysprite.gd у нас будет что-то вроде lifted func $MoveRightTrait.move_right [as move_right] (возможно, 'as' может быть необязательным, если имя будет таким же). Теперь вызов move_right для объекта скрипта, созданного из mysprite.gd, будет автоматически делегировать соответствующий дочерний узел. Возможно, сигналы можно было бы пузырить, чтобы их можно было прикрепить к дочернему узлу от корня? Возможно, целые узлы можно было бы пузырить всего за lifted $MoveRightTrait [as MvR] без func, signal или var. В этом случае все методы и свойства в MoveRightTrait будут доступны из mysprite напрямую как mysprite.move_right или через mysprite.MvR.move_right, если используется «как MvR».

Это одна из идей того, как упростить создание ИНТЕРФЕЙСА для структуры сцены в корне ветки экземпляра, увеличив их характеристику «черного ящика» и обеспечив удобство написания сценариев наряду с мощью системы динамических сцен Godot. Конечно, нужно было бы учесть множество побочных деталей. Например, в отличие от базовых классов дочерние узлы могут быть удалены во время выполнения. Как должны вести себя всплывающие/поднятые функции и свойства при вызове/доступе в этом случае ошибки? Если узел с правильным NodePath добавляется обратно, начинают ли снова работать поднятые свойства? [ДА, ИМО] Также было бы ошибкой использовать «поднятые» в классах, не производных от Node, поскольку в этом случае никогда не будет дочерних элементов, из которых можно поднять/поднять. Кроме того, возможны конфликты имен с дублированием «как {имя}» или «снято $Timer1, снято $Timer2», где узлы имеют свойства/методы с одинаковыми именами. Интерпретатор сценариев в идеале должен обнаруживать такие логические проблемы.

Я чувствую, что это даст нам многое из того, что мы хотим, хотя на самом деле это просто синтаксический сахар, который избавляет нас от необходимости писать функции пересылки и инициализации. Кроме того, поскольку это принципиально просто концептуально, его не должно быть сложно реализовать или объяснить.

В любом случае, если вы зашли так далеко, спасибо за чтение!

Я везде использовал слово «поднято», но это просто иллюстративно.
Что-то вроде using var $Timer.wait_time as detonation_time или using $Timer , очевидно, так же хорошо. В любом случае вы получаете удобное псевдонаследование от дочерних узлов, создавая согласованную единую точку доступа к желаемой функциональности в корне ветки, которую нужно инстансировать. Требование к многократно используемым функциональным возможностям заключается в том, что они расширяют Node или его подкласс, чтобы их можно было добавлять в качестве дочерних элементов к более крупному компоненту.

Другой взгляд на это заключается в том, что ключевое слово «extends» в сценарии, который наследуется от Node, дает вам отношение «is-a», в то время как использование ключевого слова «using» или «lifted» в сценарии для «пузыря» Члены узла-потомка дают вам нечто похожее на «реализует» [эй, возможное ключевое слово], которое существует в языках с одиночным наследованием, но несколькими «интерфейсами» (например, Java). В неограниченном множественном наследовании (например, в С++) базовые классы образуют [статическое, записанное] дерево. По аналогии я предлагаю наслоить удобный синтаксис и исключить шаблоны поверх существующих деревьев узлов Godot.

Если когда-либо будет определено, что это то, что стоит изучить, необходимо рассмотреть следующие аспекты пространства дизайна:
1) Должны ли мы допускать в «использование» только непосредственных детей. IOW using $Timer , но не using $Bomb/Timer'? This would be simpler but would force us to write boilerplate in some cases. I say that a full NodePath ROOTED in the Node to which the script is attached should be legal [but NO references to parents/siblings allowed]. 2) Should there be an option that find_node the "using"-ed node instead of following a written in NodePath? For example с использованием «Таймера» with a string for the pattern would be slower but the forwarding architecture would continue to work if a referenced node's position in the sub-tree changes at run time. This could be used selectively for child nodes that we expect to move around beneath the root. Of course syntax would have to be worked out especially when using a particular member (eg. с использованием var «Таймер».wait_time as detonation_time is icky). 3) Should there be a way query for certain functionality [equivalent to asking if an interface is implemented or a child node is present]? Perhaps "using" entire nodes with aliases should allow testing the alias to be a query. So с использованием MoveRightTrait as DirectionalMover in a script would result in node.DirectionalMover returning the child MoveRightTrait. This is logical because node.DirectionalMover.move_right() calls the method on the child MoveRightTrait. Other nodes without that statement would return null. So the statement , если node.DirectionalMover:` по соглашению станет проверкой функциональности.
4) Паттерн состояния должен быть реализуем путем замены "использующего" узла другим, который имеет альтернативное поведение, но с тем же интерфейсом [утиный ввод] и тем же NodePath, что и указанный в операторе "using". С тем, как работает дерево сцен, это получится почти бесплатно. Однако системе придется отслеживать сигналы, подключенные через родителя, и восстанавливать соединения в замененном дочернем элементе.

Я работаю с GDScript уже некоторое время, и я должен согласиться, что какая-то черта/примесь и функция прокси/делегирования крайне необходимы. Довольно раздражает необходимость настраивать весь этот шаблон только для того, чтобы подключать свойства или выставлять методы дочерних элементов в корне сцены.

Или добавление уровней дерева только для имитации компонентов (это довольно быстро становится довольно громоздким, потому что тогда вы ломаете все пути узлов с каждым новым компонентом). Может быть, есть лучший способ, что-то вроде мета/мульти-скрипта, позволяющего использовать несколько скриптов на одном узле? Если у вас есть идиоматическое решение, пожалуйста, поделитесь...

Добавление C++ (GDNative) в микс делает ситуацию еще хуже, потому что _ready и _init ведут себя там по-разному (читай: инициализация со значениями по умолчанию работает наполовину или не работает вообще).

Это основное, с чем мне приходится работать в GDScript. Мне часто нужно разделить функциональность между узлами, не структурируя всю мою структуру наследования вокруг этого — например, у моего игрока и владельцев магазинов есть инвентарь, у моего игрока + предметов + врагов есть статистика, у моего игрока и врагов есть экипированные предметы и т. д.

В настоящее время я реализую эти общие «компоненты» как классы или узлы, загружаемые в «сущности», которым они требуются, но это беспорядочно (добавляет множество поисков узлов, делает практически невозможным утиную печать и т. д.), а альтернативные подходы имеют свои недостатки, поэтому Я не нашел лучшего способа. Traits/mixins абсолютно спасли бы мне жизнь.

К чему это сводится, так это к возможности совместного использования кода между объектами без использования наследования, что, я думаю, необходимо и невозможно сделать в Godot прямо сейчас.

То, как я понимаю черты Rust (https://doc.rust-lang.org/1.8.0/book/traits.html), заключается в том, что они похожи на классы типов Haskell, где вам требуется, чтобы некоторые параметризованные функции были определены для типа вы добавляете трейт, а затем можете использовать некоторые универсальные функции, определенные для любых типов, которые реализуют трейт. Являются ли трейты Rust чем-то отличным от того, что предлагается здесь?

Этот, вероятно, будет перенесен оптом, так как здесь он широко обсуждался.

Этот, вероятно, будет перенесен оптом, так как здесь он широко обсуждался.

_Я нахожу "перемещение" предложений бессмысленным imo, их лучше закрыть и попросить повторно открыть в godot-предложениях, если люди проявят интерес, и пусть другие предложения действительно реализуются, если это необходимо. В любом случае..._

Я наткнулся на этот вопрос год назад, но только сейчас начинаю понимать потенциальную полезность системы черт.

Делюсь своим текущим рабочим процессом в надежде вдохновить кого-то лучше понять проблему (и, возможно, предложить лучшую альтернативу, кроме внедрения системы черт).

1. Создайте инструмент для создания шаблонов компонентов для каждого используемого типа узла в проекте:

@willnationsdev https://github.com/godotengine/godot/issues/23101#issuecomment -431468744

Теперь, что бы сделать ЭТО проще, было бы наличие системы фрагментов или системы макросов, чтобы разработчикам было проще создавать эти повторно используемые разделы декларативного кода.

Идти по твоим стопам... 😅

tool
extends EditorScript

const TYPES = [
    'Node',
    'Node2D',
]
const TYPES_PATH = 'types'
const TYPE_BASENAME_TEMPLATE = 'component_%s.gd'

const TEMPLATE = \
"""class_name Component{TYPE} extends {TYPE}

signal host_assigned(node)

export(bool) var enabled = true

export(NodePath) var host_path
var host

func _ready():
    ComponentCommon.init(self, host_path)"""

func _run():
    _update_scripts()


func _update_scripts():

    var base_dir = get_script().resource_path.get_base_dir()
    var dest = base_dir.plus_file(TYPES_PATH)

    for type in TYPES:
        var filename = TYPE_BASENAME_TEMPLATE % [type.to_lower()]
        var code = TEMPLATE.format({"TYPE" : type})
        var path = dest.plus_file(filename)

        print_debug("Writing component code for: " + path)

        var file = File.new()
        file.open(path, File.WRITE)
        file.store_line(code)
        file.close()

2. Создайте статический метод для повторного использования для инициализации компонентов на хосте (например, root):

class_name ComponentCommon

static func init(p_component, p_host_path = NodePath()):

    assert(p_component is Node)

    # Try to assign
    if not p_host_path.is_empty():
        p_component.host = p_component.get_node(p_host_path)

    elif is_instance_valid(p_component.owner):
        p_component.host = p_component.owner

    elif is_instance_valid(p_component.get_parent()):
        p_component.host = p_component.get_parent()

    # Check
    if not is_instance_valid(p_component.host):
        push_warning(p_component.name.capitalize() + ": couldn't find a host, disabling.")
        p_component.enabled = false
    else:
        p_component.emit_signal('host_assigned')

Вот как компонент (трейт) выглядит после создания с помощью первого скрипта:

class_name ComponentNode2D extends Node2D

signal host_assigned(node)

export(bool) var enabled = true

export(NodePath) var host_path
var host

func _ready():
    ComponentCommon.init(self, host_path)

(Необязательно) 3. Расширьте компонент (черту)

@vnen https://github.com/godotengine/godot/issues/23101#issuecomment -471816901

Это моя идея. Я считаю, что это достаточно просто и охватывает почти все случаи повторного использования кода. Я бы даже позволил классу, использующему трейт, переопределить методы трейта (если это возможно сделать во время компиляции). Черты также могут расширять другие черты.

Идти по твоим стопам... 😅

class_name ComponentMotion2D extends ComponentNode2D

const MAX_SPEED = 100.0

var linear_velocity = Vector2()
var collision

export(Script) var impl
...

На самом деле, экспортированные Script используются в этих компонентах для управления поведением определенных типов хоста/корневого узла для каждого компонента. Здесь ComponentMotion2D будет иметь в основном два скрипта:

  • motion_kinematic_body_2d.gd
  • motion_rigid_body_2d.gd

Таким образом, дети по-прежнему управляют поведением host / root здесь. Терминология host исходит от меня, когда я использую конечные автоматы, и здесь черты, возможно, не будут идеально подходить, потому что состояния лучше организованы как узлы imo.

Сами компоненты «зашиты» в корень, делая их членами onready , эффективно уменьшая шаблонный код (за счет фактической необходимости ссылаться на них как object.motion )

extends KinematicBody2D

onready var motion = $motion # ComponentMotion2D

Не уверен, что это поможет решить проблему, но в C# есть так называемые методы расширения, которые расширяют функциональность типа класса.

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

расширение.gd

# any script that uses this method must be an instance of `Node2D`
static func distance(self source: Node2D, target: Node2D):
    return source.global_position.distance_to(target.global_position)

# any script that uses this method must be an instance of `Rigidbody2D`
# a `Sprite` instance cannot use this method
static func distance(self source: Rigidbody2D, target: Node2D):
    return source.global_position.distance_to(target.global_position)

Затем, когда вы хотите использовать метод distance , вы просто делаете это:

player.gd

func _ready() -> void:
    print(self.distance($Enemy))
    print($BulletPoint.distance($Enemy))

Я знаком с этим, но это не помогает решить проблему. Хехе, спасибо однако.

Методы расширения @TheColorRed уже были предложены, но я не думаю, что они осуществимы на динамическом языке. Я также не думаю, что они решают основную проблему, из-за которой началось это обсуждение.


С другой стороны, я, вероятно, открою многие предложения по GDScript как GIP (включая это, если @willnationsdev не возражает).

Я по-прежнему считаю, что трейты имеют смысл для горизонтального разделения кода в объектно-ориентированном языке (без множественного наследования, но я не хочу идти по этому пути).

Я не думаю, что они осуществимы на динамическом языке

Является ли GDS динамическим? Методы расширения могут быть ограничены только типизированными экземплярами и будут работать точно так же, как и в других языках - просто синтаксический сахар во время компиляции, заменяющий вызов метода вызовом статического метода (функции). Честно говоря, я бы предпочел сутенерство (расширенные методы) прототипам аля JS или другим динамическим способам прикрепления методов к классам или даже просто к экземплярам.

Что бы мы ни решили сделать, надеюсь, мы не назовем это «сутенером».

Является ли GDS динамическим?

В этом контексте существует множество определений термина «динамический». Для ясности: тип переменной может быть неизвестен во время компиляции, поэтому проверку расширения метода необходимо выполнять во время выполнения (что так или иначе повлияет на производительность).

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

Если мы начнем делать это, мы могли бы также сделать GDScript только типизированным. Но это совсем другая дискуссия, которую я не буду здесь обсуждать.

Дело в том, что вещи не должны запускаться или переставать работать из-за того, что пользователь добавил типы в скрипт. Это почти всегда сбивает с толку, когда это происходит.

Опять же, я не думаю, что это решает проблему в любом случае. Мы пытаемся прикрепить один и тот же код к нескольким типам, а метод расширения добавит его только к одному типу.

Честно говоря, я бы предпочел сутенерство (расширенные методы) прототипам аля JS или другим динамическим способам прикрепления методов к классам или даже просто к экземплярам.

Никто не предлагал (пока) прикреплять методы динамически (во время выполнения), и я этого тоже не хочу. Трейты будут применяться статически во время компиляции.

Сначала я сделал комментарий о Haxe и его библиотеке макросов миксинов, но потом понял, что большинство пользователей все равно не будут использовать сторонний язык.

Я недавно столкнулся с потребностью в этом.

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

например, у меня есть некоторые классы, которые не могут наследоваться от одного и того же родителя, но используют аналогичный набор API:
Склад: Финансы, Удаление, Взаимодействие с мышью и другие
Транспортное средство: Финансы, Удаление, Взаимодействие с мышью и другие
VehicleTerminal: Финансы, Удаление, Взаимодействие с мышью и другие

Для финансов я использовал композицию, так как для этого требуется наименьший шаблонный код, поскольку get_finances_component() является достаточным API, поскольку он вообще не заботится об игровых объектах.

Другие:
MouseInteraction и Delection Мне просто нужно было скопировать и вставить, так как ему нужно знать об игровых объектах, некоторая композиция здесь не работает, если я не сделал какое-то странное делегирование:

Warehouse:
  func delete():
      get_delete_component().delete(self);

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

MouseInteraction и Delection Мне просто нужно было скопировать и вставить, так как ему нужно знать об игровых объектах, некоторая композиция здесь не работает, если я не сделал какое-то странное делегирование.

В настоящее время я получаю доступ к компонентам через узлы onready . Я делаю что-то подобное:

# character.gd

var input = $input # input component

func _set(property, value):
    if property == "focused": # override
        input.enabled = value
    return true

Итак, это:

character.input.enabled = true

становится таким:

character.focused = true

Как любезно указал @Calinou , моя проблема https://github.com/godotengine/godot-proposals/issues/758 тесно связана с этим. Что вы думаете о предложении добавить черту в группу? Это может резко увеличить потребность в сценариях и других накладных расходах.

Просто было бы здорово иметь способ внедрить общий код в классы, и если они экспортировали значения, чтобы они отображались в инспекторе, а методы и свойства были доступны и обнаруживались автодополнением кода.

Предложения по функциям и улучшениям для Godot Engine в настоящее время обсуждаются и рассматриваются в специальном трекере ошибок Godot Improvement Proposals (GIP) ( godotengine/godot-proposals ). Средство отслеживания GIP имеет подробный шаблон проблемы, разработанный таким образом, чтобы предложения включали всю необходимую информацию, чтобы начать продуктивное обсуждение и помочь сообществу оценить обоснованность предложения для движка.

Основной трекер ( godotengine/godot ) теперь предназначен исключительно для отчетов об ошибках и запросов на вытягивание, что позволяет участникам лучше сосредоточиться на работе по исправлению ошибок. Поэтому сейчас мы закрываем все старые предложения по функциям в основном трекере проблем.

Если вы заинтересованы в этом предложении по функциям, откройте новое предложение в трекере GIP, следуя заданному шаблону задачи (после проверки того, что он еще не существует). Не забудьте указать ссылку на этот закрытый вопрос, если он включает какое-либо соответствующее обсуждение (которое вам также рекомендуется обобщить в новом предложении). Заранее спасибо!

Примечание: это популярное предложение, если кто-то переместит его в «Предложения Годо», постарайтесь также подвести итоги обсуждения.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги