つぶやき
スクラップはめちゃくちゃ手軽で良き
何が良いかというと、
- 「ちょっとしたコードのメモをしたいけど、Twitterはコード書けないんだよなー」
- 「Qiitaは一つの記事をガッツリ書く感じなんだよなー」
が無くなる
PRを介してのレビューだとモグラたたきになってしまいがちなので、Live Shareをやってみる
LiveShare、今は割とバグが多い感じ
- 同期切れ
- コードの色がカオスになる
GitHub Discussionsで社内の質問・議論のログを残す仕組みを設計中
- 社内チャットでの議論が流れてしまうと、「あれどうなったっけ?」でチャットを遡るのが大変
- 流れてしまうの良くないから、議論で結論が出た後はスプレッドシートで議事録的なものをまとめるようになった
といった感じなのだけど、議論内容を構造化してスプレッドシートに書き直すのがまぁめんどくさい
達成したいのは、スプレッドシートからの完全移行
- 過去ログをデータベース化して、検索性を向上させる、流れを追いやすくする
- 埋もれてしまう調査・知識の蓄積
- 議論内容を構造化して議事録にする手間の解消
- チャットの議論内容をスプレッドシートに書き込む
- リーダーに「こんな感じにまとまったので、確認お願いします!」という連絡をチャットで行う
現在の構造だと、「チャット->スプレッドシート->チャット」といった感じの反復横跳びが発生している
だから、チャットとスプレッドシートの役割をDiscussionsで統合出来たら楽だなーと思って動いてる
Discussionsを運用するうえでここら辺が懸念になってくる
- 何を起票するかの判断基準
- ログに残すべきではないディスカッションが作られてしまうことによるノイズの増加
- 結局みんながチャットで済ませてしまわないか
- 結局チャットとの反復になってしまわないか
- 通知
Discussions、通知はメンションを送ればアプリのプッシュ通知が行くので大丈夫そう
DIscussionsへの導線をどうするか
- Chatworkのチャンネルの概要にDiscussionsのリンクを貼る
- Discussionsで話した方が良い話題が出たら、起票してDiscussionsへの移動を促す
参考
- 重要な指摘があったら、質問者が一問一答形式で書ききっちゃうのはアリ
Discussionsの運用開始したけど、ひとまず「埋もれてしまう調査・知識の蓄積」は機能している感じ
Discussions、議論が捗る。
- 並列で議論できる(同時に出てきた懸念を別々の議論として展開し、問題を分けて議論できる)
- 試行して結果が出るまで結論を保留に出来る(チャットだとタイムラインに流れて忘れ去られる)
これ強い
回答マーク機能も便利だけど、回答をマークするタイミングが地味に難しいから何とかしたい
Discussionsの運用も、よく考えたらアプリの運用と変わらないなー
チームのみんなが利用する導線があって、分かりやすくて、便利で、もう一回使いたいと思えるように設計すればいいんだな
試行して結果が出るまで結論を保留に出来る(チャットだとタイムラインに流れて忘れ去られる)
これ強い
チャットだと非同期モドキだったのが、Discussionsで完全に非同期になった感じ
Discussionsの運用は以下のスクラップに移動
Zenn、イイ
スクラップの使い方を記事にすると面白いかもしれん
日記にするのも面白いかもしれん
インプット・アウトプットのメモね、これはやってるな
設計の勉強はインプット、Discussionsの運用メモはアウトプットといった感じになっている
ウルトラワイドモニター買った
とりあえず便利になるんじゃないかと思っていること
- UnityとVisual Studioを同時に表示(Visual Studioのデバッグ機能が本格的に活用できるはず)
- 画面を広く使いたい機能を使い易くなる(ノードエディタなど)
- Blenderでのモデリング効率UP(UV展開、参考画像参照)
- 英語のゲームをしながら、片方のウィンドウで機械翻訳して学習効率UP
- UMLの設計を見ながらコーディング
とりあえず、Blenderで作業しながら参考画像をブラウズできるのは便利なのは確認
ウルトラワイドで画面をフルに使いたい場合、専用のレイアウトを作る必要があるからそこをどうするか
基本的に1つのウィンドウをウルトラワイドで表示することはほとんどなくて、2ウィンドウを分割で表示するのがメインになる
今のところBlenderでウルトラワイドが輝きそうな場面は、
- UV展開
- ノードエディター
- 2つのカメラで確認しながらモデリング
GitHub vs Notion について考えたい
Notionはとにかく運用設計が難しい
真ん中で分けて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);
}
}
tが0だったらminOffsetの値になるよ
tが0.25だったらminOffsetとcenterの中間値になるよ
tが0.5だったらcenterの値になるよ
tが0.75だったらcenterとmaxOffsetの中間値になるよ
tが1だったらmaxOffsetの値になるよ
あー、目のトラッキングうまくいかなくなったと思ったら、アニメーションのAvatarにEyeをちゃんと割り当ててたからHumanoidアニメーションでEyeも一緒に更新されるようになってたぁ_(:3 」∠)_
指トラッキングは、指の最終点および第一関節からの方向をターゲットとして設定してやればいけるか…?
DI難しい。規模感とかが掴めない
あくまでも依存性の注入だから、それぞれのスコープ内で単一の物を登録するのが良い感じ
いつの間にか、パラメータを引数で渡すみたいな使い方をしそうだったけど、機能を抽象化して分離するのが主なので、パラメータを渡すみたいな使い方は良くないな
依存性を注入するには注入先がスコープ内にいる必要があるけど、大規模なプロジェクトでもDIってうまく運用できるものなのかな?
もうちょい使ってDIの勘所を探っていく
CIのセットアップをするたびに無限修正してる
TimelineでPlayableAsset.CreatePlayable
は、Timeline初期化時に各Clipごとに呼ばれる
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);
}
雑に書いたけど、UnityEditorの機能はちゃんとUNITY_EDITORで囲わないとビルドエラー出るよ
AddressagblesでのAssetBundleの依存関係の基本を勉強中
暗黙的な依存性は、そのAssetBundleにコピーされる。暗黙的な依存性をAddressableにすると、依存性がコピーされず、そのAssetBundleが読み込まれる
シーンをAddressableにする時とか、そのシーンに含まれるAudioClipとかをAddressableにすると、複数のシーンで同じAudioClipを使っていても、そのAudioClipのAssetBundleが読み込まれる
あ~、Analyze便利だ
SplashとRootシーンにはAssetBundleにコピーされるような参照は置かないようにしないとな
unity_builtin_extra以外の重複した依存性は潰したけど、Splashに含めないといけないものはどうするか…?
Splashにはメインのアートワークだけ置くことになるかな?
効果音のプリロード、シーンのロード時に一括でロードしたいけど、頻繁に使用されない物も丸々全てロードされるのは困る。一方で、再生する瞬間にロードを挟むと、ロードで効果音の再生に遅延が発生してしまうので避けたい。プリロードの仕組みを作りたいけど、どう設計すればよいのか、まだ分からない
Eventsystemって結構Rootシーンにポン置きしてるけど、SelectedGameObjectみたいななものの対応を考えると、ちゃんとシーンごとに置かないといけないっぽいな…
スマホで動くものしか作ってきてないので、Navigation周りは何も分からん
プレハブ作って各シーンに配置して、SelectedGameObjectだけOverrideの方針で行ってみようか
シーンをAddressableにすると、InputActionsAssetもAddressablesに入るのか
TextMeshProSettingsでFontAssetが暗黙的にResourcesに組み込まれるの、結構困るな…
ポップアップスタック管理基盤での優先度の処理どうするか?
エラー系ポップアップの上に一般ポップアップを出したくないわけだけど、仮にその処理が呼び出される場合、どう処理するか?例外?
うーん、どこかで止まってくれるのかな。osrtingOrderとかSortingLayerは現在開かれている物よりも上のものしかPushできない設計にするべきだけど、
エラー系ポップアップが出ているっていることは、どこかで例外が出たり、returnが発生しているはずなので例外投げるだけで信じて待つだけでも大丈夫かな?
m_PopupStack.Push(popup,option)
以上のことはしたくないよね
Rx見てて知ったのだけど、ExceptionDispatchInfo
ってやつは、catch
した例外をブロックの外でスタックトレースを保持したまま再スローするための機能らしい。
public void OnError (Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
おそらく以下と同じ意味
try {
}
catch {
throw;
}
TextMesh Proよ、ResourcesでFontAssetに依存するのをやめておくれ
上のつぶやきで既に同じ発言してたので、よっぽどイヤなのだろう
特にUnity推奨パッケージで、こういう設計されていると困ってしまう
ShaderGraphでSpriteAtlas関連の厄介な現象に遭遇
UniTaskのキャンセルベストプラクティス、調べてもほぼ見つからなかった記憶があるので、自分でベストプラクティスを蓄積していくしかない
まず非同期関数では、まず最初に例外チェックをするのが定石になっている。
これは関数の契約の観点からそうしてる。
その次に、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...
}
もしコンポーネントであれば、this.GetCancellationTokenOnDestroy()
と合わせて、linkedToken
を生成している。
コンポーネントの関数は原則、コンポーネントの寿命に従った方が良いと考えているから。
CancellationToken linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.GetCancellationTokenOnDestroy()).Token;
linkedToken.ThrowIfCancellationRequested();
あと、関数の最後にもキャンセルチェックを入れるようにしている。
await Hoge(cancellationToken);
HogeHoge();
こういう処理だったとき、Hogeが仮に最後までやりたい処理を出来ていたとしても、HogeHogeは呼んでほしくないため。
非同期関数を呼び出すときにSuppressCancellationThrow
を使うかどうかだけど、基本的には素のUniTaskのままでキャンセル処理するようにしている。
なんかのスライドで、「try~catchのオーバーヘッドを気にして、SuppressCancellationThrow
を使う」という旨の話があったけど、実際のところキャンセル時のオーバーヘッドってどれほどのものだろう?
UniTask.Timeout
ってタスクを止めてくれるわけではなかったのか…
TimeoutController
を使えと
UnityでNull許容参照型を使いたいけど、UnityEngine.Object
はこれに移行できるのだろうか…?
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();
}
}
うげ、PoolableScrollViewを実装しているけど、uGUIのNavigationを明示的に指定することを考えると、思った以上にちゃんと設計しないといけない
InputSystemUIInputModule.PointerBehaviour
、デフォルトではSingleMouseOrPenButMultiTouchAndTrack
だけど、複数ポインターを許してしまうと意図しない挙動を引き起こしやすくなりそうなので、SingleUnifiedPointer
にした方がよさそう
PoolableScrollView、Viewport内での事前計算Rectの可視性チェックで実装しているけど、0~1と要素のインデックスでRectを求めて、それを可視性チェックする方法もあるんだなと知った
事前計算はシンプルなレイアウト(Vertical, Horizontal, Grid...)と、ViewportとContentのオフセット計算でサクッと使えるけど、ちょっとおしゃれなレイアウトをしたい場合は0~1とインデックスを使った動的な計算を採用するのもありかも?
シーンのエントリーポイント、責任が肥大化しがちだけど、やはりPresenterとか作って機能を分散管理した方が良いか…
しかし、まだフレームワークが固まらないので、一旦責任集中させたまま様子を見てみる
Presenterとしてイベントとか登録するのを分散させるのは避けた方が良いな。Presenterで使う機能とかを分離していった方が良い
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とか、ロジックが長くなってしまいそうな処理の場合は、積極的にオペレーターを使う。
ゲームのホーム画面のメニューポップアップを閉じたときのポーズ終了処理を書いているのだけど、オペレーターを使った方が分かりやすいパターンが出てきたな…
ターゲットは「メニューを閉じたとき」なので、明らかにオペレーターでやった方が定義として分かりやすい。
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コールバックが定義されているべきだけど、現在そういうのは実装していないからここの定義で発行しないといけないという認識になるな。
これは使う側での条件設定というよりは、(本来)発行する側の条件だ。まぁ、このパターンになったら拡張メソッド作った方が良さげ
これで解決!
m_GameOptionsPopup.Popup.OnClosed()
.SubscribeAwait(OnMenuClose)
.AddTo(disposables);
発行側の問題だったね
3DのオブジェクトとEventSystemを紐づけるの、PhysicsRaycasterを使えばいいっぽいな。いつもPhysics.Raycast
を使っていたので、もうすぐでUnity歴10年なのに使ったことがなかった
OpenUPMで自分のパッケージの更新を確認してみたら、想像以上に多い446/monthダウンロードで「おぉ…」ってなった。作ったゲームのダウンロード数よりも多いや
皆も使おう
GitHub Actionsのワークフローを久々に触るたびに地獄を見ている
自動プッシュの権限のメモ
-
Developer Settings/Personal access tokens/Fine-grained tokens
で必要な権限のトークンを作る(今回はプッシュがしたいので、リポジトリのReadWrite権限を付与した) - リポジトリのSecretsに登録
- ワークフローで、そのトークンを使用する
期限(30日~90日。無期限は非推奨)があるので、期限が切れたらトークンを再発行してSecretに登録し直す
マスターデータの定義に変更ががあった時、MasterMemoryとMessagePackのコードジェネレーターを実行して自動コミットするワークフローを作った
出来れば、Unityのmetaを生成する必要があるのだけど、ここに時間使っても仕方ないので、プルしてUnity上でリフレッシュしてmeta生成でいい
CEDECでUI Toolkitの今を見たけど、やはり実践運用するには制限が厳し過ぎるので、uGUIを置き換えるのはあと2~3年必要そう。しかし、それだけ時間が掛かっている間に、AIで根本のワークフローが変わっている可能性もなくはないのが今の世界である
CIの為のUnityの手動アクティベーション、いつの間にかPersonalライセンスがサポートされなくなっていて、途方に暮れている
メモがてら貼っておく。
秘伝のタレみたいなことになっているので、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
次は、.xlsx
を読み込んで、MasterMemoryのバイナリデータを自動生成するやつ作る
SourceGeneratorを作ろうとしたらデバッグのTargetProjectにConsoleApp1が表示されなくて困っていたら、GPTくんに教えてもらったクリーン&ビルドで解決できた
SourceGenerator、そもそもUnityから離れて純粋な.NETのクラスライブラリを作るのが初めてなので結構苦戦している。しかし個人開発は、「いかに後の管理コスト・実装コストを下げるか」なので、SourceGeneratorをちゃんと作っておきたいところ。
そして、ChatGPTは知らないことの雰囲気を掴むのにちょうど良い。そのまま生で使える答えは今のところ出せない(出されたら失職)けど、知らないことの雰囲気を掴むのにちょうど良い
GitHubActionsとかほぼ触らない人間が、ChatGPTでワークフロー出力して本格的な開発ムーブできるの強い
SourceGenerator、触り始めるまで「partialを拡張するしかできないのか?」とちょっとだけ思っていた(partialを拡張する感じの記事が多かったので)
実際のところ、文字通りソースをジェネレートできるので大体のことはできる
ソースUnityでジェネレート出来た
あとは、実際に使うための設計とか実装を詰める
「Unityでコンパイルエラー起きる> <」ってやってたら、Unityが「Microsoft.CodeAnalysis 3.8を使えなー」って書いてるのに従って4.8くらいのやつからダウングレードしたら動いた
SourceGeneratorで生成した型からCustomAttributesを取得したいけど、扱いがかなり特殊っぽくて、IsDefinedやGetCustomAttribute(s)が上手く機能しない
ようやくSourceGeneratorでxlsx読み込み用のコードを生成して、MemoryDatabaseをビルドするところまで行けた。次は定義したマスターデータからxlsxを生成できるようにしたいのと、ScriptableObjectでの変更をxlsxにエクスポートできる機能が欲しい
EasySaveのSaveRaw
/LoadRaw
を使えばMessagePackのシリアライザをセーブデータに使えそうだな。セーブデータのIOはオレオレ実装すると面倒なことになるだろうし、かといってEasySaveのデフォルトのシリアライザは気が引けるし
今までES3File
を使ってたけど、ES3File
のRaw関数を使う場合は内部的にJSONでデシリアライズしてDictionaryにキャッシュしているからMessagePackでシリアライズしたbyte配列を渡すのはできないようだ。
代わりに単純なES3.SaveRaw
/ES3.LoadRaw
を使えば、純粋なbyte配列のデシリアライズができそう
MessagePackはインターフェースのシリアライズをサポートしている様。
ただ、単純にUnion
属性をインターフェースに重ねるのは気持ち悪い感じがするのでpartial interface
の出番
よくよく考えたらUnionの第一引数でID割り当てないといけないからpartialはダメだわ
YAMLのワークフローを書くたびに、ChatGPTのありがたみを噛みしめる
Unityのライブラリのドキュメント生成にはDocFXを使っているけど、ドキュメントのリファレンスブランチはmain
から切ってやった方がいいな。ドキュメント生成の為にcsproj
とかを.gitignore
から外す必要があるのだけど、csproj
をmain
に置いておくと不便って言われるわ
main
からdocumentation
ブランチ切って、そっちで.gitignore
から.csproj
を外す。で、ドキュメントを更新したいときにmain
からマージしてきて、documentation
では、csprojの更新をプッシュする。でFA
循環参照はな~、その循環参照を生んでるコード全体が一つの神クラスみたいなもので、神クラスをpartialでぱっと見は見やすいようにしただけなんだよな
UnityのPlaymodeテストはSetup時にシーンの初期化をしてくれないようなので、対策としてUnitySetupでアンロード処理を挟む
ログ
正しく1シーンになっている
センスの良いテスト用APIを作った
InputSystemのコールバックとUIからの入力をどうやって併用するか問題
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);
}
}
}
GetFullPath
で相対パスを処理できることを知った
const string kTablesPath = "../MasterData";
Path.GetFullPath(Path.Combine(Directory.GetParent(Application.dataPath).FullName, kTablesPath));
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);
}
}
SpriteAtlas、IncludeInBuildが無効の場合はSpriteAtlasManagerで依存性解決しないとロードされないっぽいね
シーン上に参照を置いたままだと、シーン開始時に速攻でSpriteAtlasManagerで解決しないといけないので、デバッグとか狩野プレイの時は注意
あと、Spriteの開店がおかしいバグ的なものがあったのでAllowRotationは切っておいた
Canvas要素にはAllowRotation入れるなってリファレンスに書いてあったわ。汎用のアトラス効率化ってわけではないらしい
Navigathenaの記事ではSceneEntryPointを起点に出来るから楽だと書いたものの、Presenterの処理が全部入ってくるのはなかなかしんどいのではと考えている
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];
}