Usar o .NET 4.x no Unity

C# e .NET, as tecnologias subjacentes ao script do Unity, continuam a receber atualizações, uma vez que a Microsoft as lançou originalmente em 2002. Mas os desenvolvedores do Unity podem não estar cientes do fluxo constante de novos recursos adicionados à linguagem C# e ao .NET Framework. Isso ocorre porque antes do Unity 2017.1, o Unity tem usado um runtime de script equivalente ao .NET 3.5, que ficou anos sem atualizações.

Com o lançamento do Unity 2017.1, o Unity introduziu uma versão experimental do seu runtime de script atualizado para uma versão compatível com o .NET 4.6 e o C# 6. No Unity 2018.1, o runtime equivalente ao .NET 4.x não é mais considerado experimental, enquanto o runtime equivalente ao .NET 3.5 mais antigo agora é considerado como a versão herdada. E com o lançamento do Unity 2018.3, o Unity está projetando tornar o runtime de script atualizado a seleção padrão, além de atualizar mais, para o C# 7. Para obter mais informações e as atualizações mais recentes sobre este roteiro, leia a postagem de blog do Unity ou acesse seu Fórum de visualizações de script experimental. Enquanto isso, confira as seções abaixo para saber mais sobre os novos recursos disponíveis com o runtime de script do .NET 4.x.

Pré-requisitos

Habilitar o runtime de script do .NET 4.x no Unity

Para habilitar o runtime de script do .NET 4.x, execute as seguintes etapas:

  1. Abra as PlayerSettings no Inspetor do Unity, selecionando Editar > Configurações do projeto > Player.

  2. No cabeçalho Configuração, clique no menu suspenso Versão do Runtime de Script e selecione Equivalente ao .NET 4.x. Será solicitado que você reinicie o Unity.

Selecionar o equivalente do .NET 4.x

Escolher entre perfis do .NET 4.x e do .NET Standard 2.0

Depois de mudar para o runtime de script equivalente ao .NET 4.x, você pode especificar o Nível de compatibilidade de API usando o menu suspenso nas PlayerSettings (Editar > Configurações do projeto > Player). Há duas opções:

  • .NET Standard 2,0. Esse perfil corresponde ao perfil do .NET Standard 2.0 publicado pela .NET Foundation. O Unity recomenda o .NET Standard 2.0 para novos projetos. Ele é menor do que o .NET 4.x, o que é vantajoso para plataformas com restrições de tamanho. Além disso, o Unity se comprometeu a dar suporte a esse perfil em todas as plataformas compatíveis com o Unity.

  • .NET 4. x. Esse perfil fornece acesso à mais recente API do .NET 4. Ele inclui todo o código disponível nas bibliotecas de classes do .NET Framework e também dá suporte a perfis do .NET Standard 2.0. Se seu projeto requer uma parte da API não incluída no perfil do .NET Standard 2.0, use o perfil do .NET 4.x. No entanto, algumas partes dessa API podem não ser compatíveis com todas as plataformas do Unity.

Você pode ler mais sobre essas opções na postagem de blog do Unity.

Adicionar referências de assembly ao usar o nível de compatibilidade de API do .NET 4.x

Ao usar a configuração do .NET Standard 2.0 no menu suspenso Nível de compatibilidade de API, todos os assemblies no perfil de API são referenciados e utilizáveis. No entanto, ao usar o perfil maior do .NET 4.x, alguns dos assemblies que acompanham o Unity não são referenciados por padrão. Para usar essas APIs, você deve adicionar manualmente uma referência de assembly. Você pode exibir os assemblies com os quais o Unity é fornecido no diretório MonoBleedingEdge/lib/mono da instalação do editor do Unity:

Diretório MonoBleedingEdge

Por exemplo, se estiver usando o perfil do .NET 4.x e desejar usar HttpClient, você deverá adicionar uma referência de assembly para System.Net.Http.dll. Sem ele, o compilador reclamará que uma referência de assembly está ausente:

referência de assembly ausente

O Visual Studio regenera os arquivos .csproj e .sln para projetos do Unity cada vez que eles são abertos. Como resultado, não será possível adicionar referências de assembly diretamente no Visual Studio, porque elas serão perdidas ao reabrir o projeto. Em vez disso, um arquivo de texto especial chamado CSC. rsp deve ser usado:

  1. Crie um novo arquivo de texto chamado CSC. rsp no diretório de ativos raiz do projeto de Unity.

  2. Na primeira linha no arquivo de texto vazio, digite: -r:System.Net.Http.dll e, em seguida, salve o arquivo. Você pode substituir "System.Net.Http.dll" com qualquer assembly incluído no qual uma referência pode estar ausente.

  3. Reinicie o editor do Unity.

Aproveitando a compatibilidade do .NET

Além dos novos recursos de linguagem de programação e de sintaxe do C#, o runtime de script do .NET 4.x oferece aos usuários do Unity acesso a uma enorme biblioteca de pacotes do .NET que são incompatíveis com o runtime de script do .NET 3.5 herdado.

Adicionar pacotes do NuGet a um projeto do Unity

O NuGet é o gerenciador de pacotes para o .NET. O NuGet é integrado ao Visual Studio. No entanto, os projetos do Unity exigem um processo especial para adicionar pacotes NuGet. Isso ocorre porque quando você abre um projeto no Unity, seus arquivos de projeto do Visual Studio são regenerados, desfazendo configurações necessárias. Para adicionar um pacote NuGet ao seu projeto Unity, faça o seguinte:

  1. Procure no NuGet para localizar um pacote compatível que você deseja adicionar (.NET Standard 2.0 ou .NET 4.x). Este exemplo demonstra a adição do Json.NET, um pacote popular para trabalhar com JSON, a um projeto do .NET Standard 2.0.

  2. Clique no botão baixar :

    botão download

  3. Localize o arquivo baixado e altere a extensão dele de .nupkg para .zip.

  4. Dentro do arquivo zip, navegue até o diretório lib/netstandard2.0 e copie o arquivo Newtonsoft.Json.dl.

  5. Na pasta Ativos na raiz do projeto do Unity, crie uma nova pasta chamada Plugins. Plugins é um nome de pasta especial no Unity. Veja a documentação do Unity para obter mais informações.

  6. Cole o arquivo Newtonsoft.Json.dll no diretório Plugins do projeto do Unity.

  7. Crie um arquivo chamado link.xml no diretório Ativos do seu projeto do Unity e adicione o XML a seguir. Isso garantirá que o processo de remoção de código de bytes do Unity não remova os dados necessários ao exportar para uma plataforma IL2CPP. Embora esta etapa seja específica para essa biblioteca, você poderá encontrar problemas com outras bibliotecas que usam reflexão de maneiras semelhantes. Para obter mais informações, consulte a documentação do Unity sobre esse tópico.

    <linker>
      <assembly fullname="System.Core">
        <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
      </assembly>
    </linker>
    

Com tudo pronto, agora você pode usar o pacote Json.NET.

using Newtonsoft.Json;
using UnityEngine;

public class JSONTest : MonoBehaviour
{
    class Enemy
    {
        public string Name { get; set; }
        public int AttackDamage { get; set; }
        public int MaxHealth { get; set; }
    }
    private void Start()
    {
        string json = @"{
            'Name': 'Ninja',
            'AttackDamage': '40'
            }";

        var enemy = JsonConvert.DeserializeObject<Enemy>(json);

        Debug.Log($"{enemy.Name} deals {enemy.AttackDamage} damage.");
        // Output:
        // Ninja deals 40 damage.
    }
}

Esse é um exemplo simples do uso de uma biblioteca sem dependências. Quando os pacotes do NuGet dependem de outros pacotes do NuGet, você precisa baixar essas dependências manualmente e adicioná-las ao projeto da mesma maneira.

Novos recursos de linguagem e de sintaxe

Usar o runtime de script atualizado dá aos desenvolvedores do Unity acesso a C# 6 e a uma série de novos recursos de linguagem e de sintaxe.

Inicializadores de propriedade automática

No runtime de script do Unity .NET 3.5, a sintaxe de propriedade automática torna mais fácil definir rapidamente as propriedades não inicializadas, mas a inicialização precisa ocorrer em outro lugar no seu script. Agora, com o runtime .NET 4.x, é possível inicializar as propriedades automáticas na mesma linha:

// .NET 3.5
public int Health { get; set; } // Health has to be initialized somewhere else, like Start()

// .NET 4.x
public int Health { get; set; } = 100;

Interpolação de cadeia de caracteres

Com o runtime mais antigo do .NET 3.5, a concatenação de cadeia de caracteres exigia uma sintaxe estranha. Agora, com o tempo de execução do .NET 4. x, o recurso de $ interpolação de cadeia de caracteres permite que as expressões sejam inseridas em cadeias em uma sintaxe mais direta e legível:

// .NET 3.5
Debug.Log(String.Format("Player health: {0}", Health)); // or
Debug.Log("Player health: " + Health);

// .NET 4.x
Debug.Log($"Player health: {Health}");

Membros aptos para expressão

Com a mais nova sintaxe C# disponível no runtime do .NET 4.x, expressões lambda podem substituir o corpo de funções para torná-las mais sucintas:

// .NET 3.5
private int TakeDamage(int amount)
{
    return Health -= amount;
}

// .NET 4.x
private int TakeDamage(int amount) => Health -= amount;

Você também pode usar membros aptos para expressão em propriedades somente leitura:

// .NET 4.x
public string PlayerHealthUiText => $"Player health: {Health}";

Padrão assíncrono baseado em tarefa (TAP)

A programação assíncrona permite que operações demoradas sejam realizadas sem que isso faça com que o aplicativo pare de responder. Essa funcionalidade também permite que seu código aguarde a conclusão de operações demoradas antes de continuar com código que dependa dos resultados dessas operações. Por exemplo, você poderia esperar o carregamento de um arquivo ou a conclusão de uma operação de rede.

No Unity, a programação assíncrona normalmente é feita com corrotinas. No entanto, desde o C# 5, o método preferencial de programação assíncrona no desenvolvimento do .NET é o TAP (Padrão Assíncrono Baseado em Tarefa) usando o async e palavras-chave await com System.Threading.Task. Em resumo, em uma função async, você pode await a conclusão de uma tarefa sem bloquear a atualização do resto do seu aplicativo:

// Unity coroutine
using UnityEngine;
public class UnityCoroutineExample : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(WaitOneSecond());
        DoMoreStuff(); // This executes without waiting for WaitOneSecond
    }
    private IEnumerator WaitOneSecond()
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("Finished waiting.");
    }
}
// .NET 4.x async-await
using UnityEngine;
using System.Threading.Tasks;
public class AsyncAwaitExample : MonoBehaviour
{
    private async void Start()
    {
        Debug.Log("Wait.");
        await WaitOneSecondAsync();
        DoMoreStuff(); // Will not execute until WaitOneSecond has completed
    }
    private async Task WaitOneSecondAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        Debug.Log("Finished waiting.");
    }
}

O TAP é um assunto complexo, com nuances específicas do Unity que os desenvolvedores devem considerar. Como resultado, o TAP não é uma substituição universal para corrotinas no Unity; no entanto, é outra ferramenta que se pode aproveitar. O escopo desse recurso está além do abordado por este artigo, mas algumas dicas e práticas recomendadas gerais são fornecidas abaixo.

Referência de introdução ao TAP com o Unity

Estas dicas podem ajudar você a começar a usar o TAP no Unity:

  • As funções assíncronas destinadas a serem aguardadas devem ter o tipo de retorno Task ou Task<TResult> .
  • Funções assíncronas que retornam uma tarefa devem ter o sufixo "Async" acrescentado aos seus nomes. O sufixo "Async" ajuda a indicar que uma função deve sempre ser aguardada.
  • Use somente o tipo de retorno async void para funções que disparam funções assíncronas do código síncrono tradicional. Essas funções em si não podem ser esperadas e não devem ter o sufixo "Async" em seus nomes.
  • O Unity usa o UnitySynchronizationContext para garantir que funções assíncronas sejam executadas no thread principal por padrão. A API do Unity não está acessível fora do thread principal.
  • É possível executar tarefas em threads em segundo plano com métodos como Task.Run e Task.ConfigureAwait(false) . Essa técnica é útil para o descarregamento de operações onerosas do thread principal para aprimorar o desempenho. No entanto, o uso de threads em segundo plano pode levar a problemas difíceis de depurar, tais como condições de corrida.
  • A API do Unity não está acessível fora do thread principal.
  • Tarefas que usam threads não são compatíveis com builds WebGL do Unity.

Diferenças entre corrotinas e TAP

Há algumas diferenças importantes entre corrotinas e TAP/async-await:

  • As corrotinas não podem retornar valores, mas Task<TResult> pode.
  • Você não pode colocar um yield em uma instrução try-catch, tornando o tratamento de erro difícil com as corrotinas. No entanto, o try-catch funciona com o TAP.
  • O recurso de co-rotina do Unity não está disponível nas classes que não derivam de MonoBehaviour. TAP é ótimo para programação assíncrona nessas classes.
  • Atualmente, o Unity não sugere TAP como uma substituição completa de corrotinas. A criação de perfil é a única maneira de saber os resultados específicos de uma abordagem em relação à outra para qualquer projeto.

Observação

Desde o Unity 2018.2, métodos assíncronos com pontos de interrupção de depuração não são totalmente compatíveis; no entanto, essa funcionalidade é esperada no Unity 2018.3.

operador nameof

O operador nameof obtém o nome de cadeia de caracteres de uma variável, tipo ou membro. Alguns casos em que nameof é útil são o registro de erros e a obtenção do nome da cadeia de caracteres de uma enumeração:

// Get the string name of an enum:
enum Difficulty {Easy, Medium, Hard};
private void Start()
{
    Debug.Log(nameof(Difficulty.Easy));
    RecordHighScore("John");
    // Output:
    // Easy
    // playerName
}
// Validate parameter:
private void RecordHighScore(string playerName)
{
    Debug.Log(nameof(playerName));
    if (playerName == null) throw new ArgumentNullException(nameof(playerName));
}

Atributos de informações do chamador

Os atributos de informações do chamador fornecem informações sobre o chamador de um método. Você deve fornecer um valor padrão para cada parâmetro que você deseja usar com um atributo de informações do chamador:

private void Start ()
{
    ShowCallerInfo("Something happened.");
}
public void ShowCallerInfo(string message,
        [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
        [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
        [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
    Debug.Log($"message: {message}");
    Debug.Log($"member name: {memberName}");
    Debug.Log($"source file path: {sourceFilePath}");
    Debug.Log($"source line number: {sourceLineNumber}");
}
// Output:
// Something happened
// member name: Start
// source file path: D:\Documents\unity-scripting-upgrade\Unity Project\Assets\CallerInfoTest.cs
// source line number: 10

Using static

Using static permite que você use funções estáticas sem digitar seu nome de classe. Com o using static, você poderá economizar espaço e tempo se precisar usar várias funções estáticas da mesma classe:

// .NET 3.5
using UnityEngine;
public class Example : MonoBehaviour
{
    private void Start ()
    {
        Debug.Log(Mathf.RoundToInt(Mathf.PI));
        // Output:
        // 3
    }
}
// .NET 4.x
using UnityEngine;
using static UnityEngine.Mathf;
public class UsingStaticExample: MonoBehaviour
{
    private void Start ()
    {
        Debug.Log(RoundToInt(PI));
        // Output:
        // 3
    }
}

Considerações de IL2CPP

Ao exportar seu jogo para plataformas como iOS, o Unity usará seu mecanismo IL2CPP para "transcompilar" o IL para código C++ que é então compilado usando o compilador nativo da plataforma de destino. Nesse cenário, há vários recursos do .NET que não são compatíveis, tais como partes de reflexão e o uso da palavra-chave dynamic. Embora você possa controlar o uso desses recursos em seu próprio código, você pode encontrar problemas ao usar DLLs de terceiros e SDKs que não foram escritos com o Unity e o IL2CPP em mente. Para obter mais informações sobre esse tópico, veja a documentação sobre restrições de script no site do Unity.

Além disso, conforme mencionado no exemplo de Json.NET acima, o Unity tentará retirar o código não utilizado durante o processo de exportação de IL2CPP. Embora isso normalmente não seja um problema, com bibliotecas que usam reflexão, ele pode remover acidentalmente Propriedades ou métodos que serão chamados em tempo de execução que não podem ser determinados no momento da exportação. Para corrigir esses problemas, adicione um arquivo link.xml ao seu projeto que contém uma lista de assemblies e namespaces nos quais não executar o processo de remoção. Para obter detalhes completos, veja a documentação do Unity sobre a remoção de código de bytes.

Projeto do Unity de exemplo do .NET 4.x

A amostra contém exemplos de vários recursos do .NET 4.x. Você pode baixar o projeto ou exibir o código-fonte no GitHub.

Recursos adicionais