UNITYタイムラインをカスタマイズするよ
タイムラインとは
ムービーシーンとかカットシーンと呼ばれるもの
カメラワーク、モーション、エフェクト等を定義しておき、実行時にキャラをバインドする事で
同じ動作のムービーを別キャラ(別衣装)で行うことが出来る
例えばモンハンのムービーシーンのような、その時装備しているものと容姿でムービーを再生できる
そのような便利な機能がUNITYにはある
Timelineの作成
メニューの Windows->Sequencies->Timeline でタイムラインのタブを表示
適切なGameObjectを作成しTimelineタブでCreateを押すと
Timeline.playable というファイルが作成される
と同時にPlayableDirectorのコンポーネントが追加される
タイムラインの左側をクリックすると各種のトラックを作成する事が出来る
動画編集などをした事ある人なら分ると思うが、各要素はトラックという単位で区切られ
例えば音声トラックには音声のクリップを複数配置して、タイムライン上でいつ再生するかを設定する
UNITYのタイムラインではさらに複数トラックをトラックグループという単位でまとめる事が可能
UNITYから準備されているトラックは6種類だが、自分でカスタムトラックを作る事が出来る
実際のゲーム開発では、独自のトラックを作る事がほとんどだと思うが
まずはUNITYが提供するトラックを紹介しよう
ActivationTrack
GameObjectの有効化、無効化を制御する
ActivationTrack を作成しタイムライン上で右クリックを行い、Add ActivationClipを選択すると
タイムライン上にActivationClipを作成できる
今回はテストにCubeオブジェクトを作りそれを少しして表示する
シーンにCubeを作りTrackにバインド(左側のNoneにCubeを設定)する
注意するのはこの時のGameObjectはシーン上にインスタンス化されたものを指定する必要があり
Prefab等のインスタンス化されたものは指定できない(後で説明するが、Prefab等を表示する場合は動的にBindingsを設定する必要がある)
クリップを調整して表示非表示のタイムラインを組み、Previewの再生をすると
Game画面にタイムラインのレンダリングが表示される
トラックにオブジェクトをバインディングしたら、PlayableDirectorのBindingsにも同じようにバインディングされています
(というか、同じものです)
PlayableDirectorは再生時にトラックに適切なオブジェクトをバインドしたり、クリップをロードしたりアンロードしたり
全体の司令塔です(だからDirector)
シーンの方もクリップの設定に従ってオブジェクトのON/OFFが切り替わります
(もちろんTransformやらの値も、タイムラインに合わせて変化します)
で、タイムラインで使用するGameObjectは、タイムラインの子に追加すると管理が楽です
AnimationTrack
AnimationTrackは、オブジェクトの移動や回転、アニメーションの再生等を行います
Animatorコンポーネントを持つオブジェクトをBindできます
トラックを右クリックで Add from AnimationClipで再生するアニメーションを選ぶ
当然トラックに複数のアニメーションを並べて連続再生する事も可能
クリップのつなぎ目をオーバーライドさせれば、アニメーションの補間もできます
ClipTransformOffset等を設定すれば、移動アニメーションも可能です
それらの動作に関しては、アニメーションカーブや、キーフレームを設定できます
今回はそのへんは行わず、適当なアニメーションを再生してみます
streamable.com
それっぽくなってきた
AudioTrack
音の再生を行うClipを作成します
動作は推測の通りなので割愛
ControlTrack
シーンあるいはPrefabからオブジェクトを有効化/無効化します
ActivationTrackに似てますが、こちらは主にParticle等を生成する時に使います
タイムライン上で右クリックで、Add from GameObjectします
シーン上のGameObjectを指定する事も可能ですが、今回はPrefabから直接生成します
何か適当なParticleを作成してPrefab化しておきそれを設定します
ParentObjectにシーン上のオブジェクトを指定すれば3D空間上のその位置にパーティクルを出す事が出来ます
SignalTrack
任意のタイミングでイベントを発行する機能です
わりと最近のUNITYで追加されました
今度調べます
PlayableTrask
非常に有用な機能です
スクリプトを書いて独自のトラック、クリップを作成します
だいたいゲームの仕事でプログラマがやるべきは、このトラックの作成です
例えばブレンドシェイプをコントロールしたり、揺れもの、まばたき、目線
その他標準のトラックでは制御できない事をスクリプトで行います
非常に有用ですが、非常に複雑なためこちらは後程調査します
【UNITY 2020.2.6f1】Cinemachine FreeLookカメラでTPS操作をする。New InputSystemにも対応
Cinemachineとは
UNITYの提供するカメラ制御のコンポーネント
Cinemachineのインストール
PackageManagerよりインストールをする
その後メニューから Cinemachine-Cinemachine FreeLook を選ぶ
シーンに CM FreeLook1というGameObjectが作成されており、CinemachineFreeLookコンポーネントが付いており
MainCameraに、CinemachineBrainコンポーネントが付いている
キャラクターをカメラが追従してほしいので
CinemachineFreeLookのFollowにキャラクターのGameObjectを、LookAtに注視点(キャラの頭とか)を設定する
このまま実行すると、Cinemachineが昔のInputを使っているため、New InputSystem を使っている場合はエラーになる
InputSystemに変更するには、CM FreeLook1 GameObjectにAddComponentで Cinemachine Input Providerスクリプトを付ける
これでNew Input Systemに変更されるのでエラーはなくなる
ただし、現状ではマウスの動きをキャプチャできないので、CinemachineInputProviderのXY Axisに Look Actionを設定する
これで、キャラクターの動きにカメラが追従し、マウスでカメラを回す事ができる
ただし、このままではキャラの向いている方向に動くので
例えばキャラが右を向いている時に上を押すと右に歩き
もちろんそういう操作のゲームもあるが、今回はカメラの向きでキャラが動くように
つまりキャラがどの方向を向いていても上を押すとキャラがカメラからみて奥へ動くようにする
そのために、カメラからみたforwardベクトルとrightベクトルがワールド座標でのベクトルに変換するため
TransformDirectionを使いワールド座標に変換し
それにキー入力のx,zを掛ければ求まる(もちろんy成分は無視する)
また、移動方向を向くためにLookAtを使う
もちろん、キャラ座標で計算したり最適化は出来るがとりあえずいいだろう
Vector2 m = move.ReadValue<Vector2>(); // カメラの方向で移動させる Vector3 forward = playerCamera.transform.TransformDirection(Vector3.forward); Vector3 right = playerCamera.transform.TransformDirection(Vector3.right); moveDir = (m.x * right + m.y * forward) * speed; moveDir.y = 0; // 進行方向を向く(今のところ補完入れてない) transform.LookAt(transform.position + moveDir);
コメントの通り補完は入れていないけど、今後はCharactorControllerを別のアセットに置き換える可能性もあるので
とりあえずここはこれで。
これで、カメラの回転とキャラ移動が思い通りになった
あとは、横に歩いている時に少しずつカメラが回って正面を向くようにするカメラが最近多いので
Cinemachineでも、BindingModeを変更すればそのあたりの挙動かわるようだ(よくわかってない)
とりあえず今までのまとめ(まだゲーム内容決めてないけど)
【UNITY 2020.2.6f1】New Input System(1.0.2)を使ってみる
New Input Systemについて
新しい入力が出来ました
引き続き旧来の入力方法を使う事が出来ますが
これを期にInputSystemを覚えようと思います
旧来の方法
旧来のInputではInputManagerのAxes
下記のように入力を取得していました
h = Input.GetAxis("Horizontal"); v = Input.GetAxis("Vertical"); j = Input.GetButton("Jump");
New Input Systemを使う
細かいことはほかの人のブログで・・
まず、PackageManagerからInputSystemをInstallします
メニューのWindows PackageManagerから PackageManagerを起動し
左上のリストから、Packages: Unity Registryを選び、 Input Systemを検索
今回は最新の1.0.2 を選択しInstallします
ちなみに、Samplesからサンプルのパッケージをインストールする事もできます
インストールを終えたら、New Input Systemを使うように変更します
ProjectSettingsのPlayerSettingから、Active Input Handlingを Input System Package(New)に変更します
そうするとUNITYエディタが再起動します
これで、InputSystemが有効になり、旧来の入力が効かなくなります
InputSystemを設定する
InputSystemの設定は少し面倒です
まず、右クリックから Create-InputActionを行い、InputActionAssetを作成する
ダブルクリックを行い、ActionMaps、Actionsと、入力キーのぶんだけ作成します
例えば、移動はxzの2軸なのでMoveという名前で Vector2で作成します
キーボードだとWASDと矢印キーにそれぞれUp、Down、Left、Rightを設定
他にもGamePad、Touch、Joystick、XRも設定する
FireはActionTypeはButtonで、マウス左クリックやゲームパッドの右トリガーなどに割り当てる
そのほか使うボタンを登録します
そしてそれを登録すればよいのですが
めんどくさいので UNITYが用意したものを使います
PlayerInputコンポーネント
動かしたいオブジェクトに、AddComponentから PlayerInput を追加します
すでに基本的なキー入力のAssetが設定されているので、非常に手抜きができます
DefaultMapをPlayerにし、BehaviorをSendMessagesにする
実際に値を取得する時は下記のようになる
public void OnMove(InputValue value) => move = value.Get<Vector2>(); public void OnLook(InputValue value) => look = value.Get<Vector2>(); public void OnFire(InputValue value) => fire = value.Get<float>() > 0; public void OnJump(InputValue value) => jump = value.Get<float>() > 0;
このように、Onアクション名 というCallbackで指定したアクションが取れます
コールバックで処理するのが面倒だなー、従来のようなポーリングで取りたい場合は
上記のようにコールバックで値を変数に保存してもいいですが
PlayerInputには、ポーリングをするインタフェースも用意されてます
// InputActionを定義 Private InputAction move, jump; void Start() { PlayerInput playerInput = GetComponent<PlayerInput>(); // 各アクションの参照を取得 move = playerInput.actions["Move"]; jump = playerInput.actions["Jump"]; } void Update() { Vector2 m = move.ReadValue<Vector2>(); // 2軸の値はVector2で取得 bool j = jump.ReadValue<float>() > 0; // bool値はfloat値で 0より大きいかで判断する {
これで、ポーリングでもイベントでも取得が可能です
AWSの操作をするSlackBotを作りたいのである
作りたいもの
クラウドを本格的に使う際に色々心配事もあるし、結局仕事でも使うでしょ?
例えば今月の利用金額を調べたり、Metrixを表示したり、起動中のインスタンス表示したり落としたり
これがPCでログインしたりせず、Slack上でコメントするだけで制御できることがどれだけ便利か
以前のプロジェクト
以前、Go言語の勉強用にGOSICKという名前で作った
これは当初、Goの色々な機能を使いたくてPluginを使いたかったりしたのでGoにした
ところが、この程度のBotであればデプロイを行わず、サーバレスにすべきなので
Serverless Frameworkで作り直そうとしていた
当初はAzureのBizSparkに入ってたりでAzureCSharpで作ろうとしていたが
SlackBot程度の処理負荷の低いものであればNodeがベストではないかと思い
AWS Nodeで作り直そうと思う
使用フレームワーク
サーバレスは実際は直接AWS上にコード書いてもいいが、ローカルでテストしたりするために
ServerlessFrameworkや AWS SAMといったものを使うと便利
その2者が有名なのでどちらを使おうかな・・
権限
権限をつける等は将来的には必要だろう
例えば社員誰でも請求額見れるとか問題がある会社も多いだろう
ユーザー単位でAPIに権限をつけるか、チャンネル単位か等の設計などは後回しで
まずは全員フル権限にする
コマンド形式(仮
メンションにするか、@なしで反応するかは後程考えるとして
まずは文字で単純に解析したいな
xxx をコマンド名として下記のようなコマンドを想定
また、; で複数コマンドを同時に出来るようにしようかな?
xxx ec2 list xxx ec2 down インスタンス名 xxx ec2 list; lambda list; ...
使用言語
Nodeで動かす。Typescriptにしたい
WSL2のポートをlocalhostに公開する
WSL2で開発する時、Dockerなどを使わなくともポートをWindows側から叩くことができる
これでWeb開発がよりスムーズになる
また、WSL2に使うメモリやプロセッサ数、Swapなども細かく設定し、PCリソースを管理することも可能
URP10.2.2 URPシェーダーをShaderLabで書いてみる
URPのシェーダーを作ろうとすると、UNITYからは全部ShaderGraphになる
ので、テキストエディタでShaderを書く事は推奨されていないようだし
実際に仕事でも、アーティストと会話するために、ノードエディタのShaderGraphを使う方が良いと思うが
勉強のためにあえてShaderLabで書いてみる
URP UnlitShaderを調査
まずは、最も簡単なはずのUnlitから書いてみる
テンプレートからはShaderLabは作れないので、ShaderGraphのUnlitを新規で作成します
床に適用してみた。テクスチャも影もなくなった
作成されたUnlitShaderGraphからShaderLabのコードを取得するには、Inspectorから ViewGeneratedShader を選択するとコードが表示される
パスは、通常パス、ShadowCaster、DepthOnlyの3つで、それがTransparent、Opaqueの両方あるので合計6個のパスがあります
おそらく、SRP Batchingのために、TransparentとOpaqueの両方のシェーダをまとめているんだと思う
Feature関連
// Pragmas #pragma target 2.0 #pragma only_renderers gles gles3 glcore #pragma multi_compile_instancing #pragma multi_compile_fog #pragma vertex vert #pragma fragment frag // DotsInstancingOptions: <None> // HybridV1InjectedBuiltinProperties: <None> // Keywords #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma shader_feature _ _SAMPLE_GI // GraphKeywords: <None> // Defines #define ATTRIBUTES_NEED_NORMAL #define ATTRIBUTES_NEED_TANGENT #define FEATURES_GRAPH_VERTEX /* WARNING: $splice Could not find named fragment 'PassInstancing' */ #define SHADERPASS SHADERPASS_UNLIT /* WARNING: $splice Could not find named fragment 'DotsInstancingVars' */
色々と書いてあるが、おおざっぱに
Instancing、Fog、dots instancing、ライトマップ系、アトリビュート、そしてshaderpass unlit
等設定しています
dots instancingとか、最近のUNITYの高速化、Data-Oriented Technology Stackの事でしょう
今度ちゃんと調べてみたいです
Includes
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/TextureStack.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderGraphFunctions.hlsl"
これらのコードをIncludeしています
BuiltIn-Shaderではなく、URPのシェーダーを見に行ってますね
入出力
struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; #if UNITY_ANY_INSTANCING_ENABLED uint instanceID : INSTANCEID_SEMANTIC; #endif }; struct Varyings { float4 positionCS : SV_POSITION; #if UNITY_ANY_INSTANCING_ENABLED uint instanceID : CUSTOM_INSTANCE_ID; #endif #if (defined(UNITY_STEREO_MULTIVIEW_ENABLED)) || (defined(UNITY_STEREO_INSTANCING_ENABLED) && (defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE))) uint stereoTargetEyeIndexAsBlendIdx0 : BLENDINDICES0; #endif #if (defined(UNITY_STEREO_INSTANCING_ENABLED)) uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex; #endif #if defined(SHADER_STAGE_FRAGMENT) && defined(VARYINGS_NEED_CULLFACE) FRONT_FACE_TYPE cullFace : FRONT_FACE_SEMANTIC; #endif };
おそらく入力パラメータAttributesは #defineで有効にしたものが入ってくるのだろう
それにInstancing用のID
Varyingsは、InstancingやVR等で条件コンパイルされてるが、ポジション程度しかない
それらの入力値を使い、データを抽象化しているのか、シェーダーライブラリ内で使う構造体に変換しているのだと思われる
SurfaceDescriptionInputs、VertexDescriptionInputs、PackedVaryingsと変換関数がある
実際の処理
#include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderPass.hlsl" #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Varyings.hlsl" #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/UnlitPass.hlsl"
実際の処理はシェーダーライブラリの中だ
その中でも実際にシェーダーの中身は下記になる
PackedVaryings vert(Attributes input) { Varyings output = (Varyings)0; output = BuildVaryings(input); PackedVaryings packedOutput = PackVaryings(output); return packedOutput; } half4 frag(PackedVaryings packedInput) : SV_TARGET { Varyings unpacked = UnpackVaryings(packedInput); UNITY_SETUP_INSTANCE_ID(unpacked); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(unpacked); SurfaceDescriptionInputs surfaceDescriptionInputs = BuildSurfaceDescriptionInputs(unpacked); SurfaceDescription surfaceDescription = SurfaceDescriptionFunction(surfaceDescriptionInputs); #if _AlphaClip half alpha = surfaceDescription.Alpha; clip(alpha - surfaceDescription.AlphaClipThreshold); #elif _SURFACE_TYPE_TRANSPARENT half alpha = surfaceDescription.Alpha; #else half alpha = 1; #endif #ifdef _ALPHAPREMULTIPLY_ON surfaceDescription.BaseColor *= surfaceDescription.Alpha; #endif return half4(surfaceDescription.BaseColor, alpha); }
まあ、ここだけ見ると単純です
シンプルなUnlitシェーダー
白く塗りつぶすだけの非常に簡単なシェーダーを作った
Shader "MyShader/URPUnlit" { SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque" "UniversalMaterialType" = "Unlit" "Queue" = "Geometry" } Pass { Name "Pass" // Render State Cull Back Blend One Zero ZTest LEqual ZWrite On HLSLPROGRAM // Pragmas #pragma target 4.5 #pragma exclude_renderers gles gles3 glcore #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl" struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; }; struct Varyings { float4 positionCS : SV_POSITION; }; float3 TransformObjectToWorld(float3 positionOS) { return mul(unity_ObjectToWorld, float4(positionOS, 1.0)).xyz; } float4 TransformWorldToHClip(float3 positionWS) { return mul(unity_MatrixVP, float4(positionWS, 1.0)); } Varyings vert(Attributes input) { Varyings output = (Varyings)0; output.positionCS = TransformWorldToHClip(TransformObjectToWorld(input.positionOS)); return output; } half4 frag(Varyings input) : SV_TARGET { return half4(1,1,1,1); } ENDHLSL } } }
includeは2個あるが、UNITYのシェーダーのCORE的なものと、RPから入ってくるUNITY定数の宣言
思ったより簡単に書けたが、色々な処理を無視しているし
影とかは難しそうな気配・・
PS,XBOX,Switch等の対応とかも考えると、ShaderGraphで書く方が楽だろうなあ・・・
テクスチャマッピングする
これは非常に簡単だった
今までのBuiltIn-Shaderの作法にのっとり
Texture2DをPropertiesに出し、Texture2DとSamplerを宣言し
AttributesとVaryingsに TEXCOORD0を入れて
SAMPLE_TEXTURE2Dでマッピングした結果をリターンすればいい
意外と素直
影を落とす
"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
をIncludeすることにした
そして今回はメインライトからの影のみを取るので
pragma multi_compile _ _MAIN_LIGHT_SHADOWS
をつけた
shadowAttenuationを計算するのだが、ShadowMapをSampleするには、ワールド座標のPositionが必要なので
positionWSを計算することに
TransformObjectToWorld(input.positionOS)
で簡単に取得できた
ワールド座標系のオブジェクトポジションを使い
TransformWorldToShadowCoord(input.positionWS)
をすると、ShadowCoordが取得できるので、そこから
GetMainLightを使い、メインライトの情報、影の情報が取れる
今回はライトカラーなどは使わずshadowAttenuationだけを使い、テクスチャにそのまま乗算した
とりあえず影はできた
今回の完成シェーダコード
Shader "MyShader/URPUnlit" { Properties { [NoScaleOffset] MainTex("_MainTex", 2D) = "white" {} [HideInInspector][NoScaleOffset]unity_Lightmaps("unity_Lightmaps", 2DArray) = "" {} [HideInInspector][NoScaleOffset]unity_LightmapsInd("unity_LightmapsInd", 2DArray) = "" {} [HideInInspector][NoScaleOffset]unity_ShadowMasks("unity_ShadowMasks", 2DArray) = "" {} } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque" "UniversalMaterialType" = "Unlit" "Queue" = "Geometry" } Pass { Name "Pass" // Render State Cull Back Blend One Zero ZTest LEqual ZWrite On HLSLPROGRAM // Pragmas #pragma target 4.5 #pragma exclude_renderers gles gles3 glcore #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" TEXTURE2D(MainTex); SAMPLER(samplerMainTex); struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float4 uv0 : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float4 texCoord0 : TEXCOORD0; float3 positionWS: TEXCOORD1; }; Varyings vert(Attributes input) { Varyings output = (Varyings)0; output.positionCS = TransformWorldToHClip(TransformObjectToWorld(input.positionOS)); output.texCoord0 = input.uv0; output.positionWS = TransformObjectToWorld(input.positionOS); return output; } half4 frag(Varyings input) : SV_TARGET { half4 o = SAMPLE_TEXTURE2D(MainTex, samplerMainTex, input.texCoord0.xy); Light l = GetMainLight(TransformWorldToShadowCoord(input.positionWS)); return o*l.shadowAttenuation; } ENDHLSL } } }
Instancingやメイン以外のライト等、いろいろすっ飛ばしているので実用性はないが
URPのシェーダーは、こんな感じで書く事は可能だ
ShadowCasterやDepthOnlyパスは消してしまったが同じように簡単に作る事ができそう
ただし、URPはバージョンアップで色々変わってしまうので
ShaderGraphを使う方が楽と思う