カテゴリ:脱出ゲーム作成 »

脱出ゲームは作れるか? with Unity (4) - アイテム使用と詳細表示

今できているもので脱出するなら、鍵を持っているだけでドアが開いてもいいんですが、これからのことを考えて一応、アイテム欄で鍵を選択してから使えるようにしたいです。
あと、やっぱりこれからのことを考えて、アイテムの詳細表示もできるようにしたいです。



  1. アイテム欄にボタンを設置する

  2. アイテム欄クリックで「持つ」

  3. ボタン用スクリプトの説明

  4. 詳細表示カメラを作る

  5. 詳細表示ボタンを追加する

  6. 詳細表示ボタン用スクリプトの説明

  7. 詳細表示ウィンドウを閉じるボタン

  8. ここまでの成果


アイテム欄にボタンを設置する


前回、鍵にコライダーをつけてからプレハブにしましたが、アイテム欄でのクリック判定は別にコライダーじゃなくても良かったですね……。
なので、アイテム欄に表示する要素をCanvasに突っ込んでおけばいいかなと思いました。
一番手前に透明ボタン、その後ろにアイテム本体、一番後ろに、選択状態を表すハイライト表示、って感じでいいかな。


ボタンの並べ方についてはこちらの記事を参考にさせてもらいました。
Unityで脱出ゲームを作る part.1 / アイテムリスト編 - teriyaki note
参考にしたページではVertical Layout Groupという機能を使っています。
「vertical」って縦ですよね? そちらのページではアイテムを縦に並べていましたし。
同じ機能のグループを探したらHorizontal Layout Groupという機能が見つかったので、こっちを使うことにします。
公式から引用すると、Horizontal Layout Group コンポーネントは、子のレイアウト要素を互いに横並びに配置します。とのことなので、こっちでよさそうです。


まず新しくCanvasを作って、それにHorizontal Layout Group コンポーネントを追加します。
「Add Component」ボタン→「Layout」→「Horizontal Layout Group」です。
Horizontal Layout Group コンポーネントを追加
設定はこうしてみました。
Horizontal Layout Group コンポーネントの設定
「Control Child Size」にチェックを入れると、子オブジェクトの方でサイズを変えられません。
「Child Force Expand」にチェックを入れると、子オブジェクトの数に応じて、グループの範囲いっぱいになるように幅や高さを調節してくれる……ってことでしょう、多分。
きれいに並べたいけど、幅と高さを勝手に変えられたくないので、それらのチェックは外しました。
この設定で、いくつかの子オブジェクトを非アクティブにしてみると、ボタンのサイズは変わらず、スペースが自動的に詰められます。
なのでとりあえず今はこれでいいかな。


ボタンは透明にしたいので、「Source Image」はnoneでも良かったんですが、何となく枠線を入れた方が分かりやすいかなと思ったので、枠線の画像を用意することにしました。
なんかUnityのボタンスタイルとして枠線のみとかあるのかなと思ってたんですが、ない……です? よね?
今度はこちらの記事を参考にさせてもらいました。
UnityのuGUIでフレームのみのボタンを作る - スマゲ
そのまま真似して、32*32、枠線の幅は2px。
作った画像を「Assets」→「Import New Asset」で読み込んで、「Texture Type」を「Sprite(2D and UI)」に変更、「Sprite Editor」ボタンを押したんですが、こんなウィンドウが出ました。
Sprite Window が登録されてないって
Sprite Window が登録されてないから、Package Manager からインストールしてね、だそうです。
こういう機能もインストールしないと使えないんですね。
必要ないものは入れておかない、素敵だと思います!
ので、言われたとおり「Window」メニュー→「Package Manager」でウィンドウを出して、上部のドロップダウンリストから「All Packages」にして、「2D Sprite」を選択、右下の「Install」ボタンでインストールしました。
で、Sprite Editorでこう設定してできあがり。
SpriteEditorで枠線の設定
「Border」に元の線幅プラス1を設定するといいってことでしょうか。


以上の設定をしてボタンを10個配置しました。
ボタンを横並びに整列できた


選択状態にある場合に表示する背景を、今作ったボタンの更に子オブジェクトとして配置します。
差し当たりImageにしておきました。
選択状態にあるアイテム用の背景を配置

ここでちょっとつまったこと。
背景を半透明にすると、枠付きボタンより前に出てきちゃうんです。
これ、UIの表示順の決まりのせいで、ヒエラルキーウィンドウで下にあるものほど前に表示されるんですって。
多くのお絵かきソフトの逆ですね。
今回、前に出したい枠付きボタンが親オブジェクトなので、下に移動させることができません。
調べたら、UIにCanvasコンポーネントを追加することで、「Override Sorting」という項目を選べるようになって、そこから並び順を指定できるようになるってことが分かりました。
(参考にしたページ→uGUIの表示順を自由に変更する方法見つけた! - Qiita
追加するには、UIオブジェクトのインスペクターウィンドウ一番下の「Add Component」ボタン→「Layout」→「Canvas」を選択します。
ボタンにCanvasコンポーネントを追加
これを背景のImageに追加して、「Order in Layer」を-1にすると、後ろに引っ込んでくれました。
UIの重なり順を変えられた
……? でも、ヒエラルキーウィンドウでの並び順でいうなら、背景を半透明にしなかった時の挙動が納得いかないんですけど……なんでだろう???


こうなってくると、中に入れるアイテムのオブジェクトも、枠線ボタンの子オブジェクトにした方が都合が良さそうです。
またちょっとよく分からないんですが、CanvasRender ModeScreen Space - Cameraにすると、Render Cameraで指定したカメラにしか映らなくなりますよね?
でもその中に入れた、UIじゃないオブジェクトは他のカメラにも映るんです。
これはこういうものなんでしょうか……。
理由は分からないですが、今回はこれで上手いこといったので、このまま進めます。
現在のアイテムリスト(ヒエラルキーウィンドウ)
鍵の他に2つアイテムが置いてありますが、動作確認用に入れてみただけです。



アイテム欄クリックで「持つ」


アイテム欄のアイコンをクリックすると、「そのアイテムを持っている」という状態にしたいです。
もう一度クリックで、解除もします。


視点変更ボタンの時みたいにswitch文で書いてもいいのかもしれませんが、ものすごくめんどくさそうなので、ボタン用のスクリプトを作って、それぞれのボタンにアタッチしてみることにしました。
差し当たりこんな感じ↓



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemBtnCtrl : MonoBehaviour
{

private GameObject GSObj;
private GameSystem gs;
private GameObject ItemBtnList;
private GameObject back;
private string empty;
private string itemName;
void Start() {
GSObj = GameObject.Find("ObjectForGameSystem");
gs = GSObj.GetComponent<GameSystem>();
ItemBtnList = GameObject.Find("itemBtnHorizontal");
back = transform.Find("Image").gameObject;
empty = "none";
itemName = transform.GetChild(1).gameObject.name.Replace("itemBtn_", "");
}

public void BtnClick() {
if (back.activeSelf) {
back.SetActive(false);
gs.GetComponent<GameSystem>().selectedItem = empty;
} else {
foreach(Transform child in ItemBtnList.transform) {
child.transform.Find("Image").gameObject.SetActive(false);
}
back.SetActive(true);
gs.GetComponent<GameSystem>().selectedItem = itemName;
}
}

}

スクリプトを作ったら、アイテムボタンのインスペクターにドロップしてアタッチ、ButtonコンポーネントOn Click()で使えるように設定します。↓
ボタンにスクリプトをアタッチ

ボタンの使い方については「【uGUI】Buttonの使い方 - Qiita」というページの「クリック処理を行うまでの流れの整理」セクションの説明がとても分かりやすかったです。



ボタン用スクリプトの説明



private GameObject GSObj;
private GameSystem gs;


void Start() {
GSObj = GameObject.Find("ObjectForGameSystem");
gs = GSObj.GetComponent<GameSystem>();
}

最初に作ったゲーム全体の管理をするスクリプト「GameSystem」の変数を扱いたいので、それをアタッチしてるゲームオブジェクトを取得して、それについているコンポーネントとして、スクリプトを変数に代入しています。
他のスクリプトの変数を読み書きする方法がいまいち分からなかったんですが、調べたらこれでうまくいったのでほぼコピペみたいにして使いました。




private GameObject ItemBtnList;


void Start() {
ItemBtnList = GameObject.Find("itemBtnHorizontal");
}
public void BtnClick() {


foreach(Transform child in ItemBtnList.transform) {
child.transform.Find("Image").gameObject.SetActive(false);
}
}

全てのアイテムボタン背景を非アクティブにする部分です。
さっき作ったHorizontal Layout Groupを取得して、foreachで回して、背景を非アクティブにしています。
今選択状態にしようとしてるアイテムも、めんどくさいのでまとめて非アクティブにしちゃいます。




private GameObject back;
private string empty;
private string itemName;
void Start() {
back = transform.Find("Image").gameObject;
empty = "none";
itemName = transform.GetChild(1).gameObject.name.Replace("itemBtn_", "");
}

public void BtnClick() {
if (back.activeSelf) {
back.SetActive(false);
gs.GetComponent<GameSystem>().selectedItem = empty;
} else {


back.SetActive(true);
gs.GetComponent<GameSystem>().selectedItem = itemName;
}
}

今選択状態にしようとしているアイテムの名前を、ボタンの連番じゃなくて、アイテム本体のオブジェクトの名前にしようと思いました。
その方が実際持った時の処理を書く時に分かりやすいかなって。
最初は、アイテム本体のオブジェクトにタグをつけて、FindGameObjectsWithTagで取得したら分かりやすいなって思ってたんです。
親オブジェクトから子オブジェクトを名前で取得する時、transform.Find("子オブジェクトの名前").gameObjectってできたので、これもtransform.FindGameObjectsWithTag("タグの名前")ってできるかなってやってみたんですが、できませんでした(´・ω・`)
なんでできないんだろう……その方が便利な気がするのに……。
なので、全子オブジェクトの中からインデックスを指定して取得するtransform.GetChild(インデックス)を使いました。
全部のアイテムボタンの階層構造は同じなので、これでいけるんじゃないでしょうか。
アイテムボタンにはアイテム名の前に「itemBtn_」をつけた名前をつけているので、Replaceでそれを外したものを変数に代入しています。


今選択状態にあるなら、背景を非アクティブにして、「GameSystem」で扱う変数「selectedItem」の値を「none」に。
選択状態じゃないなら、背景をアクティブにして、「selectedItem」の値をアイテム名にしています。



詳細表示カメラを作る


ここまで私がやっている、アイテム欄に3Dオブジェクトを配置するやり方がいいのかどうか分からないんですけど……まあせっかくそこにアイテムがあるので、これをそのまま映したらいいかなって思いました。
アイテム欄の手前に新しくカメラを置いて、幅と高さを画面より小さく設定、「Culling Mask」をアイテムリストにして、遠近法もなしにして、「Depth」を今のところ一番大きく、「Size」を調節すると映る範囲みたいなのが変わるようなので、それもいい感じに設定。
……まあ、こういう感じにしました↓
アイテム詳細表示カメラの設定

アイテム詳細表示カメラの結果

このカメラのtransform.positionを、今表示しようとしてるアイテムの位置を元にして決めたらいいんじゃないかと。


詳細表示ウィンドウを閉じるボタンも必要ですね。
これは詳細表示カメラより更に手前に表示されないと困るので、新しくCanvasを作りました。
要らないかもしれないんですけど、詳細表示ウィンドウの周りに半透明のImageを敷いて、別ウィンドウであることを強調しよっかなとも思いました。
ので、こんな感じに。
詳細表示を閉じるボタン



詳細表示ボタンを追加する


アイテム欄のアイテムにマウスポインタを置いた時に、詳細表示ボタンが出るようにしたいです。
OnMouseOver()という関数もあるみたいですが、これはあのInput.GetMouseButton()の時みたいに、ポインタが乗ってる間中毎フレーム呼び出されちゃうやつでした。
ので、今回はOnMouseEnter()OnMouseExit()を使います。
さっき作ったボタン用スクリプトの中に追加で書いちゃえばいいんじゃないでしょうか。
ただこのマウスイベントをキャッチする関数、ただボタンに指定しただけじゃ動かないみたいなんです。
なので、アイテムボタンにBox Colliderをつけました。


まずボタン用に画像を用意してプロジェクトにインポート、画像をスプライトにします。
さっきのボタン枠と同じ作業ですね。
で、「Source Image」にそのスプライトを指定したボタンをアイテムボタンの中に新しく作って、大きさと位置を調節します。
詳細表示ボタンをSceneビューで配置

最初は表示させたくないので、インスペクターウィンドウでチェックを外して非アクティブに。
で、ボタン用のスクリプトに、ポインタを乗せた時、外した時の処理を書き足します。



void OnMouseEnter() {
showDetailBtn.SetActive(true);
}
void OnMouseExit() {
showDetailBtn.SetActive(false);
}

詳細表示ボタンの処理は、またボタン自体にスクリプトを当てた方がやりやすそうなので、それ用にスクリプトを作りました。



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShowDetailBtnCtrl : MonoBehaviour
{

[SerializeField] GameObject itemDetailCamera;
[SerializeField] GameObject closeDetailCanvas;
private GameObject itemBtn;
private Vector3 thisPos;
private Vector3 cameraPos;
private Vector3 cameraPosTmp;
void Start() {
itemBtn = transform.parent.gameObject;
thisPos = itemBtn.transform.position;
cameraPosTmp = itemDetailCamera.transform.position;
cameraPos = new Vector3(thisPos.x, cameraPosTmp.y, cameraPosTmp.z);
}

public void BtnClick() {
itemDetailCamera.transform.position = cameraPos;
itemDetailCamera.SetActive(true);
closeDetailCanvas.SetActive(true);
}

}


詳細表示ボタン用スクリプトの説明



[SerializeField] GameObject itemDetailCamera;
[SerializeField] GameObject closeDetailCanvas;

詳細表示カメラと閉じるボタンのCanvasは最初非アクティブなので、SerializeField属性にしました。
全部の詳細表示ボタンでこれを指定するのは面倒かもしれませんが、コンポーネントの値としてペーストしていけばそんなに大変でもないかと。
インスペクターウィンドウでスクリプトのコンポーネントメニューから「Copy Component」でコピーして、
コンポーネントをコピー
貼り付けたい方のコンポーネントメニューから「Paste Component Values」で値だけをペーストです。
コンポーネントの値をペースト

【2020.06.28追記】Buttonコンポーネントの値もコピペすると、OnClick()の対象オブジェクトもそのままコピペされちゃうので注意です。
今回の場合、どのボタンを押してもコピペ元のボタンのBtnClick()が呼び出されちゃうってことです。




private GameObject itemBtn;
private Vector3 thisPos;
private Vector3 cameraPos;
private Vector3 cameraPosTmp;
void Start() {
itemBtn = transform.parent.gameObject;
thisPos = itemBtn.transform.position;
cameraPosTmp = itemDetailCamera.transform.position;
cameraPos = new Vector3(thisPos.x, cameraPosTmp.y, cameraPosTmp.z);
}
public void BtnClick() {
itemDetailCamera.transform.position = cameraPos;
}

アイテムボタン本体の方のtransform.positionと、詳細表示カメラの今のtransform.positionを変数に入れておいて、その値を元にして新しいpositionを作って、クリックしたらカメラをその位置に移動させています。




public void BtnClick() {
itemDetailCamera.SetActive(true);
closeDetailCanvas.SetActive(true);
}

で、詳細表示カメラと、閉じるボタンのキャンバスをアクティブにします。



詳細表示ウィンドウを閉じるボタン


これはプロジェクトにひとつだけですし、ゲーム全体を管理するスクリプトの方に書いちゃいます。
第2回の記事で作ったスクリプトの、クリックされた時の処理switchcaseを追加しました。



void Update()
{
if (Input.GetMouseButtonDown(0)){
if (eventsystem.currentSelectedGameObject==null&&!itemDetailCamera.activeSelf) {
searchRoom();
} else if (eventsystem.currentSelectedGameObject!=null) {
switch (eventsystem.currentSelectedGameObject.name){
case "lookDownBtn":


break;
case "closeDetailBtn":
itemDetailCamera.SetActive(false);
closeDetailCanvas.SetActive(false);
break;
}
}
}
}


ここまでの成果


アイテム欄のアイテムをクリックで持った状態に、詳細表示ボタンをクリックで拡大表示させることができました。
アイテムの詳細表示までできた



ジャンル : コンピュータ
テーマ : ゲーム開発

コメントの投稿

非公開コメント

ステータス
細野ゆとり
あそびにん
せいべつおんな
レベル
ちから
すばやさ
こうげき力
しゅび力
EX
最新記事
カテゴリー
月別アーカイブ
全記事表示リンク

全ての記事を表示する

おえかき
ブログ内ページランキング
twitter
その他SNS
検索フォーム
オンラインカウンター
現在の閲覧者数:
カレンダー
06 | 2020/07 | 08
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
リンク
カウンター
RSSリンクの表示
QRコード
QR