原カバンは鞄のお店ではありません。

Unityを使ったゲーム制作のあれこれを綴っていきます。

【備忘録】UnityアセットのKoreographerで音ゲーを作る

f:id:Karvan:20210727220033p:plain

小学生理論

取りあえず自分の周りの不都合な出来事はオリンピックの所為にしとけば良いと思っている皆さんこんにちは。きっと私の給料が上がらないも彼女ができないのも四連休後の月曜日がひどく憂鬱なのもオリンピックの所為に違いありません。まぁ、でも「オリンピックは開催されるのにロックフェスは~、子供の運動会は~、週末の飲み会は~」とか言っている割に平然と繁華街に出かけて夏休みの旅行プランとか計画している人は記憶障害か何かだと思います。自分以外の誰かが我慢して閉じこもれば良いって事なんでしょうね。

 

Koreographerというアセット

Unityのアセットで「Koreographer」という結構高額なアセットがあります。これは音楽と同期してイベントを発行してくれる、というアセットで、このブログでもずっと以前に取り上げています。

www.karvan1230.com

イベントの設定は音源の波形が表示された専用のエディタを使用するのですが、こちらは音源のBPMに合わせたグリッド線に沿って設定するので、一定のリズムでイベント発行させたい場合にはかなり使い勝手が良いものとなっています。

 

f:id:Karvan:20210727220308p:plain

白い線がグリッドで1ビートの間隔を表しています。上の図では4ビート(1小節)単位に太い線が引かれています。

逆に時間を指定してのイベント発行(曲開始から〇秒後にイベント発行等)には対応していないので、一般的なノーツが振ってくるような音ゲーを作ろうとした場合、画面をタップして欲しいタイミング=音が鳴るタイミングは設定できても、そこから〇秒前に遡ったノーツを生成するタイミングを設定するのには非常に不向きです。

 

以前、このアセットを使用したゲームを制作した時にはBPMから1ビートの時間を計算し、そこから逆算してイベント発行タイミングを設定していたので一曲分フルで設定しようとするとかなり面倒で手間のかかる作業でした。

 

今回はその手間を省いて「画面をタップして欲しいタイミング=音が鳴るタイミング」を設定するだけで〇秒前に遡ったノーツを生成するタイミングを計る手順を確立したので、(忘れないように)備忘録として記事に書きたいと思います。Koreographerとか興味ない方はごめんなさい。

 

事前処理

結論から先に書くと、「Update内でノーツを生成するタイミングを計る」という事になるのですが、これを行うために事前の処理が必要となります。

 

f:id:Karvan:20210727220534p:plain

上の図の赤い線の箇所が「画面をタップして欲しいタイミング」でイベント発行時の引数にはInt型の値を設定しています。

これらのイベントの設定は楽曲毎にKoreographerで作成されるKoreographerphyというコンポーネント内にイベント毎にKoreographyEventクラスとして情報が保持されているので、Start関数等の初期処理でこのKoreographyEventクラスを全てList等に取得しておきます

f:id:Karvan:20210727220635p:plain

 

Updateでの処理

KoreographyEventクラスにはイベントの発行位置をStartSampleプロパティに保持しています

これは上のエディタ画面の「Start Sample Location」の値で「曲開始からの時間」にサンプリングレート(KoreographyのSampleRateプロパティ)を掛け合わせた値を示しています。(以降、サンプルタイムと呼ぶ)

f:id:Karvan:20210727220831p:plain

 

この仕様に合わせて、ノーツを生成する為のオフセット時間にサンプリングレートを掛けた値をオフセット値として求めて

f:id:Karvan:20210727220906p:plain

Update内で現在のサンプルタイムにオフセット値を加算した値とKoreographyEventクラスのStartSampleプロパティを比較することでノーツを生成するタイミングをチェックします。

f:id:Karvan:20210727221012p:plain

説明はちょっと分かりづらいですが、ソースを見れば単純なロジックで実装できることが分かると思います。

 

使用例

Koreographerには上記の手法で作成された音ゲーがサンプルとして付属していますが、見栄え的には出来が良くないので自前で作ってみました。
飛んでいる鳥の前に障害物が出現、それを避ける場所にターゲットが表示されるのでノーツがそこへ移動したら画面をタップして鳥を移動させます。

f:id:Karvan:20210727221124g:plain

gifファイルで音が出せないので、右横の〇が色が変わるタイミングが「音が鳴るタイミング」だと思ってください。
ノーツが移動するタイミイグ = 〇の色が変わるタイミングになっていると思います。

 

やっぱり面倒くさいな・・・・

【Unity】ベジェ曲線を求めて放物線を描く

f:id:Karvan:20210720193626p:plain

決意と怠惰

今日こそは家に帰ったら速攻でゲーム制作に取り掛かるぞ、と決心したものの夕飯を食べて一息ついたら何故かPSのコントローラーを握っている皆さんこんにちは。最近、UBI製ゼルダの伝説とでも言うべき「イモータルズ フィニクス ライジング」というゲームを購入して見事に土日が潰れました。キャラクター同士の会話内容はちっとも分からないんですが、とにかく探索が楽し過ぎてメインストーリーを進めずに寄り道ばかりしています。熊さん強すぎ。

 

ベジェ曲線

下の図のように移動する鳥が移動方向斜め前にあるリングの中を通る動作を作りたい場合、通常なら目的地のリングに向かって一直線に飛ばせばよいのですが、それだとちょっと面白くない。もう少し動きを加えて放物線を描きながらリングを通過させたい。

f:id:Karvan:20210720194213p:plain

そういった場合の放物線(曲線)の計算方法は色々あるかと思うのですが、その中でもメジャーな手法としてベジェ曲線を使った手法があります。

ベジェ曲線とはコンピュータ上で滑らかな曲線を表現できる計算方法で、最も単純な「2次ベジェ曲線」の場合だと曲線の始点と終点、その間にある中継点を指定するだけで曲線を作る事ができます。

f:id:Karvan:20210720194106p:plain

Google先生に尋ねると色々な解説が各所で掲載されているので詳細はそちらを参考にしてもうとして、この「2次ベジェ曲線」をUnity上で計算する場合はVector3のLerpを使います。

f:id:Karvan:20210720194123p:plain

上記の関数の引数tは始点と終点の距離を1とした時の距離でこの値を0~1に変えることで曲線上にある点を求めることが出来ます。
ここで重要なのは中継点の位置で、始点と終点の中間地点から上(Y軸)へ距離を取った地点を中継点とすると

 

f:id:Karvan:20210720194154p:plain

こんな感じの曲線が求められます。

もちろんこの曲線上を移動するようにしても問題ないのですが、この場合、最初に鳥はリングに向かって斜め方向に飛び、リングに到達すると進行方向(Z軸方向)に一旦向きを直して移動することになり、ちょっとぎこちない動作になります。

f:id:Karvan:20210720194416p:plain

 

垂線を求める

スムーズに方向を変えるような曲線にしたい場合は、中継点を中間地点の上(Y軸)に置くのではなく、始点から終点へ向かうベクトルの(XZ平面の)垂線上に置くことが必要になります。

f:id:Karvan:20210720194531p:plain

こうした指定したベクトルの垂線を求めたい場合にはVector3のProjectを使います。

f:id:Karvan:20210720194626p:plain

Vector3.ProjectはUnityのリファレンスによれば「ベクトルを別のベクトルに投影します。」と書かれています。
つまり、上の式のProjectionは「vectorベクトルの垂線」と「onNormalベクトル」の交点を表すと考えることができます。

f:id:Karvan:20210720194651p:plain

なので、投影ベクトルのProjectionから投影元のvectorの差分ベクトルを求めればonNormalベクトルの垂線ベクトルを求めることが出来ます

f:id:Karvan:20210720194706p:plain

後は中間点からこのベクトルに沿って距離を取った地点を中継点に置けばスムーズに方向を変える曲線を求めることが出来ます。

 

f:id:Karvan:20210720194853p:plain

上から見るとこんな感じ

f:id:Karvan:20210720194922p:plain

 仕上げ

垂線ベクトルに沿った点を求めることができたので、その地点から上(Y軸)へ距離を取った地点を最終的な中継点としてベジェ曲線を求めました。

f:id:Karvan:20210720195047p:plain

ちょっと見ずらい・・・
後ろから見るとこんな感じ

f:id:Karvan:20210720195116p:plain

この求めた曲線に沿って鳥を移動させます。

f:id:Karvan:20210720195240g:plain

こんな感じ。一直線に進むよりも結構ダイナミックな動きになったと思います。

 

 

【進捗報告】アレコレ手を出し過ぎて頭が痛い

f:id:Karvan:20210713211430p:plain

夏が始まる

最近、通勤途中にすれ違う女性からちょくちょく視線を感じるので髪切ったせいかなぁ、モテ期がとうとう・・・とか思っていたら、首に掛けた扇風機のお陰だと気づいた皆さんこんにちは。首掛け扇風機ってもっと普及していると思っていたのですが以外に見かけませんね。この蒸し暑い中、マスクをつけて歩くのに扇風機がないとかどんな苦行だよ。

 

脱出ゲームを作っている

このブログの中でも度々紹介していますが、去年の夏頃からPC向けの脱出ゲームを作っています。開発期間はもう一年近くになりますが未だ進捗度は50~60%ぐらい。
完成までの道のりは長そうですが、先日のIndie Live Expoでは「2021年11月リリース」と紹介してもらったのでそれ迄には完成させたいと思っています。

f:id:Karvan:20210713211640g:plain

 

直近の作業としては、上の動画にあるようにエレベーターを追加し、それに乗る事で新ステージへ移動する(別シーンへ遷移する)ようにしました。

これまでは一つのシーン内で各ステージを作っていたのですが、オブジェクトがドンドン増えていくと負荷も増えていくし、シーンビュー内もごちゃごちゃして分からりづらくなるしで、作業が辛くなっていたので思い切って新しいシーンを作る事にしました。
使っているフレームワーク(アセット)の仕様上、シーン遷移を挟むとどうしても「Loading画面」が表示されてしまうのですが、それが出来るだけ不自然に見えないようにプレイヤーにはエレベーターに乗ってもらう事にしました。ほんとはシームレスに遷移するのが理想なんですが。

 

 また、当然ながら新パズルも作っています。

f:id:Karvan:20210713211919p:plain

 

f:id:Karvan:20210713211939p:plain

こういう問題や

 

f:id:Karvan:20210713212103p:plain

 

f:id:Karvan:20210713212114p:plain

こういう問題

 

f:id:Karvan:20210713212306p:plain

こういった問題。これはパズルと言うより数学の問題に近いですが、そこはユーザに優しく救済ルートも用意しています。

 

f:id:Karvan:20210713212439p:plain

まぁ、指示の通り箱を開けたらどうなるかはお楽しみ。

 

 カジュアルゲームを作っている

そんな感じで11月リリース目標で脱出ゲームを作っている中、直近のunity1weekに投稿した作品(The two met)がそこそこ好評だったので調子に乗ってスマフォ向けのカジュアルゲームとして開発しています。

f:id:Karvan:20210622205053p:plain

 

f:id:Karvan:20210713212903g:plain

unity1weekではフラットな画面で二個の球を操作するゲームでしたが、製品版ではそれに加えて上の動画のようにペンギンを主人公にしたステージとクォータービューで立体的に見えるステージを用意する予定です。

当初はこの夏の間に開発してリリースする予定だったのですが後述の理由によりちょっと無理そう。

 

 集英社の企画に挑戦する

前回のブログでも紹介しましたが、現在、集英社の方でインディーゲーム企画の募集が行われています。

game-creators.camp

大賞は賞金100万円と開発資金援助なので狙いに行かない理由はありません。
今年の初めにunity1weekで投稿した作品(Supra animus quo)が思いのほか高評価で色んな方が褒めてくるので、これをベースにストーリーパートを加えたゲームを企画しようと思っています。

f:id:Karvan:20210713213313p:plain

まぁ企画書だけでも応募可能なのですが、なんせ説明するのが難しいゲームなのでプロトタイプ版でも実際にゲームを作って審査員に遊んでもらってアピールするか、プレイ動画を見せてアピールする以外に賞レースに勝ち残れないと思っています。

締切が9/30までなので、それまでにデモ版がプレイ動画を作成しないといけません。それにSTG系のゲームも別企画として応募したい。もちろんプレイ動画を添えて。

 

脱出ゲームも作り、カジュアルゲームも作り、コンテスト用のゲームも作る・・・アレコレ手を出し過ぎて頭が痛い、という話でした。

 

【Unity】お手軽なCinemachineのVirtualCamera切替

f:id:Karvan:20210706212743p:plain

出費過多

いつまでたってもiPadのDaznが「しばらくお待ちください。」エラーのまま改善されないのでiPadAirに買い直して、ついでに保護フィルムとカバーケースを購入していたら、アセットストアの半額セールにつられて衝動買いを抑えらえずにいて、尚且つ五年使ったスマホのバッテリーがいよいよ限界らしく機種変更をしないといけないし、腕時計も調子が悪いし云々で出費過多な皆さんこんにちは。

コロナ禍で外に出掛けることが少なくなり一時期出費は抑えられていたのですが、気が付けば貯金が減っていきます。先日はPCも購入したし(未だ納品されない)、どうしよう。

 

集英社ゲーム企画コンテスト

講談社に続き集英社の方でもインディーゲームを対象としたオリジナルゲームコンテストが開催されています。

www.koubo.co.jp

募集期間:~2021/09/30 23:59 まで
応募内容:JPEGかPDFで企画書を提出(任意でゲーム動画かデモ版を提出可)
賞・特典:大賞100万円 優秀賞10万円

 

応募者は事前に「集英社ゲームクリエイターズCAMP」に登録する必要があります。また、大賞作品には開発資金も提供されるようです。

こういった大手企業からのインディーゲームに対する支援企画がどんどん立ち上がってくるのは大変有難い事ですね。
きっと講談社と同じように応募が殺到すると思いますが私もチャレンジしたいと思います。

 

VirtualCameraの切り替え

前回の記事ではBlend List Cameraを使ってVirtualCameraの切替を行いましたが、CinemachineにおけるVirtual Cameraの切り替えは優先順位付けにより行われるのでBlend List Cameraを使わずとも優先順位(=Priorityプロパティ)を変更するだけでVirtualCameraの切り替えを行う事ができます。
しかもCinemachineは優秀なのでVirtualCameraの切替をスムーズな形で行ってくれます。

 

 「スムーズな」というのは例えばカメラA→カメラBに切り替えを行った場合、カット割りのようにカメラAの映像→カメラBの映像を切り替えるのではなく、カメラAの映像からカメラBの映像の間を補完しながら映像を切り替えてくれます。

なのでユーザ側から見ればカメラがA→B切り替わったとは感じず、一台のカメラがAの位置からBの位置へ移動したように見えます。

f:id:Karvan:20210622205238p:plain

 

f:id:Karvan:20210622211337g:plain

 

TriggerEnterで切り替える

具体例として下の図ような移動する鳥を追いかけるCameraAと地面を写すCameraBの切り替えを作ってみます。

f:id:Karvan:20210706214458p:plain

 

最初はCameraBが地面を捉えた画像を表示し、

f:id:Karvan:20210706214524p:plain

 

移動する鳥がフレームインしたらCameraAへ切り替えます。

f:id:Karvan:20210706214606p:plain

 

切替のタイミングは前方に設置したBoxCollider内に鳥のオブジェクトが侵入した瞬間とするのでTriggerEnter内でCameraAのPriorityを高い値に変更します。

f:id:Karvan:20210706214641p:plain

 

実行結果

作っている最中にちょっと欲が出たので、鳥の移動が終了したら鳥を前方から映すCameraCに切り替わるようにもしました。

 f:id:Karvan:20210706220112p:plain

 

実行結果はこんな感じ。
スクリプトではVirtualCameraのPriorityを変更してるだけですが、かなり良い感じにカメラが遷移してくれます。

 

f:id:Karvan:20210706220334g:plain

 

【Unity】Cinemachine Blend List Cameraでカメラを順に切り替える

f:id:Karvan:20210622205053p:plain

バックアップは大事

Unityのアップデートをしたら何故かプロジェクト内のプレハブの設定が全て初期化されて絶望の淵に立った皆さんこんにちは。
ややこしいのはフレームワークとして購入したアセットのプレハブも同様に設定値やらオブジェクトの指定やらが吹き飛んだ事で、アセットストアから再インストールしようとしても「現在は最新バージョンになってます」的なメッセージが表示されてインストールできないし、フレームワークとして使っているから一旦アンインストールするとか怖くてできないし、修復しようにも正しい設定値が分からないしで、本当に目の前が真っ暗になったのですが、サブHDDの方にバックアップがある事を思い出して何とか元に戻すことができました。バックアップは大事。

 

カメラの切替

Unityではシーン内のオブジェクトの動作は一旦カメラで撮影してその映像を画面に表示する、という形式のため、場面を大きく変える、違う角度から物をみる、といった時にはそれに応じてカメラを切り替える必要があります。

 

f:id:Karvan:20210622205238p:plain

 

ただ場面に応じてカメラを逐一用意するとなるとリソースを無駄に食うし、スクリプトで切り替え処理を作るのも面倒くさい。
そういった場合にはCinemachineを利用してVirtual Cameraの切り替えをTimeline等で行う方が効率が良いと思います。
Cinemachineはunity標準のカメラ制御用のアセットでPackageMangerからインストールすることができます。
使い方についても色々なサイトで紹介されているので、それを参考にすれば導入は難しくないと思います。

 

【Unity】CinemachineのVirtual Camera切替をコントロールする - テラシュールブログ

【Unity】【Cinemachine】Virtual Cameraをブレンドしながら切り替える方法まとめ - LIGHT11

 

Blend List Camera

複雑なカメラ割やカットシーンを多投する場合はTimelineを組んで作成した方がよさそうですが、例えばステージクリア時の演出など一回限りのカメラワーク再生で良い場合は「Cinemachine Blend List Camera」を使う方が便利です。
Cinemachine Blend List Camera コンポーネントは、指定したVirtual Cameraの切り替えを指定通り行ってくれるコンポーネントです。但し、そのVirtual CameraはCinemachine Blend List Cameraの子オブジェクトとなっている必要があります

メニューから、Cinemachine > Create Blend List Camera を選択すると

f:id:Karvan:20210622205447p:plain

 Cinemachine Blend Camera コンポーネントが付与された親オブジェクトと、その子オブジェクトとしてVirtualCameraが付与された2つのオブジェクトが出来ます。

f:id:Karvan:20210622205637p:plain

 

子オブジェクトのVirtualCameraをそれぞれ表示したい位置に設置し、角度やsize等を設定します。

f:id:Karvan:20210622205722p:plain

 

f:id:Karvan:20210622205740p:plain

 

Cinemachine Blend Camera側では切り替えの設定を行います。

 

f:id:Karvan:20210622205943p:plain

[Hold]カメラを有効する時間
[sec]次のカメラに切り替わる時間
カメラ切り替わり時のEasingもプルダウンから指定できます。

 

設定が終われば実行するだけですがBlend List Cameraはこのコンポーネントが有効化されたときに一度限り再生されます。
この為、スクリプトで再生したいタイミングでBlend List Cameraを有効にする必要があります。
有効化はオブジェクトをアクティブにするかコンポーネントのenabledをtrueにします。

 

using Cinemachine;

CinemachineBlendListCamera _blendListCamera;

{
    …
    // 再生タイミング
    _blendListCamera.enabled = true;
    …
}

 

実行結果

実際の動作はこんな感じになります。

f:id:Karvan:20210622211337g:plain

動画ではカメラが移動してズームイン/アウトしていますが、これらはCinemachine側で行ってくれます。
同じような処理を自力でスクリプトを組むとなると少し面倒なのでこれは便利ですね。

【Unity-C#】Interfaceの利点を学ぶ

f:id:Karvan:20210615212000p:plain

 

ありがとうIndie Live Expo

ブログの閲覧数がやたら増えているなぁと不思議に思っていたらIndie Live Expoで紹介されたお陰だと気づいた皆さんこんにちは。6時間近く放送された番組の中で15秒ちょっと映っただけなんですがね、紹介されたところで誰も気に掛けないだろう、とやさぐれていた自分が恥ずかしいです。どうも有難うございます。Twitterのフォロワーもちょくちょく増えてほんとビックリ。

 

Interface

さて、UnityのスクリプトはC#が標準なのでInterfaceを定義して使う事ができます。
InterfaceとはC#の参考書などによれば「クラス外部からみた規約だけを定めるもの」とうたわれており、それがピンとこない場合は「実装がないメソッド定義をアチコチで使い回すもの」とでも思えば良いです。

 

例えばゲーム内に敵A、敵Bがおり、それぞれに独自の制御用クラス(EnemyA_Control,EnemyB_Control)が実装されているケースを考えてみます。

Manager的なクラスが敵A,Bにゲーム内のイベント(ゲーム開始や終了等)に応じた処理を依頼する場合、ManagerクラスはEnemyA_Control,EnemyB_Controlそれぞれのクラスを意識して「EnemyA_Controlのゲーム開始時処理」「EnemyB_Controlのゲーム開始時処理」をコールする必要があります。

f:id:Karvan:20210615212336p:plain

 

 敵が二種類しかいない場合はそれでも構いませんが、敵の数が増えていくと面倒くさいことこの上ない。
処理を依頼するタイミングは同じなのだから(クラスは違えど)コールするメソッドも同じにすることができればコーディングの量も減って以後の作業も楽になります。
なので、そういった要望を実現するためにInterfaceは使用されます。

f:id:Karvan:20210615212613p:plain

 

定義と使い方

インターフェースを定義するには「interface」を使用します。interface内のメソッドやプロパティなどはすべてpublicとして扱われます。

    interface (インターフェース名){
        メソッド名(引数);
        メンバー変数;
        ...
    }

実装側はクラスの継承定義を同じようにインターフェース名を設定します。

    public class クラス名 : (継承クラス名), (インターフェース名)
    {
        メソッド名(引数);
        メンバー変数;
        ...
    }

ここで実装側はInterfaceが持つメソッド、プロパティなどをすべて実装する必要があることに注意が必要です。
(定義のみで中身は空でもよい)

具体的に、先ほどの敵A、Bにゲーム開始時処理のInterfaceを実装したい場合

    // インターフェース定義
    interface IEnemyInterface {
        void StageStartProc();    // ゲーム開始時処理
    }

上記のインターフェース定義を行った後に、各制御用クラスにインターフェース定義を追加します。

    // 敵A側
    public class EnemyA_Control : MonoBehaviour, IEnemyInterface {
        
        void StageStartProc() {
            // 敵Aのゲーム開始時処理
            
        }
    }

    // 敵B側
    public class EnemyB_Control : MonoBehaviour, IEnemyInterface {
        
        void StageStartProc() {
            // 敵Bのゲーム開始時処理
            
        }
    }

 

マネージャークラス側はGetComponentでクラス名でなくインターフェイス名を指定してコンポーネントを取得、ゲーム開始時処理をコールします。

    public List<GameObject> EnemyList;    // 敵A,Bが格納されたリスト
    
    // ゲーム開始時処理
    void StageStart()
    {
        foreach(GameObject obj in EnemyList)
        {
            // インターフェイス名を指定してコンポーネントを取得
            IEnemyInterface objIF = obj.GetComponent(typeof(IEnemyInterface)) as IEnemyInterface;
            
            // ゲーム開始時処理をコール
            objIF.StageStartProc();
        }
    }

Interfaceを利用するとクラス間の疎結合も可能なので色々と利点が多いと思います。

 

【Unity】ステンシルバッファを使って窓を作る

f:id:Karvan:20210608213544j:plain

全く話題にならない神ゲーム

後学の為に色んな実況者のゲーム実況を観ていたらYouTubeのお勧めが何故かVtuberの切り抜きで溢れている皆さんこんにちは。ロックマン2の実況が面白くて色々観てたらこうなった。でも、Vtuber同じような名前大杉。

そんな中、久しぶりに購入したゲームが神ゲーでした。タイトルは『The Pathless(ザ・パスレス)』。最近流行ってきたトゥーン調のオープンワールドゲームで、『ABZU』の開発会社による新作アクションパズルゲームです。

f:id:Karvan:20210608213647j:plain

美しいグラフィックと爽快感のある操作性が最大の魅力で、フィールド内を移動するだけでも楽しい。加えてフィールド内に点在するパズルもプレイヤーの観察力を問われる内容で程よい難易度。本当に神ゲーと呼ぶにふさわしいと思うのですが、巷では全く話題になってません。実況動画も攻略サイトも殆どないし。

思うにオープンワールドながらもゲームのメインがパズルなのでボス戦以外に戦闘とかないし、NPCと会話や取引するみたいなイベントもなく全く実況向きでない、実際にプレイしてみないと良さが殆ど伝わらない事が原因なのではないかと。

個人的にあまりに惜しいのでこのブログで紹介しています。
とにかく爽快感のあるゲームがやりたい方、異世界を自由に探索するゲームに興味がある方にはうってつけのタイトルだと思いますので、是非、プレイしてください。

 

 ステンシルバッファ

unityのシェーダでステンシルバッファは描画の際に当該のピクセルを画面に表示するかしないかを決める「マスク」を設定する役目を持ちます。

要はマスクなので「描画したい部分」「描画したくない部分」を明確に定義できるというわけです。

ステンシルバッファの操作はStencilのプロパティを使用します。Stencilのプロパティの宣言は次のようになります。

Ref ステンシルに書き込む値
Comp 使用する比較関数
Pass 比較関数が真のときにの操作

 

 ・比較関数

Greater ステンシル値がバッファ値より大きいピクセルのみレンダリング
GEqual ステンシル値がバッファ値以上のピクセルのみレンダリング
Less ステンシル値がバッファ値より小さいピクセルのみレンダリング
LEqual ステンシル値がバッファ値以下のピクセルのみレンダリング
Equal ステンシル値がバッファ値と等しいピクセルのみレンダリング
NotEqual ステンシル値がバッファ値と等しくないピクセルのみレンダリング
Always 常にステンシル テストは成功
Never 常にステンシル テストは失敗

 

・処理

Keep バッファの現在コンテンツを保持します
Zero バッファにゼロを書き込みます
Replace ステンシル値をバッファに書き込みます
IncrSat バッファの現在値を増分させる
DecrSat バッファの現在値を減分させる
Invert すべてのビットを無効にする
IncrWrap バッファの現在値を増分する。値がすでに 255 の場合は 0 にする
DecrWrap バッファの現在値を減分する。値がすでに 0 の場合は 255 にする

 

窓を作る

「描画したくない部分」を作れると言う事は3Dモデルの形状を変更せずに穴を開けられる、と言う事でそれを利用して窓を作ってみます。手順は

  1. 窓となるオブジェクトにStencilでマークするシェーダを適用する
  2. 壁側のオブジェクトにはStencilでマークされた場所以外を描画するシェーダを適用

となります。

まず最初に窓側のオブジェクト用に透明shaderを作ります。

Shader "WriteStencil"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // 透明にする
                return fixed4(0, 0, 0, 0);
            }
            ENDCG
        }
    }
}

 このシェーダにStencilのプロパティを追加します。(CGPROGRAMの上あたり)

// ステンシルバッファの設定
Stencil
{
    // ステンシルの番号
    Ref 2
    
    // Always: このシェーダでレンダリングされたピクセルのステンシルバッファを「対象」とするという意味
    Comp Always
    
    // Replace: 「対象」としたステンシルバッファにRefの値を書き込む、という意味
    Pass Replace
}

 

壁を作る

壁用に標準的なStandardシェーダを作ります。これはCreateメニューから選んで作ることができます。

f:id:Karvan:20210608215255p:plain

 

この作成したStandardシェーダにStencilのプロパティを追加します。

// 壁側のStencil
Stencil
{
    // ステンシルの番号
    Ref 2
    
    // NotEqual: ステンシル番号がバッファ値と等しくないステンシル番号のピクセルのみレンダリング
    Comp NotEqual
}

 

 結果

作った窓シェーダーと壁シェーダーをそれぞれ3dモデルに適用してみます。

f:id:Karvan:20210608215756g:plain

壁を透過して背景が表示されているのが分かると思います。
ステンシルバッファを利用してモデルを変更することなく窓を作ることができました。

◇プライバシーポリシー

●個人情報の利用目的

当ブログでは、メールでのお問い合わせ、メールマガジンへの登録などの際に、名前(ハンドルネーム)、メールアドレス等の個人情報をご登録いただく場合がございます。

これらの個人情報は質問に対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、個人情報をご提供いただく際の目的以外では利用いたしません。

●個人情報の第三者への開示

当サイトでは、個人情報は適切に管理し、以下に該当する場合を除いて第三者に開示することはありません。

・本人のご了解がある場合
・法令等への協力のため、開示が必要となる場合

個人情報の開示、訂正、追加、削除、利用停止
ご本人からの個人データの開示、訂正、追加、削除、利用停止のご希望の場合には、ご本人であることを確認させていただいた上、速やかに対応させていただきます。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。

このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

●免責事項

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

●プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。