内容来源:up主游戏石匠,仅作笔记,推荐关注该up主。
UniTask是Github上的开源库,为Unity提供一个高性能异步方案,可以代替协程实现异步操作,中文文档
优点:
- 不需要依赖于MonoBehaviour,性能比协程好
- 可以进行 try catch,取消操作
- 默认使用主线程,与Unity协同,而C#得Task是在另一个线程中运行
- 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,不然打包可能报错
异步加载资源
加载文本
usingCysharp.Threading.Tasks;usingUnityEngine;/// <summary>/// 不需要继承自MonoBehaviour/// </summary>publicclassUniTaskLoadAsync{/// <summary>/// 返回UniTask<Object>类型,这种类型事为Unity定制的,作为替代原生Task<T>的轻量级方案/// </summary>publicasyncUniTask<Object>LoadAsync<T>(string path)whereT:Object{var asyncOperation = Resources.LoadAsync<T>(path);//这个await会将ResourceRequest(class)封装到UniTask的ResourceRequestAwaiter(struct)中returnawait asyncOperation;}}
publicclassUniTaskTest:MonoBehaviour{/// <summary>/// 加载文本/// </summary>privateasyncvoidLoadTextAsync(){UniTaskLoadAsync loader =newUniTaskLoadAsync();//Test是Resources目录下的文本文件var textObj =await loader.LoadAsync<TextAsset>("Test");string str =((TextAsset)textObj).text;
Debug.LogError(str);}}
加载场景
/// <summary>/// 加载场景过程中显示进度值/// </summary>privateasyncvoidLoadSceneAsync(){var progress = Progress.Create<float>(x =>{//这里可以修改界面上的进度条
Debug.Log("进度值:"+ x);});//ToUniTask创建一个进度相关的回调await SceneManager.LoadSceneAsync("Scenes/TestScene1").ToUniTask(progress);}
加载网络图片
publicImage Image;/// <summary>/// 加载网络图片/// </summary>privateasyncvoidLoadWebPictureAsync(){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,newRect(Vector2.zero,newVector2(texture.width, texture.height)),newVector2(0.5f,0.5f));
Image.sprite = sprite;
Image.SetNativeSize();}
Delay操作
publicasyncvoidDelayTest(){//性能最好,可以设置等待时机,PlayerLoopTiming 对应Unity中playerloop的更新时机await UniTask.Yield(PlayerLoopTiming.LastUpdate);//等待1秒,类似 yield return new WaitForSeconds(1),可以设置 ignoreTimeScaleawait UniTask.Delay(TimeSpan.FromSeconds(1),false);//执行在下一帧的update之后,类似 yield return null,和 UniTask.Yield() 效果一样await UniTask.NextFrame();//这一帧的最后,类似 yield return new WaitForEndOfFrame(),this是一个MonoBehaviourawait 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
usingCysharp.Threading.Tasks;usingTMPro;usingUnityEngine;usingUnityEngine.UI;publicclassUniTaskWhen:MonoBehaviour{publicButton FirstButton;publicButton SecondButton;publicTextMeshProUGUI Text;privatebool firstClick =false;privatebool secondClick =false;privatevoidStart(){//这里用两个按钮的点击模拟两种操作
FirstButton.onClick.AddListener(OnClickFirst);
SecondButton.onClick.AddListener(OnClickSecond);// WhenAllTest();WhenAnyTest();}privatevoidOnClickFirst(){
firstClick =true;}privatevoidOnClickSecond(){
secondClick =true;}/// <summary>/// 当两个按钮都点击了才执行后面操作/// </summary>privateasyncvoidWhenAllTest(){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>privateasyncvoidWhenAnyTest(){var firstOperation = UniTask.WaitUntil(()=> firstClick);var secondOperation = UniTask.WaitUntil(()=> secondClick);await UniTask.WhenAny(firstOperation, secondOperation);
Text.text = firstClick ?"first按钮点击了":"second按钮点击了";}}
取消操作
publicclassUniTaskCancel:MonoBehaviour{publicTransform FirstTransform;publicTransform SecondTransform;publicButton FirstRunButton;publicButton SecondRunButton;publicButton FirstCancelButton;publicButton SecondCancelButton;publicTextMeshProUGUI Text;//做取消时需要创建这个对象privateCancellationTokenSource _firstCancelToken;privateCancellationTokenSource _secondCancelToken;privateCancellationTokenSource _linkedCancelToken;privatevoidStart(){
FirstRunButton.onClick.AddListener(OnClickFirstMove);
SecondRunButton.onClick.AddListener(OnClickSecondMove);
FirstCancelButton.onClick.AddListener(OnClickFirstCancel);
SecondCancelButton.onClick.AddListener(OnClickSecondCancel);
_firstCancelToken =newCancellationTokenSource();// 注意这里可以直接先行设置多久以后取消// _firstCancelToken = new CancellationTokenSource(TimeSpan.FromSeconds(1.5f));
_secondCancelToken =newCancellationTokenSource();//用两个token创建新的linkedCancelToken,当其中一个取消后,linkedCancelToken也会取消,
_linkedCancelToken =
CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);}/// <summary>/// 移动first,使用try catch监听取消信号/// </summary>privateasyncvoidOnClickFirstMove(){try{awaitMoveTransform(FirstTransform, _firstCancelToken.Token);}catch(OperationCanceledException e){//发出取消信号,这里会抛异常
Text.text ="first已经被取消";}}/// <summary>/// 移动second,忽略异常的抛出,返回一个值元组,这种方式性能更好/// </summary>privateasyncvoidOnClickSecondMove(){//第一个参数表示是否取消,第二个参数时await的返回值var(cancelled, _)=awaitMoveTransform(SecondTransform, _secondCancelToken.Token).SuppressCancellationThrow();// 使用LinkedToken,当first取消后,second也会取消// var (cancelled, _) = await MoveTransform(SecondTransform, _linkedCancelToken.Token).SuppressCancellationThrow();if(cancelled){
Text.text ="second已经被取消";}}privateasyncUniTask<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;}return0;}/// <summary>/// 取消first移动,Token使用后就不能再次使用,得创建新的Token/// </summary>privatevoidOnClickFirstCancel(){
_firstCancelToken.Cancel();
_firstCancelToken.Dispose();
_firstCancelToken =newCancellationTokenSource();
_linkedCancelToken =
CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);}privatevoidOnClickSecondCancel(){
_secondCancelToken.Cancel();
_secondCancelToken.Dispose();
_secondCancelToken =newCancellationTokenSource();
_linkedCancelToken =
CancellationTokenSource.CreateLinkedTokenSource(_firstCancelToken.Token, _secondCancelToken.Token);}privatevoidOnDestroy(){
_firstCancelToken.Dispose();
_secondCancelToken.Dispose();
_linkedCancelToken.Dispose();}}
超时操作
publicclassTimeoutTest:MonoBehaviour{publicButton TestButton;privatevoidStart(){//使用UniTask.UnityAction包装了OnClickTest
TestButton.onClick.AddListener(UniTask.UnityAction(OnClickTest));}privateasyncUniTaskVoidOnClickTest(){var res =awaitGetRequest("https://www.baidu.com/",2f);
Debug.LogError(res);}privateasyncUniTask<string>GetRequest(string url,float timeout){//这个token会在timeout之后发出取消信号var cts =newCancellationTokenSource();
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
publicclassForgetSample:MonoBehaviour{publicButton StartButton;publicGameObject Target;publicconstfloat G =9.8f;privatevoidStart(){
StartButton.onClick.AddListener(OnClickStart);}/// <summary>/// 同步方法中调用异步方法/// </summary>privatevoidOnClickStart(){//不需要等待时候就调用ForgetFallTarget(Target.transform).Forget();}/// <summary>/// 使目标掉落,async UniTaskVoid是async UniTask的轻量级版本/// </summary>privateasyncUniTaskVoidFallTarget(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运行中执行回调
publicclassCallbackSample:MonoBehaviour{publicButton CallbackButton;publicGameObject Target;publicconstfloat G =9.8f;privatevoidStart(){
CallbackButton.onClick.AddListener(UniTask.UnityAction(OnClickCallback));}privateasyncUniTaskVoidOnClickCallback(){float time = Time.time;UniTaskCompletionSource source =newUniTaskCompletionSource();FallTarget(Target.transform, source).Forget();await source.Task;// UniTaskCompletionSource产生的UnitTask是可以复用的
Debug.Log($"耗时 {Time.time - time}秒");}/// <summary>/// UniTask运行中执行回调/// UniTaskCompletionSource是对UniTask和CancellationToken的封装/// </summary>privateasyncUniTaskFallTarget(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());}}}
切换到线程
publicclassThreadSample:MonoBehaviour{publicButton StandardRun;publicButton YieldRun;privatevoidStart(){
StandardRun.onClick.AddListener(UniTask.UnityAction(OnClickStandardRun));
YieldRun.onClick.AddListener(UniTask.UnityAction(OnClickYieldRun));}/// <summary>/// 线程中计算/// </summary>privateasyncUniTaskVoidOnClickStandardRun(){int result =0;//切换到其他线程await UniTask.RunOnThreadPool(()=>{ result =1;});//切换回主线程await UniTask.SwitchToMainThread();
Debug.LogError($"计算结束,当前结果是{result}");}/// <summary>/// 线程中读取文件/// </summary>privateasyncUniTaskVoidOnClickYieldRun(){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时间
usingSystem;usingSystem.Threading;usingCysharp.Threading.Tasks;usingCysharp.Threading.Tasks.Linq;usingUnityEngine;usingUnityEngine.UI;publicclassUIEventsSample:MonoBehaviour{publicButton SphereButton;publicButton DoubleClickButton;publicButton CoolDownButton;publicText DoubleEventText;publicText CoolDownEventText;publicfloat DoubleClickCheckTime =0.5f;publicfloat CooldownTime =3f;voidStart(){CheckSphereClick(SphereButton.GetCancellationTokenOnDestroy()).Forget();CheckDoubleClickButton(DoubleClickButton,this.GetCancellationTokenOnDestroy()).Forget();CheckCooldownClickButton(this.GetCancellationTokenOnDestroy()).Forget();}/// <summary>/// 球体连点/// </summary>privateasyncUniTaskVoidCheckSphereClick(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();}elseif(index ==1){//第二次点击,缩小SphereTweenScale(2, SphereButton.transform.localScale.x,10, token).Forget();}elseif(index ==2){//第三次点击销毁
GameObject.Destroy(SphereButton.gameObject);}}, token);//三次点击后,await完成,可以进行后面的逻辑
Debug.LogError("done");}privateasyncUniTaskVoidSphereTweenScale(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>privateasyncUniTaskVoidCheckDoubleClickButton(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>privateasyncUniTaskVoidCheckCooldownClickButton(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);}}
响应式属性
属性值变化时,监听的进度条,文本就会同步变化
publicclassAsyncReactivePropertySample:MonoBehaviour{publicint maxHp =100;publicfloat totalChangeTime =1f;publicText ShowHpText;publicText StateText;publicText ChangeText;publicSlider HpSlider;publicImage HpBarImage;publicButton HealButton;publicButton HurtButton;privateAsyncReactiveProperty<int> currentHp;privateint maxHeal =10;privateint maxHurt =10;privateCancellationTokenSource _cancellationTokenSource =newCancellationTokenSource();privateCancellationTokenSource _linkedTokenSource;privatevoidStart(){// 设置AsyncReactiveProperty
currentHp =newAsyncReactiveProperty<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());}privatevoidOnClickHeal(){ChangeHp(Random.Range(0, maxHeal));}privatevoidOnClickHurt(){ChangeHp(-Random.Range(0, maxHurt));}privatevoidChangeHp(int deltaHp){
currentHp.Value = Mathf.Clamp(currentHp.Value + deltaHp,0, maxHp);}/// <summary>/// currentHp变化时修改提示信息/// </summary>privateasyncUniTaskVoidCheckHpChange(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>privateasyncUniTaskVoidCheckFirstLowHp(AsyncReactiveProperty<int> hp){await hp.FirstAsync((value)=>value< maxHp *0.4f,this.GetCancellationTokenOnDestroy());
StateText.text ="首次血量低于界限,请注意!";}privateasyncUniTaskVoidOnHpChange(int hp){
_cancellationTokenSource.Cancel();
_cancellationTokenSource =newCancellationTokenSource();
_linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token,this.GetCancellationTokenOnDestroy());awaitSyncSlider(hp, _linkedTokenSource.Token);}/// <summary>/// 同步血条/// </summary>privateasyncUniTaskSyncSlider(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);}}privatevoidSetNewValue(float newValue){if(!HpSlider)return;
HpSlider.value= newValue;
HpBarImage.color = HpSlider.value/ maxHp <0.4f? Color.red : Color.white;}}
创建自定义异步可迭代器
[Serializable]publicstructControlParams{[Header("旋转速度")]publicfloat rotateSpeed;[Header("移动速度")]publicfloat moveSpeed;[Header("开枪最小间隔")]publicfloat fireInterval;}publicclassPlayerControl{publicUnityEvent OnFire;privateTransform _playerRoot;privateControlParams _controlParams;privatefloat _lastFireTime;publicvoidStart(){StartCheckInput();}/// <summary>/// 通过MonoBehaviour将参数传进来/// </summary>publicPlayerControl(Transform playerRoot,ControlParams controlParams){
_playerRoot = playerRoot;
_controlParams = controlParams;}/// <summary>/// 启动输入检测/// </summary>privatevoidStartCheckInput(){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>privateIUniTaskAsyncEnumerable<(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>privateVector3GetInputMoveValue(){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>privatefloatGetInputAxisValue(){if(!Input.GetMouseButton(1))returndefault;var result = Input.GetAxis("Mouse X")* _controlParams.rotateSpeed;return Mathf.Clamp(result,-90,90);}/// <summary>/// 点击鼠标左键的时间/// </summary>privatefloatGetIfFired(){if(Input.GetMouseButtonUp(0)){return Time.time;}return-1;}}
子弹相关逻辑整合
publicclassFireBulletSample:MonoBehaviour{publicTransform FirePoint;[SerializeField]privateGameObject bulletTemplate;[Header("射速")][SerializeField]privatefloat flySpeed;[Header("自动回收时间")][SerializeField]privatefloat bulletAutoDestroyTime;[Header("命中效果")][SerializeField]privateGameObject hitEffect;publicvoidFire(){(UniTask.UnityAction(OnClickFire)).Invoke();}/// <summary>/// 开火,将子弹飞行,销毁,碰撞,创建特效等逻辑整合到一个方法中/// </summary>privateasyncUniTaskVoidOnClickFire(){var bullet = Object.Instantiate(bulletTemplate);
bullet.transform.position = FirePoint.position;
bullet.transform.forward = FirePoint.forward;// 先飞出去,获取子弹本身的token来当作取消tokenvar bulletToken = bullet.transform.GetCancellationTokenOnDestroy();FlyBullet(bullet.transform, flySpeed).Forget();//到达设定时间销毁var waitAutoDestroy = UniTask.Delay(TimeSpan.FromSeconds(bulletAutoDestroyTime),cancellationToken: bulletToken);var source =newUniTaskCompletionSource<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>privateasyncUniTaskVoidFlyBullet(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;}}}
版权归原作者 北海6516 所有, 如有侵权,请联系我们删除。