C# for Unity必知必会

Unity .NET 4.x runtime

2020-09-08  本文已影响0人  勤奋happyfire

历史

现状

Unity 2019中已经不能再切换到老的.NET 3.5 runtime了,唯一的runtime就是.NET 4.x

但是可以选择Api Compatibility Level

当使用.NET 4.x profile时,添加assembly引用

使用NuGet为Unity工程添加包

NuGet是.NET的包管理器,Visual Studio中集成了NuGet。但是因为Unity工程打开时会刷新工程文件,所以Unity使用NuGet需要特殊的步骤。
以下以Json.NET包为例(https://www.nuget.org/packages/Newtonsoft.Json/):

<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
  </assembly>
</linker>
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.
    }
}

.NET 4.x runtime新增的语法特性

自动属性初始化

// .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;

字符串解析

// .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}");

lambda表达式成员函数(lambda表达式取代函数体)

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

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

同样可在只读属性中使用:

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

基于Task的异步模式(TAP: Task-based Asynchronous Pattern)

在Unity中,异步编程以前都是通过coroutines实现的。从C# 5开始,.NET中首选的异步编程方式是使用TAP。即使用 asyncawait 关键字,以及使用 System.Threading.Task。简而言之,在一个 async 函数中你可以 await 一个任务完成而不会阻塞你的整个应用程序的更新。

// 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.");
    }
}

TAP要点:

协程和TAP的区别

nameof 操作符

nameof操作符可获取变量,类型和成员的字符串名字,当log错误时很好用。
这个例子中获取了枚举值的名字以及获取了函数形参变量的名字。

// 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));
}

Caller info attributes

提供函数调用信息,用法如下:

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 类名; 之后,就可以在调用类的静态方法时省略类名。

// .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
    }
}

IL2CPP要考虑的事情

当在iOS等平台导出游戏时,Unity会使用IL2CPP引擎将IL转换为C++代码,然后使用目标平台的本地编译器编译。在这种情况下,有某些.NET特性不被支持,例如反射,dynamic关键字。如果是你自己的代码,你可以避免使用这些特性,但是第三方库有可能并没有考虑到IL2CPP。参考文档:https://docs.unity3d.com/Manual/ScriptingRestrictions.html
另外在IL2CPP导出时,Unity会试图去除没有使用到的代码。如果使用到了反射,就有可能去除运行时调用的代码,这些代码在导出时并不能确定会用到。为了处理这个问题,需要在 link.xml 中添加不进行strip的assembly和namespace。参考文档:https://docs.unity3d.com/Manual/ManagedCodeStripping.html

参考资料

上一篇 下一篇

猜你喜欢

热点阅读