Godot: Adicione um sistema GDScript Trait.

Criado em 18 out. 2018  ·  93Comentários  ·  Fonte: godotengine/godot

(Editar:
Para minimizar outros problemas de problemas XY:
O problema abordado aqui é que o sistema Node-Scene / linguagens de script de Godot ainda não suportam a criação de implementações agrupadas e reutilizáveis ​​que são 1) específicas para os recursos do nó raiz e 2) que podem ser trocadas e/ou combinadas. Scripts com métodos estáticos ou subnós com scripts podem ser usados ​​para o último bit e, em muitos casos, isso funciona. No entanto, Godot geralmente prefere que você mantenha a lógica para o comportamento geral da sua cena armazenada no nó raiz enquanto usa dados calculados pelos nós filhos ou delega subtarefas significativamente desviadas para eles, por exemplo, um KinematicBody2D não gerencia a animação, portanto, delega isso a um AnimationPlayer.

Ter um nó raiz mais fino que usa nós filhos "componentes" para direcionar seu comportamento é um sistema fraco em comparação. Ter um nó raiz em grande parte vazio que apenas delega todo o seu comportamento para nós filhos é contrário a esse paradigma. Os nós filhos tornam-se extensões comportamentais do nó raiz, em vez de objetos auto-suficientes que realizam uma tarefa por si mesmos. É muito desajeitado, e o design pode ser simplificado/aprimorado, permitindo que consolidemos toda a lógica no nó raiz, mas também nos permita dividir a lógica em partes diferentes e combináveis.

Suponho que o tópico deste problema seja mais sobre como lidar com o problema acima do que especificamente sobre GDScript, mas acredito que GDScript Traits seria a abordagem mais simples e direta para resolver o problema.
)

Para os desinformados, os traits são essencialmente uma maneira de misturar duas classes em uma (praticamente um mecanismo de copiar/colar), apenas, em vez de copiar/colar literalmente o texto dos arquivos, tudo o que você está fazendo é usar uma instrução de palavra-chave para vincule os dois arquivos. (Edit: o truque é que, embora um script possa herdar apenas uma classe, ele pode incluir várias características)

Estou imaginando algo em que qualquer arquivo GDScript pode ser usado como um trait para outro arquivo GDScript, desde que o tipo traited estenda uma classe herdada pelo script mesclado, ou seja, um GDScript que estende Sprite não pode usar um Resource GDScript como uma característica, mas pode usar um GDScript Node2D. Eu imaginaria uma sintaxe semelhante a esta:

# 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")

Eu posso ver duas maneiras de fazer isso:

  1. Pré-analisar o script via RegEx para "^trait \processo de recarga). Teríamos que não suportar o aninhamento de características ou reexaminar continuamente o código-fonte gerado após cada iteração para ver se mais inserções de características foram feitas.
  2. Analise o script normalmente, mas ensine o analisador a reconhecer a palavra-chave, carregar o script referido, analisar esse script e, em seguida, anexar o conteúdo de seu ClassNode ao ClassNode gerado do script atual (efetivamente, pegando os resultados analisados ​​de um script e adicionando-o aos resultados analisados ​​do outro script). Isso daria suporte automaticamente ao aninhamento de tipos com características.

Por outro lado, as pessoas podem querer que o traço GDScript tenha um nome, mas podem NÃO querer que o class_name do GDScript apareça no CreateDialog (porque não deve ser criado por conta própria). Nesse caso, pode NÃO ser uma boa ideia fazer com que qualquer script o suporte; apenas aqueles que estão especialmente marcados (talvez escrevendo 'trait' no topo do arquivo?). Enfim, coisas para pensar.

Pensamentos?

Edit: Após algumas ponderações, acredito que a opção 2 seria muito melhor, pois 1) saberíamos de QUAL script um segmento de script veio (para melhor relatório de erros) e 2) seríamos capazes de identificar erros à medida que eles acontecem desde o os scripts incluídos devem ser analisados ​​em sequência, em vez de apenas analisar tudo no final. Isso reduziria o tempo de processamento que adiciona ao processo de análise.

archived discussion feature proposal gdscript

Comentários muito úteis

@aaronfranke Traits , basicamente a mesma coisa que Mixins , tem um caso de uso completamente diferente das interfaces precisamente porque incluem implementações dos métodos. Se uma interface fornecesse uma implementação padrão, ela não seria mais uma interface.

Traits/Mixins estão presentes em PHP, Ruby, D, Rust, Haxe, Scala e muitas outras linguagens (conforme detalhado nos Wikis vinculados), então eles já devem estar amplamente familiarizados com pessoas que possuem um amplo repertório de familiaridade com linguagens de programação.

Se fôssemos implementar interfaces (que também não me oponho, especialmente com a tipagem estática opcional), seria efetivamente apenas uma maneira de especificar assinaturas de função e exigir que os scripts GDScript relevantes implementassem essas assinaturas de função, com características incluídos (se já existiam até esse ponto).

Todos 93 comentários

Qual é o benefício em vez de: extends "res://move_right_trait.gd"

@MrJustreborn Porque você pode ter vários traços em uma classe, mas só pode herdar um script.

Se bem entendi, isso é basicamente o que o C# chama de " interfaces ", mas com métodos não abstratos? Pode ser melhor chamar as interfaces de recursos em vez de características para serem familiares aos programadores.

@aaronfranke Traits , basicamente a mesma coisa que Mixins , tem um caso de uso completamente diferente das interfaces precisamente porque incluem implementações dos métodos. Se uma interface fornecesse uma implementação padrão, ela não seria mais uma interface.

Traits/Mixins estão presentes em PHP, Ruby, D, Rust, Haxe, Scala e muitas outras linguagens (conforme detalhado nos Wikis vinculados), então eles já devem estar amplamente familiarizados com pessoas que possuem um amplo repertório de familiaridade com linguagens de programação.

Se fôssemos implementar interfaces (que também não me oponho, especialmente com a tipagem estática opcional), seria efetivamente apenas uma maneira de especificar assinaturas de função e exigir que os scripts GDScript relevantes implementassem essas assinaturas de função, com características incluídos (se já existiam até esse ponto).

Talvez uma palavra-chave como includes ?

extends Node2D
includes TraitClass

Embora outros nomes como trait, mixin, has, etc. também sejam bons.

Eu também gosto da ideia de ter alguma opção para excluir o tipo class_name do menu adicionar. Pode ficar muito confuso com tipos pequenos que não funcionam por conta própria como nós.

Pode até ser apenas um tópico de destaque próprio.

(Acidentalmente excluí meu comentário, woops! Além disso, obrigatório "por que você não permite vários scripts, a unidade faz isso" )

Como isso funcionará no VisualScript, se funcionar?

Além disso, poderia ser benéfico incluir uma interface de inspetor para características, se as características fossem implementadas? Imagino que alguns casos de uso para traits possam incluir casos de uso onde existam apenas traits e nenhum script (pelo menos, nenhum script além de um que inclua os arquivos de traits). No entanto, ao pensar mais sobre isso, me pergunto se o esforço colocado em fazer essa interface pode valer a pena, em comparação com apenas fazer um script que inclua os arquivos de características.

@LikeLakers2

Como isso funcionará no VisualScript, se funcionar?

Se for feito da maneira que sugeri, não aconteceria com o VisualScript. Apenas GDScript. Qualquer sistema de características implementado para VisualScript seria projetado de forma completamente diferente porque VisualScript não é uma linguagem analisada. No entanto, não exclui a possibilidade (apenas precisaria ser implementado de maneira diferente). Além disso, talvez devêssemos considerar obter suporte à herança do VisualScript primeiro? lol

Além disso, poderia ser benéfico incluir uma interface de inspetor para características, se as características fossem implementadas?

Não teria muito sentido. As características simplesmente transmitem detalhes ao GDScript, entregando a ele as propriedades, constantes, sinais e métodos definidos pela característica.

Imagino que alguns casos de uso para traits possam incluir casos de uso onde existam apenas traits e nenhum script (pelo menos, nenhum script além de um que inclua os arquivos de traits).

Traits, como são representados em outras linguagens, nunca são utilizáveis ​​isoladamente, mas devem ser incluídos em outro script para serem utilizáveis.

Eu me pergunto se o esforço colocado em fazer essa interface pode valer a pena

Criar uma interface do Inspector de alguma forma não faria muito sentido apenas para o GDScript. Adicionar ou remover um trait envolveria editar diretamente o código fonte da propriedade source_code do recurso Script, ou seja, não é uma propriedade no próprio Script. Portanto, ou...

  1. o editor teria que ser ensinado como lidar especificamente com a edição adequada do código-fonte para arquivos GDScript para fazer isso (propenso a erros), ou ...
  2. todos os Scripts teriam que suportar traços para que o GDScriptLanguage pudesse fornecer seu próprio processo interno para adicionar e remover traços (mas nem todas as linguagens suportam traços, então a propriedade não seria significativa em todos os casos).

Qual a necessidade de tal recurso? Existe alguma coisa que isso permite que você não possa fazer agora? Ou torna algumas tarefas significativamente mais rápidas de lidar?

Prefiro manter o GDscript em uma linguagem simples do que adicionar recursos complexos quase nunca usados.

Ele resolve o problema Child-Nodes-As-Script-Dependencies com o qual esse cara teve um problema , mas não vem com o mesmo tipo de bagagem que o MultiScript tinha porque é restrito a um único idioma. O módulo GDScript pode isolar a lógica de como as características se relacionam entre si e com o script principal, enquanto resolver as diferenças entre linguagens diferentes seria muito mais complicado.

Com a ausência de múltiplas importações/múltipla herança, as dependências de nós-filho-como-script são a única maneira de evitar repetir MUITO o código, e isso definitivamente resolveria o problema de uma maneira agradável.

@groud @Zireael07 Quero dizer, a abordagem mais radical e entre linguagens seria 1) reprojetar completamente o Object para usar um ScriptStack para unir scripts empilhados em uma única representação de script, 2) reintroduzir o MultiScript e criar suporte ao editor que converte automaticamente a adição de scripts em multiscripts (ou simplesmente tornar todos os scripts multiscript para simplificar, caso em que a implementação do MultiScript seria essencialmente nosso ScriptStack), ou 3) implementar uma espécie de sistema de características de linguagem cruzada para o tipo Object que pode se mesclar Scripts de extensão de referência como características, incorporando seu conteúdo como um script típico. Todas essas opções são muito mais invasivas para o mecanismo. Isso mantém tudo mais simples.

eu não acho que o traço é necessário. o que mais precisamos é um ciclo rápido de liberação do motor. Quero dizer, precisamos tornar o mecanismo mais flexível para adicionar novos recursos tão simples, basta adicionar novos arquivos dll ou mais e o mecanismo se integra automaticamente com ele, como o estilo de plug-ins na maioria dos IDE. por exemplo, eu realmente preciso desesperadamente que o websocket funcione, não preciso esperar até o lançamento do 3.1. 3.1 muito quebrado agora com tantos bugs. será ótimo se tivermos esse recurso. nova classe pode injetar automaticamente no GDScript a partir de .dll ou .so aleatórios em algum caminho. eu não sei quanto esforço isso para fazer em c++, mas espero que isso não seja muito difícil 😁

@ Fian46 Bem, se alguém tivesse implementado websockets como um plugin GDNative para download, então sim, o que você descreveu seria o fluxo de trabalho. Em vez disso, eles optaram por torná-lo um recurso integrado disponível no mecanismo vanilla. Não há nada que impeça as pessoas de criar recursos dessa maneira, então seu ponto realmente não tem relação com o tópico desta edição.

oops eu não sei que GDNative existe 😂😂😂. Trait é incrível, mas é mais fácil criar uma classe de traço falsa e instanciar e depois chamar a função como script básico?

Se um script Godot é uma classe sem nome, por que não instanciar "move_right_trait.gd" em "my_sprite.gd" ?
Desculpe minha ignorância se não entendi a questão.

Eu entendo o uso de traits em linguagens mais fortemente tipadas, como Rust ou (interfaces em) C++, mas em uma linguagem de tipagem abaixada não é um pouco desnecessário? Simplesmente implementar as mesmas funções deve permitir que você obtenha uma interface uniforme entre seus tipos. Acho que estou um pouco inseguro sobre qual é o problema exato com a maneira como o GDScript lida com interfaces ou como um sistema de características realmente ajudaria.

Você também não poderia usar preload("Some-other-behavior.gd") e armazenar os resultados em uma variável para obter basicamente o mesmo efeito?

@fian46 @DriNeo Bem, sim e não. Carregar scripts e usar classes de script já resolvem isso, mas o problema vai além disso.

@TheYokai

A implementação das mesmas funções deve permitir que você obtenha uma interface uniforme entre seus tipos

O problema não é conseguir uma interface uniforme que você está certo, a digitação de pato resolve muito bem, mas sim organizar (combinar/trocar) grupos de implementações relacionadas de forma eficiente.


Na versão 3.1, com classes de script, você pode definir funções estáticas em um script Reference (ou qualquer tipo na verdade) e então usar esse script como um namespace para acessar globalmente essas funções no 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!")

No entanto, quando se trata de conteúdo não estático, especialmente coisas que usam funções específicas do nó, é problemático dividir a lógica.

Por exemplo, e se eu tiver um KinematicBody2D e quiser ter um comportamento "Saltar" e um comportamento "Executar"? Cada um desses comportamentos precisaria de acesso aos recursos de manipulação de entrada e move_and_slide do KinematicBody2D. Idealmente, eu seria capaz de trocar a implementação de cada comportamento independentemente e manter todo o código para cada comportamento em scripts separados.

Atualmente, todos os fluxos de trabalho com os quais estou familiarizado simplesmente não são ideais.

  1. Se você mantiver todas as implementações no mesmo script e apenas trocar quais funções estão sendo usadas...

    1. alterar "comportamentos" pode envolver a troca de várias funções como um conjunto, então você não pode agrupar as mudanças de implementação de forma eficaz.

    2. Todas as funções para cada comportamento (X * Y) estão em seu único script, então ele pode ficar inchado muito rapidamente.

  2. Você pode substituir o script inteiro, mas isso significa que você precisa criar um novo script para cada combinação de comportamentos mais qualquer lógica que use esses comportamentos.
  3. Se você usar nós filhos como dependências de script, isso significa que você teria esses nós Node2D "componentes" estranhos que pegam seus pais e chamam o método move_and_slide PARA ele, o que é meio não natural, relativamente falando.

    • Você tem que supor que seu pai vai implementar esse método ou fazer lógica para verificar se ele tem o método. E se você fizer uma verificação, poderá falhar silenciosamente e potencialmente ter um bug silencioso em seu jogo ou transformá-lo em um script de ferramenta desnecessariamente apenas para definir um aviso de configuração no nó para informá-lo visualmente no editor que há um problema.

    • Você também não obtém o preenchimento de código adequado para as operações pretendidas dos nós, pois eles derivam do Node2D e o objetivo principal é direcionar o comportamento de um pai KinematicBody2D.

Agora, admito que a opção 3 é o fluxo de trabalho mais eficaz atualmente, e que seus problemas podem ser resolvidos em grande parte usando a tipagem estática útil para GDScript em 3.1. No entanto, há uma questão mais fundamental em jogo.

O sistema de cena de nó de Godot geralmente tem a forma de usuários criando nós ou cenas que fazem um trabalho específico, em seu próprio sistema fechado. Você pode instanciar esses nós/cenas em outra cena e fazer com que eles calculem dados que são usados ​​pela cena pai (como é o caso do relacionamento Area2D e CollisionShape2D).

No entanto, o uso do mecanismo vanilla e a recomendação geral de práticas recomendadas é manter o comportamento de sua cena bloqueado para o nó raiz e/ou seu script. Dificilmente você tem nós de "componente de comportamento" que realmente dizem à raiz o que fazer (e quando está lá, parece muito desajeitado). Os nós AnimationPlayer/Tween são as únicas exceções discutíveis em que posso pensar, mas até mesmo suas operações são direcionadas pela raiz (está efetivamente delegando o controle a eles temporariamente). (Edit: Mesmo neste caso, animar e tweening não são o trabalho do KinematicBody2D, então faz sentido que essas tarefas sejam delegadas. Movimento, no entanto, como correr e pular é sua responsabilidade) É mais simples e natural permitir uma implementação de trait para organizar o código, pois mantém os relacionamentos entre os nós estritamente data-up/behavior-down e mantém o código mais isolado em seus próprios arquivos de script.

Eh, marcar-se como 'implementando uma interface/característica' também deve cumprir um teste * is * , o que é conveniente para testar a funcionalidade de algo.

@OvermindDL1 Quero dizer, dei um exemplo de como fazer um teste como esse, mas usei in , pois queria distinguir entre herança e uso de traço.

Acho que meio que entrei em um problema XY aqui, foi mal. Eu tinha acabado de vir de 2 outras edições (#23052, #15996) que abordavam esse tópico de uma forma ou de outra e pensei em enviar uma proposta, mas não dei todo o contexto.

@groud esta solução resolverá um dos problemas levantados contra #19486.

@willnationsdev ótima ideia, estou ansioso por isso!

Do meu entendimento limitado, o que esse sistema de Trait deseja realizar é habilitar algo semelhante ao fluxo de trabalho mostrado neste vídeo: https://www.youtube.com/watch?v=raQ3iHhE_Kk
(Leve em conta, estou falando do _workflow_ mostrado, não do recurso usado)

No vídeo, ele é comparado com outros tipos de fluxos de trabalho, com suas vantagens e desvantagens.

Pelo menos que eu saiba, esse tipo de fluxo de trabalho é impossível no GDScript agora, por causa de como a herança funciona.

@AfterRebelion Os primeiros minutos daquele vídeo em que ele isola a modularidade, a capacidade de edição e a depuração da base de código (e os detalhes relacionados a esses atributos) é de fato a busca por esse recurso.

Pelo menos que eu saiba, esse tipo de fluxo de trabalho é impossível no GDScript agora, por causa de como a herança funciona.

Este bit não é bem verdade, porque Godot realmente faz isso muito bem em relação às hierarquias de nós e cenas de design. As cenas são modulares por natureza, as propriedades podem ser exportadas (e até animadas) diretamente do editor sem nunca lidar com código, e tudo pode ser testado/depurado isoladamente porque as cenas podem ser executadas independentemente.

A dificuldade é quando a lógica que normalmente seria terceirizada para um nó filho deve ser executada pelo nó raiz porque a lógica depende dos recursos herdados do nó raiz. Nesses casos, a única maneira de usar a composição é fazer com que os filhos comecem a dizer aos pais o que fazer, em vez de deixá-los cuidar de seus próprios negócios enquanto os pais os usam.

Isso não é um problema no Unity porque GameObject não tem nenhuma herança real que os usuários possam aproveitar. No Unreal, pode ser um pouco (?) de um problema, pois eles têm hierarquias internas baseadas em nós/componentes semelhantes para Atores.

Ok, vamos jogar um pouco de Advogado do Diabo aqui ( @MysteryGM , você pode se divertir com isso). Passei algum tempo pensando em como eu poderia escrever tal sistema no Unreal e isso está me dando uma nova perspectiva sobre ele. Desculpe pelas pessoas que estavam pensando que isso seria uma boa ideia / estavam empolgados com isso:

A introdução de um sistema de características adiciona uma camada de complexidade ao GDScript como uma linguagem que pode dificultar a manutenção.

Além disso, as características como um recurso tornam mais difícil isolar de onde as variáveis, constantes, sinais, métodos e até subclasses podem realmente vir. Se o script do seu Node de repente tiver 12 características diferentes, você não saberá necessariamente de onde tudo está vindo. Se você vir uma referência a algo, terá que examinar 12 arquivos diferentes para saber onde uma coisa está localizada na base de código.

Isso, na verdade, prejudica a depuração do GDScript como uma linguagem, pois qualquer problema pode exigir que você separe em média 2 ou 3 locais diferentes na base de código. Se algum desses locais for difícil de encontrar porque eles dizem que estão localizados em um script, mas na verdade estão localizados em outro lugar - e se a legibilidade do código não indicar claramente qual coisa é responsável pelos dados / lógica - então esses 2 ou 3 etapas estão sendo multiplicadas em um conjunto de etapas arbitrariamente grande e altamente estressante.

O tamanho e o escopo crescentes de um projeto ampliam ainda mais esses efeitos negativos e tornam o uso de características uma qualidade bastante insustentável.


Mas o que pode ser feito para resolver o problema? Não queremos nós filhos de "lógica de componente" dizendo às raízes da cena o que fazer, mas também não podemos confiar na herança ou alterar scripts inteiros para resolver nosso problema.

Bem, o que qualquer mecanismo não-ECS faria nessa situação? A composição ainda é a resposta, mas neste caso um Node completo não é razoável quando levado a escala / complica a dinâmica da hierarquia de propriedade. Em vez disso, pode-se apenas definir objetos de implementação não-nós que abstraem a implementação concreta de um comportamento, mas que ainda são de propriedade do nó raiz. Isso pode ser feito com um script 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(...)

Se as exportações funcionassem de forma que pudéssemos editá-las no Inspetor como enumerações para classes que derivam um tipo específico como podemos para novas instâncias de recursos, seria legal. A única maneira de fazer isso agora seria corrigir as exportações do script de recurso e, em seguida, fazer com que seu script de implementação estendesse o recurso (por nenhum outro motivo). Embora, fazer com que eles estendam Resource seria uma boa ideia se a implementação em si exigir parâmetros que você gostaria de poder definir a partir do Inspector. :-)

Agora, o que tornaria ISSO mais fácil seria ter um sistema de snippet ou sistema de macro para que a criação dessas seções de código declarativo reutilizadas fosse mais fácil para os desenvolvedores.

De qualquer forma, sim, acho que meio que identifiquei problemas gritantes com um sistema de traços e uma abordagem melhor para resolver o problema. Viva os problemas de XY! /s

Editar:

Portanto, o fluxo de trabalho do exemplo acima envolveria definir o script de implementação e, em seguida, usar a instância do script em tempo de execução para definir o comportamento. Mas e se a implementação em si exigir parâmetros que você gostaria de definir estaticamente do Inspetor? Aqui está uma versão baseada em um recurso.

# 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(...)

Relacionado: #22660

@AfterRebelion

Leve em consideração, estou falando do fluxo de trabalho mostrado, não do recurso usado

É irônico que, depois de você esclarecer isso e eu concordar com o fluxo de trabalho ideal e discordar de partes posteriores do comentário, eu continue dizendo basicamente como o "recurso usado" nesse vídeo é realmente a maneira ideal de lidar com este problema em Godot de qualquer maneira . Haha.

# 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
...

Uau, não sabia que a exportação era tão poderosa. Esperava que só pudesse interagir com primitivas e outras estruturas de dados.

Isso torna meu comentário anterior inválido.
Como você disse, se algum tipo de macro for implementado para facilitar sua implementação, seria a melhor maneira de implementar esse fluxo de trabalho sem a necessidade de MultiScript. Talvez não seja tão versátil quanto o Unity, porque você ainda precisaria declarar todos os scripts possíveis de antemão, mas ainda é uma boa opção.

@AfterRebelion

Como você disse, se algum tipo de macro for implementado para facilitar sua implementação, seria a melhor maneira de implementar esse fluxo de trabalho sem a necessidade de MultiScript.

Bem, a abordagem baseada em Resource que mencionei no mesmo comentário, combinada com um melhor suporte ao editor do #22660, tornaria a qualidade comparável ao que o Unity pode fazer.

Talvez não seja tão versátil quanto o Unity, porque você ainda precisaria declarar todos os scripts possíveis de antemão, mas ainda é uma boa opção.

Bem, se eles corrigirem as dicas de tipo de array em 3.2, você poderá definir um array exportado para caminhos de arquivo que devem estender um script e adicionar efetivamente o seu próprio. Isso pode até ser feito por meio de um plug-in na versão 3.1 usando a classe EditorInspectorPlugin para adicionar conteúdo personalizado ao Inspetor para determinados recursos ou nós.

Quero dizer, se você quisesse um sistema do tipo "Unity", então você realmente teria sub-nós que informam ao root o que fazer e você só precisaria buscá-los referindo-se ao seu nome, sem declará-los manualmente ou adicioná-los do script do nó raiz. O método Resource geralmente é muito mais eficaz e mantém uma base de código mais limpa.

Como o sistema de traços sobrecarregaria desordenadamente a usabilidade do código GDScript, com base nas razões que descrevi, vou prosseguir e encerrar este problema. As armadilhas de sua adição superam em muito quaisquer benefícios comparativamente escassos que possamos receber de tal sistema, e esses mesmos benefícios podem ser implementados de maneiras diferentes com mais clareza e usabilidade.

Bem, eu perdi esta discussão enquanto eu estava fora. Ainda não li tudo, mas tive a ideia de adicionar traits ao GDScript para resolver o problema de reutilização de código, que acho muito mais elegante e claro do que a falsa herança múltipla de anexar um script a um tipo derivado. Embora o que eu faria fosse criar arquivos de traços específicos, não permitindo que nenhum arquivo de classe fosse um traço. Eu não acho que seria muito ruim.

Mas estou aberto a sugestões para resolver o problema principal, que é a reutilização de código em vários tipos de nós.

@vnen a solução que encontrei, que é o último item, é terceirizar seções reutilizáveis ​​para scripts de recursos.

  • Eles ainda podem ser expostos e suas propriedades editadas no Inspetor, como se fossem variáveis ​​de membro em um nó.

  • Eles preservam uma trilha clara de onde os dados e a lógica estão vindo, algo que pode ser facilmente comprometido se muitas características forem incluídas em um script.

  • Eles não adicionam nenhuma complexidade indevida ao GDScript como linguagem. Por exemplo, se eles existissem, teríamos que resolver como qualidades compartilhadas (propriedades, constantes, sinais) seriam mescladas no script principal (ou, se não mescladas, forçar os usuários a lidar com conflitos de dependência).

  • Os scripts de recursos são melhores, pois podem ser atribuídos e alterados a partir do Inspetor. Designers, escritores, etc. poderão alterar os objetos de implementação diretamente do editor.

@willnationsdev eu vejo (embora o nome "scripts de recursos" pareça estranho, já que todos os scripts são recursos). O principal problema com esta solução é que ela não resolve algo que as pessoas esperam com a abordagem de herança: adicionar variáveis ​​e sinais exportados ao nó raiz da cena (em particular ao instanciar a cena em outro lugar). Você ainda pode editar as variáveis ​​exportadas dos sub-recursos, mas se torna menos prático (você não pode dizer de relance quais propriedades você pode editar).

O outro problema é que ele exige que o código clichê seja muito repetido. Você também precisa ter certeza de que os scripts de recursos estão realmente definidos antes de chamar as funções.

A vantagem é que não precisamos de nada, já está disponível. Acho que isso deve ser documentado e as pessoas que usam o método atual "anexar script ao nó derivado" podem comentar sobre a solução para ver quão viável é.

@vnen

mas torna-se menos prático (você não pode dizer de relance quais propriedades você pode editar).

Você pode detalhar o que você quer dizer aqui? Não vejo como pode haver uma falta substancial de clareza sobre quais propriedades são acessíveis, especialmente se algo como #22660 for mesclado.

  1. Eu instancia a cena, quero saber como posso editá-la e então olho para o script do nó raiz dessa cena.
  2. Dentro do roteiro, eu vejo...

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

    • use FourWayMoveTrait

  3. Supondo que tenhamos um meio de rastrear o identificador de cliques (que realmente deveria ser um recurso 3.1.1!) para abrir o script, acabamos abrindo o script associado e podemos visualizar suas propriedades.

Parece o mesmo número de passos para mim, a menos que eu esteja perdendo alguma coisa.

Além disso, quais propriedades são editáveis ​​é realmente ainda mais claro com recursos, eu diria que, se uma implementação possui propriedades específicas para ela, o fato de os dados estarem relacionados à implementação é mais claro se você precisar acessar com o instância de implementação, ou seja, move_impl.<property> .

O outro problema é que ele exige que o código clichê seja muito repetido. Você também precisa ter certeza de que os scripts de recursos estão realmente definidos antes de chamar as funções.

Isso é verdade, no entanto, ainda acho que os benefícios de ser uma exportação com uma inicialização superam o custo da verborragia adicionada:

("High-Level Teammate", HLT, como designers, escritores, artistas, etc.)

  • Pode-se atribuir os valores diretamente do Inspetor, em vez de ter que abrir o script, localizar a linha correta a ser alterada e depois alterá-la (já mencionado, mas leva a...).

  • Pode-se especificar que o conteúdo exportado tenha um requisito de tipo básico. O Inspetor pode fornecer uma lista enumerada de implementações permitidas automaticamente. Os HLTs podem então atribuir com segurança apenas derivações desse tipo. Isso ajuda a isolá-los da alternativa de precisar conhecer as ramificações de todos os diferentes scripts de traços que circulam por aí. Também teríamos que modificar o autocompletar no GDScript para suportar a busca de arquivos de características nomeados e não nomeados em resposta à visualização da palavra-chave use .

  • Pode-se serializar uma configuração de uma implementação como um arquivo *.tres. Os HLTs podem então arrastá-los e soltá-los do dock FileSystem ou até mesmo criar seus próprios direitos no Inspector. Se alguém quisesse fazer o mesmo com características, teria que criar uma característica derivada que fornecesse um construtor personalizado para substituir o padrão. Em seguida, eles usariam esse traço como uma "pré-configuração" por meio de um construtor codificado de forma imperativa.

    1. Mais fraco porque é imperativo e não declarativo.
    2. Mais fraco porque o construtor deve ser definido explicitamente no script.
    3. Se a característica não tiver nome, o usuário precisará saber onde a característica está localizada para usá -la adequadamente em vez da característica básica padrão. Se a característica for nomeada, ela obstruirá o namespace global desnecessariamente.
    4. Se eles alterarem o script para dizer use FourWayMoveTrait em vez de use MoveTrait , não haverá mais nenhuma indicação duradoura de que o script seja compatível com o MoveTrait base. Ele permite confusão HLT sobre se o FourWayMoveTrait pode até mudar para um MoveTrait diferente sem quebrar as coisas.
    5. Se um HLT estivesse criando uma nova implementação de traço dessa maneira, ele não conheceria necessariamente todas as propriedades que podem/precisam ser definidas a partir do traço base. Este não é um problema com recursos criados no Inspetor.
  • Pode-se até ter vários recursos do mesmo tipo (se houver uma razão para isso). Uma característica não suportaria isso, mas desencadearia conflitos de análise.

  • Pode-se alterar as configurações e/ou seus valores individuais sem sair da janela de visualização 2D/3D. Isso é muito mais confortável para HLTs (eu conheço muitos que ficam irritados quando precisam olhar o código).

Com tudo isso dito, concordo que o clichê é irritante. Para lidar com isso, prefiro adicionar um sistema de macro ou snippet para simplificá-lo. Um sistema de snippets seria uma boa ideia, pois poderia suportar qualquer linguagem que possa ser editada no ScriptEditor, em vez de apenas GDScript sozinho.

Cerca de:

mas torna-se menos prático (você não pode dizer de relance quais propriedades você pode editar).

Eu estava falando sobre o inspetor, então quero dizer o "HLT" aqui. Pessoas que não vão olhar para o código. Com traits podemos adicionar novas propriedades exportadas ao script. Com scripts de recursos, você só pode exportar variáveis ​​para o próprio recurso, portanto, elas não serão exibidas no inspetor, a menos que você edite o sub-recurso.

Eu entendo seu argumento, mas vai além do problema original: evite repetir código (sem herança). As características são para programadores. Em muitos casos, há apenas uma implementação que você deseja reutilizar e não deseja expô-la no inspetor. Claro que você ainda pode usar os scripts de recursos sem exportar apenas atribuindo diretamente no código, mas ainda não resolve o problema de reutilizar as variáveis ​​e sinais exportados da implementação comum, que é uma das principais razões pelas quais as pessoas estão tentando usar herança.

Ou seja, as pessoas estão tentando usar a herança de um script genérico não apenas para funções, mas também para propriedades exportadas (que geralmente estão relacionadas a essas funções) e sinais (que podem ser conectados à interface do usuário).

Este é o problema difícil de resolver. Existem algumas maneiras de fazer isso apenas com GDScript, mas, novamente, elas exigem que o código padrão seja copiado.

Eu só posso imaginar as idas e vindas que teriam que acontecer para que os scripts externos se comportassem como se fossem escritos diretamente no próprio script principal.

Seria uma coisa boa se o céu e a terra não tivessem que ser movidos para alcançá-lo. X)

@vnen eu vejo o que você está dizendo agora. Bem, já que parece que ainda pode haver alguma vida nesta edição, vou reabri-la na próxima vez que tiver uma chance.

Alguma novidade sobre a reabertura? Agora que a GodotCon 2019 está anunciada, e o Godot Sprint é uma coisa, talvez valha a pena falar sobre isso.

@AfterRebelion Acabei de esquecer de voltar e reabri-lo. Obrigado por me lembrar. XD

@willnationsdev Gostei do que li sobre o EditorInspectorPlugin! pergunta rápida, isso significa que posso criar um inspetor personalizado para um tipo de dado... por exemplo... adicionar um botão ao inspetor.
(já faz algum tempo que eu queria fazer isso, para ter uma maneira de acionar eventos para fins de depuração com um botão no inspetor eu poderia fazer o botão pressionar executar algum script)

@xDGameStudios Sim, isso e muito mais é possível. Qualquer Controle personalizado pode ser adicionado ao Inspetor, seja na parte superior, na parte inferior, acima de uma propriedade ou abaixo de uma categoria.

@willnationsdev não sei se posso entrar em contato com você por mensagem privada!! Mas gostaria de saber mais sobre o EditorInspectorPlugin (como algum código de exemplo).. algo nas linhas de um tipo de recurso personalizado (por exemplo) MyResource que possui uma propriedade export "name" e um botão de inspeção que imprime o "name" variável se eu pressioná-lo (no editor ou durante a depuração)! A documentação está faltando muito neste assunto... Eu mesmo escreveria a documentação se soubesse como usar isso! :D obrigado

Eu também gostaria de saber mais sobre isso. X)

Então, isso é o mesmo que um script de carregamento automático com subclasses contendo funções estáticas?

por exemplo, seu caso se tornaria Traits.MoveRightTrait.move_right()

ou ainda mais simples, tendo diferentes scripts de autoload para diferentes classes de traits:

Movement.move_right()

Não, as características são um recurso específico do idioma equivalente a copiar e colar o código-fonte de um script em outro script (como uma mesclagem de arquivo). Então, se eu tiver um traço com move_right() , e eu declarar que meu segundo script usa esse traço, ele também pode usar move_right() , mesmo que não seja estático e mesmo que acesse propriedades em outro lugar da classe. Isso resultaria em um erro de análise se a propriedade não existisse no segundo script.

Descobri que tenho código duplicado (funções menores, por exemplo, fazer um arco) ou nós supérfluos porque não posso ter herança múltipla.

Isso seria incrível, eu me vejo tendo que fazer um script com exatamente a mesma funcionalidade apenas para ser usado em diferentes tipos de nós, causando extend s diferentes, basicamente apenas para uma linha de código. A propósito, se alguém souber como fazer isso com o sistema atual, me avise.

Se eu tiver funcionalidade para um script com extends Node , existe uma maneira de anexar o mesmo comportamento a outro tipo de nó sem ter que duplicar o arquivo de origem e substituir pelo extend apropriado?

Algum progresso nisso? Eu continuo tendo que duplicar código ou ter que adicionar nós, como eu disse antes. Eu sei que não será feito em 3.1, mas talvez apontar para 3.2?

Oh, eu não tenho trabalhado nisso. Na verdade, estou mais adiantado na implementação do meu método de extensão do que nisso. Eu preciso falar com vnen sobre ambos, pois gostaria de trabalhar com ele os melhores detalhes de sintaxe/implementação.

Edit: se alguém quiser experimentar a implementação de traits, é bem-vindo. Só precisa coordenar com os devs.

"implementação do método de extensão"?

@Zireael07 #15586
Ele permite que as pessoas escrevam scripts que podem adicionar novas funções "embutidas" para classes de mecanismo. Minha interpretação da sintaxe seria algo assim:

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

Então, em outro lugar, eu seria capaz de fazer:

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

Ele secretamente faria uma chamada para o script de extensão constantemente carregado e chamaria a função estática de mesmo nome que está vinculada à classe Array, passando a instância como o primeiro parâmetro.

Eu tive algumas conversas com @vnen e @jahd2602 sobre isso. Uma coisa que vem à mente é a solução de Jai para o polimorfismo: importar o namespace de uma propriedade.

Dessa forma, você poderia fazer algo como o seguinte:

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()"

O ponto é que a palavra-chave using importa o namespace de uma propriedade, de modo que b.foo() é realmente apenas açúcar sintático para b.a.foo() .

E então, certificando-se de que b is A == true e que B podem ser usados ​​em situações tipadas que aceitam A também.

Isso também tem o benefício de que as coisas não precisam ser declaradas como características, isso funcionaria para qualquer coisa que não tenha nomes de qualidade comuns.

Um problema é que isso não combina bem com o sistema de herança atual. Se A e B forem Node2D e fizermos uma função em A: func baz(): print(self.position) , qual posição será impressa quando chamarmos b.baz() ?
Uma solução pode ser fazer com que o chamador determine self . Chamar b.foo() chamaria foo() com b como self e bafoo() chamaria foo() com a como self.

Se tivéssemos métodos independentes como Python (onde x.f(y) é açúcar para f(x,y) ), isso poderia ser muito fácil de implementar.

Outra ideia não relacionada:

Concentre-se apenas em funções independentes, estilo JavaScript.

Se adotarmos a convenção x.f(y) == f(x,y) para funções estáticas, poderíamos facilmente ter o seguinte:

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))

Isso teria um impacto mínimo no sistema de classes, pois na verdade afeta apenas os métodos. Se realmente quiséssemos que isso fosse flexível, poderíamos usar o JavaScript completo e apenas permitir que as definições de função fossem valores atribuíveis e chamáveis.

@jabcross Parece bom, eu gosto do conceito de algum tipo de namespace opcional, e a ideia da função é interessante.

Em relação ao namespace, eu me pergunto por que não simplesmente using A , as outras coisas declarativas parecem estranhas.

Curioso também como isso precisaria ser resolvido com a herança múltipla. Suponho que a opção A seja forçar os dois scripts a herdar o mesmo tipo, para que ambos sejam estendidos no topo da mesma classe, sem nenhuma mesclagem especial.

Opção B, talvez palavras-chave GDScript adicionais para especificar uma classe de traço e de qual classe você gostaria de obter dicas. Seria a mesma ideia, mas apenas passos extras para parecer mais explícito.

No topo da A.gd:

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

Ohhh, eu realmente gosto do conceito de importação de namespace. Ele resolve não apenas o problema da característica, mas também potencialmente o conceito de "métodos de extensão" para adicionar conteúdo aos tipos de mecanismo.

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 Se nós também adicionamos lambas e/ou objetos permitidos para implementar um operador de chamada (e tivéssemos um tipo callable para valores compatíveis), então poderíamos começar a adicionar uma abordagem mais funcional ao código GDScript (que acho que seria uma ótima ideia). Concedido, andando mais no território #18698 de @vnen naquele ponto, mas...

Temos que considerar que o GDScript ainda é uma linguagem dinâmica e algumas dessas sugestões requerem uma verificação em tempo de execução para despachar corretamente as chamadas, adicionando uma penalidade de desempenho (mesmo para pessoas que não usam os recursos, pois precisa verificar todas as chamadas de função e talvez pesquisa de propriedades também). É também por isso que não tenho certeza se adicionar extensões é uma boa ideia (especialmente porque elas são essencialmente açúcar de sintaxe).

Prefiro o sistema de traços puros, onde os traços não são classes, mas uma coisa própria. Dessa forma, eles podem ser totalmente resolvidos em tempo de compilação e fornecer mensagens de erro em casos de conflitos de nomes. Acredito que isso resolveria o problema sem sobrecarga de tempo de execução adicional.

@vnen Ahhh, não sabia disso sobre o custo do tempo de execução. E se isso se aplica a qualquer implementação de método de extensão, acho que também não seria o ideal.

Se fizéssemos um sistema de traço puro, você estava pensando em fazer trait TraitName no lugar de extends emparelhado com using TraitName em outros scripts? E você estaria implementando isso sozinho, ou seria delegado?

Se fizéssemos um sistema de traço puro, você estava pensando em fazer trait TraitName no lugar de extends emparelhado com using TraitName em outros scripts?

Essa é minha ideia. Acredito que seja bastante simples e cubra praticamente todos os casos de uso para reutilização de código. Eu até permitiria que a classe usando o trait sobrescrevesse os métodos do trait (se for possível fazer em tempo de compilação). Os traços também podem estender outros traços.

E você estaria implementando isso sozinho, ou seria delegado?

Eu não me importaria de dar a tarefa para outra pessoa que está à altura. Estou com pouco tempo de qualquer maneira. Mas devemos concordar com o design de antemão. Sou bastante flexível com os detalhes, mas deve 1) não incorrer em verificações de tempo de execução (acredite que o GDScript se presta bem a coisas impossíveis de descobrir na compilação), 2) ser relativamente simples e 3) não adicionar muito tempo de compilação.

@vnen Curtindo essas ideias. Queria saber como você imagina que um traço seria capaz de fazer coisas como autocompletar para as classes que o incluiriam, ou isso não seria possível?

Queria saber como você imagina que um traço seria capaz de fazer coisas como autocompletar para as classes que o incluiriam, ou isso não seria possível?

Um traço seria essencialmente uma "importação" na minha opinião. Deve ser trivial mostrar a conclusão, supondo que a conclusão para os membros funcione.

@vnen Eu imagino que você essencialmente analisaria um ClassNode com um sinalizador trait definido nele. E então, se você fizer uma instrução using , ela tentará mesclar todas as propriedades/métodos/sinais/constantes/subclasses no script atual.

  1. Se um método colidir, a implementação do script atual substituirá o método base, como se estivesse substituindo um método herdado.

    • Mas o que fazer se uma classe base já possui o método "merged"?

  2. Se uma propriedade, sinal e constante se sobrepuser, verifique se é o mesmo tipo/assinatura de sinal. Se não houver incompatibilidade, considere apenas uma "fusão" da propriedade/sinal/constante. Caso contrário, notifique o usuário sobre um conflito de tipo/assinatura (um erro de análise).

Péssima ideia? Ou devemos simplesmente proibir conflitos não-métodos? E as subclasses? Tenho a sensação de que devemos fazer disso conflitos.

@willnationsdev Soa como o "problema do diamante" (também conhecido como "diamante mortal da morte"), uma ambiguidade bem documentada com diferentes soluções já aplicadas em várias linguagens de programação populares.

Oque me lembra:
@vnen as características serão capazes de estender outras características?

@jahd2602 Ele já sugeriu isso como uma possibilidade

Os traços também podem estender outros traços.

@jahd2602 Com base nas soluções Perl/Python, parece que elas basicamente formam uma "pilha" de camadas contendo o conteúdo de cada classe para que os conflitos da última característica usada fiquem em cima e substituam as outras versões. Isso soa como uma solução muito boa para este cenário. A menos que você ou @vnen tenham pensamentos alternativos. Obrigado pela visão geral vinculada das soluções jahd.

Algumas questões.

Primeiro: De que maneiras devemos apoiar a instrução using?

Estou pensando que a instrução using deve exigir algum GDScript de valor constante.

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

Estou pensando em todos os itens acima.

Segundo: Qual deve ser a sintaxe permitida para definir uma característica e/ou seu nome?

Alguém pode não necessariamente querer usar uma classe de script para sua característica, então não acho que devemos impor um requisito trait TraitName . Estou pensando que trait deve estar em uma linha.

Então, se for um script de ferramenta, é claro que deve ter uma ferramenta no topo. Então, se for um traço, deve definir se é um traço na linha seguinte. Opcionalmente, permita que alguém indique o nome da classe de script seguindo a declaração de trait na mesma linha e, se o fizer, não permita que também usem class_name . Se eles omitirem o nome do traço, então class_name <name> está bem. Então, ao estender outro tipo, poderíamos inserir o extends após a declaração do traço e/ou em uma linha própria após a declaração do traço. Então, eu consideraria cada um deles válido:

# 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

Terceiro: devemos, para fins de autocompletar e/ou declaração de intenção/requisitos, permitir que um traço defina um tipo de base que ele deve estender?

Já discutimos que os traços devem apoiar a herança de outros traços. Mas devemos permitir um TraitA para extend Node , permitir que o script TraitA obtenha o preenchimento automático do Node, mas também acionar um erro de análise se fizermos uma instrução using TraitA quando a classe atual não estender o Node ou qualquer um de seus tipos derivados?

Quarto: Em vez de ter características estendendo outras características, não podemos simplesmente manter a instrução extends reservada para extensões de classe, permitir que uma característica não precise dessa instrução, mas em vez de estender uma característica básica, permitir traços para simplesmente ter suas próprias declarações using que sub-importam esses traços?

# 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".

Claro, o benefício aqui seria que você seria capaz de agrupar várias características em um único nome de característica usando várias instruções using , semelhantes aos arquivos de inclusão C++ que incluem várias outras classes.

Quinto: se temos um trait, e ele tem using ou extends para um método, e então implementa seu próprio, o que fazemos quando ele chama, dentro dessa função .<method_name> para executar a implementação base? Assumimos que essas chamadas sempre são executadas dentro do contexto de herança de classe e a hierarquia de características não tem influência aqui?

cc @vnen

Primeiro: De que maneiras devemos apoiar a instrução using?

Estou pensando que a instrução using deve exigir algum GDScript de valor constante.

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

Estou bem com tudo isso. Mas para o caminho eu uso uma string diretamente: using "res://my_trait.gd"

Segundo: Qual deve ser a sintaxe permitida para definir uma característica e/ou seu nome?

Alguém pode não necessariamente querer usar uma classe de script para sua característica, então não acho que devemos impor um requisito trait TraitName . Estou pensando que trait deve estar em uma linha.

Então, se for um script de ferramenta, é claro que deve ter uma ferramenta no topo. Então, se for um traço, deve definir se é um traço na linha seguinte. Opcionalmente, permita que alguém declare o nome da classe de script seguindo a declaração de trait na mesma linha e, se o fizer, não permita que também use class_name . Se eles omitirem o nome do traço, então class_name <name> está bem. Então, ao estender outro tipo, poderíamos inserir o extends após a declaração do traço e/ou em uma linha própria após a declaração do traço. Então, eu consideraria cada um deles válido:

# 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 em um traço não deve fazer nenhuma diferença, já que eles não são executados diretamente.

Concordo que uma característica não tem necessariamente um nome global. Eu usaria trait de maneira semelhante a tool . Deve ser a primeira coisa no arquivo de script (exceto para comentários). A palavra-chave deve opcionalmente ser seguida pelo nome da característica. Eu não usaria class_name para eles, já que não são aulas.

Terceiro: devemos, para fins de autocompletar e/ou declaração de intenção/requisitos, permitir que um traço defina um tipo de base que ele deve estender?

Sinceramente, não gosto de adicionar recursos na linguagem por causa do editor. É aí que as anotações seriam úteis.

Agora, se quisermos fazer traços aplicados apenas a um determinado tipo (e seus derivados), tudo bem. Na verdade, acho que isso é melhor por causa das verificações estáticas: permite que uma característica use coisas de uma classe enquanto a compilação pode realmente verificar se elas estão sendo usadas com os tipos corretos e outros enfeites.

Quarto: Em vez de ter características estendendo outras características, não podemos simplesmente manter a instrução extends reservada para extensões de classe, permitir que uma característica não precise dessa instrução, mas em vez de estender uma característica básica, permitir traços para simplesmente ter suas próprias declarações using que sub-importam _esses_ traços?

Bem, isso é principalmente uma questão de semântica. Quando mencionei que traços podem estender outros, eu realmente não quis usar a palavra-chave extends . A principal diferença é que com extends você só pode estender um, com using você pode incorporar muitos outros traços em um. Estou bem com using , desde que não haja ciclos, não é um problema.

Quinto: se temos um trait, e ele tem using ou extends para um método, e então implementa seu próprio, o que fazemos quando ele chama, dentro dessa função .<method_name> para executar a implementação base? Assumimos que essas chamadas sempre são executadas dentro do contexto de herança de classe e a hierarquia de características não tem influência aqui?

Essa é uma pergunta complicada. Eu diria que um traço não tem nada a ver com herança de classe. Portanto, a notação de ponto deve chamar o método no traço pai, se houver um, caso contrário, gerará um erro. Um traço não deve estar ciente das classes em que está.

OTOH, um trait é quase como um "include", então seria aplicado literalmente à classe, portanto, chamando a implementação pai. Mas, honestamente, eu simplesmente proibiria a notação de ponto se o método não for encontrado no traço pai.

E quanto a uma característica que exige que a classe tenha uma ou mais outras características? Por exemplo, uma característica DoubleJumper que requer tanto a característica Jumper , uma característica Upgradable e uma classe que herda KinematicBody2D .

Rust, por exemplo, permite que você use assinaturas de tipo como essas. Algo como KinematicBody2D: Jumper, Upgradable . Mas como estamos usando : para anotar o tipo, podemos usar KinematicBody2D & Jumper & Upgradable ou algo assim.

Há também a questão do polimorfismo. E se a implementação do trait for diferente para cada classe, mas expor a mesma interface?

Por exemplo, queremos um método kill() no traço Jumper , que é usado por Enemy e Player . Queremos implementações diferentes para cada caso, mantendo ambas compatíveis com a mesma assinatura de tipo Jumper . Como fazer isso?

Para o polimorfismo, você apenas criaria uma característica separada que inclui a característica com kill() e então implementaria sua própria versão específica do método. Usar traços que substituem os métodos de traços incluídos anteriormente é como você lidaria com isso.

Além disso, acho que não havia planos (ainda) para fazer uma dica de tipo ter requisitos de características. Isso é algo que gostaríamos de fazer?

criar uma característica separada

Isso não geraria um monte de arquivos de características únicos? Se pudéssemos fazer declarações de características aninhadas (semelhante à palavra-chave class ), isso poderia ser mais conveniente. Também podemos substituir os métodos diretamente na classe que está usando o trait.

Eu realmente apreciaria um sistema de assinatura de tipo forte (talvez com composição booleana e opcionais/não nulos). Os traços se encaixariam perfeitamente.

Não tenho certeza se foi discutido, mas acho que deve ser possível invocar uma versão específica do traço de uma função. Por exemplo:

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()

que imprime: A , B , B .


Também não estou totalmente certo sobre o "sem custo de tempo de execução". Como seriam tratados scripts carregados dinamicamente (não disponíveis durante a exportação) com classes que estão usando características que foram definidas antes da exportação? Estou entendendo mal alguma coisa? Ou esse caso não é considerado "tempo de execução"?

Não tenho certeza se foi discutido, mas acho que deve ser possível invocar uma versão específica do traço de uma função.

Eu já estava considerando isso, mas não tenho certeza sobre permitir que uma classe use características conflitantes (ou seja, características que definem o método com o mesmo nome). A ordem das declarações using não deve fazer diferença.

Também não estou totalmente certo sobre o "sem custo de tempo de execução". Como seriam tratados scripts carregados dinamicamente (não disponíveis durante a exportação) com classes que estão usando características que foram definidas antes da exportação? Estou entendendo mal alguma coisa? Ou esse caso não é considerado "tempo de execução"?

Não se trata de exportar. Isso definitivamente afetará o tempo de carregamento, já que a compilação acontece no carregamento (embora eu não ache que seja muito significativo), mas não deve afetar quando o script estiver sendo executado. Idealmente, os scripts devem ser compilados na exportação, mas isso é outra discussão.

Olá pessoal.

Sou novo no Godot e tenho me acostumado com ele nos últimos dias. Enquanto tentava descobrir as melhores práticas a serem usadas para criar componentes reutilizáveis, decidi por um padrão. Eu sempre faria com que o Node raiz de uma sub-cena, que se destina a ser instanciada em outra cena, exportasse todas as propriedades que pretendo definir de fora. Tanto quanto possível, eu queria tornar o conhecimento da estrutura interna do branch instanciado desnecessário para o resto da cena.

Para fazer isso funcionar, o nó raiz precisa "exportar" as propriedades e, em seguida, copiar os valores para o filho apropriado em _ready. Então, por exemplo, imagine um nó Bomb com um Timer filho. O nó bomba raiz na sub-cena exportaria "detonation_time" e então faria $Timer.wait_time = detonation_time em _ready. Isso nos permite configurá-lo bem na interface do usuário do Godot sempre que o instanciamos sem ter que tornar os filhos editáveis ​​e detalhar o Timer.

Contudo
1) É uma transformação muito mecânica, então parece que algo semelhante pode ser suportado pelo sistema
2) Provavelmente adiciona uma pequena ineficiência ao definir o valor apropriado diretamente no nó filho.

Antes de prosseguir, isso pode parecer tangencial ao que está sendo discutido porque não envolve permitir uma espécie de herança "privada" (no jargão C++). No entanto, eu realmente gosto do sistema de construção de comportamentos de Godot compondo elementos de cena em vez de mais engenharia de herança. Esses relacionamentos "escritos" são imutáveis ​​e estáticos. OTOH, a estrutura da cena é dinâmica, você pode até alterá-la em tempo de execução. A lógica do jogo está tão sujeita a mudanças durante o desenvolvimento que acho que o design de Godot se encaixa muito bem no caso de uso.

É verdade que os nós filhos são usados ​​como extensões comportamentais dos nós raiz, mas isso não faz com que eles não tenham autossuficiência, IMO. Um cronômetro é perfeitamente autocontido e previsível em seu comportamento, independentemente do que está acostumado ao tempo. Se você usa uma colher para tomar sopa ou tomar sorvete, ela desempenha adequadamente sua função, mesmo atuando como uma extensão de sua mão. Eu vejo os nós raiz como maestros que coordenam os comportamentos dos nós filhos para que eles não precisem saber diretamente um do outro e, portanto, possam permanecer autocontidos. Os nós pai/raiz são gerentes vinculados à mesa que delegam responsabilidades, mas não fazem muito trabalho direto. Como eles são finos, é fácil criar um novo para um comportamento ligeiramente diferente.

No entanto, acho que os nós raiz também devem atuar como a INTERFACE principal para a funcionalidade de todo o branch instanciado. Todas as propriedades que podem ser ajustadas na instância devem ser "configuráveis" no nó raiz da ramificação, mesmo que o proprietário final da propriedade seja algum nó filho. A menos que esteja faltando alguma coisa, isso deve ser organizado manualmente na versão atual do Godot. Seria bom se isso pudesse ser automatizado de alguma forma para combinar os benefícios de um sistema dinâmico com scripts mais fáceis.

Uma coisa que estou pensando é um sistema de "herança dinâmica", se você quiser, disponível para subclasses de Node. Haveria duas fontes de propriedades/métodos em tal script, aquelas do script que ele estende e aquelas "borbulhadas" de crianças dentro da estrutura da cena. Então meu exemplo com o Bomb se tornaria algo como export lifted var $Timer.wait_time [= value?] as detonation_time dentro da seção de variáveis ​​de membro do script bomb.gd. O sistema geraria essencialmente $Timer.wait_time = detonation_time no retorno de chamada _ready e geraria o getter/setter que permitirá que $Bomb.detonation_time = 5 do pai do nó Bomb resulte em $Timer.wait_time = 5 sendo definido.

No exemplo do OP com MoveRightTrait, teríamos o nó ao qual mysprite.gd está anexado tendo MoveRightTrait como um nó filho. Então em mysprite.gd teríamos algo como lifted func $MoveRightTrait.move_right [as move_right] (talvez 'as' possa ser opcional quando o nome for o mesmo). Agora, chamar move_right em um objeto de script criado a partir de mysprite.gd delegaria automaticamente ao nó filho apropriado. Talvez os sinais possam ser borbulhados para que possam ser anexados a um nó filho da raiz? Talvez nós inteiros possam ser borbulhados com apenas lifted $MoveRightTrait [as MvR] sem func, signal ou var. Neste caso, todos os métodos e propriedades em MoveRightTrait seriam acessíveis de mysprite diretamente como mysprite.move_right ou através de mysprite.MvR.move_right se o 'as MvR' for usado.

Essa é uma ideia de como simplificar a criação de uma INTERFACE para uma estrutura de cena na raiz de uma ramificação instanciada, aumentando sua característica de "caixa preta" e obtendo conveniência de script junto com o poder do sistema de cena dinâmico de Godot. Claro, haveria muitos detalhes secundários a serem considerados. Por exemplo, ao contrário das classes base, os nós filhos podem ser removidos em tempo de execução. Como as funções e propriedades borbulhadas/elevadas devem se comportar se chamadas/acessadas nesse caso de erro? Se um nó com o NodePath correto for adicionado de volta, as propriedades levantadas começam a funcionar novamente? [SIM, IMO] Também seria um erro usar 'lifted' em classes não derivadas de Node, pois nunca haveria filhos para fazer bolhas/levantar nesse caso. Além disso, conflitos de nome são possíveis com "as {name}" duplicado ou "lifted $Timer1 lift $Timer2" onde os nós têm propriedades/métodos com o mesmo nome. Idealmente, o interpretador de scripts detectaria esses problemas lógicos.

Eu sinto que isso nos daria muito do que queremos, embora seja realmente apenas açúcar sintático que nos poupa de ter que escrever funções de encaminhamento e inicializações. Além disso, como é fundamentalmente simples conceitualmente, não deve ser tão difícil de implementar ou explicar.

Enfim, se você chegou até aqui, obrigado por ler!

Eu usei "levantado" em todos os lugares, mas isso é apenas ilustrativo.
Algo como using var $Timer.wait_time as detonation_time ou using $Timer é obviamente tão bom quanto. Em qualquer caso, você consegue pseudo-herdar convenientemente de nós filhos, criando um único ponto de acesso coerente para a funcionalidade desejada na raiz da ramificação a ser instanciada. O requisito sobre as partes reutilizáveis ​​de funcionalidade é que elas estendam o Node ou uma subclasse dele para que possam ser adicionados como filhos ao componente maior.

Outra maneira de ver isso é que a palavra-chave "extends" em um script que herda de um Node fornece seu relacionamento "is-a" enquanto usa a palavra-chave "using" ou "lifted" em um script para "borbulhar" um os membros do nodo descendente dão a você algo semelhante a "implements" [hey, possível palavra-chave] que existe em linguagens com herança única, mas múltiplas "interfaces" (por exemplo, Java). Em herança múltipla irrestrita (como c++) as classes base formam uma árvore [estática, escrita]. Por analogia, estou propondo uma sintaxe conveniente de camadas e eliminação de clichês sobre as árvores de Node existentes de Godot.

Se for determinado que isso é algo que vale a pena explorar, há aspectos do espaço de design a serem considerados:
1) Devemos permitir apenas crianças imediatas em um "uso". IOW using $Timer mas não 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 usando "Timer" 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. usando var "Timer".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 usando MoveRightTrait como 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 se node.DirectionalMover:` se tornaria um teste para a funcionalidade por convenção.
4) O State Pattern deve ser implementável substituindo um nó "using"-ed por outro que tenha um comportamento variante, mas a mesma interface [duck type] e o mesmo NodePath referenciado na instrução "using". Com a forma como a árvore de cena funciona, isso funcionaria quase de graça. No entanto, o sistema teria que rastrear os sinais conectados por meio de um pai e restaurar as conexões no filho substituído.

Eu tenho trabalhado com GDScript há algum tempo e tenho que concordar, algum tipo de recurso de trait/mixin e proxy/delegation é extremamente necessário. É bem chato ter que configurar todo esse clichê apenas para conectar propriedades ou expor métodos de filhos na raiz da cena.

Ou adicionar níveis da árvore apenas para simular componentes (fica bastante complicado rapidamente, porque você quebra todos os caminhos de nós com cada novo componente). Talvez haja uma maneira melhor, algo como meta/multi script permitindo vários scripts em um nó? Se você tiver uma solução idiomática, por favor, compartilhe...

Colocar C++ (GDNative) no mix torna as coisas ainda piores, porque _ready e _init se comportam de maneira diferente (leia-se: a inicialização com valores padrão funciona pela metade ou não funciona).

Esta é a principal coisa que eu tenho que resolver no GDScript. Muitas vezes, preciso compartilhar funcionalidades entre nós sem estruturar toda a minha estrutura de herança em torno dela - por exemplo, meu jogador e lojistas têm um inventário, meu jogador+itens+inimigos têm estatísticas, meu jogador e inimigos têm itens equipados, etc.

Atualmente eu implemento esses 'componentes' compartilhados como classes ou nós carregados nas 'entidades' que os requerem, mas é confuso (adiciona muitas buscas por nós, torna quase impossível digitar com pato, etc) e abordagens alternativas têm suas próprias desvantagens, então Eu não encontrei uma maneira melhor. Traços/mixins absolutamente salvariam minha vida.

O que se trata é poder compartilhar código entre objetos sem usar herança, o que eu acho que é necessário e não é possível fazer de forma limpa em Godot agora.

A maneira como eu entendo os traços do Rust (https://doc.rust-lang.org/1.8.0/book/traits.html), é que eles são como classes de tipo Haskell, onde você requer que algumas funções parametrizadas sejam definidas para o tipo você está adicionando uma característica, e então você pode usar algumas funções genéricas definidas sobre quaisquer tipos que implementam uma característica. Os traços de Rust são algo diferente do que é proposto aqui?

Este provavelmente será migrado por atacado, pois houve uma extensa discussão aqui.

Este provavelmente será migrado por atacado, pois houve uma extensa discussão aqui.

_Acho o "movimento" de propostas sem sentido imo, elas são melhor fechadas e solicitadas a reabrir em godot-propostas se as pessoas manifestarem interesse, e deixar outras propostas realmente implementadas se necessário. De qualquer forma..._

Eu tropecei nessa questão há um ano, mas só agora começo a entender a utilidade potencial do sistema de traços.

Compartilhando meu fluxo de trabalho atual na esperança de inspirar alguém a entender melhor o problema (e talvez sugerir uma alternativa melhor além da implementação do sistema de características).

1. Crie uma ferramenta para gerar modelos de componentes para cada tipo de nó usado no projeto:

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

Agora, o que tornaria ISSO mais fácil seria ter um sistema de snippet ou sistema de macro para que a criação dessas seções de código declarativo reutilizadas seja mais fácil para os desenvolvedores.

Seguindo seus passos... 😅

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. Crie um método estático para ser reutilizado para inicializar componentes no host (raiz, por exemplo):

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')

É assim que um componente (trait) se parece uma vez gerado com o primeiro script:

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)

(Opcional) 3. Estenda o componente (característica)

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

Essa é minha ideia. Acredito que seja bastante simples e cubra praticamente todos os casos de uso para reutilização de código. Eu até permitiria que a classe usando o trait sobrescrevesse os métodos do trait (se for possível fazer em tempo de compilação). Os traços também podem estender outros traços.

Seguindo seus passos... 😅

class_name ComponentMotion2D extends ComponentNode2D

const MAX_SPEED = 100.0

var linear_velocity = Vector2()
var collision

export(Script) var impl
...

Na verdade, os Script s exportados são usados ​​nesses componentes para direcionar o comportamento de tipos específicos de nó de host/raiz por componente. Aqui, ComponentMotion2D terá principalmente dois scripts:

  • motion_kinematic_body_2d.gd
  • motion_rigid_body_2d.gd

Então as crianças ainda dirigem o comportamento host / root aqui. A terminologia host vem de mim usando máquinas de estado, e aqui é onde os traços não seriam perfeitos, talvez, porque os estados são melhor organizados como nós imo.

Os próprios componentes são "conectados" na raiz, tornando-os membros onready , diminuindo efetivamente o código clichê (com a despesa de realmente ter que referenciá-los como object.motion )

extends KinematicBody2D

onready var motion = $motion # ComponentMotion2D

Não tenho certeza se isso ajudaria a resolver o problema, mas o C# tem uma coisa chamada métodos de extensão que estendem a funcionalidade de um tipo de classe.

Basicamente a função deve ser estática, e o primeiro parâmetro é obrigatório e deve ser self . Ficaria assim como uma definição:

extensão.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)

Então, quando você quiser usar o método distance , basta fazer isso:

player.gd

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

Eu estou familiarizado com isso, mas isso não ajuda a resolver o problema. Hehe, mas obrigado.

Os métodos de extensão @TheColorRed já foram propostos, mas não acho que sejam viáveis ​​em uma linguagem dinâmica. Eu também não acho que eles resolvam a raiz do problema que inicialmente iniciou esta discussão.


Em outra nota, provavelmente abrirei muitas das propostas de GDScript como GIPs (incluindo isso, se @willnationsdev não se importar).

Ainda acredito que as características fazem mais sentido compartilhar código horizontalmente em uma linguagem OO (sem herança múltipla, mas não quero seguir esse caminho).

Eu não acho que eles são viáveis ​​em uma linguagem dinâmica

O GDS é dinâmico? Os métodos de extensão podem ser limitados apenas a instâncias tipadas e funcionariam exatamente da mesma forma que em outras linguagens - apenas um açúcar sintático durante a compilação substituindo a chamada de método pela chamada de método estático (função). Honestamente, eu preferiria cafetões (ext. métodos) antes de protótipos ala JS ou outras formas dinâmicas de anexar métodos a classes ou mesmo apenas instâncias.

O que quer que decidamos fazer, espero que não decidamos chamá-lo de "cafetões".

O GDS é dinâmico?

Existem muitas definições de "dinâmico" neste contexto. Para ser claro: o tipo de variável pode não ser conhecido em tempo de compilação, então a verificação da extensão do método precisa ser feita em tempo de execução (o que prejudicará o desempenho de uma forma ou de outra).

Os métodos de extensão podem ser limitados apenas a instâncias tipadas

Se começarmos a fazer isso, podemos fazer GDScript apenas digitado. Mas essa é uma outra discussão que eu não quero chegar aqui.

O ponto é: as coisas não devem começar ou parar de funcionar porque o usuário adicionou tipos a um script. É quase sempre confuso quando isso acontece.

Novamente, eu não acho que isso resolve o problema de qualquer maneira. Estamos tentando anexar o mesmo código a vários tipos, enquanto um método de extensão o adicionará apenas a um tipo.

Honestamente, eu preferiria cafetões (ext. métodos) antes de protótipos ala JS ou outras formas dinâmicas de anexar métodos a classes ou mesmo apenas instâncias.

Ninguém propôs (ainda) anexar métodos dinamicamente (em tempo de execução) e eu também não quero isso. As características seriam aplicadas estaticamente no momento da compilação.

Originalmente, fiz um comentário sobre o Haxe e sua biblioteca de macros mixin, mas depois percebi que a maioria dos usuários não usará uma linguagem de terceiros de qualquer maneira.

Recentemente me deparei com essa necessidade.

Eu tenho alguns objetos com os quais o usuário pode interagir, mas não pode compartilhar o mesmo pai, mas eles precisam de grupos semelhantes de APIs

por exemplo, tenho algumas classes que não podem herdar do mesmo pai, mas usam um conjunto semelhante de APIs:
Armazém: Finanças, Exclusão, MouseInteraction + outros
Veículo: Finanças, Exclusão, MouseInteraction + outros
VehicleTerminal: Finanças, Exclusão, MouseInteraction + outros

Para as Finanças eu usei composição, pois isso requer o mínimo de código de placa de caldeira, pois get_finances_component() é uma API suficiente, pois realmente não se importa com os objetos do jogo.

Os outros:
MouseInteraction and Delection Acabei de copiar e colar, pois ele precisa saber sobre os objetos do jogo, algumas composições não funcionam aqui, a menos que eu tenha feito alguma delegação estranha:

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

mas isso realmente não me permite substituir como a exclusão funciona onde, se fosse uma classe herdada, eu poderia ter a capacidade de reescrever parte do código de exclusão, se necessário.

MouseInteraction and Delection Acabei de copiar e colar, pois ele precisa saber sobre os objetos do jogo, algumas composições não funcionam aqui, a menos que eu tenha feito alguma delegação estranha

Eu acesso componentes através de nós onready atualmente. Estou fazendo algo parecido:

# character.gd

var input = $input # input component

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

Então, é isso:

character.input.enabled = true

torna-se isto:

character.focused = true

Como @Calinou gentilmente apontou, meu problema https://github.com/godotengine/godot-proposals/issues/758 está intimamente relacionado a isso. O que você acha da proposta de poder adicionar uma característica a um grupo? Isso poderia drasticamente a necessidade de scripts e outras despesas gerais.

Seria ótimo ter uma maneira de injetar código compartilhável em classes e, se eles tiverem valores exportados, eles aparecerão no inspetor e terão os métodos e propriedades disponíveis e detectados pelo preenchimento de código.

As propostas de recursos e melhorias para o Godot Engine estão agora sendo discutidas e revisadas em um rastreador de problemas dedicado às propostas de melhoria Godot (GIP) ( godotengine/godot-proposals ). O rastreador GIP possui um modelo de problema detalhado, projetado para que as propostas incluam todas as informações relevantes para iniciar uma discussão produtiva e ajudar a comunidade a avaliar a validade da proposta para o mecanismo.

O rastreador principal ( godotengine/godot ) agora é dedicado exclusivamente a relatórios de bugs e Pull Requests, permitindo que os contribuidores tenham um foco melhor no trabalho de correção de bugs. Portanto, agora estamos fechando todas as propostas de recursos mais antigas no rastreador de problemas principal.

Se você estiver interessado nesta proposta de recurso, abra uma nova proposta no rastreador GIP seguindo o modelo de problema fornecido (depois de verificar se ela já não existe). Certifique-se de fazer referência a esta questão encerrada se ela incluir alguma discussão relevante (que você também deve resumir na nova proposta). Desde já, obrigado!

Nota: esta é uma proposta popular, se alguém a mover para Propostas Godot, tente resumir a discussão também.

Esta página foi útil?
0 / 5 - 0 avaliações