赞
踩
内容来源:up主游戏石匠,仅作笔记,推荐关注该up主。
UniTask是Github上的开源库,为Unity提供一个高性能异步方案,可以代替协程实现异步操作,中文文档
优点:
通过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();
}
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
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 "超时"; } }
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()); } } }
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操作可以很简单地组合起来,还支持时间操作。
球体三次点击, 执行不同操作
按钮双击处理
点击按钮后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; } } }
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}"); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。