【Unity】VContainer、MessagePipe、R3、DOTweenを用いた汎用的なTween処理
汎用的なTween処理を目標
現在戦車ゲーム MetalCityというUnityのゲーム開発をすすめていて、VContainer、MessagePipe、R3、DOTweenを用いた汎用的なTween処理を実装できればと思い、諸々設計してみました。まだ手探りですが、とりあえず形になったので備忘録として記事にしてみます。
まだ初期の処理しか記述していないので公開用Githubリポジトリはまだ準備しておりません。もう少し実装が進んでゲームとして成立してきたタイミングで公開用Githubのリポジトリも用意しようと思います
クラス図
Tween系処理
以下はTween系の処理を実装しています。他のプロジェクトへの移植を意識して、namespaceをプロジェクト名ではなく_Tweenとしています
ITweenEffect
Tween処理を定義したインターフェースです、Tweenのライブラリを変更しても対応できるようにインターフェースにしています
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace _Tween.Scripts.Domain.UI.Tween
{
/// <summary>
/// Tween処理を実行するインターフェース
/// </summary>
public interface ITweenEffect
{
/// <summary>
/// UIのalphaを0にする
/// </summary>
UniTask SetAlphaZero(CanvasGroup canvasGroup);
/// <summary>
/// 外部から受け取ったRectTransformのsizeDeltaを0にする
/// </summary>
UniTask SetSizeDeltaZero(RectTransform rectTransform);
/// <summary>
/// UIのフェードイン
/// </summary>
UniTask FadeIn(CanvasGroup canvasGroup, CancellationToken cancellationToken);
/// <summary>
/// UIのフェードアウト
/// </summary>
UniTask FadeOut(CanvasGroup canvasGroup, CancellationToken cancellationToken);
/// <summary>
/// UIのフェード処理
/// </summary>
/// <param name="canvasGroup"></param>
/// <param name="endValue"></param>
/// <param name="duration"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
UniTask Fade(CanvasGroup canvasGroup, float endValue, float duration, CancellationToken cancellationToken);
/// <summary>
/// UIのサイズ変更
/// </summary>
/// <param name="rectTransform"></param>
/// <param name="endValue"></param>
/// <param name="duration"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
UniTask SizeDelta(RectTransform rectTransform, Vector2 endValue, float duration,
CancellationToken cancellationToken);
}
}
DOTweenExecution
上記のITweenEffectで実際にTween処理を行う、DOTween用の実処理を行うクラスです
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using TMPro;
using UnityEngine;
namespace _Tween.Scripts.Domain.UI.Tween
{
/// <summary>
/// DOTweenのトゥイーン関連処理を実行するクラス
/// </summary>
public class DOTweenExecution : ITweenEffect
{
private const float defalutFadoInEndValue = 1f;
private const float defalutFadoInDuration = 0.3f;
private const float defalutFadoOutEndValue = 0f;
private const float defalutFadoOutDuration = 0.3f;
public UniTask SetAlphaZero(CanvasGroup canvasGroup)
{
canvasGroup.alpha = 0;
return UniTask.CompletedTask;
}
public UniTask SetSizeDeltaZero(RectTransform rectTransform)
{
rectTransform.sizeDelta = Vector2.zero;
return UniTask.CompletedTask;
}
/// <summary>
/// フェード実行
/// </summary>
/// <param name="canvasGroup"></param>
/// <param name="endValue"></param>
/// <param name="duration"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask Fade(
CanvasGroup canvasGroup,
float endValue,
float duration,
CancellationToken cancellationToken
)
{
return canvasGroup.DOFade(endValue, duration)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// フェードイン実行
/// </summary>
/// <param name="canvasGroup"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask FadeIn(
CanvasGroup canvasGroup,
CancellationToken cancellationToken
)
{
return canvasGroup
.DOFade(defalutFadoInEndValue, defalutFadoInDuration)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// フェードアウト実行
/// </summary>
/// <param name="canvasGroup"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask FadeOut(
CanvasGroup canvasGroup,
CancellationToken cancellationToken
)
{
return canvasGroup.DOFade(defalutFadoOutEndValue, defalutFadoOutDuration)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// サイズ変更
/// </summary>
/// <param name="rectTransform"></param>
/// <param name="endValue"></param>
/// <param name="duration"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask SizeDelta(
RectTransform rectTransform,
Vector2 endValue,
float duration,
CancellationToken cancellationToken
)
{
return rectTransform.DOSizeDelta(endValue, duration)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// X軸への移動
/// </summary>
/// <param name="rectTransform"></param>
/// <param name="endValue"></param>
/// <param name="duration"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask LocalMoveX(
RectTransform rectTransform,
float endValue,
float duration,
CancellationToken cancellationToken
)
{
return rectTransform.DOLocalMoveX(endValue, duration)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// 文字の非表示
/// </summary>
/// <param name="textMeshProUGUI"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask HideText(TextMeshProUGUI textMeshProUGUI, CancellationToken cancellationToken)
{
return textMeshProUGUI.DOText(String.Empty, 0)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
/// <summary>
/// 文字の表示
/// </summary>
/// <param name="textMeshProUGUI"></param>
/// <param name="text"></param>
/// <param name="speed"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public UniTask ShowText(TextMeshProUGUI textMeshProUGUI, string text, float speed, CancellationToken cancellationToken)
{
return textMeshProUGUI
.DOText(text, text.Length * speed)
.SetEase(Ease.Linear)
.Play()
.ToUniTask(cancellationToken: cancellationToken);
}
}
}
FeedLoopAnimation
上記のITweenEffectの処理を用いてフェードイン・フェードアウトのアニメーションのループ処理を実行するクラスです
using System;
using System.Threading;
using _Tween.Scripts.Domain.UI.Tween;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace _Tween.Scripts.Domain.UI.Effect
{
/// <summary>
/// フェードイン・アウトのループアニメーション処理
/// </summary>
public class FeedLoopAnimation
{
private readonly ITweenEffect _tweenEffect;
private readonly CanvasGroup _canvasGroup;
private readonly CancellationToken _cancellationToken;
private float _duration = 1;
public FeedLoopAnimation(
ITweenEffect tweenEffect,
CanvasGroup canvasGroup,
CancellationToken cancellationToken,
float duration = 1
)
{
_tweenEffect = tweenEffect;
_canvasGroup = canvasGroup;
_cancellationToken = cancellationToken;
_duration = duration;
}
public async UniTask FadeLoop()
{
try
{
while (!_cancellationToken.IsCancellationRequested)
{
// Fadeで時間指定を行い、フェードイン・アウトを行う
await _tweenEffect.Fade(_canvasGroup, 1, _duration, _cancellationToken);
await _tweenEffect.Fade(_canvasGroup, 0, _duration, _cancellationToken);
}
}
catch (OperationCanceledException)
{
// キャンセル
}
}
}
}
TitleUI系処理
以下はゲームタイトルのUI系の処理を実装しています。こちらはゲーム内のみの実装のため、namespaceをゲーム名(_MetalCity)としています
TitleLifetimeScope
タイトル用のLifetimeスコープです、MessagePipeとTweenで使用するクラスをDIしています。
using _Tween.Scripts.Domain.UI.Tween;
using _MetalCity.Scripts.Structure.Signal;
using VContainer;
using VContainer.Unity;
using MessagePipe;
namespace _MetalCity.Scripts.Installer
{
/// <summary>
/// Title Lifetime Scope
/// </summary>
public class TitleLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
base.Configure(builder);
// MessagePipeの登録
var options = builder.RegisterMessagePipe();
// シグナルの登録
builder.RegisterMessageBroker<PushStartButton>(options);
// ITweenEffectにDOTweenExecutionを設定する
builder.Register<ITweenEffect, DOTweenExecution>(Lifetime.Singleton);
}
}
}
StartButton
スタートボタン押下の処理を記述したクラスです、ボタンのクリックの定義はR3で行っています、ボタンはフェードイン・フェードアウトのループ処理を実現するため、FeedLoopAnimationクラスで実施しています。またボタン押下時にMessagePipeでPushStartButtonの構造体を発行しています。
using System.Threading;
using _Tween.Scripts.Domain.UI.Effect;
using _Tween.Scripts.Domain.UI.Tween;
using Cysharp.Threading.Tasks;
using MessagePipe;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
using _MetalCity.Scripts.Structure.Signal;
namespace _MetalCity.Scripts.Domain.UI.Title
{
/// <summary>
/// タイトルUI スタートボタン押下処理
/// </summary>
[RequireComponent(typeof(Button), typeof(CanvasGroup))]
public class StartButton : MonoBehaviour
{
[SerializeField] private Button _button;
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private float _duration = 5;
private ITweenEffect _tweenEffect;
private IPublisher<PushStartButton> _pushStartButtonPublisher;
private CancellationTokenSource _cancellationTokenSource;
[Inject]
public void Construct(
ITweenEffect tweenEffect,
IPublisher<PushStartButton> pushStartButtonPublisher
)
{
_tweenEffect = tweenEffect;
_pushStartButtonPublisher = pushStartButtonPublisher;
}
private void Reset()
{
_button = GetComponent<Button>();
_canvasGroup = GetComponent<CanvasGroup>();
}
private void Start()
{
_button.onClick.AddListener(PushButton);
_cancellationTokenSource = new CancellationTokenSource();
var feedLoopAnimation = new FeedLoopAnimation(_tweenEffect, _canvasGroup, _cancellationTokenSource.Token, _duration);
feedLoopAnimation.FadeLoop().Forget();
}
private void OnDestroy()
{
_cancellationTokenSource.Cancel();
}
private void PushButton()
{
_pushStartButtonPublisher.Publish(new PushStartButton());
// ボタンインタラクティブを無効化
_button.interactable = false;
}
}
}
GameStartScreen
ゲームスタート後の暗転処理を実施するクラスです、MessagePipeでPushStartButtonの構造体を購読していて、受信するとアニメーション処理を実施します
using System.Threading;
using _Tween.Scripts.Domain.UI.Tween;
using _MetalCity.Scripts.Structure.Signal;
using Cysharp.Threading.Tasks;
using MessagePipe;
using R3;
using UnityEngine;
using VContainer;
namespace _MetalCity.Scripts.Domain.UI.Title
{
/// <summary>
/// Game Start Screen
/// </summary>
[RequireComponent(typeof(CanvasGroup))]
public class GameStartScreen : MonoBehaviour
{
[SerializeField] private CanvasGroup _canvasGroup;
private float _duration = 1;
private ITweenEffect _tweenEffect;
private ISubscriber<PushStartButton> _pushStartButtonSubject;
private CancellationTokenSource _cancellationTokenSource;
[Inject]
public void Construct(
ITweenEffect tweenEffect,
ISubscriber<PushStartButton> pushStartButtonSubject
)
{
_tweenEffect = tweenEffect;
_pushStartButtonSubject = pushStartButtonSubject;
}
private void Reset()
{
_canvasGroup = GetComponent<CanvasGroup>();
}
private async UniTask Start()
{
_tweenEffect.SetAlphaZero(_canvasGroup);
gameObject.SetActive(false);
_pushStartButtonSubject.Subscribe(foo =>
{
Show();
}).AddTo(this);
}
public async UniTask Show()
{
gameObject.SetActive(true);
_cancellationTokenSource = new CancellationTokenSource();
await _tweenEffect.Fade(_canvasGroup, 1, _duration, _cancellationTokenSource.Token);
}
}
}
PushStartButton
ゲームスタートボタン押下時のMessagePipe用で、ボタン押下時のシグナルとしての用途のみの構造体です、
namespace _MetalCity.Scripts.Structure.Signal
{
/// <summary>
/// StartButton Push Signal
/// </summary>
public struct PushStartButton
{
}
}
Discussion