Unity - DOTween库的使用问题
这几天做了个小游戏,给家里人玩玩,其中很多东西需要自己去弄:程序 : CSharp、ShaderLab动画 : DOTween(没去用 Unity 的 Animation 或是 Timeline)美术 : 配色,部分纹理UI : 使用UGUI特效 : Unity 粒子系统音效 : 自己网上找免费资源模型 Unity 内置 Cube,Sphere,Cylinder 足够了,要是对外发布,可能还要自己
文章目录
这几天做了个小游戏,给家里人玩玩,其中很多东西需要自己去弄:
- 程序 : CSharp、ShaderLab
- 动画 : DOTween(没去用 Unity 的 Animation 或是 Timeline)
- 美术 : 配色,部分纹理
- UI : 使用 UGUI(不熟悉,都是临时查阅资料就能用)
- AI : 因为游戏比较简单,AI 也没用行为树,直接配置控制甩武器的速度参数即可即可
- 特效 : Unity 粒子系统(不熟悉,都是临时查阅资料就能用)
- 音效 : 自己网上找免费资源
- 模型 : Unity 内置 Cube,Sphere,Cylinder 足够了,要是对外发布,可能还要自己学习建模 Blender(有计划,现在还没开始,惭愧)
使用 DOTween 来实现的小游戏部分动画内容
封面
封面继续
菜单项
进入对战场景
这里就不演示对战内容了(也特效,AI,镜头效果,结算,等)
还可以实现一些出场动画,等,其实这个小游戏项目可以完善的内容挺多的
最后是战斗中返回开始界面
总体来说,DOTween 虽然用起来不够顺手,但是也够用,相比,iTween,LeanTween 都好用一些。
部分动画我本想用 Unity Animation 来实现的,但是反而觉得可控制不太友好,所以就使用 Tween 库来封装一些类来处理,也是比较灵活的,如果是公司项目要给策划用的编辑器就不一样了,项目的接口设计会完全不一样。
这个小游戏项目从无到有,完全没用到以前积累的东西,也是蛋疼。
也是对 Unity 不够熟悉。
代码写得非常的简单易懂,基本没封装什么东西,就封装了一个 Loading 场景的统一处理,其他都是乱七八糟的代码。。。-_-!!!
后面有空需要去参考一些 Unity 现成的一些框架,参考他们的设计,再做适合自己项目的封装。
不然以后使用起来制作就相当麻烦了。
在使用一些动画的时候,找了一些 Tween 缓存库。
于是找了下面三个(最后我选择使用了:DOTween)。
unity tween libs
花了一个上午,看完了三个 tween 库的介绍与文档,总结:
-
Do Tween
- Unity 中的 Tween 引擎,说是引擎也不为过,因为功能相当丰富,而且很多平台多有封装库,如果想要在多个平台使用 tween 库,这是不二之选。
- Do Tween 官方文档 - 文档做得相当良心,谁看谁知道。
- Dotween常用方法详解 - 中文的话这位博主总结的挺不错。
-
LeanTween
- docment : http://dentedpixel.com/LeanTweenDocumentation/classes/LeanTween.html
- 风格与以前使用 AS3 的 Greensock 比较像
-
iTween
- 个人感觉是制作最不用心的,作为学习的话,也是可以用,因为库相当精简,回调函数我看过竟然限制于 GameObject 内的组件的方法,用 SendMessage 来广播的极地效率的方式。
- iTween assetstore
- Unity3D关于iTween知识详解和接口总结
DOTween
个人选择使用的是:DOTween,因为功能还是比较强大的。(说真的,这些所有的 Unity Tween 库使用起来都没有以前使用 AS3 的 greensock 那么顺手)
DOTween 使用的一些注意问题
协程中的 WaitForCompletion
如果 tween_show_loading
的动画足够短,并且直接 在 tween_show_loading
之前,还有一个比较长时间的动画,那么直接 yield return tween_show_loading.WaitForCompletion
可能会报一些警告:This Tween has been killed and is now invalid
代码如下:
IEnumerator _StartHideLoading()
{
_KillRollingTrackerTween();
tween_rolling_tracker = tracker.DOLocalMove(target_roll_tracker_move_in_pos, 1.0f)
.SetEase(Ease.Linear);
var seq = loadingBar.Hide();
if (tween_rolling_tracker.active) yield return tween_rolling_tracker.WaitForCompletion();
Debug.Log("after yield return tween_rolling_tracker.WaitForCompletion();");
//if (seq.active) yield return seq.WaitForCompletion();
yield return seq.WaitForCompletion();
Debug.Log("after yield return seq.WaitForCompletion();");
_KillHideLoadingSeq();
tween_hide_loading = contentCG.DOFade(0.0f, 0.5f);
yield return tween_hide_loading.WaitForCompletion();
Debug.Log("after yield return seq_hide_loading.WaitForCompletion();");
loadingRoot.SetActive(false);
}
解决:如下面 判断一个 tween 是否完成的方式,在前面多加一句:if (xxx.active)
就可以了
判断一个 tween 是否完成
- 第一:
bool Tween.IsComplete() ;
但要确保你的 tween 不是 autoKill,默认所有的 tween 都是 autoKill 的,除非显示设置AutoKill(false);
- 第二:
Tween.active
如果你的 tween 是 autoKill 的,那么可以使用tween.active
来判断
下面的代码演示就是一个 autoKill 的tween 在判断是否完成了或有效的方法
// 如果还是有效的、激活的,或是未那么可以使用 WaitForCompletion 来等待完成
if (tween_show_loading.active) yield return tween_show_loading.WaitForCompletion();
kill 一个 tween
有还几种方式
tween.Kill();
DOTween.Kill(tween.id);
DOTween.Kill(tween.intId);
DOTween.Kill(tween.stringId);
但是最好还是使用:一个独立的 string 来保存起来,再 Kill
(为何要用 Kill,因为有些动画需要重复频率操作很多的特别需要各种 Kill,以前使用 AS3 就经常需要这么处理,不然动画会有冲突)
伪代码如下:
tween 同一时刻只有一个,可以这么写:
private void ShowXXX()
{
DOTween.Kill($"{GetType().Name}"); // 如果你嫌弃效率问题,可以保存起来
xxx.DOXXX().SetXXX().SetXXX().SetID($"{GetType().Name}");
}
如果 tween 对象可以有多个,那么需要其他的写法,或是使用 Sequence 来处理。
如果 Sequence 处理起来吃力,就使用 Unity 的 Coroutine + Sequence 来处理即可
可能有同学问,为何不使用 int 的方式,保存一个 int 比起一个 string 效率好多了,内存也少
但是我也试过了,会有问题,具体我也没去看 DOTween 开源的逻辑了,后面如果需要,还需要看它的 Git 代码,还考虑需要亲手去修改代码。
使用 int 的方式的 伪代码,如下:
private static int tween_id_counter;
private int tween_id = -1;
private void ShowXXX()
{
if (tween_id != -1) {
DOTween.Kill(tween_id);
}
tweenId = ++tween_id_counter;
xxx.DOXXX().SetXXX().SetXXX().SetId(tweenId).OnComplete(()=>{tweenId = -1});
}
上面的 tween 同一时刻只能有一个,如果上次的没有运行完,那么就需要先将之前未完成的先 kill 掉。
我的代码曾经这样使用过,发现动画有异常。但是如果使用上面 string 的方式就没有问题。暂时没去查看代码(使用的是 .dll 的 DOTween)
Pause 与 Resume 不对称
有 Pause 但 “没有” Resume。注意打双引号的 没有
因为它的 Resume 还是使用 Play 来处理。
如,下伪代码:
private Tween tween;
private void ToggleTween()
{
if (tween == null)
{
tween = go_transform.DOMoveX(
()=>the_x,value=>the_x=value,100,1.0f)
.SetLoops(-1, LoopType.Restart));
}
else
{
// resume
if (tween.isPlaying()) tween.Play().SetLoops(-1, LoopType.Restart); // 注意参数要重新设置
// pause
else tween.Pause();
}
}
其中注意我的注释中的一句, resume 处理需要恢复参数,所以 DOTween 的 Resume 是用会 Play() 来处理的(我习惯了很多 Tween 库都是有 Resume 的,一下当时没找到,试了一下 Play,果然还真是,只不过,后来发现不会重复播放了,所以我有重新设置会参数,注意 Pause 后的 tween 再次 play 会从当前的一些数据继续播放,但是就是前面说的,一些状态参数又需要重新设置了,这些设置不应该的,按道理是可以做到不到重复设置状态参数的,但暂时没有兴趣去看源码)
OnComplete 在 Sequence 与 Tween 中使用
在 Tween 中使用如下伪代码:
trans.DOXXX().OnComplete(()=>{Debug.Log("Test1");});
一般不出什么意外 "Test1"
的输出还是比较确定的
但如果使用 Sequence:
var seq = DOTween.Sequence();
seq.Append(trans.DOXXX(....));
seq.Append(trans.DOXXX(....));
seq.AppendInterval(1.0f);
seq.OnComplete(()=>{Debug.Log("Test2");});
基本上补回输出 "Test2"
但如果 Sequence 完成的回调使用 AppendCallback
来替代就比较稳定
var seq = DOTween.Sequence();
seq.Append(trans.DOXXX(....));
seq.Append(trans.DOXXX(....));
seq.AppendInterval(1.0f);
seq.AppendCallback(()=>{Debug.Log("Test2");}); // 注意此句使用 AppendCallback
这样就可以输出 "Test2"
而我看了一下:Sequence 就一个空类,但是继承与 Tween 的,因为作者使用 public static RET_TYPE Extension_Func(this XXX xxx, ArgType1 .., ArgTypeN...);
的方式来扩展 Sequence 与 Tween 的方法来处理的
所以这些 OnComplete
的差异也是不应该的
更多推荐
所有评论(0)