ノートの端の書き残し

UnityやらC#やら触っていたときのメモ

Unreal Engine5勉強 残像を残す

空中ダッシュで残像を残す

こんな感じ
ダッシュ自体は前に作っているので省略。

実現までやること

残像用のActor BPを作る。(操作キャラと同じメッシュ構成にする) 残像用のマテリアルを作って残像用Actor BPのメッシュに割り当てる。 ダッシュ中一定間隔で残像用BPをスポーンしつつ自身のポーズスナップショットを渡す。 残像用のAnimation BPを作り、スナップショットを割り当てる。

毎フレームアルファが小さくなるように残像用マテリアルを更新する。 後始末として、一定時間で残像Actorを消す。

参考

スナップショットに関して。

historia.co.jp

ヒストリア様様。UE5でも全く同じ(UIがほんの少し違うが)要領でできた。

Unreal Engine5勉強 Niagara編

Niagara

Unreal Engineにおけるパーティクルシステム。ナイアガラ。
Cascade(カスケード)の後継?と思われる。名前的に。 UE5でカスケードを作る方法はわからないが、カスケードアセットをインポートして使用はできる。 また、カスケードをナイアガラに変換するプラグインもある模様。

docs.unrealengine.com

NiagaraシステムとNiagaraエミッター

「エミッター」をまとめたものが「システム」。さらにエミッターは細かいモジュールでパーティクルの生成、更新を行う。

モジュール

なんか色々あるが、とりあえず、生成時に働くべきか毎フレーム働くべきかで大別されそう。ある程度はどのテンプレエミッタから始めればいいかを把握するのがよさそう。(稲妻エフェクトはstatic beamから作りやすいとか)

その他

各モジュールのパラメータはパーティクルの状態(位置、速度)や乱数、定数、外部に公開できる自作変数によって設定できる。

注意点

Niagaraは既に正式版ではあるものの、高頻度で改修が行われているらしく、最新版と全くUIなども同じ参考資料はかなり少なそう。ちょっと違う可能性は考えて調べものをした方がいい。

参考にしたページ

めちゃくちゃ丁寧でわかりやすく、本当にありがたい。公開してくださっているものを一通りやればあとは自分で調べてやっていけそうな気がした。

www.ultra-noob.com

Unityプログラマ視点のUnreal Engine5メモ

UniRx(イベント)

イベントディスパッチャーでイベントが生成できる。イベントさえ書ければ根本的な問題は解決できるが、ブループリントでも使い勝手をRxに近付けたいところ。

非同期

だいぶやり辛い感じがする。一定時間毎フレーム何かする、とかはマクロライブラリに作っておく。なんとかしたい。

ユーティリティメソッド

関数ライブラリ、マクロライブラリといった、ユーティリティ用のブループリントが作れる。マクロじゃないと非同期的なノードが使えないけど、基本関数の方がいい気はする。

シングルトン

GameInstanceがそれ。セーブデータとかオプションとかはここが無難か。

UI

Widgetと呼ばれる。Viewがデザイナーで、Controllerがグラフ。ゲーム中に一時停止して表示したいとかよくあるが、インゲームを一時停止するPauseノードが最初からあるし、InputモードをUIのみにする(インゲーム側ではユーザーからの入力を全て無視する)なども最初からノードがあるのでありがたく使う。UE5でEpic謹製のCommon UIなるプラグインが追加されたそう。

シーンPresenter

各レベルにはGameModeを設定し、GameModeがどのControllerクラスを使うか、どのキャラクターがユーザー操作を受け付けるかなど決める。マウスの非表示とかはController。だいたいこの辺がエントリポイントだと思ってよさそう。

プレハブ内プレハブ

Child Actorを使う。ソケットを使って、具体的にどこに配置するか決める。全部Transformで扱うより健全そう。

UEブループリント勉強中

Unreal EngineのThird Person Templateを使ってブループリントの練習中……

成果物


カスタムロボの空中ダッシュ的なやつのつもりです……
ダッシュ回数制限があって、着地したら回数が回復する。

ブループリントはこんな感じになりました。もともとのThird Person Templateが半分くらいありますが。

汚い……ブループリントのベストプラクティスがわからん

わからないこと

Unityでの開発中に、便利ライブラリにどれだけ頼っていたかを思い知りました。DOTweenだったりUniRxだったりUniTaskだったり。なくても実装自体はできますが、脳死でできるほどではない。UEでもこういった有名便利ライブラリとかあるんでしょうかね。C++での開発ならReactiveExtensionもasync/awaitもパッと入れられるんでしょうが。

ブループリントの所感

コードを書いて開発してきた経験が4年ほど。ビジュアルスクリプティングは数日ですが、すでにビジュアルスクリプティングの方が手早く実装できそうな感じがあります。もちろんこれはブループリントの機能やUEのテンプレートが充実しているからですが。
さらに言えば、スクリプトを書いてきた経験から、ブループリントでは多分こんな感じに書けるんだろうという推測が立つというのもあります。コードを書くのもブループリントを書くのも本質的には何も変わらないのが実感できますね。

プログラムはなぜ動くのか 第3版

プログラムはなぜ動くのか 第3版

www.amazon.co.jp

を読みました。以下、感想です。

感想

恥ずかしながら詳細を知らなかったことが多いです……。もちろんヒープやスタック、データ構造は意識しているのですが、実際のCPUの動きを詳細にイメージしてはおらず、アセンブリ言語もまともに読んだことが無かったため、学べてよかったです。
これからはSharpLabでILだけじゃなくでアセンブリ言語も読むようにします。

ところでAmazonのレビューにもあったんですが、確かに機械学習の話は唐突ですねw第2版では代わりに乱数の項目だったようなので、第2版の方が良かったのかなぁと思いました。たぶんPythonとか機械学習とか言った方が売れるんでしょう。

良いコード / 悪いコードで学ぶ設計入門(ミノ駆動本)読書感想文

書籍Amazonリンク

www.amazon.co.jp

本の概要

オブジェクト指向を基本とした設計思想についてTwitterやQiita、テックコンなどで発信を行っているミノ駆動さんの設計本。

本の感想

読みながら、各章ごとに軽く感想を書いていきます。

1章 悪しき構造の弊害を知覚する

設計が下手だとこういう問題があるよという軽い話。 具体例は第1章ということもあってかなり低レベルなものを選んでいると思う。が、集団で開発していると(継ぎ足しの継ぎ足しなどで)マジで見るので辛い。

2章 設計の初歩

変数を使いまわすな、意味のあるまとまりを作って名前を適切に付けろ、という内容。
個人的には、色々設計を学んで最終的に戻ってくる真理もこういう話(命名、モジュールの責務の明確化)だと思ってる。

3章 クラス設計 -すべてに繋がる設計の基盤-

そのクラスの役割ならそのクラスに書け、という話。具体例として値オブジェクトが挙がっているのでクラスの役割として不変性や不正値の検出に言及。

4章 不変の活用 -安定動作を構築する-

値オブジェクトの説明と言える。ゲーム的には新しいクラスインスタンスは作りたくないので、C#的に言えばここは構造体に読み替えるかなと思う。
値オブジェクトだからインスタンスの同一性には無頓着で良いが、値オブジェクトはそういうものだ、という説明をせずにこの話を持ってくるのはやや怖い気はする。(知らない間に別インスタンスに変わっているのは基本的に弊害であるはずだ。)
ただ、不変をデフォにしたいという気持ちは同意する。僕もprivate readonlyと書くとメンタルが回復する。

5章 低凝集 -バラバラになったものたち-

意味単位でクラス化して、そのクラスの役割であるメソッドはそのクラスに定義しろという話。
4章までと違って、普遍的に言えることであり、そのため具体的にこういうケースはこうすればいいという理解が初心者には難しい、かなり難易度が高い章ではないかと思う。
が、その分重要度はかなり高い。

6章 条件分岐 -迷宮化した分岐処理を解きほぐす技法-

interfaceによるポリモーフィズムを使えという話。完全に同意する。使用者側でキャストしてたら意味ねーぞというツッコミも良い。(マーカーインターフェースを使い、使用者側でasキャストしているプロジェクトを2回見たことがある。interfaceを使う意味が全く無い。)

7章 コレクション -ネストを解消する構造化技法-

Linqを使えという話と、コレクションを自作クラスで包んで不要な処理を隠蔽しろという話。どちらもその通りだが(値オブジェクトのとき同様、ゲーム的には頻繁なクラスインスタンス生成は避けたいが)関連性が薄いので章を分けても良いのでは。後者はコレクションは一例であってもう少し広く言える話だと思った。

8章 密結合 -絡まって解きほぐせない構造-

ミノ駆動さんの発信を見ていればすごく既視感がある章。単一責任原則の理解と、DRY原則の誤解、継承より委譲、疎結合など、最重要要素がてんこもり。恐らく実体験無しにここを理解するのは難しいが、ここを理解できて実践できているかはすごく重要だと思う。単一責任原則を守っていれば長くても200行、だいたい100行くらいのクラスに収まる、というのは普段Unityで開発している僕の感覚にも一致している。

9章 設計の健全性をそこなうさまざまな悪魔たち

変更容易性を高く保つための細かいテクニック。悪例として挙がっている「技術駆動パッケージング」は自分も反対なのだが、なぜかゲームエンジンの仕様事例ではよくみる(Scenesフォルダ、BluePrintsフォルダ、Scriptsフォルダなど)。フォルダ名で何のためのアセット群か判断するものがあるのでそれは仕方ないと思うが、その必要が無い部分もその分け方で本当に分かりやすいか考えてみてほしいと常々思う。

10章 名前設計 -あるべき構造を見破る名前-

超重要。僕も、命名は最も重要だと思っており、完全に同意する。仕様変更によって言葉の意味が変わりうる、という点は見逃していたので今後注意したい。

11章 コメント -保守と変更の正確性を高める書き方-

Whyを書け、という話や、コメントを過信するなという話。個人的には、悪例「コメントで命名をごまかす」が面白かった(ありすぎる)。
ちなみに自分は結構コメントは書く方だと思う。Whyのコメントだったり、例えばMVPのPresenterの初期化メソッド内で細分化してメソッドに分ける意味無いけどメソッドがちょっと長いし、お互いに関係無い処理は見た目分かれてるっぽくしたいなぁと思ったら、改行とコメントを入れることがある。コンパイラが0コストでインライン化してくれるなら普通にメソッドにわけるのだが。

12章 メソッド(関数) -良きクラスには良きメソッドあり-

コマンド・クエリ分離は名前を知らなかったが、最近同じことを考えていた。利便性のためにモディファイアを実装することもあるが、その場合名前は〇〇And△△みたいな、コマンドしてクエリするというのがわかる名前にするだろうとか考えていた。
null引数やフラグ引数をやめろ、というのも真っ当な主張。

13章 モデリング -クラス設計の土台-

単一責任原則の解釈として、クラスは1つだけ目的を持つようにしろ、という話。僕も単一責任原則は設計において最重要な概念だと考えるが、その解釈はかなり曖昧な表現が多いと思っている。僕はよく「何のためのクラスか一言で明確に説明できる」ようにと言うが、これも結局、聞き手が未熟であれば「目的」の範囲が不適切に広くなってしまう可能性を避けられない。正直ここは有識者の下で指導を受けたり実践で学ばないと身に付けるのは難しいのではないかと思っている。

14章 リファクタリング -既存コードを成長に導く技-

テストコード、IDEの機能を使用して実際にリファクタを行う手順。
実際クソコードのリファクタは精神的にかなり辛いことが多いので、使えるものは使ってそのとき考える必要があることを減らし、精神を守る手段は非常に大切だと思う。

15章 設計の意義と設計への向き合い方

凝集度、結合度の計算方法(ツール)の紹介や、現実的に設計、というかリファクタするにあたっての注意点など。ところで個人的には循環的複雑度よりは認知的複雑度の方が好き。(認知的複雑度は5以下に収めたいと思っている)

16章 設計を妨げる開発プロセスとの戦い

コミュニケーションを取れ、という話とその具体例。まさに自分がチームの設計レベルを上げなければならない状況なので、この辺りの経験者の考えは参考になった。この本は大部分は設計初心者向けという感じだが、ここだけは中級者以上向け。

17章 設計技術の理解の深め方

主に書籍の紹介。何が良くないかと探す意識を持って読むと良い、初歩の初歩を抜けた人や中級者以上向けの書籍が多数紹介されています。特定の言語に特化した書籍は省かれているので、ここから評判の良いものを何冊か選び、さらに自分のよく使う言語に特化したものを合わせて読むのが良いと思った。
C#であればAdaptive Codeが自分の読んだことのある中ではおススメ。

www.amazon.co.jp

全体を通して感想

自分は設計に関しては中級者以上だとは思いますが、今の知識が間違っていないかの確認や、未だにチームメンバーの初心者にうまく説明することができていないため、その手助けにならないかという目的で読みました。その目的は概ね達成できたのではないかと思っています。
ミノ駆動さんが普段からRPGツクールで設計指南の動画を作ったりとフランクなタイプの方ということもあり、堅苦しくなく、サッと読めます。
入門書なので理論や様々なデザインパターンを学ぶことはできませんが、入門書としては取っつきやすさからおススメできると感じました。

僕はLinqのことを何もわかっていなかった

Linq

コレクションのイテレーションを気軽に回せる処理。

var intEnumerable = Enumerable.Range(0, 10);
var intList = intEnumerable.ToList(); // 0, 1, 2, ... , 8, 9
var oddList = intEnumerable.Where(value => value % 2 == 1).ToList(); // 1, 3, 5, 7, 9

ここ数年でLinqに最適化が入っている

という話は知っていました。ただ、具体的にどうなのというのをちゃんと見たことがありませんでした。

IPartition<T>, IIListProvider<T>

Linqと言えばIEnumerable、というイメージを持っていました。もちろん、MoveNextしてCurrentを取ってくるのが基本ですからそれは間違いではないんですが、現在では特定の状況でパフォーマンス改善を図るためのより細かいインターフェースがありました。

IIListProvider<T>

source.dot.net

using System.Collections.Generic;
 
namespace System.Linq
{
    internal interface IIListProvider<TElement> : IEnumerable<TElement>
    {
        TElement[] ToArray();
 
        List<TElement> ToList();

        int GetCount(bool onlyIfCheap);
    }
}

つまり、これは直後にToArrayToListが来た場合に最適化するためのインターフェースと言えます。
何も考えずにIEnumerableからToListをしてしまうと、だいたいこういう処理になります。

var list = new List<T>();
foreach (var element in enumerable) { list.Add(element) };

enumerableの数が多いと、Listの内部配列の再配置が頻発して酷く無駄なヒープアロケーションが発生します。なので、事前に要素数がわかるなら先に必要十分な配列長を確保したい気持ちがあります。それを独自に実装して呼び出させるためのインターフェース、ということですね。
例えば、OrderByで生成されうるイテレータクラスのToListを見てみます。

OrderedEnumerableIterator.ToList

source.dot.net

public List<TElement> ToList()
{
    Buffer<TElement> buffer = new Buffer<TElement>(_source);
    int count = buffer._count;
    List<TElement> list = new List<TElement>(count);
    if (count > 0)
    {
        int[] map = SortedMap(buffer);
        for (int i = 0; i != count; i++)
        {
            list.Add(buffer._items[map[i]]);
        }
    }
 
    return list;
}

確かにListのキャパシティを指定して生成していますね。

IPartition<T>

source.dot.net

using System.Diagnostics.CodeAnalysis;
 
namespace System.Linq
{
    /// <summary>
    /// An iterator that supports random access and can produce a partial sequence of its items through an optimized path.
    /// </summary>
    internal interface IPartition<TElement> : IIListProvider<TElement>
    {
        IPartition<TElement> Skip(int count);
 
        IPartition<TElement> Take(int count);
 
        TElement? TryGetElementAt(int index, out bool found);
 
        TElement? TryGetFirst(out bool found);
 
        TElement? TryGetLast(out bool found);
    }
}

コレクションを全部見る必要が無いと思われる場合に最適化するためのインターフェースと考えられます。例えばIList<T>型のインスタンスSelectを使うと生成されうるイテレータクラスのElementAtを見てみましょう。

source.dot.net

public TResult? TryGetElementAt(int index, out bool found)
{
    if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive)
    {
        found = true;
        return _selector(_source[_minIndexInclusive + index]);
    }
 
    found = false;
    return default;
}

sourceがIList型だと知っているので、直接インデックスを渡して取得しているのがわかりますね。

終わり

C#を学び始めるとLinqは遅いと習ってしまいがちです。確かにListを回すならfor、配列ならforeachで良いですが、とはいえ、Linqには圧倒的な可読性、変更容易性の高さがあります。見てきたように、実は今ではLinqは可能なら無駄な処理を行わないように、という最適化もしっかり入っているので、Linqは遅いんだ、という先入観は卒業してもよいかもしれません。