Unity性能调优手册12第三方插件:DOTween,UniRx,UniTask

2023-11-27 10:42:26 浏览数 (1)

翻译自https://github.com/CyberAgentGameEntertainment/UnityPerformanceTuningBible/

第三方插件

本章介绍了在执行第三方库(在Unity中开发游戏时经常使用)时从性能角度考虑的一些事项

DOTween

DOTween *1是一个允许脚本创建平滑动画的库。例如,一个放大和缩小的动画可以很容易地写成下面的代码 *1 http://dotween.demigiant.com/index.php

代码语言:javascript复制
public class Example : MonoBehaviour {
public void Play() {
    DOTween.Sequence()
        .Append(transform.DOScale(Vector3.one * 1.5f, 0.25f))
        .Append(transform.DOScale(Vector3.one, 0.125f));
    }
}

SetAutoKill

由于创建渐变的过程,如DOTween.Sequence()或transform.DOScale(…),基本上涉及内存分配,考虑重用经常重放的动画实例。 默认情况下,当动画完成时,渐变会自动被丢弃,所以SetAutoKill(false)会抑制这一点。第一个用例可以用下面的代码替换 重用Tween实例

代码语言:javascript复制
private Tween _tween;
private void Awake() {
    _tween = DOTween.Sequence()
    .Append(transform.DOScale(Vector3.one * 1.5f, 0.25f))
    .Append(transform.DOScale(Vector3.one, 0.125f))
    .SetAutoKill(false)
    .Pause();
}
public void Play() {
    _tween.Restart();
}

注意,调用SetAutoKill(false)的补间如果没有被显式销毁,就会泄漏。当不再需要Kill()时调用它,或者使用下面描述的SetLink。

代码语言:javascript复制
private void OnDestroy() {
    _tween.Kill();
}

SetLink

调用SetAutoKill(false)或使用SetLoops(-1)无限重复的补间不会被自动销毁,所以你需要自己管理它们的生命周期。建议这样的渐变在SetLink(GameObject)中与关联的GameObject相关联,这样当GameObject是破坏了,补间也被破坏了。

代码语言:javascript复制
private void Awake() {
    _tween = DOTween.Sequence()
    .Append(transform.DOScale(Vector3.one * 1.5f, 0.25f))
    .Append(transform.DOScale(Vector3.one, 0.125f))
    .SetAutoKill(false)
    .SetLink(gameObject)
    .Pause();
}

DOTween Inspector

在Unity编辑器中播放时,一个名为[DOTween]的游戏对象,你可以通过选择名为[DOTween]的游戏对象从检查器中检查DOTween的状态和设置

检查那些即使与其相关的GameObjects已经被丢弃也会继续移动的渐变对象,以及那些处于 暂停状态和泄漏而不被丢弃。 译者增加部分 【腾讯文档】Lua中实现Dotween https://docs.qq.com/doc/DWlphTk1NS2hPQ2JJ 【腾讯文档】C#实现Dotween https://docs.qq.com/doc/DWmpaSnlLaUN2Qm1S

UniRx

UniRx *2是一个实现针对Unity优化的响应式扩展的库。有了Unity丰富的操作符和帮助器,可以用简洁的方式编写复杂条件的事件处理。 *2 https://github.com/neuecc/UniRx

Unsubscribe

UniRx允许你订阅流发布者IObservable来接收它的消息通知。 订阅时,将创建接收通知的对象实例、处理消息的回调等。为了避免这些实例在订阅方生命周期之后仍留在内存中,订阅方基本上有责任在不再需要接收通知时退订。 有几种方法可以取消订阅,但出于性能考虑,最好显式地Dispose保留Subscribe的IDisposable返回值。

代码语言:javascript复制
public class Example : MonoBehaviour {
    private IDisposable _disposable;
    private void Awake() {
        _disposable = Observable.EveryUpdate()
        .Subscribe(_ => {
        // Processes to be executed every frame
        });
    }
    private void OnDestroy() {
        _disposable.Dispose();
    }
}

如果你的类继承自MonoBehaviour,你也可以调用AddTo(this)在你自己的Destroy时自动取消订阅。虽然在内部调用AddComponent来监视Destroy会有开销,但是使用这个方法是一个好主意,因为它更容易编写。

代码语言:javascript复制
private void Awake() {
Observable.EveryUpdate()
    .Subscribe(_ => {
    // Processing to be executed every frame
    })
    .AddTo(this);
}

UniTask

UniTask是Unity中一个强大的高性能异步处理库,具有基于值的UniTask类型的零分配异步处理。它还可以根据Unity的PlayerLoop控制执行时间,从而完全取代传统的协同程序。

UniTask v2

UniTask v2是对UniTask的一次重大升级,于2020年6月发布。UniTask v2具有显著的性能改进,例如整个异步方法的零分配,并添加了诸如异步LINQ支持和对外部资产的等待支持等特性。* 3 *3 https://tech.cygames.co.jp/archives/3417/ 另一方面,从UniTask v1更新时要小心,因为它包含破坏性的更改,例如UniTask. delay(…)和其他由Factory在调用时调用的任务,禁止对正常的UniTask实例进行多个等待,等等。然而,积极的优化进一步提高了性能,所以基本上UniTask v2是可行的方法。

UniTask Tracker

UniTask Tracker可以用来可视化等待的UniTask及其创建的堆栈跟踪。

例如,假设您有一个MonoBehaviour,当它与某物碰撞时它的_hp递减。

代码语言:javascript复制
public class Example : MonoBehaviour {
    private int _hp = 10;
    public UniTask WaitForDeadAsync() {
        return UniTask.WaitUntil(() => _hp <= 0);
    }
    private void OnCollisionEnter(Collision collision) {
        _hp -= 1;
    }
}

如果这个MonoBehaviour的_hp在完全耗尽之前被destroy, _hp将不会再耗尽,因此WaitForDeadAsync的返回值UniTask将失去完成的机会,并将继续等待 建议您使用此工具检查由于终止条件配置错误而导致的UniTask泄漏。 Tips 防止Task泄漏 示例代码泄漏任务的原因是它没有考虑到任务本身在满足终止条件之前被销毁的情况。 要做到这一点,只需检查任务本身是否已被销毁。或者,this.GetCancellationTokenOnDestroy()获得的CancellationToken可以传递给WaitForDeadAsync,以便任务在销毁时被取消。

代码语言:javascript复制
// Pattern for checking whether the user is Destroyed or not
public UniTask WaitForDeadAsync() {
    return UniTask.WaitUntil(() => this == null || _hp <= 0);
}
// Pattern for passing a CancellationToken
public UniTask WaitForDeadAsync(CancellationToken token) {
    return UniTask.WaitUntil(
        () => _hp <= 0,
        cancellationToken: token);
}

WaitForDeadAsync(CancellationToken)调用的例子

代码语言:javascript复制
Example example = ...
var token = example.GetCancellationTokenOnDestroy();
await example.WaitForDeadAsync(token);

在Destroy时,前一个UniTask完成无事件,而后一个OperationCanceledException被抛出。哪种行为更可取取决于具体情况,应该选择适当的实现。

全书总结

本文档到此结束。我们希望通过这本书,那些“对性能调优没有信心”的人能够开始思考,“我有点懂了,我想试试。”随着越来越多的人在他们的项目中实践它,他们将能够更快地处理问题,并且他们的项目的稳定性将会增加。 您还可能遇到无法用本书提供的信息解决的复杂事件。但即使在这种情况下,你所做的也将是一样的。你仍然需要分析,分析原因,并采取一些行动。 从现在开始,请在实践中充分运用自己的知识、经验和想象力。我希望您会喜欢这种方式的性能调优。谢谢你一直读到最后

0 人点赞