ゲーム開発ではいろいろなアニメーションが必要になる。
- キャラクターモーション
- バトルエフェクト
- UIアニメーション
基本的にはデザイナーさんに作ってもらうけど、ちょっとしたアニメーションはスクリプトで組んでしまうことも多い。
そういう時に便利なのがDOTween!
目次
特徴
ドキュメントが丁寧
http://dotween.demigiant.com/documentation.php
パフォーマンスに気を使ってる
似たようなアニメーションはiTweenでも行えるが、DOTweenの方がパフォーマンスが良い。
パフォーマンス比較
スクリプトからのアクセスが容易
基本、なんらかのクラスの拡張メソッドで定義されている。
transform.DOMove(Vector3.one, 1);
image.DOColor(Color.red, 1);
また、戻り値にはTweener
クラスが返されるので、以下のように設定を繋げて書ける。
transform.DOMove(Vector3.one, 1).SetDelay(1).SetEase(Ease.Linear).SetLoops(-1, LoopType.Yoyo);
↑transform
を1秒待って(Delay)から直線的な動き(Linear)でヨーヨーのようなループ運動(Loop)させる。
Tweenのチェインが可能
例えば「移動してから」→「大きくなって」→「回転させる」というアニメーションを組みたい場合、
transform.DOMove(Vector3.one, 1);
transform.DOScale(2, 1);
transform.DORotate(new Vector3(0, 0, 180), 1);
とすると全部のTweenが同時に実行されてしまう。一連のTweenを繋げて(チェインして)行いたい場合、
var tweener1 = transform.DOMove(Vector3.one, 1);
var tweener2 = transform.DOScale(2, 1);
var tweener3 = transform.DORotate(new Vector3(0, 0, 180), 1);
DOTween.Sequence().Append(tweener1).Append(tweener2).Append(tweener3);
という風にSequence
に登録してやれば可能。これが本当に便利!
動き方の種類が豊富
Easeで動き方を変えられるが、種類が豊富(加速・減速させたり、バウンドさせたり)。
DOTweenのイージング一覧を世界一詳しく&分かりやすく説明する
[Unity]DoTweenのEaseいろいろあってよくわかんない…な人へ[DoTween]
DOTweenで作成したモーション17個を含むプロジェクトを公開
無料版もあってオープンソース
内部実装を見ると勉強になる(キャッシュの仕組みとか)。
https://github.com/Demigiant/dotween
License
読み方
「ドゥーツウィーン」ではなく「ドッツウィーン」(ドットで繋げていくTweenだから?)。
使い方
自分がよく使うものをまとめた。
Global settings
DOTween
クラスのstatic変数にグローバル設定がある。
// デフォルトのEaseをLinearに変更
DOTween.defaultEaseType = Ease.Linear;
ほとんどはそのままでいいが、デフォルトのEaseは直線的になってた方がいいので変更。
Tools > Demigiant > DOTween Utility Panel > Preferences
からも変更可能(こっちの方がプログラムを書かなくていいので良い)。
Tween
基本の形
// アニメーション後、次の処理へ
transform.DOMove(Vector3.one, 1).SetDelay(1).SetEase(Ease.OutQuad).OnComplete(() =>
{
// 次の処理
});
// 相対移動(endValueは絶対座標。現在位置からの移動量を指定したい場合はRelativeをtrueに)
transform.position = new Vector3(1, 0, 0);
transform.DOMoveX(endValue: 1, duration: 1).SetRelative(true);
// ループアニメーション
transform.DOScale(2, 1).SetLoops(-1, LoopType.Yoyo);
// 再生中か
var tweener = transform.DOMoveX(1, 1);
if(tweener.IsPlaying()) Debug.Log("再生中");
// 停止
tweener.Kill();
移動
// transform.position
transform.DOMove(Vector3.one, 1);
transform.DOMoveX(1, 1);
transform.DOMoveY(1, 1);
transform.DOMoveZ(1, 1);
// transform.localPosition
transform.DOLocalMove(Vector3.one, 1);
transform.DOLocalMoveX(1, 1);
transform.DOLocalMoveY(1, 1);
transform.DOLocalMoveZ(1, 1);
// rectTransform.anchoredPosition
rectTransform.DOAnchorPos(Vector2.one, 1);
rectTransform.DOAnchorPosX(1, 1);
rectTransform.DOAnchorPosY(1, 1);
スケール
// transform.localScale
transform.DOScale(new Vector3(2, 2, 2), 1);
transform.DOScale(2, 1);
transform.DOScaleX(2, 1);
transform.DOScaleY(2, 1);
transform.DOScaleZ(2, 1);
// rectTransform.sizeDelta
rectTransform.DOSizeDelta(Vector2.one, 1);
回転
// transform.rotation
transform.DORotate(new Vector3(0, 0, 180), 1, RotateMode.Fast);
transform.DORotateQuaternion(Quaternion.Euler(0, 0, 180), 1);
// transform.localRotation
transform.DOLocalRotate(new Vector3(0, 0, 180), 1, RotateMode.Fast);
transform.DOLocalRotateQuaternion(Quaternion.Euler(0, 0, 180), 1);
色/フェイド
// material.color
material.DOColor(Color.red, 1);
material.DOFade(0, 1);
// spriteRenderer.color
spriteRenderer.DOColor(Color.red, 1);
spriteRenderer.DOFade(0, 1);
// image.color
image.DOColor(Color.red, 1);
image.DOFade(0, 1);
// text.color
text.DOColor(Color.red, 1);
text.DOFade(0, 1);
面白い動き
// シェイク(一定時間のランダムな動き)
var duration = 1f; // 時間
var strength = 1f; // 力
var vibrato = 10; // 揺れ度合い
var randomness = 90f; // 揺れのランダム度合い(0で一定方向のみの揺れになる)
var snapping = false; // 値を整数に変換するか
var fadeOut = true; // 揺れが終わりに向かうにつれ段々小さくなっていくか(falseだとピタッと止まる)
transform.DOShakePosition(duration, strength, vibrato, randomness, snapping, fadeOut);
transform.DOShakeScale(duration, strength, vibrato, randomness, fadeOut);
transform.DOShakeRotation(duration, strength, vibrato, randomness, fadeOut);
// パンチ(ボクシングでジャブをもらった時のような動き)
var punch = Vector3.one; // 力
var duration = 1f; // 時間
var vibrato = 10; // 揺れ度合い
var elasticity = 1f; // 弾力
var snapping = false; // 値を整数に変換するか
transform.DOPunchPosition(punch, duration, vibrato, elasticity, snapping);
transform.DOPunchScale(punch, duration, vibrato, elasticity);
transform.DOPunchRotation(punch, duration, vibrato, elasticity);
// ジャンプ(You can fly!!)
var endValue = new Vector3(500, 0); // 最終地点
var jumpPower = 300; // 力
var numJumps = 2; // 最終地点までにジャンプする回数
var duration = 2; // 時間
var snapping = false; // 値を整数に変換するか
transform.DOJump(endValue, jumpPower, numJumps, duration, snapping);
transform.DOLocalJump(endValue, jumpPower, numJumps, duration, snapping);
Sequence
// Append(順番に処理)
// 1 → 2 → 3 → 4
var sq1 = DOTween.Sequence()
.Append(transform.DOMoveX(1, 1)) // 1
.AppendInterval(0.5f) // 2
.Append(transform.DOMoveY(1, 1)) // 3
.AppendCallback(() => Debug.Log("Complete")); // 4
// Insert(途中に加える。並列処理)
// 567
var sq2 = DOTween.Sequence()
.Insert(0, transform.DOMoveX(1, 1)) // 5
.Insert(1.5f, transform.DOMoveY(1, 1)) // 6
.InsertCallback(2.5f, () => Debug.Log("Complete")); // 7
// Prepend(先頭に加える。優先処理)
// Join(直前の処理に繋げる)
// 89 → 1 → 2 → 3 → 4
if(isTutorial)
{
// チュートリアルでは最初に特別アニメーションを再生
sq1.Prepend(transform.DOScale(2, 1)) // 8
.Join(transform.DORotate(new Vector3(0, 0, 180), 1)); // 9
}
// Sequence自体も登録可能!
// 89 → 1 → 2 → 3 → 4 → 567
DOTween.Sequence().Append(sq1).Append(sq2);
To
// 一定時間、繰り返し処理を行う
var progress = 0f;
DOTween.To(
() => progress, // getter(現在値を取得)
value => // setter(更新値を設定)
{
// 1秒間、0~1の値がくる
progress = value;
Debug.Log($"{(int)(progress * 100)}%");
},
1, // endValue(最終値)
1); // duration(時間)
// こうすればボックス化を避けられる
DOTween.To(
value => // setter(更新値を設定)
{
Debug.Log($"{(int)(value * 100)}%");
},
0, // startValue(開始値)
1, // endValue(最終値)
1); // duration(時間)
// 1秒で現在色を赤色に変化させる
DOTween.To(() => image.color, c => image.color = c, Color.red, 1);
細かい処理を行いたい場合はToを使えば解決!(余談だが、他のTweenも内部的にはToで実装されてる)
とても便利なToだけど、ラムダ式にローカル変数を使う場合はボックス化のコストが掛かることに注意(progressみたいな使い方)。
Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方
実装例
以前リリースしたアプリのダイアログ演出(PlayAnimationの中でDOTweenを使用してる)。
イメージとしてはiPhoneの標準ダイアログにほんの少しアレンジを加えた感じ♪
Demo.cs
using UnityEngine;
public class Demo : MonoBehaviour
{
public void OpenDialog()
{
Dialog.Create().SetMessage("君の夢が").Open(() => Debug.Log("完了しまうま"));
}
}
Dialog.cs
using UnityEngine;
using UnityEngine.UI;
using System;
using DG.Tweening;
using static DG.Tweening.Ease;
public class Dialog : MonoBehaviour
{
[SerializeField] Image background;
[SerializeField] RectTransform window;
[SerializeField] Text message;
[SerializeField] Text buttonText;
Graphic[] graphics;
Action onClose;
public bool IsShowing { get; private set; }
public string Message => message.text;
public string ButtonText => buttonText.text;
public static Dialog Create()
{
var d = Instantiate(Resources.Load<Dialog>(nameof(Dialog)));
d.graphics = d.GetComponentsInChildren<Graphic>();
return d;
}
public Dialog SetMessage(string message)
{
this.message.text = message;
return this;
}
public Dialog SetButtonText(string buttonText)
{
this.buttonText.text = buttonText;
return this;
}
public Dialog Open(Action onClose = null)
{
this.onClose = onClose;
IsShowing = true;
PlayAnimation();
return this;
}
public Dialog Close()
{
IsShowing = false;
PlayAnimation();
return this;
}
public void OnButtonClick() => Close();
void PlayAnimation()
{
if(IsShowing)
{
DOTween.Sequence()
.Append(window.DOScale(1, 0.3f).SetEase(OutBack))
.Append(background.DOFade(0.5f, 0.1f).SetEase(Linear));
}
else
{
background.DOFade(0, 0.3f).SetEase(OutCubic);
}
var start = IsShowing ? 0f : 1f;
var end = IsShowing ? 1f : 0f;
var duration = 0.3f;
var ease = IsShowing ? InCubic : OutCubic;
var tweener = DOTween.To(a =>
{
foreach(var g in graphics)
{
if(g == background) continue;
var color = g.color;
color.a = a;
g.color = color;
}
},
start, end, duration).SetEase(ease);
if(!IsShowing)
{
tweener.OnComplete(() =>
{
onClose?.Invoke();
Destroy(gameObject);
});
}
}
}
最後に
個人的にAssetは最低限しか入れない自分が、DOTweenだけはどのプロジェクトでも入れてる。
使いやすくて、設計がしっかりしてて、パフォーマンスも良くて、とても完成度が高いAssetだと思う☆
〜オセロを作りながらゲームのプログラムを学ぼう〜