GitKraken Glo のカードの一覧を CSV に出力する

個人用のタスク管理ツールとして、「GitKraken Glo」という Trello 風のカンバンボードを使っているのですが、このサービス、エクスポート機能がまだ無いんですよね。

幸い API は公開されていて、それを Python から叩ける py-glo-board というライブラリを作ってくださった方がいらっしゃるので、今回はこれを利用して、全カードの一覧を CSV に出力するスクリプトを作成しました。

github.com

全カードと言っておいて申し訳ないのですが、1 ボード 100 枚のカードまでしか取ってこれません💦 使う際は、不要なカラムをアーカイブするなどして、100 枚以下に収めてください😭(Pull Request 歓迎です🙌)

サウンドデバイスの名前に「2-」がついてしまう問題

サウンドデバイスを異なるポートに接続したり外したりをしていると、たまに名前の頭に「2-」というプレフィックスが付いてしまいます。

f:id:Gigacee:20191218125157p:plain

実害は無いのですが、なんとも気持ち悪い。消してしまいましょう。

デバイスマネージャーを開き……。

f:id:Gigacee:20191218125200p:plain

該当のサウンドデバイスをアンインストールします。

再起動を要求されるので、そのまま再起動。すると……。

f:id:Gigacee:20191218125201p:plain

晴れて綺麗なデバイス名になりました😊

DOTweenList: DOTween の複数の Tween をまとめて再生したり、待機したりできるようにする

はじめに

DOTween を使っていて、複数のアニメーションを同時に再生したり、完了を待機したりしたいという場面はよくありますよね。

単体のアニメーションなら作るのも楽ですが、アニメーションというものは複数の動きが複雑に連関しがちなもので、複雑になればなるほどコードも長く、ぐちゃぐちゃになっていきます。

例えば「A, B, C のアニメーションがすべて完了するまで待機」みたいなことをしたい場合、それらすべてに完了コールバックを設定して、すべてのコールバックが実行されたかどうかを毎フレーム確認する……みたいに書かねばならず*1、大変面倒です。

bool flagA;
bool flagB;
bool flagC;

void PlayTweens()
{
    transformA.DOMoveX(100f, 1f).OnComplete(() => flagA = true);
    transformB.DOMoveX(100f, 2f).OnComplete(() => flagB = true);
    transformC.DOMoveX(100f, 3f).OnComplete(() => flagC = true);
}

void Update()
{
    if (flagA && flagB && flagC)
    {
        // すべての Tween が完了したら実行される処理
    }
}

さらにその上、アニメーション中にボタンを押したらスキップできる、みたいな仕様が加わることも……。

そういったことがもっと簡単にできるように、複数の Tween を一つにまとめて一括で処理をしたり、待機したりできるようなクラスを作ってみました。

github.com

これを用いると、先の例はこんなふうに書けます。

async UniTask PlayTweensAsync()
{
    await new DOTweenList(
        transformA.DOMoveX(100f, 1f),
        transformB.DOMoveX(100f, 2f),
        transformC.DOMoveX(100f, 3f)
    );

    // すべての Tween が完了したら実行される処理
}

簡潔で、なかなか良くないですか?

説明

GitHub の README にも説明書きはありますが、ここではそれよりもう少し詳しめに説明をしていこうと思います。

注意

以下のアセットが別途必要です。

待機のところで UniTask を使用しています*2

インストール

上記 2 つのアセットをインポートしたあと、DOTweenList.cs を任意のディレクトリに配置してください。

UnityPackage を使用してインストールすることもできます。

使い方

準備

まず、DOTweenList に処理したい Tween を詰めます。以下の 3 つの方法があります:

(i) コンストラクター

var dotweenList = new DOTweenList(
    rectA.DOAnchorPosX(600f, 0.6f),
    rectB.DOAnchorPosX(600f, 0.8f),
    rectC.DOAnchorPosX(600f, 1f)
);

一番簡単。まとめたい Tween が最初から全部揃っている場合は、これを使うと良いでしょう。

(ii) Add()メソッド

var dotweenList = new DOTweenList();

dotweenList.Add(rectA.DOAnchorPosX(600f, 0.6f));
dotweenList.Add(rectB.DOAnchorPosX(600f, 0.8f));
dotweenList.Add(rectC.DOAnchorPosX(600f, 1f));

Add()を使えば、あとから別の Tween を追加することもできます。

(iii) AddTo()拡張メソッド

var dotweenList = new DOTweenList();

rectA.DOAnchorPosX(600f, 0.6f).AddTo(dotweenList);
rectB.DOAnchorPosX(600f, 0.8f).AddTo(dotweenList);
rectC.DOAnchorPosX(600f, 1f).AddTo(dotweenList);

Add()の拡張メソッド版です。お好みの方をお使いください。

処理を実行

DOTweenList には、以下の処理を実行できます。

// 再生
dotweenList.PlayForward();

// 逆再生
dotweenList.PlayBackwards();

// 最初に戻す
dotweenList.Rewind();

// 最後に進める
dotweenList.Complete();

// 強制終了
dotweenList.Kill();

メソッド名の末尾にByIdを加えることで、特定の ID の Tween のみに処理を実行できます。

dotweenList.PlayForwardById("id");

dotweenList.PlayBackwardsById("id");

dotweenList.RewindById("id");

dotweenList.CompleteById("id");

dotweenList.KillById("id");

また、PlayForward()PlayBackwards()には引数として bool 値を渡すことができ、true を設定するとアニメーションが途中であっても強制的に最初から(PlayBackwards()なら最後から)再生することができます。

// 最初から再生
dotweenList.PlayForward(true);

// こう書くのと同じ
dotweenList.Rewind();
dotweenList.PlayForward();

この他、以下のメソッドが用意されています。

// Tween がどれか一つでも再生中なら true を返す
dotweenList.IsPlaying();

// リストをクリアする
dotweenList.Clear();

// すべての Tween の再生開始から終了までにかかる時間を取得する
// 注意:Tween が再生中であっても、開始からの時間が返る
dotweenList.GetTotalTime();

// 特定の ID を持つすべての Tween の再生にかかる時間を取得する
// 注意:Tween が再生中であっても、開始からの時間が返る
dotweenList.GetTotalTimeById("id");

待機

awaitを付与することで、再生終了まで待機することができます。

// 再生を開始し、終了まで待機する
await dotweenList.PlayForward();

// すでに再生中の Tween を待つこともできる
await dotweenList;

// 昔ながらのコルーチンで待機しても OK
yield return dotweenList.PlayForward();

そして目玉機能! 拡張メソッドのSkippable()を用いれば、指定した条件で Tween をスキップすることができます。

// 再生終了まで待機する。何かキーを入力するとスキップする
await dotweenList.PlayForward().Skippable(() => Input.anyKeyDown);

DOTween Animation

DOTween Pro の機能である DOTween Animation を DOTweenList に詰める場合は、以下のように書きます。

var dotweenList = new DOTweenList(GetComponent<DOTweenAnimation>().GetTweens());

最後に

使っていておかしなところや改善点を見つけたら、Issues でご報告ください。Pull Request も大歓迎です!

*1:もしかしたらもっと良い書き方があるのかも知れませんが……もしあったらコメントで教えて下さい!

*2:UniTask は、Tween に限らず非同期処理をしたいときにめちゃくちゃ有用なので、await/async を使っているならぜひ導入すべきです。

UniTask.Delay にスキップ機能を追加する

ゲームを作っていると、「一定時間待機するが、途中で何かキーを押したらスキップできる」みたいな処理を入れたいときがよくあります。

「一定時間待機する」は

await UniTask.Delay(delay_ms)

で、「途中で何かキーを押したらスキップ」は

await UniTask.WaitUntil(() => Input.anyKeyDown)

で、それぞれ実現できるので、これをWhenAnyで融合すれば良いのですが、いちいち書くのも面倒なので、以下のようなメソッドを作りました。

using System;
using System.Threading;
using UniRx.Async;
using UnityEngine;

public static class UniTaskHelper
{
    public static async UniTask DelaySkippable(int delay_ms, Func<bool> cond)
    {
        var cts = new CancellationTokenSource();

        await UniTask.WhenAny(
            UniTask.Delay(delay_ms, cancellationToken : cts.Token),
            UniTask.WaitUntil(cond, cancellationToken : cts.Token)
        );

        cts.Cancel();
    }
}

以下のようにして使います:

await UniTaskHelper.DelaySkippable(1000, () => Input.anyKeyDown);

Input.anyKeyDown の部分を変えれば、好きな条件でスキップできるようになります。

※ このコードの原型は、naichi さんに教えていただきました。どうもありがとうございました😌

Prefab の名前を Revert する

Prefab は、大元のほうの名前を変えても、シーンに配置されているほうの Prefab の名前は変わりません

f:id:Gigacee:20191101013228g:plain

Revert したくても、それらしいメニューは無い……。

と思いきや、Inspector を Debug モードに切り替えたらできました。

f:id:Gigacee:20191101014310g:plain

ただ、何か別の変更を Apply した時点で再び名前が変わらなくなってしまうので、あまり意味はないかもです😥

シーン中のすべての Prefab を Revert / Apply する

using Unity.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class AllPrefabEdit
{
    [MenuItem("Tools/Edit/Revert All Prefab Instances %F1")]
    public static void RevertAllPrefabInstances()
    {
        foreach (var rootObj in SceneManager.GetActiveScene().GetRootGameObjects())
        {
            foreach (var obj in rootObj.DescendantsAndSelf())
            {
                if (PrefabUtility.IsAnyPrefabInstanceRoot(obj))
                {
                    PrefabUtility.RevertPrefabInstance(obj, InteractionMode.AutomatedAction);

                    Debug.Log($"Revert succeeded: {obj.name}");
                }
            }
        }
    }

    [MenuItem("Tools/Edit/Apply All Prefab Instances %F2")]
    public static void ApplyAllPrefabInstances()
    {
        foreach (var rootObj in SceneManager.GetActiveScene().GetRootGameObjects())
        {
            foreach (var obj in rootObj.DescendantsAndSelf())
            {
                if (PrefabUtility.IsAnyPrefabInstanceRoot(obj))
                {
                    PrefabUtility.ApplyPrefabInstance(obj, InteractionMode.AutomatedAction);

                    Debug.Log($"Apply succeeded: {obj.name}");
                }
            }
        }
    }
}

メニューの Tools > Edit か、Ctrl+F1, Ctrl+F2 で実行できます。

LINQ to GameObject が必要です。

複数の ReactiveProperty をまとめて監視する

UniRx を使っていて、複数の ReactiveProperty をまとめて監視して何か処理を行いたいときがたまにあります。

例えば、「デバッグモードが ON」かつ「ミュート設定が ON」になったときのみ音声をミュートする、とか。

ベタ書き

デバッグモードとミュート設定をそれぞれ BoolReactiveProperty で持ち、どちらかの値が変わったらミュート状態を切り替える、という実装を考えます。

とりあえず、何も考えずそのままベタ書きしたのが以下のコードです。

public BoolReactiveProperty debugMode;
public BoolReactiveProperty muteAllSounds;

public AudioSource audioSource;

void Start()
{
    debugMode.Subscribe(x => ToggleMute());
    muteAllSounds.Subscribe(x => ToggleMute());
}

void ToggleMute()
{
    audioSource = debugMode.Value && muteAllSounds.Value;
}

そのままだ。そのままですが、だいぶ頭の悪いコードですね……。

せっかく ReactiveProperty を使っているのに、流れてきた値 (x) を使っていないあたりに頭の悪さが滲み出ています。

ストリームを合成する

「ストリームは合成できる」ということを知っていれば、先程のコードは以下のように書き換えることができます。

public BoolReactiveProperty debugMode;
public BoolReactiveProperty muteAllSounds;

public AudioSource audioSource;

void Start()
{
    Observable.Merge(debugMode, muteAllSounds)
        .Subscribe(x => ToggleMute());
}

void ToggleMute()
{
    audioSource = debugMode.Value && muteAllSounds.Value;
}

10 点だったのが、20 点くらいにはなったかな……。

しかしこのやり方だと、流れてくるxの値がdebugModeのものなのかmuteAllSoundsのものなのか判らないので、やっぱり使えないままです。

両方の値が流れてくるようにする

ストリームを Merge で合成すると、debugModeの値が変わった時にはdebugModeの値のみが、muteAllSoundsの値が変わった時にはmuteAllSoundsの値のみが流れて来るので、良くなかった、と。

なら、debugModeの値が変わろうとmuteAllSoundsの値が変わろうと、両方の値が流れてくるようになれば良いわけです。

そのようにしたのがこちら。

public BoolReactiveProperty debugMode;
public BoolReactiveProperty muteAllSounds;

public AudioSource audioSource;

void Start()
{
    Observable.CombineLatest(debugMode, muteAllSounds)
        .Subscribe(list =>
        {
            audioSource = list.All(x => x);
        });
}

だいぶ賢いコードになりましたね!

ストリームを CombineLatest で合成すると、どれかの値が変わったら全部の値が List にまとめられて流れてくるので、「list 内の全要素が true だったら*1ミュート」とすれば、やりたいことが実現できるというわけ。

補足

先程、CombineLatest の説明で「どれかの値が変わったら全部の値が List にまとめられて流れてくる」と書きましたが、これは微妙に違くて、すべてのストリームに一回以上値が流れていないと List は流れてきません。

それなのになぜ上記のコードが valid なのかというと、ReactiveProperty は値が変化した時だけでなく Subscribe した瞬間にもその時点での値が一回流れるので、自動的に「一回以上値が流れている」が満たされるからですね。

参考:

neue cc - Unityにおけるコルーチンの省メモリと高速化について、或いはUniRx 5.3.0でのその反映

(UniRxの)ReactivePropertyはSubscribe時に必ず値をプッシュするようになってる

*1:list.All(x => x) の部分。

The coloring of this site is Dracula PRO🧛🏻‍♂️
This website uses the FontAwesome icons licensed under CC BY 4.0.

2020 GIGA CREATION