GoでメソッドチェインのMapReduceライブラリ作ろうか悩んだ
全部For文で筋力プレイしたくない
ってことで、MapReduce系のライブラリ作ろうかと悩んで下記のコードをサンプルで作った
Go Playground - The Go Programming Language
// You can edit this code! // Click here and start typing. package main import "fmt" type Map[T any] struct { value []T err bool } func NewMap[T any](v []T) *Map[T] { return &Map[T]{value: v, err: false} } func errMap[T any]() *Map[T] { return &Map[T]{nil, true} } func copyMap[T any](v []T, e bool) *Map[T] { return &Map[T]{value: v, err: e} } // var errMap Map[T] = &Map[T]{value: nil, err: true} func (s *Map[T]) Foreach(fn func(T) T) *Map[T] { if s.err { return errMap[T]() } nv := make([]T, len(s.value)) for i, v := range s.value { nv[i] = fn(v) } return copyMap(nv, false) } func (s *Map[T]) Ok() *Map[T] { if s.err { return errMap[T]() } return copyMap(s.value, false) } func (s *Map[T]) Ng() *Map[T] { if s.err { return errMap[T]() } return copyMap(s.value, true) } func (s *Map[T]) Get() ([]T, bool) { return s.value, s.err } func main() { hoge := NewMap[int]([]int{1, 2, 3, 4, 5}) result, err := hoge.Ok().Ok().Foreach(func(v int) int { return v * 2 }).Get() if err { fmt.Println("Error") } else { fmt.Printf("%v\n", result) } }
思い付きで書いたコードだけど
他のMapReduceライブラリだと、リフレクションを使うもの
コード自動生成系
があったが
リフレクションは最終手段にしたい事、interface{}になりキャストが発生
実行速度や動作不安定さが怖いので使いたくない
コードジェネレート系も、色々なからみで使いたくない
ので、ジェネリクスを使いサンプル実装してみた
まず、メソッドチェインをしたかったが、Goの基本は 値とerrのタプルを返却するものなので
タプルだとメソッドチェインが出来ないため
ジェネリクスを使い2値をもつStructを作りメソッドチェインを実現させ
Goには例外がないので、メンバ変数でErrを保持し処理をし
最後にGetメンバで、値とErr値を取得
最低限の動作はできそう
本当は遅延評価とかしたいんですけどね・・
Goで遅延評価考えたくないし、そもそもクロージャの記述がだるく、関数型に向いてないよねえ
例外処理的な事考えずに素直に関数型っぽくする方が楽かなあ・・
Docker-OSX再び
Docker-OSXはKVM上で動いているようだ
KVMはLinuxカーネルで動くように出来ているため
当然Linuxからは期待できる
MacOSからDocker-OSXを動かす事は現時点では絶望的と思った
問題はWindowsのWSL2
結論から言うとWindows11のWSL2からは可能らしい
wsl.confに nestedVirtualization=true を設定が必要
Windows10のWSL2では、以前Microsoftから、insider previewにてnestedVirtualizationに対応するというアナウンスがあったが
現在はそのアナウンスがなくなり、Windows10では諦めた可能性がある
そのため、現時点ではWindows10では無理かもしれない
Windows11のマシンを作り、テストを行おうかなあ?
エンジニアトーク YoutubeLive1回目終了&Windows10の上にDockerでXCODEビルド&デバッグ環境を作りたい
エンジニアトークのYoutubeLiveを毎週水曜20時~行う事にしました
初回なので、企画の説明や自己紹介的なものをメインにお話ししました
企画の趣旨としては、自分の興味ある話題を中心に質問や相槌を受けながらトークをする
私が一般的な会話が出来ず、エンジニアの話題で盛り上がるのが一番楽しいので
誰も来なければ私の独壇場になってしまうので、誰か来て話題ふってください・・
そして、とても収穫になった話題
視聴者さんからの相談で、Dockerを使ってスマホ開発環境をローカル汚染せずに作りたいと
Androidの場合は、LinuxのDockerイメージにAndroidStudioをインストールし、TCP接続で実機あるいはローカルのエミュに接続出来る事は想像できる
一応、以前Docker上のLinuxにxrdpをインストールして、リモートマシンから接続しGUIを使える事は確認している
ただ当時は、動きが少し怪しかったけど、今は直ってればいいなあ
Windows11だとWSL2でネイティブにGUI対応しており、WindowsのWindowと同じように扱えるらしい
興味ある
ただし、XCODEをDockerで動かす事は考えた事がなかった
HakintoshやOSX-Dockerという、MacOSを他のOSで動かすプロジェクトは知っていて
動かせる事はしっていたものの、安定稼働するか不安だし、そもそも合法とはいえない
Legality of Docker-OSX · Discussion #267 · sickcodes/Docker-OSX · GitHub
個人で使うにはあれだが、仕事で使う事が現状どうなのか?と言われるとねえ
ただ、実際にMacbookも色々あって5台ぐらい所持しているし、個人で遊ぶ分には許してね!とは思わなくもないので
ちょっと調査してみたい
Docker-OSX用のGPUドライバも動くか気になるし、色々なポイントはある
Windows11上のWSL2でしか動かないという話だが、xrdp使ってGUI起動できる可能性もある
それとは別に、Windows11のWSL2は、Linux GUIをWindowsと同列に実行できて興味もある
WSL で Linux GUI アプリを実行する | Microsoft Docs
もちろんWindows2022のWindowsコンテナも気になるし
AWSサーバレスを使いたい。 その資格はない、おぉその資格はない
モチベーション
プログラマとして、EC2なりECSにモノリシックなサーバを作る方が慣れているが
サーバレスを積極的に使いたい
どちらもメリットはあるが
サーバレス(ファンクション単位)にすると、ライブラリを使ったり管理が面倒だったり
デプロイ作業がやりにくかったりする
お互いの機能を呼び出すにもAPIで無駄なリクエスト投げ合ったり。。
ただし、小さい関数を作るには非常に向いているし、サーバ料金が安くなる事が多い
なんといっても、サーバ管理やスケーリング等が全て不要になる
昔はnode.jsが多かったが、今はランタイムも色々選べるようになったし
カスタムランタイムを使えば何の言語でも実行可能
これはやらなければならない
フレームワーク
サーバレスはデプロイが面倒
モノリシックサーバの場合はEC2なりコンテナを作り、ソースをコピーすればいいが
サーバレスだと、API Gatewayを設定したり、Lambdaの関数をUploadして設定したり
AWSコンソールで色々と設定が必要
デプロイも、関数毎にわかれているので、関数の数だけデプロイが必要
デプロイ忘れの関数が残ってると本当に大変
なので、何らかのフレームワークを使うのが良いと思う
今回はAWS限定で
CloudFormation
AWSの公式の IaaC(Infrastructure as Code)
要は面倒なAPI GatewayやLambda、あるいはDynamoDB等のデプロイを
スクリプトを書いて自動化できる
Terraformみたいなやつ
基本的にAWSのデプロイはこれを使うので下記のフレームワークもCFを使用している
Amplify、AppSync
GraphQLベースのサーバレスアプリケーション用のフレームワーク
という認識
SAM(AWS Serverless Application Model )
AWS公式のサーバレス用フレームワーク
下記のServerlessFrameworkと機能的によく似ている
が、AWS限定である
ServerlessFramework
オープンソースのサーバレスのデプロイやテストを行うフレームワーク
上記のSAMはAWS専用だが、こちらはAzure、GCP、ALIクラウド、テンセントクラウド・・に対応している
ServerlessFrameworkの方が進んでたが、最近はSAMも十分に機能があると思うのでどちらを使っても良い
ただ、ServerlessFrameworkの方が情報が多い気がするので、今回はこちらを使う
作るもの
API GatewayがWebsocket対応していて、使ってみたかったので、Websocketにする
まずはチャットサービスを作ろうと思う
準備
aws-cli、node、その他必要なものは入っている認識で
serverless-frameworkをインストール
serverlessコマンドでテンプレート作成やデプロイが出来るが、エイリアスとして sls slssが割り当てられている
sls コマンドでプログラムのテンプレートを作成可能
slsを入力すると
❯ AWS - Node.js - Starter AWS - Node.js - HTTP API AWS - Node.js - Scheduled Task AWS - Node.js - SQS Worker AWS - Node.js - Express API AWS - Node.js - Express API with DynamoDB AWS - Python - Starter AWS - Python - HTTP API AWS - Python - Scheduled Task AWS - Python - SQS Worker AWS - Python - Flask API AWS - Python - Flask API with DynamoDB Other
と、テンプレートを選ぶ事が可能だが、一覧にないものを選ぶには、-t オプションを使う
sls create -t aws-nodejs-typescript -n sample
と打つと、Typescriptを使った、sampleアプリのテンプレートが生成される
途中でAWSのCredencialを設定したり、IAMのロールが必要
IAMはAdmin権限のあるユーザーを作りその情報を入力すれば通った
今回はメニューより、Express API with DynamoDB を選びそこから改良する事に
Typescript化は今度やる
このテンプレートは、DynamoDBを使用し、REST APIにより usersで登録、user/:userId で情報取得
のようなものになっている
sls deploy で、AWSで上記の構成とコードのアップロードが出来る
以降は、毎回全部deployしなくとも関数単位でのdeployも可能
sls deploy function -f エントリポイント
簡単にGithubと連携する事も出来るっぽいが、それは後日しよう
ただし、今回はローカルでテスト実行しようと思う
serverless-offlineと、serverless-dynamodb-local をインストールする必要がある
このあたりは、npmコマンドでインストールする
これらはServerlessFrameworkのプラグインである
npm i serverless-offline serverless-dynamodb-local
dynamodb-localに関しては本体(AWS提供)をインストールする必要がある
sls dynamodb install
ただし、dynamodb-localはJavaで動くのでJREが必要
そして、上記プラグインを serverless.ymlに記載する
plugins: # - serverless-webpack - serverless-dynamodb-local - serverless-offline
webpackは今は使わないが、今後使う予定
プラグインは順番も重要なので必ず上記の順番で書くように
sls offline start
でオフラインで動くはずである・・・ が、上手く動作しない
シンプルなテンプレート(DynamoDBを使わない)だとちゃんと動作するので
dynamodb-localの動作に問題があるようだ
試しにサーバにデプロイしたらちゃんと動いた
ただし、色々と serverless.ymlやコードをいじったり、AWSのリージョン指定したりCredencialをいじったらそのうち動くようになった
まあ、WindowsでWSL2上で動かしたりしているので、内部IPアドレスまわりの問題かもしれない
dynamodb-localの動作確認は aws dynamodb list-tables --endpoint-url http://localhost:8000 などで出来る。
Websocket化
これを見ながら書けばいけた
とりあえず覚えておくこととして
serverless.ymlに
functions: connect: handler: onconnect/app.handler events: - websocket: route: $connect disconnect: handler: ondisconnect/app.handler events: - websocket: route: $disconnect sendmessage: handler: sendmessage/app.handler events: - websocket: route: sendmessage
こんな感じでRouteを指定し、それぞれのhandlerコードを書いた
あとは上記URLを参考に。。
sls offline start
でオフライン実行できる
ただし、RestAPIのようにCurlで叩けないので、Websocket用のツール wscatをインストールする
wscat -c ws://localhost:3001/dev
とすると、Websocketに接続し、プロンプト状態になる
{"message":"sendmessage", "data":"hello world"}
というデータを送るとレスポンスが返ってくる
ローカルでの動作も確認できた
やり残し作業
- TypeScript化
- 自動Deploy
- Client(S3とCloudFront あと、Reactかなあ?)
- ルーム機能(DynamoDBのキー変更)
- メッセージに発言者ID等を付与
- ログイン(名前入力&LocalStorage保存程度)
- ログインユーザ一覧
UNITY Editor拡張で、タイムラインをスクリプトから生成する
お題
タイムラインを自動生成しよう
トラックやクリップも外から作成してみます
それに伴い、エディタ拡張の勉強
メニューよりEditorWindowを表示
とりあえず最も汎用的な?メニュの拡張から
メニューの拡張
Editor拡張で最も使われると思うので、よーく覚えよう
UnityEditor.MenuItem - Unity スクリプトリファレンス
MenuItemを使う
ドキュメントの通り、非常に機能が豊富なので細かい事は説明しません
まず、Editor専用コードなので、Editorディレクトリを作りその下にコードを置く事
そして、Editor拡張は静的関数しか呼べないので、そういう設計にする事
主な機能をざっと
メニューアイテムの追加
[MenuItem("hoge/fuga")]
のように属性をつけると、メニューに hoge->fuga と追加される
メニュー名に日本語も使えます
当然そのメニューを選んだ時に、その関数が呼ばれます
ホットキーも簡単に付ける事ができます
また、Validateもする事が出来ますし、メニューの表示順番も変更できます
Validate関数を設定すれば、関数がFalseを返すと無効、Trueを返すと有効になります
ヒエラルキー
MenuItemのパスの頭をGameObjectにすると、ヒエラルキーで右クリックした際のメニューに追加できます
[MenuItem("GameObject/CustomEditor")] private static void Create() { Debug.Log("Menu"); }
ヒエラルキー右クリックでちゃんと表示されました
もちろん、メニューの のGameObjectにも表示される
Assets
MenuItemのパスの先頭をAssetsにすると、Assetsビューのコンテキストメニューに表示される
[MenuItem("Assets/CustomEditor", false, 1)] private static void Create() { Debug.Log("Menu"); }
もちろん、メニューのAssetsにも表示される
CONTEXT
コンポーネントの 右上の3点アイコンを押したときのメニューを作れます!
もちろん、自作コンポーネントだけじゃなく、UNITYのデフォルトコンポーネントも可能です
[MenuItem("CONTEXT/PlayableDirector/CustomEditor", false, 1)] private static void Create() { Debug.Log("Menu"); }
今回はPlayableDirectorコンポーネントにメニューを追加しました
今回使うもの
今回は、選択したGameObjectにPlayableDirectorコンポーネントを追加したいので
ヒエラルキー上のGameObjectのコンテキストメニューに追加する
コンテキストメニュー
public class CustomEditorWindow : EditorWindow { [MenuItem("GameObject/CustomEditor", false, 1)] private static void Create() { // 生成 GetWindow<CustomEditorWindow>("CustomEditor"); }
ヒエラルキーのGameObjectメニューにCustomEditorを追加します
そして、CustomEditorWindow(自分のクラス)を呼び、GUIを表示させます
今回は EditorWindowを継承し、ウインドウを表示させます
EditorWindow
private void OnGUI() { using (new GUILayout.HorizontalScope()) { targetGo_ = Selection.activeGameObject; if (GUILayout.Button("CreateTimeline")) { // ここにボタン押したときの処理が入る } } }
とりあえずボタンが1個あるだけの単純なウインドウ
以上でEditor拡張のおさらい終了
Timeline等をコードから生成
本題という事になる
GameObjectにPlayableDirectorのアタッチ
if(targetGo is null) { return; } // 選択中のGameObjectにPlayableDirectorコンポーネントをつける var director = targetGo.GetComponent<PlayableDirector>(); if(director == null) { director = targetGo.AddComponent<PlayableDirector>(); }
AddComponentを使って普通にPlayableDirectorを作成する
TimelineAssetを作成しAssetデータベースへ保存する
TimelineAssetを作成します
つまり、タイムラインの情報すべてを保存するものです
ScriptableObjectで作成します
ただし、このままでは、シーン上に作られます
実際の運用では、タイムラインはファイル(Asset)にして読み込んで使うと思うので
AssetDatabaseに登録します
拡張子は playableです
// TimelineAssetの作成 var timelineAsset = playableDirector.playableAsset as TimelineAsset; if (timelineAsset == null) { timelineAsset = ScriptableObject.CreateInstance<TimelineAsset>(); director .playableAsset = timelineAsset; } var path = "Assets/Resources/sample.playable"; AssetDatabase.DeleteAsset(path); AssetDatabase.CreateAsset(timelineAsset, path); AssetDatabase.SaveAssets();
AssetDatabaseの仕様で、ディレクトリが無い場合はエラーになるので、ディレクトリは事前に作っておく事
そして、既にAssetが存在する場合は上書きするとエラーになるので、事前にDeleteしておきます
SaveAssetは、即座にデータベースに反映するおまじないです
トラックの作成&バインディングを設定
今回はGroupトラックを作成し、そこにトラックを作成します
型は前回作った NewPlayableTrackです
Bindingにはゲームオブジェクトをつけます。今回はCubeという名前のゲームオブジェクトをつけます
var groupTrack = timelineAsset.CreateTrack<GroupTrack>(null, "Group"); var track = timelineAsset.CreateTrack<NewPlayableTrack>(groupTrack, "NewPlayable"); var b = GameObject.Find("Cube"); director.SetGenericBinding(track, b);
クリップ作成とKeyFrame作成
クリップを作成します。もちろん NewPlayableAssetで
そしてそのクリップにKeyFrame(AnimationCurve)を設定します
今回は最初が1fで、デフォルトのカーブでエンドが0fになる形で
もちろん、カーブのEaseを変えたり、KeyFrameを増やす事は容易
var clip = track.CreateClip<NewPlayableAsset>(); clip.CreateCurves(null); var curve = new AnimationCurve(); curve.AddKey(new Keyframe(0f, 1f)); curve.AddKey(new Keyframe((float)clip.duration, 0f)); clip.curves.SetCurve("", typeof(NewPlayableAsset), "hoge", curve);
リフレッシュ
最後にリフレッシュ
EditorUtility.SetDirty(timelineAsset); TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
成果物
とりあえず、目的の事は出来た
タイムラインをスクリプトから生成する
CreatePlayable周りの調査。AssetからBehaviourを、BehaviourからBindingを参照したい
CreatePlayable周りがちょっとややこしかったので調査
まず初めに断っておくが、本来はMixerを作るのがベストだが、今回はあえてMixerを作らずに頑張ってみる
ScriptPlayable
var playable = ScriptPlayable<NewPlayableBehaviour>.Create(graph); var trackBinding = go.GetComponent<PlayableDirector>().GetGenericBinding(this) as GameObject; playable.GetBehaviour().target_ = trackBinding; return playable;
前回はこのようにしてBehaviourにバインディングを渡した
もちろん、単なるプロパティ経由なのでどんなものでも渡せる
ただし、この場合 PlayableAsset::CreatePlayableが呼ばれない
そのため、PlayableAssetがBehaviourを知る方法がメンドクサイなど問題が生じる
なので、この方式を変更しようと思う
TrackAsset::CreatePlayable
BaseClassのCreatePlayableを呼ぶと、その中からPlayableAsset::CreatePlayableが呼ばれるようになる
具体的には
NewPlayableTrack
return base.CreatePlayable(graph, go, clip);
Assetの方は下記コードで、Behaviourの参照を手に出来る
templateという変数名にするのが通例のようだ
今回は template_というプロパティーを持たせる
public NewPlayableBehaviour template_; public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { return ScriptPlayable<NewPlayableBehaviour>.Create(graph, template_); }
前にコード書いたときは上記で、template_にBehaviourの参照が渡ってきた覚えがあるが
コード間違えたかUNITYのバージョンか覚え違いか、Nullだったので変更した
public NewPlayableBehaviour template_; public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { var playable = ScriptPlayable<NewPlayableBehaviour>.Create(graph); template_ = playable.GetBehaviour(); return playable; }
これで確実にtemplate_にbehaviourが設定される
上記で対応できると思ったが、Inspectorを作る時に不具合が生じる。今のところUNITY2020で問題が起こっている・・
UNITY2020では異なる方法で行う必要があるかもしれないので
今後は少し古いが UNITY2019.4での作業に変更
Bindingを渡したい
BehaviourにてトラックにBindingされたオブジェクトの操作をしたい事は多々ある
が、UNITYのTimelineはBehaviourから簡単にBindingが参照できない(ほんとこれは不便)
なので、どこかでBindingを渡す必要がある
まずBindingの取得方法だが、TrackAsset::CreatePlayable あるいは、PlayableAsset::CreatePlayableの引数の
GameObjectが、タイムラインのGameObjectになっていて
そこからPlayableDirectorコンポーネントを取得し、その中のBindingsから該当するトラックのBindingを検索する
具体的なコードは
var trackBinding = go.GetComponent<PlayableDirector>().GetGenericBinding(key) as GameObject;
もちろんこのgoがGameObjectだが、問題はこのKeyである
Directorから何らかの方法でKeyを取得できるかもしれないが
ここはTrackAssetのthisを渡すのが簡単なので、TrackAssetから渡したい
結果
NewPlayableTrack
[TrackBindingType(typeof(GameObject))] [TrackClipType(typeof(NewPlayableAsset))] public class NewPlayableTrack : TrackAsset { protected override Playable CreatePlayable(PlayableGraph graph, GameObject go, TimelineClip clip) { Playable playable = base.CreatePlayable(graph, go, clip); var trackBinding = go.GetComponent<PlayableDirector>().GetGenericBinding(this) as GameObject; Debug.Log("Track::" + trackBinding); var p = (ScriptPlayable<NewPlayableBehaviour>)playable; p.GetBehaviour().binding_ = trackBinding; return playable; } }
NewPlayableAsset
public class NewPlayableAsset : PlayableAsset { public NewPlayableBehaviour template_; public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { var playable = ScriptPlayable<NewPlayableBehaviour>.Create(graph, template_); return playable; } }
以上で
PlayableAssetからは templateという名前でBehaviourが扱え
PlayableBehaviourからは bindingという名前でトラックにバインドされたオブジェクトが扱える
UNITYタイムライン PlayableTrackとBinding
PlayableTrack
プログラマとしては本題!
独自の処理をするトラックを作ります
用途はほんと色々あります
だいたい仕事では8割以上はこのカスタムトラックでタイムライン作るかも
Track、Asset、Behaviour、Mixerの4つで構成されます
PlayableTrack
カスタムのClipを入れる事のできるトラックです
UNITYから用意されているPlayableTrackをそのまま使う事も可能ですが
独自のバインディングやら初期化処理などを行う場合には自分で作ります
というか、自分で作りましょう
PlayableAsset
クリップ
実行時にインスタンス化するための情報を保持したりしている
実行時に下記のBehaviourをCreateする
PlayableBehaviour
インスタンス化したあとの実際のふるまいを定義する
カスタムトラックの肝
Mixer
上記のBehaviourの一種
クリップ同士(複数のBehaviour)をブレンドする事ができます
実際に作ってみる
Assets->Create->Playables
で、BehaviourとAssetのスクリプトのテンプレートが作成できる
PlayableAsset
[System.Serializable] public class NewPlayableAsset : PlayableAsset { // Factory method that generates a playable based on this asset public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { return Playable.Create(graph); } }
上記のような何もしないテンプレートが作成される
PlayableBehaviour
public class NewPlayableBehaviour : PlayableBehaviour { // Called when the owning graph starts playing public override void OnGraphStart(Playable playable) { } // Called when the owning graph stops playing public override void OnGraphStop(Playable playable) { } // Called when the state of the playable is set to Play public override void OnBehaviourPlay(Playable playable, FrameData info) { } // Called when the state of the playable is set to Paused public override void OnBehaviourPause(Playable playable, FrameData info) { } // Called each frame while the state is set to Play public override void PrepareFrame(Playable playable, FrameData info) { } }
こちらは、コールバックのみのBehaviourが作成されました
BehaviourとAssetを連結させる
とりあえずPlayableTrackを作りクリップを作成する
今回はNewPlayableAssetという名前なのでそれを作成します
先ほどのBehaviourのコールバックにログを仕込んでも、まだ連結させてないのでコールバックが来ません
なので接続しましょう
AssetのCreatePlayableにて、今は汎用的なPlayableを作成していますが、それを自作のBehaviourにします
public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { return ScriptPlayable<NewPlayableBehaviour>.Create(graph); }
これで、今回作ったNewPlayableBehaviourが呼ばれるようになり、ちゃんとコールバックが呼ばれるように
ScriptPlayable
public struct ScriptPlayable<T> : IPlayable, IEquatable<ScriptPlayable<T>> where T : class, IPlayableBehaviour, new() { public static ScriptPlayable<T> Null { get; } public static ScriptPlayable<T> Create(PlayableGraph graph, int inputCount = 0); public static ScriptPlayable<T> Create(PlayableGraph graph, T template, int inputCount = 0); public bool Equals(ScriptPlayable<T> other); public T GetBehaviour(); public PlayableHandle GetHandle(); public static implicit operator Playable(ScriptPlayable<T> playable); public static explicit operator ScriptPlayable<T>(Playable playable); }
IPlayableBehaviourを持つBehaviourをCreateしたり。
GetBehaviourとかGetHandleとかもしかして使うかもね
PlayableTrack
TrackAssetから派生する
[TrackClipType(typeof(NewPlayableAsset))] public class NewPlayableTrack : TrackAsset { protected override Playable CreatePlayable(PlayableGraph graph, GameObject go, TimelineClip clip) { Playable playable = base.CreatePlayable(graph, go, clip); return playable; } }
TrackClipTipeにカスタムのAssetを指定する
先ほどつくったPlayableTrackを削除し、今度は今作ったカスタムTrackを作成
そのトラック上で右クリックをすると、先ほど作ったNewPlayableAssetのクリップを作成できる
TrackBinding
カスタムトラックにオブジェクトをバインディングできます アトリビュートを追加
[TrackBindingType(typeof(GameObject))] [TrackClipType(typeof(NewPlayableAsset))]
他にもトラックの色を変えたりアトリビュートがある
TrackBindingを設定すれば指定したオブジェクトに対して影響を与えるトラックが作れます
今回はGameObject型にしていますが、Animatorを設定したり出来る
TrackBindingにシーン上のCubeを指定しよう
TrackBindingの取得
ここが非常に昔から不満点ですが
UNITYのTimelineではBehaviourからTrack情報を取得するのが難しいです
クリップの開始は OnBehaviourPlayのコールバックで取得しますがそのプロトタイプは
public override void OnBehaviourPlay(Playable playable, FrameData info)
となっており、トラックの情報が取れません
なので以下の3パターンで取得する事になります
Behaviour#ProcessFrameで取得
ProcessFrameだけはプロトタイプが
public virtual void ProcessFrame(Playable playable, FrameData info, object playerData);
となっており、playerDataにBindingが入っているのでCastすればよいです
public GameObject target; public virtual void ProcessFrame(Playable playable, FrameData info, object playerData) { target = playerData as GameObject; }
ただし、ProcessFrameは毎フレーム呼ばれる処理です。最初のフレーム時のみtargetを取得するように改造が必要ですが
そもそも初期化時に設定すればいいものを毎フレーム処理したくありません
Mixerを作りMixerのOnBehaviourPlay時に渡す
今回はMixerを作っていないので省略しますが
わりと見ますね
ただし、Mixerを作る必要があるのでMixer不要な場合は手間です
TrackのCreatePlayable時に渡す
今回はこれでいきます
Trackのコードを
protected override Playable CreatePlayable(PlayableGraph graph, GameObject go, TimelineClip clip) { var playable = ScriptPlayable<NewPlayableBehaviour>.Create(graph, 1); var trackBinding = go.GetComponent<PlayableDirector>().GetGenericBinding(this) as GameObject; playable.GetBehaviour().target = trackBinding; return playable; }
CreatePlayableで渡ってきたGameObjectからPlayableDirectorを取得し、そこからBindingを取得して
作成したBehaviour(Playable)に渡す
これでOK