ブルーノのC#プログラミング & unity勉強日記

プログラミング素人、ブルーノの自主勉強ノートです。他のプログラミングを勉強したい方の助けになれば幸いです。その他趣味の雑記もしていきたいです

【Unity】Finger IdとInput.touchesのインデックスの関係

f:id:covory10101101276:20200516101433j:plain
イエローナイフ -カナダ-

みなさんこんにちは🌑

相変わらず外出自粛の防災無線が毎日家のすぐそばで鳴り響いているブルーノです。主要都市部以外は緊急事態宣言が解除されましたね。いいなあ。あまりにも外が恋しくて最近は十何時間ある自然の動画をYoutubeで流しながら生活しています。


さて、今回も私がアプリを作るうえで少しだけ引っ掛かった、タッチの参照について調べたことをまとめていきます。


今回のケース

とあるスマホアプリの開発において、画面半分のタッチは移動に割り当てられ、画面もう半分はカメラの移動に充てられるといった、よくあるスマホ版TPSアプリのような操作を実装することになりました。
f:id:covory10101101276:20200516102835j:plain
タッチが検知されたら押されている間その指の情報を常に取得するクラスを移動用とカメラ操作用で二つ作り、タッチの動向を見守ろうとしました。Unityの仕様では、タッチはInput.Touchクラスで管理されています。
docs.unity3d.com

またそれらを配列として所有するInput.touchesもあります。
docs.unity3d.com
こちらの"touches"は小文字。

というわけで、このInput.touchesにアクセスし指の状態を取得しようと思ったのですが、実は同じ指の情報が常にInput.touchesの同じインデックスに格納されているわけではないんです。
というのも考えてみれば当たり前で、例えばまず画面に一本指をタッチします。
f:id:covory10101101276:20200516104530p:plain
このタッチ情報はInput.touchesの0番目に格納されます。
次にもう一本画面に指をタッチすると
f:id:covory10101101276:20200516104818p:plain
Input.touchesの1番目に格納されます。ではここで0番目の指が離されるとどうなるか、
f:id:covory10101101276:20200516105208p:plain
Input.touches[1]にあった指の情報が詰められて、Input.touches[0]になります。

さてこれではゲームの実装は少し困ったことになりますね。タッチされた指の配列がいちいち変わるんじゃ、手動で今押されている指はほかの指が離されてインデックス詰められて...といちいち計算させていたんじゃ大変です。
そこでInput.Touchクラスには、fingerIdという変数が備わっています。
docs.unity3d.com
Touchのインデックスがころころ変わるのに対し、このfingerIdは指が離されるまでは変わりません。これを参照し続ければ、ある指の動向をずっと見守ることができます。

とまあタッチ処理についてはここまで知っておけばあまり困ることはないんですが、少し気になったのはこの後のタッチの処理についてです。
先ほどの指の流れにfingerIdも添えて表示するとこうなります
f:id:covory10101101276:20200516110205p:plainf:id:covory10101101276:20200516110216p:plainf:id:covory10101101276:20200516110228p:plain

この状態で指をまたもう一本タッチすると、現在Input.touches[0]の指のインデックスはどうなるか、配列が普通に増えるだけなんだから、こうなるのかと私は思っていました。
f:id:covory10101101276:20200516110544p:plain
しかし、これは間違い。

実際にはこうでした。
f:id:covory10101101276:20200516110915p:plain
右のタッチのインデックスが新しくタッチされたTouchのインデックスの後ろに回ったのです。最初の一連からすると新しくタッチされた情報は常に追加される形で増えそうなものですが、これを見る限り単純にそういうわけではないようですね。
fingerIdに合わせて配列が整理されるのかな?

微妙な内容ですが自分の考えとずれがあったのでまとめておきました。
また、fingerIdもリファレンスに「ユニークなId」と強調して書いてあったからそれぞれ変な数が入っているのかとも思いましたが、案外普通だったのが個人的に一つ収穫です。


今回はここまでにしておきます。

【Unity】Physics.Raycastの引数"LayerMask"の注意

f:id:covory10101101276:20200507192306j:plain
ベルギー -ブリュッセル-

みなさんこんにちは🌞


マイナンバーカードで10万円給付申請をしようと思ったのにいつの間にかカードを無くしていて激込みの役所にまた行かなくてはならなくなってしまったブルーノです。めんどくさいけどいかなきゃ。。。


さて、今回はRayを使った様々なオブジェクトの探索についてこんがらがったので、まとめました。

Rayを使ってオブジェクトを探索するとき、Physics.Raycast()もしくはPhysics2D.Raycast()を使いますよね。
docs.unity3d.com
docs.unity3d.com

しかし間に余計なコライダーがいくつもあってRayが遮られてしまい、取得したいオブジェクトにRayがあたらない!なんてことありませんか?
そんな時は例えば以下のような対処法が挙げられます。

1. Physics.RaycastAll(Physics2D.RaycastAll())を使う

docs.unity3d.com
docs.unity3d.com

Raycastの結果を受け取る配列を用意し、そのすべてをforeach文で検証していきます。3Dと2Dで受け取り方が微妙に違うので注意。ただしforeach文での探索はコストがかかるので毎フレーム行う場合にはお勧めしません。

2. collider.RayCast()を使う

docs.unity3d.com
飛ばしたRayのうち、「このコライダーだけを探せ!」というような処理をすることができます。最初から探したいオブジェクトが決まっていればこちらを使うと早いです。1. よりもコストはかかりませんが、探したいオブジェクトが決まっていない場合には少々使いずらいです。

というわけで、今回は後述する3. の方法がメインの話となります。


3. LayerMaskを指定してオブジェクトを探索する。

実は最初に挙げたPhysics.Raycast(Physics2D.Raycast)には引数がいくつかあり、その中に引数でLayerMaskを設定できます。これは、入力したint値のレイヤーマスクのみを判別するというものです。
これは便利!!ということで試してみます。
f:id:covory10101101276:20200507200718p:plain
Layer 8に「sphere」を設定
f:id:covory10101101276:20200507200818p:plain
sphereにレイヤーを設定
f:id:covory10101101276:20200507200933p:plain
コードはこんな感じ。21行目のPhysics.raycastの第五引数にレイヤー番号8を入れる。

が、
f:id:covory10101101276:20200507200910p:plain
いくら押しても反応しない。。。。
レイヤー番号は0からだけど引数指定するときは1からなのか?とかバカなこと考えて数字を前後させてみたがダメ。IgnoreRaycastレイヤーは無視されるらしいからそれを抜いた番号なのか?とかアホなこと考えてさらに前後にずらしてみたが反応なし。。。

なぜだ。。。
しばらくネットで情報を探すと(というか一周してリファレンスを見返すと)
f:id:covory10101101276:20200507201954p:plain
DefaultRaycastLayersってなんだ? 普通に考えたら0か...?

docs.unity3d.com
3Dの方は特に目を引く記述はなかったが、
docs.unity3d.com
「マスクの値は IgnoreRaycastLayer マスクのビット補間に相当します。」
2Dの方に気になる記述を発見。ビット補間..?


どうやら、このLayerMaskの値は2bit値で管理されているようです。なのでこの引数に送るのは我々がよく認識する10進数ではなく、2進数での値のようです。
しかも単純にレイヤー番号を2進数にするわけではありません。例えば今回の場合はレイヤー番号は8なので2進数に直すと1000ですが、これも違うのです。
レイヤー番号はビットフラグという管理のしかたがされているようで、普通の管理番号をビットシフトする必要があります。
シフト演算
もうすでに文系の私は意味不明です。
が、これによると1から0を「8」つ増やした値を10進数に直した値が今回のLayerMaskの値ということになります。
ビットシフトはコーディングする際に簡単な方法があり、
f:id:covory10101101276:20200507204317p:plain
21行目にあるように 「1 << レイヤー番号」で求めることができます。


これで試すと....
f:id:covory10101101276:20200507204503p:plain
できました!長かった。。。。



こんなのわかんねーよー。しかもその説明にリファレンスからたどれないし。。。
なんとか仕組みが分かったーでも面倒だなーみんなこんなこと入力してんのか...ん?
docs.unity3d.com
f:id:covory10101101276:20200507205054p:plain
f:id:covory10101101276:20200507205003p:plain



はい、これもパターンです!
f:id:covory10101101276:20200507205301j:plain




今回はここまでにしておきます。。。

【Unity】インスペクターの変数に初めから値を入れておく方法【Reset()】と地味注意

f:id:covory10101101276:20200507000307j:plain
日本 山梨県-忠霊塔-

みなさんこんにちは🌞

久々に帰ってきたブルーノです。世間でコロナが流行って初のゴールデンウィークですがみなさんいかがお過ごしでしたでしょうか。私は友人とArkのPS4版をやって楽しんでいました。いろいろやりづらさが目立つゲームですが近年のサバイバルゲーの中では破格のグラフィックとクオリティだと思います。



さて、Unityの使い方に少し慣れてくると、[Seriarize Field]などでprivate修飾子をつけた変数をUnityエディタ上から指定する機会等が増えてくると思いますが、その値がわかりきっているのにいちいち手動で設定するのは面倒ですよね。そこで、UnityにはReset()という関数が用意されています。

docs.unity3d.com

公式リファレンスを見ればもうこちらから説明することはないほどわかりやすい機能と説明なんですが、インスペクターの変数に対して自動でReset()内の処理を行ってくれます。

f:id:covory10101101276:20200507002750p:plainf:id:covory10101101276:20200507002802p:plain
こんな感じ

値の初期化などに使えますね。しかし、少しだけこの関数を使うときに注意することがあったのでメモします。

1. インスペクターに表示されていない場合は処理が通らない

Reset()の中に記述した内容はあくまで変数がインスペクターに表示されている場合のみに行われるようです。
具体的にどういうことかというと、
f:id:covory10101101276:20200507003120p:plain
↑のように変数(Sphere)がprivateだと...

f:id:covory10101101276:20200507003035p:plain
初期化されてないのです。

Resetの中身は通っているはずなのに処理がされてないというのは不思議な感じですね。

2. いろいろ変数の修飾子をいじるとResetされてないかもしれない

では今度は、先ほどの1. で書いたコードのSphere変数にそのまま[Seriarize Field]を追加してみました。するとどうなるのでしょうか。
f:id:covory10101101276:20200507003930p:plain
f:id:covory10101101276:20200507003957p:plain
Sphere変数に値が入っていませんね。UnityのReset()という処理が入るのは単純にコードを保存したタイミングではないようです。
こういったときは変数右上の歯車のマークからResetを選択すると、再びリセットされます。
f:id:covory10101101276:20200507004502p:plainf:id:covory10101101276:20200507004512p:plain


以上のように、Reset()は初期化した際にインスペクタの値に自動で値を入れてくれる便利?な関数ですが何かと注意点があるようです。
私の場合は勘違いしてどんな記述でも通るのかと思ってAwake()のような使い方をしていたためReset()内に記述していた変数すべてがNullになっていました。まあリファレンスに書いてあることがすべてで私が早とちりしていただけなのですが笑 そんな大ごとでもすごい発見でもないですが備忘録ということで。

今回はここまでにしておきます。

No.002 完成

f:id:covory10101101276:20190716211229j:plain
アウヤンテプイ -ギアナ高地-

みなさんこんにちは🌚

もう7月も半ばなのに一向に晴れる気配がなくて気分もじめっとしてるブルーノです😓洗濯物が乾かない...
さて今回も今まで作っていた作品をやっていきましょう...と言いたいのですが実は最近作品を完成させてしまい、記録を残していませんでした笑 実は諸事情により記録をつけている余裕がありませんでした。なので今回は完成作品のスクショを貼っていきます。

完成品

f:id:covory10101101276:20190716212553p:plain
タイトル
タイトルメニュー。タイトル画面中はカメラが動き回りながら町中を写しています。

f:id:covory10101101276:20190716213714p:plain
ステージその1

f:id:covory10101101276:20190716213645p:plain
ステージその2

残りも公開しようと思ったのですがAppStoreにあげようと思うので今回の紹介はここまでにしておきます。もし気になったらぜひダウンロードしてくださいね。実際にリリースしたら記事でも紹介しようと思います。

No.002 進捗状況16[ゲーム画面のUIの実装]

f:id:covory10101101276:20190517111503j:plain
コルコバード -ブラジル-

みなさんこんにちは🌚

では今回もやっていきましょう!!

これまでゲーム中のオブジェクトやスクリプトなど、ゲーム全体の中枢をなす舞台を主に作ってきました。今回は実際にユーザーが触れるための境界、媒体となるUIを実装していきます。

MobileSingleStickControlの改良
といっても実は以前導入部分だけは触っているんですよね。ただ一番初めに触るべき場所はここじゃないと思ってそのまま放置してしまいました。過去の記事はこちら
dreameaters5239.hatenablog.com
キャラクター移動のスティックは作ってあるので今回はこのCanvasに加速のボタンとライフを実装しようと思います。
加速ボタン(CrossPlatformInput)
以前作った時計っぽいアイコンはデザイン変更しました。

f:id:covory10101101276:20190517194213p:plain
左上の白丸が加速のためのスイッチ。
会話ウィンドウも後ろに写っていますが開始時に消えるので気にしないでください😇
さて、このMobileSingleStickControlもといCrossPlatformInputは仮想ジョイスティックおよびボタンをInputManagerに割り当てたボタンに対応させることができる機能です。
f:id:covory10101101276:20190517195458p:plain
ButtonについているEventTriggerでどのタイミングで動作するかを設定し、ButtonHandlerで作動させるInputManagerのボタンを指定します。今回はTimeQuickerという名前を指定しました。なおこの指定する名前はInputManagerに登録してないと使えないので注意
f:id:covory10101101276:20190517195915p:plainf:id:covory10101101276:20190517195931p:plain
そして名前空間でUnityStandardAssets.CrossPlatformInputを指定して、InputManager.で呼び出しましょう
下のスクリプトを同じくボタンにコンポーネントとしてつけます。
f:id:covory10101101276:20190517200147p:plainf:id:covory10101101276:20190517200206p:plain
4行目でCrossPlatformInputを指定して、InputManagerを使えるようにしています。そしてこの長いスクリプトを要約すると
・内蔵しているChargeTimerをボタンを押している間増やしていく。
・タイマーが基準値を超えている時にボタンを離すと加速する。
このたった2つに落ち着きます笑
さて、このスクリプトをわざわざButtonに作ったのには実は理由があります。知り合いから教えてもらった小技で、画像を円形に表示できる機能を使って円の満ち欠けを使って時計盤っぽく表現しようと思います。
その方法が下の通り。ImageコンポーネントのImage Typeを[Filled]に、Fill MethodをRadial 360にします。
f:id:covory10101101276:20190517204451p:plain
そしてその下のスライダーを左右させると...
f:id:covory10101101276:20190517204716p:plain
画像が円形に満ち欠けする!!
そして先ほどのスクリプトにこのFill Amountにアクセスする記述をします。
f:id:covory10101101276:20190519122642p:plain
ハイライトしてある部分がその部分ですね。36~39行目に書いてある通りImageコンポーネントを取得、さらにそもそも最初にUnityEngine.UIにアクセスできるよう名前空間を指定するのも忘れないように
そしていろいろリデザインした時計でこれを起動すると!!
f:id:covory10101101276:20190519123013p:plain
動かない!
なんででしょう!どうやら原因はいろいろリデザインにあるらしいです...
いろいろImageを重ねたり子にして階層を配置してしまうとその画像に当たり判定が阻まれてしまい正常に動作しないようです。原因を探っているときよく目にしたCanvasコンポーネントをアタッチしてOverride Sortingという機能を使う対処法もなぜかうまく動作しなかったんですよね。
f:id:covory10101101276:20190520142138p:plain
全ての階層の画像にこのコンポーネントをつけて順番を変えられるはずが...?
なのでまた別の方法を見つけました。Graphic RayCasterという方法です。
f:id:covory10101101276:20190520142406p:plain
これはCanvasにアタッチするタイプのコンポーネントで、これをつけているとこの階層のImageすべてにタッチ情報が送られます。しかし、もちろんImageコンポーネントにRayCast Targetがチェックされていない場合は反応しません。これで....
f:id:covory10101101276:20190520142642p:plain
ようやく成功!!!
やっとできました!!長かったですね。。。

ライフUI
では今回はさらにもう一つゲームにはおきまりのライフゲージを今更ですが実装していこうと思います!!
UIに先ほどと同じ要領でImageたちを配置...
f:id:covory10101101276:20190520155845p:plain
彼らはタッチする必要がないので先ほどのような行程はいりません
f:id:covory10101101276:20190520160013p:plainf:id:covory10101101276:20190520160024p:plain
そしてプレイヤーのスクリプトにライフのint値および敵にあたった時の処理を記述する。この時LifePointはPublicにして他からアクセスできるようにしておきましょう。
f:id:covory10101101276:20190520160233p:plain
そしてこちらの新たに作ったLifePointScriptを先ほどのライフのUIにアタッチします。このスクリプト
・プレイヤーのライフにアクセス
・自身のImageコンポーネントにアクセスし、ライフに応じて画像の満ち欠けを3等分
という風になっています。
さあ起動してみると....

f:id:covory10101101276:20190520160549p:plain
ライフが減ってそれに応じた欠け方になった!
うまくいきましたね。イーサンがいないように見えるのはさっきプレイヤースクリプトに足したコルーチンでイーサンの姿を点滅させているからです。今回はここまでにしておきます。
まだ料理でいうと材料揃える段階から進んでいない....

No.002 進捗状況15[ゼルダっぽいタイトル画面]

f:id:covory10101101276:20190514165718j:plain
ブライスキャニオン国立公園 -アメリカ-

みなさんこんにちは🌚

GWは魔の1日バイト9連勤を果たしたブルーノです。今年のGWは天気もそんなによくなかったので遊びにはどっちみち行けなかったから良いんですけどね😠😭

では早速作っていきましょう🌻

今回はタイトルにもある通りゼルダっぽいゲームのタイトル画面を実装してみたいと思います。
ちなみにこんな感じ
www.youtube.com
特徴としては実際のマップを読み込んでおり、その上にタイトルメニューがあることですね。この形式の良いところは最初にマップデータを読み込むのでそこからゲームシーンに移ればマップのオブジェクトを隠したり一度消したりしなくて良いところだと思います。私からしてもこれは簡単だと思い、今回つくろうと思いました。

タイトルのUIの実装
タイトルとゲームスタートのボタンを作りたいと思います。例によってかめくめさんのサイトに良い記事があったのでこれを真似させていただきます。
gametukurikata.com
地味にこのサイトに貼ってある無料日本語フォントサイトのリンクが嬉しかったです。AssetStore有料だし日本語フォント少ないし🌝
f:id:covory10101101276:20190515111659p:plain
こんな感じにしました!!タイトルはどーしよーかなー

そしてGameStart()メソッドを持ったTitleSystemスクリプトを空のゲームオブジェクトにアタッチし、StartButtonに指定します

f:id:covory10101101276:20190515112048p:plain
このスクリプト
f:id:covory10101101276:20190515112110p:plain
適当なオブジェクトにアタッチし....
f:id:covory10101101276:20190515112138p:plain
StartButtonから呼び出す!
ただこれだけでは暗転するアニメーションだけでシーンが変わったりする機能は持っていません。そこで今度はシーン遷移の機能も実装します。今回のゼルダっぽいシーン遷移の方法の利点である先に建物等のロードを済ませるという作業は次のシーンにロードした建物たちを引き継ぐことでそのメリットを活かせます。ということで今のTitleスクリプトにその作業も追加しなければなりません。そこで、またかめくめ氏のこのページを参考にしました。
gametukurikata.com
データを残す方法としてシングルトンという方法を昔少し使ったのですが、いまいち覚えてなかったのでまた勉強です。
f:id:covory10101101276:20190515150336p:plain
シングルトンの中身
はい、というわけで10行目およびAwakeメソッドの中身がシングルトンの実装になります。
10行目で自身のスクリプトをstaticな形で宣言し、共用で使うオブジェクトを12行目のShareObjectに格納しておきます。
最初にテストシーンから起動すると10行目のシングルトンインスタンスには何も入っていないのでこのスクリプトが割り当てられ、25行目にあるように決められたオブジェクトたちをDontDestroyOnLoad()メソッドで壊されなくします。
この状態でこのスクリプトをもつテストシーンから同じくこのスクリプトを持ったオブジェクトがあるゲームシーンに遷移すると、staticな変数にもうインスタンスが割り当てられているということで23行目にあるように新しくAwake()を通ったゲームシーンの方のスクリプトは格納されている同じオブジェクトたちを削除し始めるんですね。おかげで各シーンに同じオブジェクトがあっても共存することなく、かつどのシーンからプレイしても同じ状況でプレイできるということですね。

要はこの唯一無二のインスタンスを保持するためのデザインパターンがシングルトンというようですね。
個人的に一つ気になったのは、ゲームシーンには一応全てのマップ用のオブジェクトが配置してあるわけですがAwake()メソッドを通る前に結局同じオブジェクトたちを一度シーンに作り出すのではないか?ということです。Awake()以前ならレンダリングもされていないから負荷はかからないのでしょうか。ここだけ少し疑問が残りました。

タイトル画面で動く背景
では最後に、タイトル画面のカメラワークを作りたいと思います。「時のオカリナ」から続く3Dゼルダはみんなタイトル画面で実際のマップを使っているのが特徴ですよね。時にRTAやTASでそれが悪用されることもありますが...
カメラワークの作り方はこちらのサイトを参考にしました。
sirohood.exp.jp
動画付きでわかりやすい!!シロフードさんありがとう!!😽
こうして...

f:id:covory10101101276:20190515170313p:plain
できた!!
画像なのでわかりにくいですが、ちゃんとタイトルメニューの後ろでカメラが町中を飛んでいます!!成功です!
今回はここまでにしておきます。、。、

No.002 進捗状況14[人に話しかける挙動]

f:id:covory10101101276:20190508164311j:plain
カッパドキア -トルコ-

みなさんこんにちは🌚

さて今回は以前作ったメッセージスクリプトと組み合わせてゲームのような話しかける処理を実装していこうと思います。

f:id:covory10101101276:20190508164535j:plain
イメージ画像
しかしこの話しかける処理を考えてみたのですがやることが
まず
・会話可能範囲内で対象にカーソルを表示 ←この時複数のキャラが被った場合の処理も考える
そして
・カメラを会話用の位置に移動
・メッセージウィンドウを表示
・キャラ同士を向かいあわせる
と、意外に多いのでとりあえずまだわからないカメラの移動をおいといて他の機能をどうにか実装しようと思います。

カーソルの実装
話しかけることのできるキャラクターをわかりやすくするためにカーソルをまず設けました。
f:id:covory10101101276:20190508210332p:plain
カーソルという空のオブジェクトの子にそれぞれ上部の逆三角錐と下部の円を置いています。地味にこの逆三角錐は初めてのProBuilderで製作したものだったりします。
unity3d.com
最近標準搭載された3Dモデリングツールです。とても簡単!
そしてこれにMessageManagerスクリプトと、対象のオブジェクトに話の内容と位置情報を格納したActiveMessageスクリプトを作成
f:id:covory10101101276:20190508210919p:plain

f:id:covory10101101276:20190508210935p:plain
MessageManagerスクリプト
f:id:covory10101101276:20190508211002p:plain
ActiveMessageスクリプト
ActiveMessageスクリプトは以前作成したメッセージウィンドウのものをちょっといじっただけですがMessageManagerスクリプトは一から書いたためスパゲッティになっています笑笑

わかりにくいので解説するとMessageManagerスクリプトでその名の通りActiveMessageスクリプトを持つオブジェクトを一括管理し、一番プレイヤーに近い会話オブジェクトと同じ座標にカーソルが常にいます。そしてカーソルオブジェクトのトリガー範囲内に入った時だけカーソルが可視化&話すことができるというわけです。製作過程でキャラの範囲が被った場合はどうしようか悩みましたがOnTriggerEnterとExitで実装すると被っている場合でもすんなり処理してくれたのでラッキーでした。そもそも実際のゲームプレイではNPCは離して配置すれば良いだけですしね。

キャラ同士を向かいあわせる
この動きがなかなか大変でした。
数学なんてここ最近ちっともやっていないので一応実装に成功した今でもベクトルだとかQuaternionだとかの理解がちゃんとできていないと思いまする。

f:id:covory10101101276:20190514164741p:plain
向かいあわせる動きをActiveMessageスクリプトに追加
どうしてもお互い向き切った後の角度の参照の仕方がわからなかったので時間で管理しました。一応2秒あればどんな角度でも向けるはずなので問題ないです。実行すると...
f:id:covory10101101276:20190514165115p:plain
お互いに向き合った!!
なんとかできました!!
ずいぶん時間が空いた&リアルが忙しかったですがなんとかまた一つ実装に成功しました。次回以降はいよいよゲームシステムの大枠に移っていきたいと思います。