开源库UniTask笔记

内容来源:up主游戏石匠,仅作笔记,推荐关注该up主。

UniTask是Github上的开源库,为Unity提供一个高性能异步方案,可以代替协程实现异步操作,中文文档
优点:

  1. 不需要依赖于MonoBehaviour,性能比协程好
  2. 可以进行 try catch,取消操作
  3. 默认使用主线程,与Unity协同,而C#得Task是在另一个线程中运行
  4. 0GC

安装

在这里插入图片描述
通过Package Manager安装,输入https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

导入后可能的报错

如果导入后报错提示 ‘ArrayPool’ does not contain a definition for ‘Shared’
那是因为项目中使用了tolua,而tolua附带了一个CString.dll的库,这个库自己定义了一个全局的ArrayPool,就会导致其他地方用的ArrayPool都指向了CString.dll中的ArrayPool,这与Unitask源码中的Cysharp.Threading.Tasks.Internal.ArrayPool冲突了。
在这里插入图片描述
反编译CString.dll可以看到其中定义的这个ArrayPool
在这里插入图片描述
把Unitask文件夹移动到 项目名/Packages 目录下,并修改源码,在报错的地方加上命名空间前缀

var pool = Cysharp.Threading.Tasks.Internal.ArrayPool<TSource>.Shared;

这样就可以解决报错,尽量不要修改 CString.dll,不然打包可能报错

异步加载资源

加载文本

using Cysharp.Threading.Tasks;
using UnityEngine;

/// <summary>
/// 不需要继承自MonoBehaviour
/// </summary>
public class UniTaskLoadAsync
{
    /// <summary>
    /// 返回UniTask<Object>类型,这种类型事为Unity定制的,作为替代原生Task<T>的轻量级方案
    /// </summary>
    public async UniTask<Object> LoadAsync<T>(string path) where T : Object
    {
        var asyncOperation = Resources.LoadAsync<T>(path);
        //这个await会将ResourceRequest(class)封装到UniTask的ResourceRequestAwaiter(struct)中
        return await asyncOperation;
    }
}
public class UniTaskTest : MonoBehaviour
{
    /// <summary>
    /// 加载文本
    /// </summary>
    private async void LoadTextAsync()
    {
        UniTaskLoadAsync loader = new UniTaskLoadAsync();
        //Test是Resources目录下的文本文件
        var textObj = await loader.LoadAsync<TextAsset>("Test");
        string str = ((TextAsset)textObj).text;
        Debug.LogError(str);
    }
}

加载场景

/// <summary>
/// 加载场景过程中显示进度值
/// </summary>
private async void LoadSceneAsync()
{
    var progress = Progress.Create<float>(x =>
    {
        //这里可以修改界面上的进度条
        Debug.Log("进度值:" + x);
    });
    //ToUniTask创建一个进度相关的回调
    await SceneManager.LoadSceneAsync("Scenes/TestScene1").ToUniTask(progress);
}

加载网络图片

public Image Image;

/// <summary>
/// 加载网络图片
/// </summary>
private async void LoadWebPictureAsync()
{
    var webRequest = UnityWebRequestTexture.GetTexture("https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png");
    var result = await webRequest.SendWebRequest();
    var texture = ((DownloadHandlerTexture)result.downloadHandler).texture;
    Sprite sprite = Sprite.Create(texture, new Rect(Vector2.zero, 
        new Vector2(texture.width, texture.height)), new Vector2(0.5f, 0.5f));
    Image.sprite = sprite;
    Image.SetNativeSize();
}

Delay操作

public async void DelayTest()
{
    //性能最好,可以设置等待时机,PlayerLoopTiming 对应Unity中playerloop的更新时机
    await UniTask.Yield(PlayerLoopTiming.LastUpdate);

    //等待1秒,类似 yield return new WaitForSeconds(1),可以设置 ignoreTimeScale
    await UniTask.Delay(TimeSpan.FromSeconds(1), false);

    //执行在下一帧的update之后,类似 yield return null,和 UniTask.Yield() 效果一样
    await UniTask.NextFrame();
    
    //这一帧的最后,类似 yield return new WaitForEndOfFrame(),this是一个MonoBehaviour
    await UniTask.WaitForEndOfFrame(this);
    
    //类似 yield return new WaitForFixedUpdate,和 await UniTask.Yield(PlayerLoopTiming.FixedUpdate)效果一样
    await UniTask.WaitForFixedUpdate();

    //延迟5帧
    await UniTask.DelayFrame(5);

    //类似 yield return new WaitUntil(() => count > 10),当count > 10时才执行后面逻辑
    await UniTask.WaitUntil(() => count > 10);
}

联动操作 WhenAll和WhenAny

在这里插入图片描述
WhenAll
在这里插入图片描述
WhenAny

using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class UniTaskWhen : MonoBehaviour
{
    public Button FirstButton;
    public Button SecondButton;
    public TextMeshProUGUI Text;

    private bool firstClick = false;
    private bool secondClick = false;

    private void Start()
    {
        //这里用两个按钮的点击模拟两种操作
        FirstButton.onClick.AddListener(OnClickFirst);
        SecondButton.onClick.AddListener(OnClickSecond);

        // WhenAllTest();
        WhenAnyTest();
    }
    
    private void OnClickFirst()
    {
        firstClick = true;
    }

    private void OnClickSecond()
    {
        secondClick = true;
    }

    /// <summary>
    /// 当两个按钮都点击了才执行后面操作
    /// </summary>
    private async void WhenAllTest()
    {
        var firstOperation = UniTask.WaitUntil(() => firstClick);
        var secondOperation = UniTask.WaitUntil(() => secondClick);
        await UniTask.WhenAll(firstOperation, secondOperation);
        // 注意,whenAll可以用于平行执行多个资源的读取,非常有用!
        // var (a, b, c) = await UniTask.WhenAll(
        //LoadAsSprite("foo"),
        //LoadAsSprite("bar"),
        //LoadAsSprite("baz"));
        Text.text = "两个按钮都点击了";
    }

    /// <summary>
    /// 当其中一个按钮点击了就执行后面操作
    /// </summary>
    private async void WhenAnyTest()
    {
        var firstOperation = UniTask.WaitUntil(() => firstClick);
        var secondOperation = UniTask.WaitUntil(() => secondClick);
        await UniTask.WhenAny(firstOperation, secondOperation);
        Text.text = firstClick ? "first按钮点击了" : "second按钮点击了";
    }
}

取消操作

在这里插入图片描述

public class UniTaskCancel : MonoBehaviour
{
    public Transform FirstTransform;
    public Transform SecondTransform;

    public Button FirstRunButton;
    public Button SecondRunButton;

    public Button FirstCancelButton;
    public Button SecondCancelButton;
    
    public TextMeshProUGUI Text;

    //做取消时需要创建这个对象
    private CancellationTokenSource _firstCancelToken;
    private CancellationTokenSource _secondCancelToken;
    private CancellationTokenSource _linkedCancelToken;

    private void Start()
    {
        FirstRunButton.onClick.AddListener(OnClickFirstMove);
        SecondRunButton.onClick.AddListener(OnClickSecondMove);

        FirstCancelButton.onClick.AddListener(OnClickFirstCancel);
        SecondCancelButton.onClick.AddListener(OnClickSecondCancel);
        
        _firstCancelToken = new CancellationTokenSource();
        // 注意这里可以直接先行设置多久以后取消
        // _firstCancelToken = new CancellationTokenSource(TimeSpan.FromSeconds(1.5f));
        _secondCancelToken = new CancellationTokenSource();
        //用两个token创建新的linkedCancelToken,当其中一个取消后,linkedCancelToken也会取消,
        _linkedCancelToken =
            CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);
    }
    
    /// <summary>
    /// 移动first,使用try catch监听取消信号
    /// </summary>
    private async void OnClickFirstMove()
    {
        try
        {
            await MoveTransform(FirstTransform, _firstCancelToken.Token);
        }
        catch (OperationCanceledException e)
        {
            //发出取消信号,这里会抛异常
            Text.text = "first已经被取消";
        }
    }

    /// <summary>
    /// 移动second,忽略异常的抛出,返回一个值元组,这种方式性能更好
    /// </summary>
    private async void OnClickSecondMove()
    {
        //第一个参数表示是否取消,第二个参数时await的返回值
        var (cancelled, _) = await MoveTransform(SecondTransform, _secondCancelToken.Token).SuppressCancellationThrow();
        // 使用LinkedToken,当first取消后,second也会取消
        // var (cancelled, _) = await MoveTransform(SecondTransform, _linkedCancelToken.Token).SuppressCancellationThrow();
        if (cancelled)
        {
            Text.text = "second已经被取消";
        }
    }

    private async UniTask<int> MoveTransform(Transform tf, CancellationToken cancellationToken)
    {
        float totalTime = 20;
        float timeElapsed = 0;
        while (timeElapsed <= totalTime)
        {
            timeElapsed += Time.deltaTime;
            await UniTask.NextFrame(cancellationToken);
            tf.transform.localPosition += Vector3.right * Time.deltaTime * 100;
        }
        
        return 0;
    }
    
    /// <summary>
    /// 取消first移动,Token使用后就不能再次使用,得创建新的Token
    /// </summary>
    private void OnClickFirstCancel()
    {
        _firstCancelToken.Cancel();
        _firstCancelToken.Dispose();
        _firstCancelToken = new CancellationTokenSource();
        _linkedCancelToken =
            CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);
    }
    
    private void OnClickSecondCancel()
    {
        _secondCancelToken.Cancel();
        _secondCancelToken.Dispose();
        _secondCancelToken = new CancellationTokenSource();
        _linkedCancelToken =
        CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);
    }

    private void OnDestroy()
    {
        _firstCancelToken.Dispose();
        _secondCancelToken.Dispose();
        _linkedCancelToken.Dispose();
    }
}

超时操作

public class TimeoutTest : MonoBehaviour
{
    public Button TestButton;

    private void Start()
    {
        //使用UniTask.UnityAction包装了OnClickTest
        TestButton.onClick.AddListener(UniTask.UnityAction(OnClickTest));
    }
    
    private async UniTaskVoid OnClickTest()
    {
        var res = await GetRequest("https://www.baidu.com/", 2f);
        Debug.LogError(res);
    }
    
    private async UniTask<string> GetRequest(string url, float timeout)
    {
        //这个token会在timeout之后发出取消信号
        var cts = new CancellationTokenSource();
        cts.CancelAfterSlim(TimeSpan.FromSeconds(timeout));

        var (failed, result) = await UnityWebRequest.Get(url).SendWebRequest().
                WithCancellation(cts.Token).SuppressCancellationThrow();
        if (!failed)
        {
            //成功了返回网页内容的开头
            return result.downloadHandler.text.Substring(0, 100);
        }

        return "超时";
    }
}

Fire and Forget

public class ForgetSample : MonoBehaviour
{
    public Button StartButton;
    public GameObject Target;
    public const float G = 9.8f;

    private void Start()
    {
        StartButton.onClick.AddListener(OnClickStart);
    }
    
    /// <summary>
    /// 同步方法中调用异步方法
    /// </summary>
    private void OnClickStart()
    {
        //不需要等待时候就调用Forget
        FallTarget(Target.transform).Forget();
    }
    
    /// <summary>
    /// 使目标掉落,async UniTaskVoid是async UniTask的轻量级版本
    /// </summary>
    private async UniTaskVoid FallTarget(Transform targetTrans)
    {
        Vector3 startPosition = targetTrans.position;
        float fallTime = 20f;
        float elapsedTime = 0;
        while (elapsedTime <= fallTime)
        {
            elapsedTime += Time.deltaTime;
            float fallY = 0.5f * G * elapsedTime * elapsedTime;
            targetTrans.position = startPosition + Vector3.down * fallY;
            //GetCancellationTokenOnDestroy 表示获取一个依赖对象生命周期的Cancel句柄,
            //当对象被销毁时,将会调用这个Cancel句柄,从而实现取消的功能
            await UniTask.Yield(this.GetCancellationTokenOnDestroy());
        }
    }
}

UniTask运行中执行回调

public class CallbackSample : MonoBehaviour
{
    public Button CallbackButton;
    public GameObject Target;
    public const float G = 9.8f;

    private void Start()
    {
        CallbackButton.onClick.AddListener(UniTask.UnityAction(OnClickCallback));
    }
    
    private async UniTaskVoid OnClickCallback()
    {
        float time = Time.time;
        UniTaskCompletionSource source = new UniTaskCompletionSource();
        FallTarget(Target.transform, source).Forget();
        await source.Task;// UniTaskCompletionSource产生的UnitTask是可以复用的
        Debug.Log($"耗时 {Time.time - time}秒");
    }
    
    /// <summary>
    /// UniTask运行中执行回调
    /// UniTaskCompletionSource是对UniTask和CancellationToken的封装
    /// </summary>
    private async UniTask FallTarget(Transform targetTrans, UniTaskCompletionSource source)
    {
        Vector3 startPosition = targetTrans.position;
        float fallTime = 20f;
        float elapsedTime = 0;
        while (elapsedTime <= fallTime)
        {
            elapsedTime += Time.deltaTime;
            //当下落时间超过1秒时设置操作
            if (elapsedTime > 1f)
            {
                // 表示操作完成
                source.TrySetResult();
                // 失败
                // source.TrySetException(new SystemException());
                // 取消
                // source.TrySetCanceled(someToken);
                
                // 泛型类UniTaskCompletionSource<T> SetResult是T类型,返回UniTask<T>
            }
            
            float fallY = 0.5f * G * elapsedTime * elapsedTime;
            targetTrans.position = startPosition + Vector3.down * fallY;
            await UniTask.Yield(this.GetCancellationTokenOnDestroy());
        }
    }
}

切换到线程

public class ThreadSample : MonoBehaviour
{
    public Button StandardRun;
    public Button YieldRun;

    private void Start()
    {
        StandardRun.onClick.AddListener(UniTask.UnityAction(OnClickStandardRun));
        YieldRun.onClick.AddListener(UniTask.UnityAction(OnClickYieldRun));
    }
    
    /// <summary>
    /// 线程中计算
    /// </summary>
    private async UniTaskVoid OnClickStandardRun()
    {
        int result = 0;
        //切换到其他线程
        await UniTask.RunOnThreadPool(() => { result = 1; });
        //切换回主线程
        await UniTask.SwitchToMainThread();
        Debug.LogError($"计算结束,当前结果是{result}");
    }

    /// <summary>
    /// 线程中读取文件
    /// </summary>
    private async UniTaskVoid OnClickYieldRun()
    {
        string fileName = Application.dataPath + "/Resources/test.txt";
        await UniTask.SwitchToThreadPool();
        string fileContent = await File.ReadAllTextAsync(fileName);
        //调用 UniTask.Yield 会自动切换会主线程
        await UniTask.Yield(PlayerLoopTiming.Update);
        Debug.LogError(fileContent);
    }
}

响应式编程

响应式编程(Reactive programming)简称Rx,他是一个使用LINQ风格编写基于观察者模式的异步编程模型。简单点说Rx = Observables + LINQ + Schedulers。Rx将事件转化为响应式的序列,通过LINQ操作可以很简单地组合起来,还支持时间操作。

UI事件,连点,双击,冷却

在这里插入图片描述
球体三次点击, 执行不同操作
在这里插入图片描述
按钮双击处理
在这里插入图片描述
点击按钮后CD时间

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using UnityEngine;
using UnityEngine.UI;

public class UIEventsSample : MonoBehaviour
{
    public Button SphereButton;
    public Button DoubleClickButton;
    public Button CoolDownButton;
    
    public Text DoubleEventText;
    public Text CoolDownEventText;

    public float DoubleClickCheckTime = 0.5f;
    public float CooldownTime = 3f;
    
    void Start()
    {
        CheckSphereClick(SphereButton.GetCancellationTokenOnDestroy()).Forget();
        CheckDoubleClickButton(DoubleClickButton, this.GetCancellationTokenOnDestroy()).Forget();
        CheckCooldownClickButton(this.GetCancellationTokenOnDestroy()).Forget();
    }

    /// <summary>
    /// 球体连点
    /// </summary>
    private async UniTaskVoid CheckSphereClick(CancellationToken token)
    {
        //将按钮的点击转换为异步可迭代器
        var asyncEnumerable = SphereButton.OnClickAsAsyncEnumerable();
        //ForEachAsync处理每一次点击时的操作,index表示第几次点击,Take(3)表示只处理前三次点击
        await asyncEnumerable.Take(3).ForEachAsync((_, index) =>
        {
            if (token.IsCancellationRequested) return;
            if (index == 0)
            {
                //第一次点击,放大
                SphereTweenScale(2, SphereButton.transform.localScale.x, 20, token).Forget();
            }
            else if (index == 1)
            {
                //第二次点击,缩小
                SphereTweenScale(2, SphereButton.transform.localScale.x, 10, token).Forget();
            }
            else if (index == 2)
            {
                //第三次点击销毁
                GameObject.Destroy(SphereButton.gameObject);
            }
        }, token);
        
        //三次点击后,await完成,可以进行后面的逻辑
        Debug.LogError("done");
    }

    private async UniTaskVoid SphereTweenScale(float totalTime, float from, float to, CancellationToken token)
    {
        var trans = SphereButton.transform;
        float time = 0;
        while (time < totalTime)
        {
            time += Time.deltaTime;
            trans.localScale = (from + (time / totalTime) * (to - from)) * Vector3.one;
            await UniTask.Yield(PlayerLoopTiming.Update, token);
        }
    }

    /// <summary>
    /// 双击按钮
    /// </summary>
    private async UniTaskVoid CheckDoubleClickButton(Button button, CancellationToken token)
    {
        while (true)
        {
        	//将点击转换为异步的UniTask,然后等待第一次点击
            var clickAsync = button.OnClickAsync(token);
            await clickAsync;
            DoubleEventText.text = $"按钮被第一次点击";
            var secondClickAsync = button.OnClickAsync(token);
            //第二次点击和等待时间谁先到,WhenAny返回那个先执行
            int resultIndex = await UniTask.WhenAny(secondClickAsync, 
                UniTask.Delay(TimeSpan.FromSeconds(DoubleClickCheckTime), cancellationToken : token));
            DoubleEventText.text = resultIndex == 0 ? $"按钮被双击了" : $"超时,按钮算单次点击";
        }
    }
    
    /// <summary>
    /// 按钮冷却时间
    /// </summary>
    private async UniTaskVoid CheckCooldownClickButton(CancellationToken token)
    {
        var asyncEnumerable = CoolDownButton.OnClickAsAsyncEnumerable();
        await asyncEnumerable.ForEachAwaitAsync(async (_) =>
        {
            CoolDownEventText.text = "被点击了,冷却中……";
            //Delay过程中不会再响应点击操作
            await UniTask.Delay(TimeSpan.FromSeconds(CooldownTime), cancellationToken : token);
            CoolDownEventText.text = "冷却好了,可以点了……";
        }, cancellationToken: token);
    }
}

响应式属性

属性值变化时,监听的进度条,文本就会同步变化

public class AsyncReactivePropertySample: MonoBehaviour
{
    public int maxHp = 100;
    public float totalChangeTime = 1f;
    public Text ShowHpText;
    public Text StateText;
    public Text ChangeText;

    public Slider HpSlider;
    public Image HpBarImage;

    public Button HealButton;
    public Button HurtButton;
    
    private AsyncReactiveProperty<int> currentHp;
    private int maxHeal = 10;
    private int maxHurt = 10;
    private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    private CancellationTokenSource _linkedTokenSource;
    
    private void Start()
    {
        // 设置AsyncReactiveProperty
        currentHp = new AsyncReactiveProperty<int>(maxHp);
        HpSlider.maxValue = maxHp;
        HpSlider.value = maxHp;
        
        currentHp.Subscribe(OnHpChange);
        CheckHpChange(currentHp).Forget();
        CheckFirstLowHp(currentHp).Forget();
        
        currentHp.BindTo(ShowHpText);
        
        HealButton.onClick.AddListener(OnClickHeal);
        HurtButton.onClick.AddListener(OnClickHurt);
        
        _linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token, 
                this.GetCancellationTokenOnDestroy());
    }
    
    private void OnClickHeal()
    {
        ChangeHp(Random.Range(0, maxHeal));
    }
    
    private void OnClickHurt()
    {
        ChangeHp(-Random.Range(0, maxHurt));
    }

    private void ChangeHp(int deltaHp)
    {
        currentHp.Value = Mathf.Clamp(currentHp.Value + deltaHp, 0, maxHp);
    }
    
    /// <summary>
    /// currentHp变化时修改提示信息
    /// </summary>
    private async UniTaskVoid CheckHpChange(AsyncReactiveProperty<int> hp)
    {
        int hpValue = hp.Value;
        // WithoutCurrent 忽略初始值
        await hp.WithoutCurrent().ForEachAsync((_, index) =>
        {
            ChangeText.text = $"血量发生变化 第{index}次 变化{hp.Value - hpValue}";
            hpValue = hp.Value;
        }, this.GetCancellationTokenOnDestroy());
    }

    /// <summary>
    /// currentHp低于临界值,显示提示信息
    /// </summary>
    private async UniTaskVoid CheckFirstLowHp(AsyncReactiveProperty<int> hp)
    {
        await hp.FirstAsync((value) => value < maxHp * 0.4f, this.GetCancellationTokenOnDestroy());
        StateText.text = "首次血量低于界限,请注意!";
    }

    private async UniTaskVoid OnHpChange(int hp)
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource = new CancellationTokenSource();
        _linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token, 
                this.GetCancellationTokenOnDestroy());
        await SyncSlider(hp, _linkedTokenSource.Token);
    }

    /// <summary>
    /// 同步血条
    /// </summary>
    private async UniTask SyncSlider(int hp, CancellationToken token)
    {
        var sliderValue = HpSlider.value;
        float needTime = Mathf.Abs((sliderValue - hp) / maxHp * totalChangeTime);
        float useTime = 0;
        while (useTime < needTime)
        {
            useTime += Time.deltaTime;
            bool result = await UniTask.Yield(PlayerLoopTiming.Update, token)
                .SuppressCancellationThrow();
            if (result)
            {
                return;
            }

            var newValue = (sliderValue + (hp - sliderValue) * (useTime / needTime));
            SetNewValue(newValue);
        }
    }

    private void SetNewValue(float newValue)
    {
        if (!HpSlider) return;
        HpSlider.value = newValue;
        HpBarImage.color = HpSlider.value / maxHp < 0.4f ? Color.red : Color.white;
    }
}

创建自定义异步可迭代器

[Serializable]
public struct ControlParams
{
    [Header("旋转速度")] public float rotateSpeed;
    [Header("移动速度")] public float moveSpeed;
    [Header("开枪最小间隔")] public float fireInterval;
}

public class PlayerControl
{
    public UnityEvent OnFire;
    
    private Transform _playerRoot;
    private ControlParams _controlParams;
    private float _lastFireTime;
    
    public void Start()
    {
        StartCheckInput();
    }

    /// <summary>
    /// 通过MonoBehaviour将参数传进来
    /// </summary>
    public PlayerControl(Transform playerRoot, ControlParams controlParams)
    {
        _playerRoot = playerRoot;
        _controlParams = controlParams;
    }

    /// <summary>
    /// 启动输入检测
    /// </summary>
    private void StartCheckInput()
    {
        CheckPlayerInput().ForEachAsync((delta) =>
            {
                _playerRoot.position += delta.Item1;
                _playerRoot.forward = Quaternion.AngleAxis(delta.Item2, Vector3.up) * _playerRoot.forward;
                if (delta.Item3 - _lastFireTime > _controlParams.fireInterval)
                {
                    OnFire?.Invoke();
                    _lastFireTime = delta.Item3;
                }
            },
            _playerRoot.GetCancellationTokenOnDestroy()).Forget();
    }

    /// <summary>
    /// 创建自定义异步迭代器
    /// </summary>
    private IUniTaskAsyncEnumerable<(Vector3, float, float)> CheckPlayerInput()
    {
        return UniTaskAsyncEnumerable.Create<(Vector3, float, float)>(async (writer, token) =>
        {
            await UniTask.Yield();
            while (!token.IsCancellationRequested)
            {
                //写入每一次要发送的内容
                await writer.YieldAsync((GetInputMoveValue(), GetInputAxisValue(), GetIfFired()));
                await UniTask.Yield();
            }
        });
    }

    /// <summary>
    /// 范围玩家的移动
    /// </summary>
    private Vector3 GetInputMoveValue()
    {
        var horizontal = Input.GetAxis("Horizontal");
        var vertical = Input.GetAxis("Vertical");
        Vector3 move = (_playerRoot.forward * vertical + _playerRoot.right * horizontal) *
                       (_controlParams.moveSpeed * Time.deltaTime);
        return move;
    }

    /// <summary>
    /// 返回旋转,根据鼠标水平方向移动距离计算
    /// </summary>
    private float GetInputAxisValue()
    {
        if (!Input.GetMouseButton(1)) return default;
        var result = Input.GetAxis("Mouse X") * _controlParams.rotateSpeed;
        return Mathf.Clamp(result, -90, 90);
    }
    
    /// <summary>
    /// 点击鼠标左键的时间
    /// </summary>
    private float GetIfFired()
    {
        if (Input.GetMouseButtonUp(0))
        {
            return Time.time;
        }

        return -1;
    }
}

子弹相关逻辑整合

public class FireBulletSample : MonoBehaviour
{
    public Transform FirePoint;

    [SerializeField]
    private GameObject bulletTemplate;
    
    [Header("射速")]
    [SerializeField]
    private float flySpeed;
    
    [Header("自动回收时间")]
    [SerializeField]
    private float bulletAutoDestroyTime;

    [Header("命中效果")]
    [SerializeField]
    private GameObject hitEffect;

    public void Fire()
    {
        (UniTask.UnityAction(OnClickFire)).Invoke();
    }
    
    /// <summary>
    /// 开火,将子弹飞行,销毁,碰撞,创建特效等逻辑整合到一个方法中
    /// </summary>
    private async UniTaskVoid OnClickFire()
    {
        var bullet = Object.Instantiate(bulletTemplate);
        bullet.transform.position = FirePoint.position;
        bullet.transform.forward = FirePoint.forward;

        // 先飞出去,获取子弹本身的token来当作取消token
        var bulletToken = bullet.transform.GetCancellationTokenOnDestroy();
        FlyBullet(bullet.transform, flySpeed).Forget();

        //到达设定时间销毁
        var waitAutoDestroy = UniTask.Delay(TimeSpan.FromSeconds(bulletAutoDestroyTime), cancellationToken : bulletToken);
        
        var source = new UniTaskCompletionSource<Collision>();
        // 注意可以使用where take(1)或FirstAsync来简化操作
        bullet.transform.GetAsyncCollisionEnterTrigger().ForEachAsync((collision) =>
        {
            if (collision.collider.CompareTag("Target"))
            {
                source.TrySetResult(collision);
            }
        }, cancellationToken: bulletToken);
        
        // 等待时间到,或者碰到了任意物体
        int resultIndex = await UniTask.WhenAny(waitAutoDestroy, source.Task);
        if (resultIndex == 1)
        {
            var collision = source.GetResult(0);
            Collider getCollider = collision.collider;
            //在子弹击中位置创建特效
            var go = Object.Instantiate(hitEffect, bullet.transform.position, Quaternion.identity);
            Object.Destroy(go, 4f);
        }
        Object.Destroy(bullet);
    }

    /// <summary>
    /// 子弹飞行
    /// </summary>
    private async UniTaskVoid FlyBullet(Transform bulletTransform, float speed)
    {
        float startTime = Time.time;
        Vector3 startPosition = bulletTransform.position;
        while (true)
        {
            await UniTask.Yield(PlayerLoopTiming.Update, bulletTransform.GetCancellationTokenOnDestroy());
            bulletTransform.position = startPosition + (speed * (Time.time - startTime)) * bulletTransform.forward;
        }
    }
}

发送Post请求

private async UniTaskVoid UnityWebRequestPost(string url, string jsonStr, Action<byte[]> callBack = null)
{
    using (var request = UnityWebRequest.Post(url, "POST"))
    {
        var body = new System.Text.UTF8Encoding().GetBytes(jsonStr);
        request.uploadHandler = new UploadHandlerRaw(body);
        request.downloadHandler = new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");

        await request.SendWebRequest().ToUniTask();

        if (request.result == UnityWebRequest.Result.Success)
        {
            callBack?.Invoke(request.downloadHandler.data);
        }
        else
        {
            Debug.LogError($"POST request failed: {request.error}");
        }
    }
}
  • 10
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vim 通常被称为“程序员的编辑器”,是一种老式的文本编辑器,主要涉及效率,灵活性和定制性。如果您是 Vim 的爱好者,并且当前正在寻找更适合编程方面的笔记应用程序,那么您应该考虑使用 VNote。 VNote 是一个受 Vim 启发的开源笔记记录实用程序,还具有 Markdown 编辑功能。当然,您可以像具有便笺管理功能的可高度配置的 Markdown 编辑器一样轻松地查看它,它不会让人失望。 开源免费 Markdown 笔记工具 VNote 中文版开源免费 Markdown 笔记工具 VNote 中文版 开源和自由 遵从 MIT 开源协议 由 Qt 和 C++ 提供强劲性能 支持主流操作系 统Linux,Windows,和 macOS 直观的笔记管理 只有纯文本,不依赖数据 独立的笔记本 ,无限层级的文件夹 ,开放的笔记 支持标签和附件 浏览和编辑外部文件 数据自主掌控 所有文件都在本地存储 一个笔记本对应一个目录 自由选择第三方同步服务,实现多端无缝工作 专注 没有双边实时预览 通过阅读和编辑模式以专注于笔记 舒适的 Markdown 体验 最小化 Markdown 阅读和书写的鸿沟 语法高亮和原地预览 高效的图片管理 交互式的大纲 UML 图表,流程图和数学公式 高度可定制 主题和样式 快捷键和软件行为 程序员为程序员优化打造 Vim 模式和 Vim 式导航 编辑器行号 多标签页 窗口分割 模糊查找和跳转 贴心顺手的快捷键 VNote 无疑是一个非常有趣的应用程序,如果有机会,它可能会证明是大多数 Vim 程序员正在寻找的记笔记应用程序。有关该项目的更多信息以及详细的文档部分,请参阅应用程序的官方 GitHub 存储

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值