ikoan unity 開発メモ

Unityに関するアレコレを日々つづっていこうと思います

揺らめく旗に動的に画像を乗っけるシェーダーとスクリプトとか

環境

Unity2018.2.14f1

概要

よく旗が揺らめくシェーダーの実装例などはありますが、その旗に他のグラフィック素材も乗っけて一緒に揺らせないか、という要望があったので大雑把に実装してみました。

実装方法

方法論としてはいくつかあるとは思います。
1)meshを自作し、旗の画像と乗っける画像の頂点情報をuv0,uv1などに保持し、シェーダーで描画
2)乗っける画像をRenderTexture化し、シェーダー内でどちらも描画
3)mesh2枚で座標軸をずらす。2Dプロジェクトなら可能か?

(3)に関しては書いてみただけです。実際問題としては実現はどうかな、と思います。
(1)は乗っける画像が複数になってくると、uvの数など多くなってきそうです。
なので、今回は(2)の手法で実装したいと思います。

実装手順

まずは旗が揺らぐshaderを用意します。
これはVertexShaderで各頂点を時間に応じてsinカーブなどで揺らすことで実現できます。

数値は適当です。いい感じに調整してください。
また、計算式にuvを使用していますが、ここら辺はvertexなどを使用してもいいかもしれません。
なお、各頂点を移動することで揺らぎを生じさせているので、頂点数の少ないQuadなどは利用できません。
Planeを使用するか、meshを自作する必要があります。

次にRenderTextureを用意します。
Cameraを適当に作成し、ClearFlagsをSolidColorにして、BackGroundColorを#000000FFにします。
また、TargetTextureにProjectViewで作成したRenderTextureを割り当てます。
RenderTexture自体を描画する際にアルファを使用する場合は、Cameraで表示するオブジェクトに対して、特殊なシェーダーを割り当てる必要があります。
詳しくは下記の記事をどうぞ。
ikoans.hatenablog.com

これでRenderTextureの準備ができました。
上に書いた通り、RenderTextureを描画する際にアルファを使用しないのであれば、2枚のテクスチャをうまいこと描画するシェーダーを自作すればいいのですが、今回はRenderTexture内でアルファを使用しています。ここが結構厄介でした。
旗自身のテクスチャは通常描画すればいいのですが、RenderTexture自身は特殊な描画が必要です。
結論から言えば、blendの仕方が異なるため、仕方なく2Pass使用することになりました。

このShaderを揺れる旗のmesh renderer のmaterial に使用し、_MainTexに旗自身のテクスチャを。_RenderTexにRenderTextureをセットすれば、、、できました!!

f:id:ikoans:20181118194104g:plain

まとめ

RenderTextureのアルファ部分についての認識と通常描画する旗の部分の上にどうRenderTextureを表示するかで思った以上に時間がかかってしまいました。
よくよく考えると旗のテクスチャと数字とをひとまとめにRenderTexture化した方が早いんじゃないかと気づきましたが、まぁ勉強できたということで。

プロジェクト一式

github.com

RenderTextureのアルファ

環境

Unity2018.2.14f1

概要

Cameraで撮ったRenderTextureを表示する際にRenderTextureのアルファ成分についてわかりづらく、よく忘れるのでまとめました。

問題

CameraAの表示内容をRenderTextureに焼き込み、それをUnlitなどで他のmeshに描画すると、CameraAのClean Colorが表示されてしまいます。これはRenderTextureの特性ともいえるでしょう。
多くの場合、このCameraAのCleanColorは透過されてほしいはずです。
これをshaderを自作することで、解決したいと思います。

解決策の概念

shaderのBlendがキモとなります。
Blend colorA colorB
もしくは
Blend colorA colorB , alphaA alphaB
のような記述になります。
どちらもAの方が該当シェーダで表現される色情報、Bがすでにフレームバッファに書き込まれている色情報と考えていいと思います。
詳しくは
Unity - マニュアル: ShaderLab構文:Blending
をご覧ください。
正直よくわからないと思います。(私もまだ感覚でしかつかめていません汗)

上記のBlendを元に整理していきます。
やりたいことは
1)「RenderTexture」のRGB値はCameraAで表示される「α(透過)も反映させた色値」、A値は「フレームバッファに書き込まれている色情報にかかるべきα情報」と保存する
2)「RenderTexture」を書き込む時に、RGBはそのまま上書きする、AはRenderTextureのA値を有効に活用する、やり方になります。

実装方法

CameraAのCleanColorは0x000000FFにします。
また、CameraAで描画するオブジェクトに使用するシェーダーのBlend部分は
Blend SrcAlpha OneMinusSrcAlpha, Zero OneMinusSrcAlpha
とします。
CleanColorを0x000000にする理由は、RenderTextureに描き込み時のBlendする際に0だったらアルファがいくつであろうとブレンドされたところで影響がないためです。
また、Blend構文を上記にすることで、RGBにはアルファが考慮された数値が入力され、AにはRenderTextureにオブジェクトが描画される際に背景にかかるα値が保存されることとなります。
つまり描画されるオブジェクトでアルファが1の部分にはA=0が保存され、アルファが0の部分にはA=1が保存されます。オブジェクトが描画されない場所もCleanColorのアルファが1のため、A=1となっています。
まとめると、RenderTextureの色値情報として
RGBはCameraAが描画する内容として、オブジェクトのアルファも考慮された内容。
Aはオブジェクトが描画される場所はオブジェクトのアルファの逆値、その他は1が入っている状態となっています。

そうした上で、RenderTextureを描画する際のshaderのblend構文を
Blend One SrcAlpha
都することで、RenderTextureの表示内容はそのまま表示しつつ、背景色にも適切にアルファが計算されてブレンドされることとなります。

csvデータをScriptableObjectに変換する

環境

Unity2018.2.14f1

概要

ゲームのマスターデータを作成する際、DBサーバーから通信して持ってくるのも一つの手段ではありますが、Unity5系から導入されたScriptableObjectを使用するのも、個人で開発する際には重宝するかな、と思っています。
今回はScriptableObjectを導入する流れを紹介したいと思います。

利点

・外部サーバーなどを使用しないので、ローカルで完結できる。そのため他のサービスなどを準備する手間がない。
・UnityEditor上でパラメータを編集できて、すぐに反映・テストしやすい。
・アプリ内に埋め込むこともできるし、いざとなればアセットバンドル化して、外部に出すことも可能。
などが考えられるでしょうか。

準備

まずは下記のExampleのように、ScriptableObjectを準備します。

Scriptable Object Example

例にあるように、Serializbleにしたstructなども使用することができます。
[CreateAssetMenu()]をつけることで、UnityEditorのProjectビューを右クリックした時に表示されるメニューからも、ScriptableObjectを作ることができます。
ただ、今回はcsvを読み込んで、ScriptableObjectを生成して見ましょう。

作成して保存する

csvを読み込むところはSystem.File.IOのFile使うと簡単ですが、自分のやりやすいやり方でいいと思います。
CreateInstance<"ScriptableObject">(); でScriptableObjectインスタンスを作成します。
作成したオブジェクトにcsvファイルから読み込んだデータをセットし、AssetDatabaseに保存することで完成です。
簡単ですね!


Create ScriptableObject Example

自動配置でないScrollView

環境

Unity2018.2.14f1

概要

uGUIのScroll Viewを検索してみると、大体が子要素を整列させている例しか上がっていないです。
子要素を自動で配置させた上で、ちゃんとスクロールさせる機能が欲しかったので調べてみました。

実装

まずは通常通りにScrollViewを実装してみます。
UI > Scroll Viewを作成するとCanvas以下に
「Scroll View」 > 「Viewport」 > 「Content」
が作成されます。
このContent以下に子要素を作成していくことでスクロールする画面を作成することができます。
スクロールエリアの描画範囲はViewportの情報によって制御されます。ここでマスクのOn/Offも設定できます。

さてここで一つ問題が。
単純にContent以下に子要素を複数配置した後にスクロールしても、「」を「」にしていた場合、スクロール位置が初期位置に戻ってしまう状態になってしまいました。
これだとスクロールしているとは言えませんね・・・

どうもスクロールできる範囲はContentのHeightが鍵になっているようです。
他のサイトさんでスクロール機能の実装方法を見てみると、Contentに「Vertical Layout Group」と「Content Size Fitter」をセットしています。確かにこれを追加すると、スクロール機能がうまくいき、それはContent Size FitterがHeightを自動計算しているからのようなんです。
実際にRectTransformのHeightパラメータのところが数値入力できないようになっていますね。
しかし、これだと、子要素が自動整列されてしまいます。
これだと求めるものとは、すこし異なってしまいますね。

自動レイアウトはVertical Layout Groupの機能なので、これをつけなければ自動でレイアウトされることもないのですが、それですとサイズをとってくれないのか、Content Size FitterのHeightも0のままとなってしまいます。
同様に子要素にLayout Elementをつけて、Ignore layoutにチェックをしても、サイズ計算などがされないようで、同じくContentのHeightが0のままとなります。

どうしたものかと悩んだ挙句、考えついた解決方法は下記の2通りでした。
1)子要素の中の一番親となる要素をあらかじめ大きくセットし、その大きな要素の中で自由に配置したいオブジェクトを配置する。
2)ContentのHeightを手動計算。

1)の方法は子要素同士のY位置が重なる場合にうまく行かなさそうだったので、一旦2)で実装しました。
※とは言ったものの、1)の方がうまくいくかも・・・
ここでContents以下の全要素の高さを取得する方法ですが・・・、いい方法が思いつかなかったので、とりあえずは適当に計算するようにしました。
その数値をHeightにセットします。
mContent.deltaSize = Vector2.up * [高さ];

これでいい感じにスクロールさせることができました!

パーティクルを一点に収束させる

環境

Unity2018.2.10f1

概要

パーティクルを一点に収束させる方法として、Emit中心に収束させるのはよく見かけますが、Emit後に全く別の場所に収束させる方法というのはなかなか見かけません。
どうにかできないかな、と思い、無理やりやって見ました。

実装

要は発射されたパーティクルの位置を無理やり書き換えてしまえばいいわけですね。
過去のUnityではパーティクルの情報をスクリプトから書き換えることは確かできなかったのですが、いつからかできるようになっていました。


Emitされたパーティクルの位置を更新する

基本的には毎フレーム、発射されたパーティクルの位置を無理やり書き換えることで対応しています。
条件式のところには、収束し始めるタイミングを入れるといいと思います。
そうしないと発射直後から一点に向かい始めてしまうので(汗。

ただ、この方法は表示されるパーティクル数が少なければいいのですが、そうでない場合、確実にCPUに大きな負荷を与えることになります。
また、収束の仕方も現在のpositionと収束positionのLerpを取っていますが、もっといいやり方があるとは思っています。が、とりあえずはこんなもので。

まとめ

パーティクル数が少ないなどの、限定条件下ではこのやり方が実装手順的にも一番楽ではないかと思います。
とはいえ、パーティクル数が多くなるとマズそうなので、GPUに委ねるやり方や並列処理を使った擬似パーティクルの実装も検討していきたいところですね。

ParticleSystemのEmit位置

環境

Unity2018.2.10.f1

パーティクルの発射位置がずれる

同一ParticleSystemをPositionだけ移動させ、中身を使いまわそうとした時に、プログラムでpositionを操作してEmit関数を呼び出しても、どうもうまくいかない。

プログラム上でposition移動させたあと、SceneViewのParticleEffectでPlayすると位置はちゃんと動いている。
なんでなのだろうと調べてみた。

ParticleSystem.Emit - Unity スクリプトリファレンス
スクリプトから指定したパーティクル数を放出します”とある。
字面を見る限り、特に問題なさそうなんだけど。
ただ、まぁ放出位置に関しては記載されていない。どうすればいいのかなぁ。

もう一つ、Emit( PartilceSystem.EmitParams emitParams, int count ); というメソッドが目に付く。
EmitParamsの中身を見てみると、
EmitParams - Unity スクリプトリファレンス
positionとやらの変数が。

これなんだろうなーと、EmitParams.positionに設定したいワールド座標を入れてみる。
なんかチラッと見えたけど、変な場所に移動したなーと思い、あーこれ相対座標になっちゃうのかなと思ったので、ParticleSystemの座標は(0,0,0)のままにしてみる。
再生すると、位置はいいんだけど、動きが変・・・。本来は全方位に散るように見えるはずなのに、収束している。
なんでだろうなー、ともう一度リファレンスを見てみると。。。

ParticleSystem.EmitParams.position - Unity スクリプトリファレンス
"To retain the effects of the shape module, set applyShapeToPosition to true."
あー、まさにこれじゃね?と思ったので、言われた通りにして実行。


動いた!!
最終的なソースは下記のようになりました。

ParticleSystem.Emitを使って、パーティクルを放出する際に、放出位置をプログラムから ...

AdMobでインタースティシャル広告を表示する

環境

Unity2018.2.10.f1

やりたいこと

AdMobを使用して、インタースティシャル広告をアプリ画面に表示させたい。
ターゲットプラットフォームはAndroid,iOSの2つ。
以前はバナー広告のメディエーションを登録しました。
同様にインタースティシャル広告をメディエーションさせながら、表示させることを目標とします。

Admob管理画面に登録

まずはAdmob管理画面を表示します。
まずは、インタースティシャルを表示したいアプリを選択し、その後左側のメニューから広告ユニットを選択します。
ページが広告ユニット一覧の画面になりますので、広告ユニットを追加します。
バナーかインタースティシャルか動画リワードの選択肢が出てくるので、今回はインタースティシャルを選択します。
フリークエンシーキャップやeCPMFloorは適宜必要なら設定しましょう。それぞれ、「1ユーザーに対し、一定期間中に何回表示できるか」と「eCPMの最低基準額」を設定できます。

次にメディエーションの設定をします。
AdMobの画面左側のメニューから、メディエーションを選択します。
色々設定項目が出てきますが、適当に設定してください。
メディエーション設定を広告ユニットに紐付ければ準備は完了です。

インタースティシャル広告の表示

表示準備はバナー広告の時とほとんど同じですね。
GoogleMobileAds.ApiMobileAds.Initialize("APPID") はバナー広告を実装している人は、すでに対応済みでしょう。
次にバナー広告では BannerView を使用していた代わりに、InterstitialView を使用します。
ここだけ変更すれば準備は大体完了です。

唯一、バナー広告と異なるのは、バナーの場合はAdRequestをLoadAdした時点でバナーが表示されますが、インタースティシャル広告の場合、広告が準備できたよーというだけで、実際に表示するには Showメソッドを呼んであげる必要がありますので、注意してください。

まとめ

一度バナー広告の設定をしてしまえば、手順としては簡単ですね。
メディエーションするために、広告会社をいくつか用意しようとすると、バナー用とインタースティシャル用、さらには動画リワード用といくつも設定しなければいけないのが、めんどいといえばめんどいですね。。。
しかもこれ、アプリごとに設定しなければいけないのがなんともまた・・・。
こうなるとアドフリくんとかは、とても便利なのでしょうね。
次回は動画リワードの設定もして行きたいと思います!