Editor de lógica visual para Unity3d. Parte 1

1. Introdução


Olá queridos leitores, no artigo de hoje, gostaria de me debruçar sobre esse fenômeno no desenvolvimento de aplicativos no Unity3d como desenvolvimento visual ou, mais precisamente, desenvolvimento usando representação visual de código e lógica. E, antes de continuar, quero esclarecer imediatamente, não se trata de programação visual, da palavra "absolutamente", não há variações do Blueprint no mundo do Unity nem gerações de código C #. Então, o que significa um editor de lógica visual? Se você está interessado na resposta a esta pergunta, seja bem-vindo em cat.

Artigos da série:
Editor de lógica visual, parte 2

O que é um editor de lógica visual


Muitas vezes, e alguns argumentam que sempre, durante o desenvolvimento, os programadores escrevem muitos códigos diferentes que fazem muitas coisas diferentes, desde os do sistema até a mecânica do jogo. Esse código, se o programador é "real", geralmente é unificado e isolado para que possa ser reutilizado (no Unity, esse código são componentes, eles também são herdeiros do MonoBehavior ). Não é difícil imaginar que pode haver muito código, especialmente se esse não for o primeiro projeto. Agora imagine que estamos iniciando um novo projeto e precisamos criar muitos protótipos rápidos e diferentes, e a equipe de programadores é limitada e o todo está ocupado com o ou os principais projetos. Os designers de jogos estão indignados, precisam testar, verificar, os produtores correm em torno dos gerentes tentando convencer um programador, o dinheiro é limitado, o tempo está acabando etc.

Do outro lado da moeda, nós (programadores) escrevemos muito código na forma de componentes, eles ficam em uma grande lista de objetos diferentes na cena. E assim expandimos a equipe, contratamos um novo programador, ele abre o palco para resolver o problema e se afoga em uma confusão de perguntas: quem causa quem, em que ordem, qual componente está conectado a qual e como, etc. Você diz razoavelmente: - “E documentação? ” Existe documentação (embora nem um pouco), mas aqui está o limite para que as novas pessoas que se juntem à equipe sejam o mais baixo possível e o tempo para esse processo o mais curto possível.

Como resolver as situações descritas acima? A resposta no título do artigo é o Visual Logic Editor. O que é isso? Este é um ambiente que permite operar visualmente em vários componentes da lógica e configurar seus relacionamentos (na versão "soft"), além de manipular objetos da cena indiretamente da cena. Se você o descreve de uma forma fabulosamente simples, é comum na infância montar projetos diferentes de cubos (apenas no nosso caso, os cubos não estão bem conectados, removendo a parte inferior, nosso design não cai).

Então, descobrimos a definição, mas o que isso nos dá no final?

  • Você pode montar projetos universais que podem ser reutilizados em projetos, o que reduz a rotina subsequente. Imagine um tipo de campo puro, que é o nosso projeto, apenas pegamos a construção montada de outro jogo, colocamos em campo e é isso.
  • Você pode criar um banco de dados de "cubos" isolados (mecânica, lógica, funcional) a partir dos quais pessoas que não são programadores poderiam construir construções por conta própria.
  • É possível substituir projetos em tempo real por outros, alterando assim o comportamento da lógica.
  • Você pode usar construções no modo adiado, por exemplo, se o NPC não estiver presente no mundo agora, nenhuma lógica associada a ele existirá em nosso "campo".
  • Como nossos cubos não são conectados por relacionamentos rígidos, podemos ativá-los, como desejamos e implementar ramificações condicionais e incondicionais arbitrariamente complexas.

Bem, isso soa muito bem? Mas o que na realidade? Se você abrir o Asset Store e ver a seção Visual Scripting , poderá ver, em princípio, um grande número de plugins diferentes. A maioria deles são variações no tema Blueprint do Unreal Engine, ou seja, em essência, geração de código. Praticamente não há sistemas que se encaixem nos conceitos de um editor de lógica visual. Os significados mais próximos são:

  1. Playmaker Sim, é um plug-in FSM, mas, mesmo assim, permite que você escreva suas próprias ações. Não é tão conveniente do ponto de vista da interface, mas, para certas coisas, é muito bom. A Blizzard não foi em vão usada em Hearthstone.
  2. Designer de comportamento / MoonBehavior , etc. plugins de árvore de estado. Está mais próximo do que foi descrito acima, mas existem muitas limitações, afinal, a árvore de estados não é uma lógica completa nos componentes.
  3. ICode Este é um análogo do craque, que é, de fato, também uma máquina de estado.

Existe uma saída que você pergunta, queridos leitores? Encontrei apenas um, escrevi meu sistema, o que encontrei, mas o caminho para ele era bastante longo e espinhoso.

O caminho


A idéia de desenvolver um plugin para o editor de lógica visual do Unity3D surgiu há muito tempo. No começo, eram apenas pensamentos que, se fossem, seria legal. Esses pensamentos apareceram no processo de trabalhar em um projeto no qual havia muitos jogos semelhantes, mais de 20 peças que precisavam ser feitas muito, muito rapidamente. A primeira implementação foi terrível em termos de interface, embora, é claro, nos permitisse desenvolver com sucesso todo o conjunto de jogos em uma determinada velocidade.

Para o próximo projeto, foi decidido criar um editor visual completo, mas, como resultado, devido à pouca experiência, a implementação não foi bem-sucedida, tudo foi muito lento, o número de conexões etc. diminuiu tanto que era impossível descobrir o que e onde (consulte screenshot e não tenha medo).

imagem

Depois disso, a ideia foi adiada por algum tempo. Os seguintes projetos eu já fiz em código puro, mas a ideia ainda pairava na minha cabeça. Gradualmente, levando em conta os erros do passado, a visão final (como me pareceu) e a lista de requisitos foram formadas. E em 2017, após a conclusão do próximo projeto freelancer, decidi que posso me dar ao trabalho de 6-7 meses neste plugin e tentar colocá-lo na Asset Store (ele ainda está e se chama Panthea VS ). Do ponto de vista da experiência de trabalho em um projeto tão complexo, tudo foi muito legal, o lado financeiro é infelizmente triste, ainda poder programar e vender são duas coisas diferentes. Foi em novembro de 2017, depois que perdi um pouco a minha motivação, me divorciei, mudei de cidade, mudei completamente minha vida e, para não cair no samoiedismo, decidi olhar para um ângulo diferente sobre o tema do editor de lógica visual. O resultado foi uViLEd , que eu decidi postar de graça. Desde que assinei um contrato em período integral, tive que trabalhar nos fins de semana e feriados e demorei todo o ano de 2018 e o início de 2019. O uViLEd é uma grande reconsideração do Panthea VS , uma refatoração completa do código para o compilador Roslyn (C # 7+), para que tudo funcione apenas a partir da versão Unity3d 2018.3.

Nota : O Panthea VS lançou vários projetos (Android e iOS, em particular, Lev's Truck e carros), em princípio, a experiência de usá-lo foi bem-sucedida, mas chegou o momento em que uma coisa é escrever um editor, outra coisa é aprender a usá-lo corretamente (por mais estranho que pareça) )

uViLEd e como usá-lo


1. Introdução


Então, o que aconteceu no final, primeiro olhamos para a foto e depois continuamos (haverá mais fotos).

imagem

Em que o editor de lógica visual se baseia?

imagem

Aqui:

  • Componentes - este é o nosso código que implementa um ou outro funcional, de fato, um análogo do MonoBehaviour , apenas no nosso caso todos os componentes são herdados da classe LogicComponent , que por sua vez é um ScriptableObject .
  • Variáveis são ScriptableObjects especiais que têm permissão para armazenar dados (qualquer um, incluindo estruturas e classes personalizadas, referências a objetos de cena, pré-fabricados e ativos). Variáveis ​​são necessárias se for necessário compartilhar dados entre componentes, ou seja, cada componente pode se referir a uma variável do tipo desejado, e ela será uma.
  • Os relacionamentos são uma descrição em forma visual de quais componentes, como e em que ordem os métodos se chamam. As relações entre componentes são determinadas usando dois campos especializados dos tipos INPUT_POINT e OUTPUT_POINT . Um link é sempre formado como o ponto de saída de um componente para o ponto de entrada de outro componente. Essas conexões não são rígidas, ou seja, não são visíveis para o programador e também não estão no código. Eles estão presentes, apenas no editor e, quando a cena começa, o próprio controlador lógico entende o código. Como isso acontece, falaremos em um artigo separado.

Tudo no compartimento - componentes, variáveis ​​e relacionamentos - forma a lógica . Em geral, nada super complicado, o programador escreve o código dos componentes e variáveis, e o criador do jogo ou outro (ou talvez o mesmo) programador / scripter forma a lógica, colocando esses componentes no editor e configurando as conexões e parâmetros.

Principais recursos do uViLEd


  • Lógica em execução (um conjunto de componentes, variáveis ​​e relacionamentos) no modo adiado, incluindo a partir de uma fonte externa (disco rígido ou servidor)
  • Configurando conexões entre componentes (ordem de chamada, ativação e desativação)
  • Fácil integração de componentes e qualquer outro código, incluindo descendentes do MonoBehavior
  • Substituindo a aparência dos componentes no editor (análogo ao CustomPropertyDrawer)
  • Definindo configurações de componente através do inspetor Unity3d
  • Adicione facilmente componentes à lógica através do arquivo de script arrastar e soltar ou através de um diretório
  • Agrupando componentes no editor lógico
  • Configurando a exibição dos componentes no editor (inversão, minimização, ativação e desativação)
  • Abrindo o editor de código do componente diretamente do editor lógico
  • Exibir dados de depuração diretamente no editor lógico durante a inicialização no editor
  • Dimensionando o editor de lógica visual
  • Se de repente um grande número de componentes estiver contido na lógica, existe a possibilidade de procurá-los com foco ao selecionar (isso se aplica a variáveis)
  • Depuração passo a passo no modo de inicialização de cena no editor Unity3d, com rastreamento de todos os dados transmitidos entre componentes e valores variáveis
  • Suporte para métodos MonoBehaviour e definição da ordem de suas chamadas. Nota : aqui queremos dizer que, por padrão, no SO, não existem métodos como Iniciar, Atualizar etc. portanto, seu suporte foi adicionado aos próprios componentes. Ao mesmo tempo, usando o atributo ExecuteOrder, você pode configurar a ordem em que os métodos Iniciar, Atualizar e assim por diante são chamados.
  • Suporte para Coroutine, assíncrono / aguardado e todos os atributos do Unity3d para o inspetor, bem como suporte para CustomPropertyDrawer

Trabalhar com o editor


Para começar a trabalhar com o editor, você deve abrir a cena e, em seguida, iniciar o próprio editor.

imagem

Após iniciar o editor, inicializamos a cena (o botão de atualização no editor), após o qual será possível criar ou adicionar lógica existente à cena.
Após criar a lógica (um arquivo que descreverá os componentes, seus parâmetros, relacionamentos entre componentes, variáveis ​​e seus valores), você poderá preenchê-la com significado. Para adicionar um componente ou variável, basta arrastar o script correspondente para a área do editor de lógica. Uma opção alternativa é usar um diretório que é gerado automaticamente usando o atributo ComponentDefinition .

imagem

Depois de adicionarmos vários componentes à lógica, eles podem ser movidos, inclusive em grupos ou combinados em um grupo visual.

imagem

Vamos considerar com mais detalhes o que representamos o próprio componente em um editor visual.

imagem

Aqui:

  • O botão de menu do componente abre um menu suspenso com o qual você pode:
    • Ativar ou desativar um componente
    • Minimize o componente (isso também pode ser feito clicando duas vezes no cabeçalho)
    • Inverter componente (trocar pontos de entrada e saída)
    • Ocultar ou mostrar área de opções do componente
    • Abra o editor de código para o componente
  • A área dos parâmetros do componente é o local onde os valores dos principais parâmetros do componente são exibidos, cuja composição depende do programador

Para configurar parâmetros (campos públicos ou com o atributo SerializeField), é necessário selecionar o componente no editor de lógica e abrir o inspetor Unity3d.

imagem

Aqui:

  • No canto superior direito, há um botão que permite alterar a cor do cabeçalho, exibido no editor de lógica
  • Nome - campo para definir o nome da instância do componente
  • Comentário - um campo para definir um comentário na instância do componente; ele é exibido no editor de lógica quando você passa o mouse sobre o componente
  • Parâmetros do componente - a área em que os parâmetros do componente são exibidos (campos públicos e campos marcados com SerializeField)
  • Links de variáveis - uma área para definir referências a variáveis ​​(mais sobre elas serão discutidas na seção sobre como trabalhar com variáveis).

Para agrupar objetos visualmente, é necessário selecioná-los, pressionar o botão direito e selecionar o item correspondente no menu. Os grupos podem ser renomeados, bem como alterar seu esquema de cores.

imagem

Para escalar a representação visual dos componentes usando a roda do mouse, tudo é bem simples.

E a última coisa que quero prestar atenção é trabalhar com as conexões entre os componentes.
Para estabelecer uma conexão, é necessário conectar o ponto de saída de um componente ao ponto de entrada de outro.

imagem

imagem

imagem

Os relacionamentos são estabelecidos com base na regra de correspondência de tipo que um ponto transmite e recebe. Exceções são feitas em um ponto de entrada que não recebe dados; qualquer ponto de saída pode ser conectado a ele. Quando uma conexão é estabelecida, o sistema verifica automaticamente a correspondência de tipo e mostra se essa conexão pode ou não ser estabelecida. Os pontos de entrada e saída são definidos no código do componente usando as seguintes classes:

INPUT_POINT OUTPUT_POINT INPUT_POINT<T> OUTPUT_POINT<T> 

As duas primeiras classes são usadas para pontos de entrada e saída que não aceitam parâmetros; a segunda, respectivamente, vice-versa. T pode ser de qualquer tipo.

 public INPUT_POINT <float> InputFloatValue = new INPUT_POINT<float>(); public OUTPUT_POINT <float> OutputFloatValue = new OUTPUT_POINT<float>(); 

Para chamar uma cadeia de links, você deve usar a função Execute.

 OutputFloatValue.Execute(5f); 

Para processar tal chamada, é necessário definir um manipulador para o ponto de entrada no código do componente (sobre onde exatamente falaremos mais adiante).

 InputFloatValue.Handler = value => Debug.Log(value); 

E, finalmente, quero mencionar um ponto importante sobre conexões. Se houver vários links a partir de um ponto, no editor é possível ajustar a ordem da chamada.

Trabalhar com variáveis


Como mencionado anteriormente, variáveis ​​são objetos especiais que permitem compartilhar dados entre componentes por meio de links para eles. Variáveis, como componentes, são criadas por programadores.

 [ComponentDefinition(Name = "Float", Path = "uViLEd Components/Base/Variable/Base", Tooltip = "Variable for a floating-point number", Color = VLEColor.Cyan)] public class VariableFloat : Variable<float> { } 

Como você pode ver, a classe base para variáveis ​​é a classe genérica Variable, onde T é o tipo de dados que está incluído na variável, T pode ser qualquer tipo que possa ser serializado.

No editor, as variáveis ​​são exibidas da seguinte maneira:

imagem

Para alterar a exibição dos valores das variáveis, redefina o método ToString no tipo T.

 public struct CustomData { public readonly int Value01; public readonly int Value02; public CustomData (int value01, int value02) { Value01= value01; Value02= value02; } public override string ToString() { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Value01 = {0}".Fmt(Value01)); stringBuilder.Append("Value02 = {0}".Fmt(Value02)); return stringBuilder.ToString(); } } 

Assim, uma variável desse tipo será semelhante a:

 public class VariableCustomData : Variable<CustomData> { } 

Para adicionar uma referência a uma variável em um componente, você deve usar uma classe especial.

 public VARIABLE_LINK<CustomData> CustomVariableLink = new VARIABLE_LINK<CustomData>(); 

Depois disso, o link pode ser definido no inspetor e no menu suspenso apenas as variáveis ​​do tipo CustomData serão exibidas, o que simplifica bastante o trabalho com elas.

Para facilitar o trabalho com variáveis, existem métodos especiais que permitem determinar quando uma variável alterou seu valor ou quando algum dado foi definido para ela.

 CustomVariableLink.AddSetEventHandler(CustomDataSet); CustomVariableLink.AddChangedEventHandler(CustomDataChanged); 

Deve-se ter em mente que o Changed funciona pela condição Equals ; portanto, se estruturas e classes forem usadas, esse método deverá ser redefinido para garantir a operação correta.

Trabalhando com objetos do Unity


Devido à natureza do sistema uViLEd, links diretos para objetos do Unity não podem ser usados ​​nele, pois não podem ser restaurados ao carregar a lógica. Para resolver esse problema, foi criado um shell VLObject especializado, que permite criar esses links, além de salvá-los e carregá-los. Entre outras coisas, esse shell possui um editor de propriedades especiais que permite obter componentes de qualquer objeto na cena (veja a figura abaixo), se você quiser acessá-los. Com o VLObject, você pode armazenar links não apenas para objetos de cena e seus componentes, mas também para prefabs e arquivos de recursos, como texturas, sons etc.

imagem

Nota : se a lógica existente for usada em outra cena, as referências aos objetos serão perdidas, incluindo referências a pré-fabricados, porque a cena atua como seu armazenamento. Isso também deve ser levado em consideração se você planeja usar a lógica como modelo, nesse caso, a melhor opção é transferir os links necessários para ela de fora (por exemplo, da lógica anexada à cena).

Também é possível restringir o tipo de objeto do Unity que será instalado no VLObject . Isso afeta apenas o inspetor do Unity e é usado para a conveniência de trabalhar com eles.

 [SerializeField] [TypeConstraint(typeof(Button))] private VLObject _button; 

Criando um componente lógico


Para criar um componente lógico, basta que o programador adicione um arquivo de script C # simples ao projeto (você também pode criar um componente ou uma variável imediatamente através de um menu especial na guia Projeto) e alterar o código para o seguinte:

 [ComponentDefinition(Name = "MyComponent", Path = "MyFolder/MySubfolder", Tooltip = "this my logic component", Color = VSEColor.Green)] public class MyLogicComponent : LogicComponent { } 

Como mencionado anteriormente, ComponentDefinition é um atributo que permite criar automaticamente um catálogo de componentes; aqui, deve-se observar que Color (a cor do cabeçalho) é definida na string como um formato HEX.

LogicComponent é a classe base de todos os componentes, que por sua vez é descendente de ScripatableObject .

A seguir, é apresentado um exemplo simples de um componente que se ramifica por um valor recebido do tipo bool:

 public class IfBool : LogicComponent { public INPUT_POINT<bool> ValueToBeChecked = new INPUT_POINT<bool>(); public OUTPUT_POINT True = new OUTPUT_POINT(); public OUTPUT_POINT False = new OUTPUT_POINT(); public override void Constructor() { ValueToBeChecked.Handler = ValueToBeCheckedHandler; } private void ValueToBeCheckedHandler(bool value) { if(value) { True.Execute(); }else { False.Execute(); } } } 

Portanto, como você pode ver no código, criamos o ponto de entrada do componente, que assume um valor do tipo bool e dois pontos de saída que são chamados dependendo do valor que obtivemos.

Talvez agora você tenha uma pergunta, que tipo de construtor é esse? Eu explico Por padrão, ScriptableObject não suporta métodos como Iniciar , Atualizar etc., mas também suporta os métodos Desperta , OnEnable , OnDisable e OnDestroy . Então, aqui está Desperta (como OnEnable ), caso ScriptableObject seja criado através do método CreateInstance , ele sempre é chamado e esse é realmente o problema. Como o objeto é criado na memória para serialização no modo editor, foi necessário excluir o código do componente de trabalhar neste momento; portanto, o analógico Awake foi adicionado como método Constructor , o mesmo se aplica aos métodos OnDisable e OnDestroy ao excluir um objeto no editor. Se você precisar processar corretamente a remoção de um componente (ao descarregar uma cena, por exemplo), deverá usar a interface IDisposable .

Em geral, como você pode ver, não há nada difícil na criação de componentes. Esta é uma classe regular, na qual pode haver qualquer código que você desejar. No caso específico, os componentes podem não conter pontos de entrada e saída, mas se comunicam usando mensagens globais. A propósito, para isso, no uViLEd existe uma classe GlobalEvent - é um sistema de mensagens baseado em tipos de dados (mais sobre isso pode ser encontrado no meu artigo).

A última coisa que gostaria de mencionar é a capacidade de configurar os pontos de entrada e saída do componente, dependendo dos parâmetros do componente.

imagem

Para fazer isso, no código do componente, basta implementar uma ou ambas as interfaces IInputPointParse e IOutputPointParse . Abaixo está um exemplo de código de classe genérica abstrata para componentes de ramificação do Switch ; os pontos de saída são criados automaticamente aqui, dependendo do parâmetro SwitchValues .

Código de classe SwitchAbstract
 public abstract class SwitchAbstract<T> : LogicComponent, IOutputPointParse { [Tooltip("input point for transmitting value, which should be checked")] public INPUT_POINT<T> ValueToBeChecked = new INPUT_POINT<T>(); [Tooltip("set of values for branching")] public List<T> SwitchValues = new List<T>(); protected Dictionary<string, object> outputPoints = new Dictionary<string, object>(); public override void Constructor() { ValueToBeChecked.Handler = ValueToBeCheckedHandler; } protected virtual bool CompareEqual(T first, T second) { return first.Equals(second); } protected virtual string GetValueString(T value) { var outputPontName = value.ToString(); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { if (outputPoints.ContainsKey(outputPontName)) { outputPontName += " ({0})".Fmt(outputPoints.Count); } } #endif return outputPontName; } private void ValueToBeCheckedHandler(T checkedValue) { foreach (var value in SwitchValues) { if (CompareEqual(checkedValue, value)) { ((OUTPUT_POINT)outputPoints[GetValueString(value)]).Execute(); return; } } } public IDictionary<string, object> GetOutputPoints() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) { outputPoints.Clear(); } #endif if (outputPoints.Count == 0) { foreach (var value in SwitchValues) { outputPoints.Add(GetValueString(value), new OUTPUT_POINT()); } } return outputPoints; } } 


Depuração lógica


O UViLEd fornece vários mecanismos para a lógica de depuração:

  1. A capacidade de exibir variáveis ​​internas e seus valores no editor de lógica no modo de início de cena. Para fazer isso, use o atributo ViewInDebugMode
  2. Capacidade de visualizar valores de variáveis ​​lógicas no modo de início de cena
  3. Possibilidade de depuração passo a passo de chamadas entre componentes e visualização de dados transferidos entre eles

O UViLEd possui um modo especial para o último item, que é ativado quando a cena começa.

imagem

Infelizmente, este modo tem certas limitações associadas à transição entre cenas. Nesse caso, os dados de depuração da cena anterior e da lógica serão perdidos e, na nova, eles começarão a ser exibidos somente a partir do momento em que a lógica for ativada no editor.

Conclusão


Neste artigo, tentei apresentar brevemente a abordagem de desenvolvimento que uso nos meus projetos atuais. Apesar do ceticismo inicial (incluindo o meu), a prática mostra significativamente a conveniência de seu uso, principalmente na prototipagem. Entre outras coisas, o trabalho dos designers de jogos foi bastante simplificado, eles não entram em cena, não cutucam objetos para configurar o processo do jogo, uma lógica separada é criada para eles com um conjunto de dados de variáveis ​​e componentes nos quais eles podem facilmente configurar tudo. Também uma grande vantagem é o fato de que, em meus projetos, geralmente o conteúdo é baixado de fora. Usando o editor de lógica visual, eu posso atualizar o equilíbrio do processo do jogo sem atualizar o aplicativo principal; em alguns casos, a própria lógica pode ser alterada.

Para mim, decidi que essa abordagem ao desenvolvimento é o lugar ideal, é claro que não é aplicável a grandes projetos, mas pode ser usada lá para alguns scripts de jogabilidade para revitalizar o mundo, no design de níveis etc. projetos em andamento (segmento infantil), até o momento, ele apresenta ótimos resultados.

O que vem depois?

Esta foi a primeira parte de uma série de artigos sobre o editor visual da lógica uViLEd, e haverá partes sobre:

  1. O núcleo do sistema : como a lógica é carregada, por que o ScriptableObject é selecionado, como a API é organizada, o que permite que você faça etc., que dificuldades surgiram e como tudo foi resolvido.
  2. Editor : como foi desenvolvido, como foi construído, quais problemas e quais soluções, etc. coisas, que eu iria refazer agora.

Escreva nos comentários se tiver alguma pergunta específica que gostaria que eu dissesse nos artigos subseqüentes.

PS : Tentei falar sobre os principais pontos do uViLEd , se você quiser, pode se familiarizar com ele baixando o plug-in da Asset Store, há documentação completa (embora em inglês): um manual do usuário, um guia para programadores e APIs.

Editor de lógica visual, parte 2

Editor de lógica visual UViLEd
Artigo Global Messaging

Source: https://habr.com/ru/post/pt448190/


All Articles