Open130

つぶやき

MakihiroMakihiro

何が良いかというと、

  • 「ちょっとしたコードのメモをしたいけど、Twitterはコード書けないんだよなー」
  • 「Qiitaは一つの記事をガッツリ書く感じなんだよなー」

が無くなる

MakihiroMakihiro

PRを介してのレビューだとモグラたたきになってしまいがちなので、Live Shareをやってみる

MakihiroMakihiro

LiveShare、今は割とバグが多い感じ

  • 同期切れ
  • コードの色がカオスになる
MakihiroMakihiro

GitHub Discussionsで社内の質問・議論のログを残す仕組みを設計中

MakihiroMakihiro
  1. 社内チャットでの議論が流れてしまうと、「あれどうなったっけ?」でチャットを遡るのが大変
  2. 流れてしまうの良くないから、議論で結論が出た後はスプレッドシートで議事録的なものをまとめるようになった

といった感じなのだけど、議論内容を構造化してスプレッドシートに書き直すのがまぁめんどくさい

MakihiroMakihiro

達成したいのは、スプレッドシートからの完全移行

  • 過去ログをデータベース化して、検索性を向上させる、流れを追いやすくする
  • 埋もれてしまう調査・知識の蓄積
  • 議論内容を構造化して議事録にする手間の解消
MakihiroMakihiro
  • チャットの議論内容をスプレッドシートに書き込む
  • リーダーに「こんな感じにまとまったので、確認お願いします!」という連絡をチャットで行う

現在の構造だと、「チャット->スプレッドシート->チャット」といった感じの反復横跳びが発生している

だから、チャットとスプレッドシートの役割をDiscussionsで統合出来たら楽だなーと思って動いてる

MakihiroMakihiro

Discussionsを運用するうえでここら辺が懸念になってくる

  • 何を起票するかの判断基準
  • ログに残すべきではないディスカッションが作られてしまうことによるノイズの増加
  • 結局みんながチャットで済ませてしまわないか
  • 結局チャットとの反復になってしまわないか
  • 通知
MakihiroMakihiro

Discussions、通知はメンションを送ればアプリのプッシュ通知が行くので大丈夫そう

MakihiroMakihiro

DIscussionsへの導線をどうするか

  • Chatworkのチャンネルの概要にDiscussionsのリンクを貼る
  • Discussionsで話した方が良い話題が出たら、起票してDiscussionsへの移動を促す
MakihiroMakihiro

Discussionsの運用開始したけど、ひとまず「埋もれてしまう調査・知識の蓄積」は機能している感じ

MakihiroMakihiro

Discussions、議論が捗る。

  • 並列で議論できる(同時に出てきた懸念を別々の議論として展開し、問題を分けて議論できる)
  • 試行して結果が出るまで結論を保留に出来る(チャットだとタイムラインに流れて忘れ去られる)

これ強い

MakihiroMakihiro

回答マーク機能も便利だけど、回答をマークするタイミングが地味に難しいから何とかしたい

MakihiroMakihiro

Discussionsの運用も、よく考えたらアプリの運用と変わらないなー

チームのみんなが利用する導線があって、分かりやすくて、便利で、もう一回使いたいと思えるように設計すればいいんだな

MakihiroMakihiro

試行して結果が出るまで結論を保留に出来る(チャットだとタイムラインに流れて忘れ去られる)
これ強い

チャットだと非同期モドキだったのが、Discussionsで完全に非同期になった感じ

MakihiroMakihiro

スクラップの使い方を記事にすると面白いかもしれん

MakihiroMakihiro

インプット・アウトプットのメモね、これはやってるな

設計の勉強はインプット、Discussionsの運用メモはアウトプットといった感じになっている

MakihiroMakihiro

とりあえず便利になるんじゃないかと思っていること

  • UnityとVisual Studioを同時に表示(Visual Studioのデバッグ機能が本格的に活用できるはず)
  • 画面を広く使いたい機能を使い易くなる(ノードエディタなど)
  • Blenderでのモデリング効率UP(UV展開、参考画像参照)
  • 英語のゲームをしながら、片方のウィンドウで機械翻訳して学習効率UP
  • UMLの設計を見ながらコーディング
MakihiroMakihiro

とりあえず、Blenderで作業しながら参考画像をブラウズできるのは便利なのは確認

MakihiroMakihiro

ウルトラワイドで画面をフルに使いたい場合、専用のレイアウトを作る必要があるからそこをどうするか

MakihiroMakihiro

基本的に1つのウィンドウをウルトラワイドで表示することはほとんどなくて、2ウィンドウを分割で表示するのがメインになる

MakihiroMakihiro

今のところBlenderでウルトラワイドが輝きそうな場面は、

  • UV展開
  • ノードエディター
  • 2つのカメラで確認しながらモデリング
MakihiroMakihiro

真ん中で分けてLerpする関数作った

public static float BinaryLerp (float center,float minOffset,float maxOffset,float t) {
	if (t <= 0.5f) {
		return Mathf.Lerp(center - minOffset,center,t * 2f);
	}
	else {
		return Mathf.Lerp(center,center + maxOffset,(t - 0.5f) * 2f);
	}
}
MakihiroMakihiro

tが0だったらminOffsetの値になるよ
tが0.25だったらminOffsetとcenterの中間値になるよ
tが0.5だったらcenterの値になるよ
tが0.75だったらcenterとmaxOffsetの中間値になるよ
tが1だったらmaxOffsetの値になるよ

MakihiroMakihiro

あー、目のトラッキングうまくいかなくなったと思ったら、アニメーションのAvatarにEyeをちゃんと割り当ててたからHumanoidアニメーションでEyeも一緒に更新されるようになってたぁ_(:3 」∠)_

MakihiroMakihiro

指トラッキングは、指の最終点および第一関節からの方向をターゲットとして設定してやればいけるか…?

MakihiroMakihiro

FinalIKのFingerRig、調べても使用例がほぼ見られないので、気合いで何とかするしかない

MakihiroMakihiro

FingerRig、ちゃんとLimitを付けないと指があらぬ方向へ曲がってしまうな…

MakihiroMakihiro

うーむ、FingerRigでのIKには限界があるかもしれんな
キーポイントから直接、各関節の回転を計算するのが良いかも

MakihiroMakihiro

モデルと自分の手の形状は一致しないので、位置で手を同期しようとするとズレが発生する。
なので、Handsから受け取った点群から指の回転を計算してそれを各指に適用するのがよさそうな気が

MakihiroMakihiro

あくまでも依存性の注入だから、それぞれのスコープ内で単一の物を登録するのが良い感じ

MakihiroMakihiro

いつの間にか、パラメータを引数で渡すみたいな使い方をしそうだったけど、機能を抽象化して分離するのが主なので、パラメータを渡すみたいな使い方は良くないな

MakihiroMakihiro

依存性を注入するには注入先がスコープ内にいる必要があるけど、大規模なプロジェクトでもDIってうまく運用できるものなのかな?

MakihiroMakihiro

TimelineでPlayableAsset.CreatePlayableは、Timeline初期化時に各Clipごとに呼ばれる

MakihiroMakihiro

TimelineのPreview中で変更したテキストを元に戻すための処理を検証で書いているのだけど、ネストを追って1つずつSerializedObjectを作っていく必要があるっぽい感じがある。SerializedPropertyはあくまでもシリアライズされた値だから、FindPropertyRelativeだと恐らくUnityのオブジェクト参照の先までは追えない?

public override void GatherProperties (PlayableDirector director, IPropertyCollector driver)
{
	var controller = director.GetGenericBinding(this) as DialogueController;
	var so = new SerializedObject(controller);
	so.Update();

	var dialogueView = (DialogueView)so.FindProperty("m_DialogueView").objectReferenceValue;
	var dso = new SerializedObject(dialogueView);
	dso.Update();

	var nameText = (TextMeshProUGUI)dso.FindProperty("m_MessageText").objectReferenceValue;

	driver.AddFromName(nameText, "m_text");

	base.GatherProperties(director, driver);
}
MakihiroMakihiro

雑に書いたけど、UnityEditorの機能はちゃんとUNITY_EDITORで囲わないとビルドエラー出るよ

MakihiroMakihiro

AddressagblesでのAssetBundleの依存関係の基本を勉強中
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/AssetDependencies.html

MakihiroMakihiro

暗黙的な依存性は、そのAssetBundleにコピーされる。暗黙的な依存性をAddressableにすると、依存性がコピーされず、そのAssetBundleが読み込まれる

MakihiroMakihiro

シーンをAddressableにする時とか、そのシーンに含まれるAudioClipとかをAddressableにすると、複数のシーンで同じAudioClipを使っていても、そのAudioClipのAssetBundleが読み込まれる

MakihiroMakihiro

あ~、Analyze便利だ

SplashとRootシーンにはAssetBundleにコピーされるような参照は置かないようにしないとな

MakihiroMakihiro

unity_builtin_extra以外の重複した依存性は潰したけど、Splashに含めないといけないものはどうするか…?
Splashにはメインのアートワークだけ置くことになるかな?

MakihiroMakihiro

効果音のプリロード、シーンのロード時に一括でロードしたいけど、頻繁に使用されない物も丸々全てロードされるのは困る。一方で、再生する瞬間にロードを挟むと、ロードで効果音の再生に遅延が発生してしまうので避けたい。プリロードの仕組みを作りたいけど、どう設計すればよいのか、まだ分からない

MakihiroMakihiro

Eventsystemって結構Rootシーンにポン置きしてるけど、SelectedGameObjectみたいななものの対応を考えると、ちゃんとシーンごとに置かないといけないっぽいな…
スマホで動くものしか作ってきてないので、Navigation周りは何も分からん

プレハブ作って各シーンに配置して、SelectedGameObjectだけOverrideの方針で行ってみようか

MakihiroMakihiro

シーンをAddressableにすると、InputActionsAssetもAddressablesに入るのか

MakihiroMakihiro

TextMeshProSettingsでFontAssetが暗黙的にResourcesに組み込まれるの、結構困るな…

MakihiroMakihiro

ポップアップスタック管理基盤での優先度の処理どうするか?
エラー系ポップアップの上に一般ポップアップを出したくないわけだけど、仮にその処理が呼び出される場合、どう処理するか?例外?

うーん、どこかで止まってくれるのかな。osrtingOrderとかSortingLayerは現在開かれている物よりも上のものしかPushできない設計にするべきだけど、

MakihiroMakihiro

エラー系ポップアップが出ているっていることは、どこかで例外が出たり、returnが発生しているはずなので例外投げるだけで信じて待つだけでも大丈夫かな?

m_PopupStack.Push(popup,option)以上のことはしたくないよね

MakihiroMakihiro

Rx見てて知ったのだけど、ExceptionDispatchInfoってやつは、catchした例外をブロックの外でスタックトレースを保持したまま再スローするための機能らしい。

public void OnError (Exception error)
{
    ExceptionDispatchInfo.Capture(error).Throw();
}

おそらく以下と同じ意味

try {

}
catch {
    throw;
}
MakihiroMakihiro

TextMesh Proよ、ResourcesでFontAssetに依存するのをやめておくれ

MakihiroMakihiro

上のつぶやきで既に同じ発言してたので、よっぽどイヤなのだろう
特にUnity推奨パッケージで、こういう設計されていると困ってしまう

MakihiroMakihiro

ShaderGraphでSpriteAtlas関連の厄介な現象に遭遇
https://twitter.com/makihiro_dev/status/1682711812226301952

MakihiroMakihiro

By Designって言い張っているけど、これは仕様じゃねぇ!
ロードマップに入っていることを望む

MakihiroMakihiro

特殊な対応をして、変な沼にハマってしまうのも困るので、よっぽど容量がカツカツとかでなければ、関連のSpriteだけSpriteAtlasから除くぐらいの対応で済ませた方が良い

MakihiroMakihiro

UniTaskのキャンセルベストプラクティス、調べてもほぼ見つからなかった記憶があるので、自分でベストプラクティスを蓄積していくしかない

MakihiroMakihiro

まず非同期関数では、まず最初に例外チェックをするのが定石になっている。
これは関数の契約の観点からそうしてる。

その次に、returnチェック。関数を動かすべきではない状態の時とかにreturnしてる。ただ、ほとんどの場合は明確に例外投げて、代わりにチェック関数でチェックしてもらうのが良いと思う。

最後、「関数動かせるぞ!」ってなったときにcancellationToken.ThrowIfCancellationRequested()でキャンセルチェックする。

void HogeMethod (Hoge hoge, CancellationToken cancellationToken = default) {
    // 例外チェック
    if (hoge == null) {
        throw new ArgumentNullException(nameof(hoge));
    }

    // 状態チェック
    if (m_IsHoge) {
        return;
    }

    cancellationToken.ThrowIfCancellationRequested();

    // Do something...
}
MakihiroMakihiro

もしコンポーネントであれば、this.GetCancellationTokenOnDestroy()と合わせて、linkedTokenを生成している。

コンポーネントの関数は原則、コンポーネントの寿命に従った方が良いと考えているから。

CancellationToken linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.GetCancellationTokenOnDestroy()).Token;
linkedToken.ThrowIfCancellationRequested();
MakihiroMakihiro

あと、関数の最後にもキャンセルチェックを入れるようにしている。

await Hoge(cancellationToken);

HogeHoge();

こういう処理だったとき、Hogeが仮に最後までやりたい処理を出来ていたとしても、HogeHogeは呼んでほしくないため。

MakihiroMakihiro

非同期関数を呼び出すときにSuppressCancellationThrowを使うかどうかだけど、基本的には素のUniTaskのままでキャンセル処理するようにしている。

なんかのスライドで、「try~catchのオーバーヘッドを気にして、SuppressCancellationThrowを使う」という旨の話があったけど、実際のところキャンセル時のオーバーヘッドってどれほどのものだろう?

MakihiroMakihiro

UnityでNull許容参照型を使いたいけど、UnityEngine.Objectはこれに移行できるのだろうか…?

MakihiroMakihiro

UniTaskのChannel<T>が単一消費者ということで、UniRxのSubjectと同じような使い方ができないために、Subbject<T>的に扱いながら、IUniTaskAsyncEnumerable<T>を公開するためのChannelのラッパーを定義した

public sealed class ConnectedChannel<T> : IDisposable
{

	readonly Channel<T> m_Channel;
	readonly IConnectableUniTaskAsyncEnumerable<T> m_Publish;
	readonly IDisposable m_Connection;

	public ChannelReader<T> Reader => m_Channel.Reader;
	public ChannelWriter<T> Writer => m_Channel.Writer;

	public IUniTaskAsyncEnumerable<T> Publish => m_Publish;

	public ConnectedChannel (CancellationToken cancellationToken = default)
	{
		m_Channel = Channel.CreateSingleConsumerUnbounded<T>();
		m_Publish = m_Channel.Reader.ReadAllAsync(cancellationToken).Publish();
		m_Connection = m_Publish.Connect();
	}

	public void Dispose ()
	{
		m_Channel.Writer.TryComplete();
		m_Connection.Dispose();
	}
}
MakihiroMakihiro

うげ、PoolableScrollViewを実装しているけど、uGUIのNavigationを明示的に指定することを考えると、思った以上にちゃんと設計しないといけない

MakihiroMakihiro

PoolableScrollView、Viewport内での事前計算Rectの可視性チェックで実装しているけど、0~1と要素のインデックスでRectを求めて、それを可視性チェックする方法もあるんだなと知った

事前計算はシンプルなレイアウト(Vertical, Horizontal, Grid...)と、ViewportとContentのオフセット計算でサクッと使えるけど、ちょっとおしゃれなレイアウトをしたい場合は0~1とインデックスを使った動的な計算を採用するのもありかも?

MakihiroMakihiro

シーンのエントリーポイント、責任が肥大化しがちだけど、やはりPresenterとか作って機能を分散管理した方が良いか…

しかし、まだフレームワークが固まらないので、一旦責任集中させたまま様子を見てみる

MakihiroMakihiro

Presenterとしてイベントとか登録するのを分散させるのは避けた方が良いな。Presenterで使う機能とかを分離していった方が良い

MakihiroMakihiro

UniRxとかUniTaskで使えるオペレーター類とイベント登録に対しての扱いが、自分の中で定まったのでメモ。

Subscribeでは原則ラムダ式を使わない

なんでかというと、イベント管理の文脈とロジックの実装がごっちゃになっちゃうので。「イベント管理をする場所、ロジックを書く場所、分けるべきでしょう」というところに落ち着きました。

readonly SerialDisposable m_Subscriptions = new();

void OnEnable ()
{
	CompositeDisposable disposables = new();

	m_TitleView.OnLoadGameClicked.SubscribeAwait(OnClickLoadGame).AddTo(disposables);
	m_TitleView.OnNewGameClicked.SubscribeAwait(OnNewGame).AddTo(disposables);
	m_TitleView.OnOptionClicked.SubscribeAwait(OnClickOption).AddTo(disposables);

	m_Subscriptions.Disposable = disposables;
}

void OnDisable ()
{
	m_Subscriptions.Disposable = null;
}

原則オペレーターを使わない

言葉足らずなので、もう少し具体的に言うと、「その文脈でSubscribeに連なる形でオペレーターを使う」ことは原則避ける。

m_View.OnSelected
    .Where(x => x != null)
    .Subscribe(x => ...)
    .AddTo(disposables);

例えば、上記のような記述は避ける。また、Subscribeにラムダ式を使わない原則も適用しているので、ロジックとイベント管理を分離する意味でも、そこは分けている。

オペレーターを使うのは基本的に、IObservable<T>IUniTaskAsyncEnumerable<T>を発行する側のみ。

public IUniTaskAsyncEnumerable<Data> OnSelect => m_SelectButton.OnClickAsAsyncEnumerable()
	.Where(_ => m_Data != null)
	.Select(_ => m_Data);

ただ、たまにSelectManyとか、ロジックが長くなってしまいそうな処理の場合は、積極的にオペレーターを使う。

MakihiroMakihiro

ゲームのホーム画面のメニューポップアップを閉じたときのポーズ終了処理を書いているのだけど、オペレーターを使った方が分かりやすいパターンが出てきたな…

ターゲットは「メニューを閉じたとき」なので、明らかにオペレーターでやった方が定義として分かりやすい。

void OnEnable ()
{
	CompositeDisposable disposables = new();

	m_GameOptionsPopup.Popup.State
		.WithoutCurrent()
		.Where(state => state.Is(VisiblityState.Hide))
		.SubscribeAwait(OnMenuClose)
		.AddTo(disposables);

	m_Subscription.Disposable = disposables;
}

UniTask OnMenuClose (VisiblityAnimationState _, CancellationToken cancellationToken)
{
	// TODO: ゲームの流れを再開する
	return UniTask.CompletedTask;
}

あー、これはポップアップの拡張メソッドとかで、OnCloseコールバックが定義されているべきだけど、現在そういうのは実装していないからここの定義で発行しないといけないという認識になるな。

これは使う側での条件設定というよりは、(本来)発行する側の条件だ。まぁ、このパターンになったら拡張メソッド作った方が良さげ

MakihiroMakihiro

これで解決!

m_GameOptionsPopup.Popup.OnClosed()
	.SubscribeAwait(OnMenuClose)
	.AddTo(disposables);

発行側の問題だったね

MakihiroMakihiro

3DのオブジェクトとEventSystemを紐づけるの、PhysicsRaycasterを使えばいいっぽいな。いつもPhysics.Raycastを使っていたので、もうすぐでUnity歴10年なのに使ったことがなかった

MakihiroMakihiro

OpenUPMで自分のパッケージの更新を確認してみたら、想像以上に多い446/monthダウンロードで「おぉ…」ってなった。作ったゲームのダウンロード数よりも多いや
https://openupm.com/packages/com.mackysoft.serializereference-extensions/

皆も使おう

MakihiroMakihiro

つっても元のアイデアはQiitaの他の人の投稿なので、完全に自作のXPoolや、今作っているゲームと並行で開発中の画面管理フレームワークを伸ばしていきたい所存。

開発中の画面管理フレームワークは、拡張性高・使い易さ高・堅牢性高の最強ライブラリになるよ

MakihiroMakihiro

GitHub Actionsのワークフローを久々に触るたびに地獄を見ている

MakihiroMakihiro

自動プッシュの権限のメモ

  1. Developer Settings/Personal access tokens/Fine-grained tokensで必要な権限のトークンを作る(今回はプッシュがしたいので、リポジトリのReadWrite権限を付与した)
  2. リポジトリのSecretsに登録
  3. ワークフローで、そのトークンを使用する

期限(30日~90日。無期限は非推奨)があるので、期限が切れたらトークンを再発行してSecretに登録し直す

MakihiroMakihiro

マスターデータの定義に変更ががあった時、MasterMemoryとMessagePackのコードジェネレーターを実行して自動コミットするワークフローを作った

MakihiroMakihiro

出来れば、Unityのmetaを生成する必要があるのだけど、ここに時間使っても仕方ないので、プルしてUnity上でリフレッシュしてmeta生成でいい

MakihiroMakihiro

CEDECでUI Toolkitの今を見たけど、やはり実践運用するには制限が厳し過ぎるので、uGUIを置き換えるのはあと2~3年必要そう。しかし、それだけ時間が掛かっている間に、AIで根本のワークフローが変わっている可能性もなくはないのが今の世界である

MakihiroMakihiro

CIの為のUnityの手動アクティベーション、いつの間にかPersonalライセンスがサポートされなくなっていて、途方に暮れている

MakihiroMakihiro

メモがてら貼っておく。
秘伝のタレみたいなことになっているので、CommitとPush周りを自分でも理解できていないけど

name: Run Code Generators

on:
  push:
    paths:
      - 'Prototype/Assets/_ProjectAssets/Scripts/Runtime/MasterData/Definitions/**'

env:
  NAMESPACE: MackySoft.UntitledNewGame.MasterData
  DEFINITIONS_PATH: Prototype/Assets/_ProjectAssets/Scripts/Runtime/MasterData/Definitions
  GENERATED_PATH: Prototype/Assets/_ProjectAssets/Scripts/Runtime/MasterData/Generated

jobs:
  generate:
    runs-on: ubuntu-latest

    steps:
      - name: Check Out Repository
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.ACTION_TOKEN }}

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.x'

      - name: Install MasterMemory Code Generator
        run: dotnet tool install -g MasterMemory.Generator

      - name: Install MessagePack Code Generator
        run: dotnet tool install -g MessagePack.Generator

      - name: Run MasterMemory Code Generator
        run: dotnet mmgen -i "$DEFINITIONS_PATH" -o "$GENERATED_PATH" -c -n $NAMESPACE

      - name: Run MessagePack Code Generator
        run: mpc -i "$DEFINITIONS_PATH" -o "$GENERATED_PATH/Formatters" -n $NAMESPACE
        
      - name: Check for changes
        id: check_changes
        run: |
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add -A
          git diff --staged --exit-code || echo "changed=1" >> $GITHUB_OUTPUT

      - name: Commit files
        if: steps.check_changes.outputs.changed == '1'
        run: |
          git commit -m "feat: Generate MasterMemory and MessagePack code" -a
          echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

      - name: Check sha
        run: echo "SHA ${SHA}"
        env:
          SHA: ${{ steps.commit.outputs.sha }}

      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.ACTION_TOKEN }}
          branch: ${{ github.ref }}
          tags: false
MakihiroMakihiro

次は、.xlsxを読み込んで、MasterMemoryのバイナリデータを自動生成するやつ作る

MakihiroMakihiro

SourceGenerator、そもそもUnityから離れて純粋な.NETのクラスライブラリを作るのが初めてなので結構苦戦している。しかし個人開発は、「いかに後の管理コスト・実装コストを下げるか」なので、SourceGeneratorをちゃんと作っておきたいところ。

そして、ChatGPTは知らないことの雰囲気を掴むのにちょうど良い。そのまま生で使える答えは今のところ出せない(出されたら失職)けど、知らないことの雰囲気を掴むのにちょうど良い

MakihiroMakihiro

GitHubActionsとかほぼ触らない人間が、ChatGPTでワークフロー出力して本格的な開発ムーブできるの強い

MakihiroMakihiro

SourceGenerator、触り始めるまで「partialを拡張するしかできないのか?」とちょっとだけ思っていた(partialを拡張する感じの記事が多かったので)

実際のところ、文字通りソースをジェネレートできるので大体のことはできる

MakihiroMakihiro

ソースUnityでジェネレート出来た
あとは、実際に使うための設計とか実装を詰める

MakihiroMakihiro

次は、生成したITableReaderTableReaderAttributeで取得して、パスにあるxlsxを見つけて、マッピングして、DatabaseBuilderに追加してデータベースをビルドする

MakihiroMakihiro

各アセンブリで実行されるので、ジェネレートする型はinternalじゃないと競合起こすっぽい

MakihiroMakihiro

SourceGeneratorで生成した型からCustomAttributesを取得したいけど、扱いがかなり特殊っぽくて、IsDefinedやGetCustomAttribute(s)が上手く機能しない

MakihiroMakihiro

ようやくSourceGeneratorでxlsx読み込み用のコードを生成して、MemoryDatabaseをビルドするところまで行けた。次は定義したマスターデータからxlsxを生成できるようにしたいのと、ScriptableObjectでの変更をxlsxにエクスポートできる機能が欲しい

MakihiroMakihiro

EasySaveのSaveRaw/LoadRawを使えばMessagePackのシリアライザをセーブデータに使えそうだな。セーブデータのIOはオレオレ実装すると面倒なことになるだろうし、かといってEasySaveのデフォルトのシリアライザは気が引けるし

MakihiroMakihiro

今までES3Fileを使ってたけど、ES3FileのRaw関数を使う場合は内部的にJSONでデシリアライズしてDictionaryにキャッシュしているからMessagePackでシリアライズしたbyte配列を渡すのはできないようだ。

代わりに単純なES3.SaveRaw/ES3.LoadRawを使えば、純粋なbyte配列のデシリアライズができそう

MakihiroMakihiro

YAMLのワークフローを書くたびに、ChatGPTのありがたみを噛みしめる

MakihiroMakihiro

Unityのライブラリのドキュメント生成にはDocFXを使っているけど、ドキュメントのリファレンスブランチはmainから切ってやった方がいいな。ドキュメント生成の為にcsprojとかを.gitignoreから外す必要があるのだけど、csprojmainに置いておくと不便って言われるわ

MakihiroMakihiro

mainからdocumentationブランチ切って、そっちで.gitignoreから.csprojを外す。で、ドキュメントを更新したいときにmainからマージしてきて、documentationでは、csprojの更新をプッシュする。でFA

MakihiroMakihiro

循環参照はな~、その循環参照を生んでるコード全体が一つの神クラスみたいなもので、神クラスをpartialでぱっと見は見やすいようにしただけなんだよな

MakihiroMakihiro

UnityのPlaymodeテストはSetup時にシーンの初期化をしてくれないようなので、対策としてUnitySetupでアンロード処理を挟む

MakihiroMakihiro

InputSystemのコールバックとUIからの入力をどうやって併用するか問題

MakihiroMakihiro

OnScreen○○系のコンポーネントでUIの入力をInputSystemに伝えて、制御ロジックではそっちを購読する感じかなー

MakihiroMakihiro

具体的な問題をいうと、Back系のボタンをuGUIで配置したとき、Viewで参照しておいてそれをViewのイベントとして公開するか?それか、InputSystemを介したイベント購読で解決するか?

MakihiroMakihiro

Missing Scriptを取り除く簡単な拡張

using UnityEditor;
using UnityEngine;

public static class RemoveMissingScriptsUtility
{

	[MenuItem("Tools/Remove Missing Scripts Recursive")]
	private static void RemoveMissingScriptsRecursive ()
	{
		var gameObject = Selection.gameObjects;
		foreach (var go in gameObject)
		{
			RemoveMissingScriptsRecursive(go);
		}
	}

	private static void RemoveMissingScriptsRecursive (GameObject gameObject)
	{
		GameObjectUtility.RemoveMonoBehavioursWithMissingScript(gameObject);
		foreach (Transform child in gameObject.transform)
		{
			GameObjectUtility.RemoveMonoBehavioursWithMissingScript(child.gameObject);
			RemoveMissingScriptsRecursive(child.gameObject);
		}
	}
}
MakihiroMakihiro

GetFullPathで相対パスを処理できることを知った

const string kTablesPath = "../MasterData";
Path.GetFullPath(Path.Combine(Directory.GetParent(Application.dataPath).FullName, kTablesPath));
MakihiroMakihiro

InputSystemのコールバックをR3.Observableに変換する簡単な拡張

public static class InputSystemObservableExtensions
{
	public static Observable<InputAction.CallbackContext> PerformedAsObservable (this InputAction source, CancellationToken cancellationToken = default)
	{
		return Observable.FromEvent<InputAction.CallbackContext>(x => source.performed += x, x => source.performed -= x, cancellationToken);
	}

	public static Observable<InputAction.CallbackContext> StartedAsObservable (this InputAction source, CancellationToken cancellationToken = default)
	{
		return Observable.FromEvent<InputAction.CallbackContext>(x => source.started += x, x => source.started -= x, cancellationToken);
	}

	public static Observable<InputAction.CallbackContext> CanceledAsObservable (this InputAction source, CancellationToken cancellationToken = default)
	{
		return Observable.FromEvent<InputAction.CallbackContext>(x => source.canceled += x, x => source.canceled -= x, cancellationToken);
	}
}
MakihiroMakihiro

SpriteAtlas、IncludeInBuildが無効の場合はSpriteAtlasManagerで依存性解決しないとロードされないっぽいね
シーン上に参照を置いたままだと、シーン開始時に速攻でSpriteAtlasManagerで解決しないといけないので、デバッグとか狩野プレイの時は注意

MakihiroMakihiro

InputSystemのInputAction.controls、Schemeが有効じゃないとSchemeに紐図いているInputControlは含まれないんだね

static InputControl GetPreferredInputControl (InputAction action)
{
	Debug.Log(action.name); // TabLeft
	Debug.Log(action.controls.Count); // 0。Gamepadでのみ有効なコントロールしかないので
	for (int i = 0; i < action.controls.Count; i++)
	{
		InputControl control = action.controls[i];
		if (control.device.enabled)
		{
			return control;
		}
	}
	return action.controls[0];
}