Unityはじめてみた 26日目 アイテムリストの制作でつまずいたところまとめ

風来のシレンっぽいアイテムウィンドウを作ろう
と思ったのですがまだ
「アイテムを選択したらそこにカーソルが合う」
までしか実装できてない状態…

「アイテムを選択したらアクションウィンドウを表示」も
させたいのですが、
いったん飛ばしてPHPに学習に入るので躓いたとこまとめ。

あわせて読みたい
Unityはじめてみた 24日目 そのクラスに無い関数を呼び出す方法×5, static関数ほか こんにちは栞です。 フェチを表現するための手段としてUnity触ってます。 前回のタンクゲームの課題が終わり、現在はアイテムリストの作成をしています。 https://shior...
前回の記事
目次

foreachとInstantiate

    //薬草には0,命の草には1とリスト作成時点でインデックスは振られている
    //items[0]で薬草にアクセスできる
    private List<string> items = new List<string>
    {
        "薬草", "命の草", "ドラゴン草", "ちからの草", "どく消し草",
        "めぐすり草", "無敵草", "復活の草", "毒草", "混乱草"
    };

    void Start()
    {
        CreateItemList();
    }

    void CreateItemList()
    {
        foreach (string item in items)
        {
            GameObject newItem = Instantiate(itemPrefab, content);
            Text itemText = newItem.GetComponentInChildren<Text>();
            itemText.text = item;
         // 残りのコード...

下線について順に解説。

foreach (string item in items)

意味
  • itemsリストの0番目の要素items[0]
    たった今宣言した変数itemに代入する
  • ブロック内の処理を実行する
  • 次はitems[1]を代入し、ブロック{}内の処理を実行する
  • これを最後の要素まで続ける。

itemはこのforeachループ内でのみ有効な変数。
このitem変数を通してitemsリストを変更することはできない。
(ちなみにリストとは要素数が可変の配列である)
itemsリストとデータ型をそろえる必要あり。

GameObject newItem = Instantiate(itemPrefab, content);

意味
GameObject newItem = Instantiate(何をインスタンス化する?, どこに?);

Instantiateは引数が2つのとき、データ型が
(Object, Transform)
となる。

ObjectGameObjectの上位にある概念。
つまり、GameObjectじゃなくても大丈夫ってこと

包含関係は
Object ∋ GameObject, Asset, Component, AudioMixer など
Asset ∋ AudioClip, Font, Texture, Prefab など

ということで今回はPrefabをインスタンス化している

余談

インスタンス化といえばクラスがパッと思い浮かぶが、
クラスそのものはオブジェクトではない。

しかし、MonoBehaviourを継承していれば
そのクラスはコンポーネントとして扱えるので、
無事インスタンス化の対象となれる。

あと、Instantiateは引数のパターンが全部で5つあるので要注意。

プレハブの生成位置を細かく調整する方法

プレハブのインスタンス化する場所(Transform)を細かく調整したいときは

  • 空のGameObjectを作り、それを親としていったんプレハブを
    ヒエラルキータブに移す
  • このとき、子要素(プレハブ製GameObject)のTransformは
    わかりやすいようにすべて0にしておく
  • 空オブジェクトをシーン上で動かし、ちょうどいい位置を探す
  • 子要素を削除する
  • スクリプトにてプレハブを生成するInstantiateの第二引数のフィールドに
    空の親オブジェクトをアタッチする

プレハブのGameObjectを参照する方法

「アイテム横のカーソルの画像を取得する」
という処理を書く機会がたくさんあり
見辛くなっていたので
GetCursorImageという関数にまとめてみた。

  //カーソルの画像を参照する処理に名前をつける
    private Image GetCursorImage(GameObject item)
    {
        return item.transform.Find("Cursor").GetComponent<Image>();
    }    

今回はtransform.Findという関数をはじめてつかってみた。

GameObjectを取得する際に用いる関数だが、
ふつうはフィールドをインスペクタータブに表示させて
アタッチさせるのが一番。
なぜなら軽いから。

でもそれができないときがある。
それはプレハブのGameObjectを取得したいとき。

その時の解決法が以下の3つ。

  • GameObject.Find()
  • FindWithTag()
  • transform.Find().gameObject

詳しく見ていこう

GameObject.Find()

GameObject.Find("取得したいGameObjectの名前")

で取得できる。お硬く書くと
GameObject.Find(string name)

単純だが、注意点が多い。

  • 処理が重い
     Update関数に入れるのは厳禁
     Start関数で一度だけつかうこと
  • Activeなものしか取得できない
  • プレハブ製のGameObjectを取得する際は
    (Clone)まで入れる

FindWithTag()

FindWithTag("取得したいGameObjectのタグの名前")

で取得できる。

  • Activeなものしか取得できない

が共通の弱点。

ちなみに複数取得したいときは
FindGameObjectsWithTag()
を使う。

こんなかんじ。データ型のあとに[]をつけるのを忘れずに
GameObject[] objects = GameObject.FindGameObjectsWithTag("object");

transform.Find().gameObject

親オブジェクト.transform.Find(取得したいGameObjectの名前).gameObject

で取得できる。

  • 取得したいGameObjectの親に対して呼び出す
     取得できるのはその子オブジェクト
  • transform.Find()の返り値はTransform
    .gameObjectがついているのはそのため

この二点に注意。

気をつけて

親オブジェクトを指定せず、transform.~~~と直接呼び出した場合は
そのスクリプトがアタッチされているGameObjectのTransformを指す。
指しているものが明示的なので、省略されている。

これもついでに気をつけて

関数を直接呼び出した場合も
そのスクリプトがアタッチされているGameObjectに対して呼び出している。
thisキーワードが省略されていると考えよ
(static関数ならそのスクリプトのクラスに対しての呼び出し)

名前空間とクラスの階層構造

色々Claudeと対話するなかで
「コンポーネントはクラスなので…」
みたいな話が出てきてしまい、
「???」
と混乱。

なので今一度、基本の概念であるクラスに立ち戻ります。
ついでにC#そのものの名前空間にまでたどり着いたのでそこにも言及。

以下は名前空間とクラスの階層構造です。
(名前空間)と書いてないものはすべてクラス。

System (名前空間)
└── Object
    ├── String
    ├── Delegate
    └── Console

UnityEngine (名前空間)
├── Object (System.Objectを暗黙的に継承)
│   ├── GameObject
│   ├── Component
│   │   ├── Behaviour
│   │   │   ├── MonoBehaviour
│   │   │   │   └── CustomComponent (ユーザー定義)
│   │   │   │       ├── PlayerController
│   │   │   │       ├── EnemyAI
│   │   │   │       └── GameManager
│   │   │   └── Animator
│   │   ├── Transform
│   │   ├── Rigidbody
│   │   ├── Collider
│   │   ├── Renderer
│   │   ├── AudioSource
│   │   ├── Camera
│   │   └── Light
│   ├── ScriptableObject
│   ├── Material
│   ├── Texture
│   ├── Shader
│   └── AudioClip
├── Time
├── Mathf
├── Debug
├── Input
├── Vector3
├── Quaternion
└── SceneManagement (名前空間)
    └── SceneManager

UserDefinedNamespace (ユーザー定義の名前空間、オプション)
└── CustomClass (System.Objectを暗黙的に継承)

間違いの例:2つの名前空間(SystemとUnityEngine)が独立していない

System (名前空間)
└── Object
  ├── UnityEngine (名前空間)
  │  ├── Object
   :  :

はクラスではない

どちらも構造体。

クラス⇒参照型
構造体⇒値型

値型はヒープではなくスタックに保存されるので、
パフォーマンスが良くなる。

メモリ⊃スタック、ヒープ、その他

スタック
 高速にアクセスできる固定サイズの小さなメモリ領域
ヒープ
 ちょっと重いが動的に割り当てできる大きなメモリ領域

Vector3やQuaternionはよく使われるので、
少しでも挙動を軽くするために構造体として設計されている。

ちなみに
クラス⇒参照型
構造体⇒値型
はいずれも

インターフェースや配列は参照型のデータ型だがクラスではなく
int,boolなどのプリミティブ型は値型だが構造体ではない。
(でも実は内部的には構造体)

名前空間とは

同じ名前のコード要素
(クラス、構造体、インターフェースなど)が
衝突しないように用意する隔離空間

というイメージ。

衝突
同じ名前のクラスを宣言したため
コンパイラがどちらを参照すればいいかわからず、
エラーを吐くこと

名前空間A・Bを定義すれば、
A・B内それぞれで同じ名前のクラスを宣言できるようになる。

大規模なプロジェクトになると必要になってくるらしい。

名前空間は

  • 名前の衝突を回避して
  • usingによって名前の短縮をして
  • コードの整理・グループ化をしてくれるなど

ありがたい存在ではあるが、
なにかプロジェクトを動かす直接的な機能を
提供するわけではなく、そういう意味では
単なる「箱」である。

System名前空間とは

C#プログラミングの基本的な機能と型を
提供しているクラスの入った箱。

C#において最上位の名前空間と言える

System.Objectクラスとは

C#プログラミングの基本的な機能と型を
提供している。

特別な位置づけにあり、
全てのC#クラスが継承しているクラスである。

基底クラス/親クラス
あるクラスが継承しているクラスのこと
 例:System.Objectは全てのC#クラスの基底クラスである

UnityEngine名前空間とは

ゲーム開発に必要なほとんどの機能を
提供しているクラスの入った箱。

UnityEngine.Objectクラスとは

「すべてのオブジェクトを
インスタンス化によって生み出すクラス」のこと。

「すべてのインスタンスはObjectクラスを持つ」
と言い換えてもいい。

オブジェクトって具体的になに?

  • GameObject
  • Component
  • Asset
    ・Prefab
    ・Script
    ・AudioClip
    ・Texture
  • AudioMixer

※あまりよく分かってないので軽く触れるにとどめておく

ちなみにAssetというクラスは存在しない。
Objectクラスを継承しているTextureやAudioMixerなどの
単なる総称というイメージか

ほかPrefab, Scriptもクラスが存在しない

その他のUnityEngineに直接属するクラスについて

UnityEngine名前空間に直接属している
Time, Mathf, Debugなどの馴染み深いクラスは
C#クラスのなかでも、静的クラスとして定義されている。

静的クラス(復習)
インスタンス化ができない。
計算やキー入力などの
「インスタンスに依存しない、ゲームの全般的な処理」が
ここにまとめられている

これらはUnityEngine.Objectクラスは継承していないが、
やはりSystem.Objectクラスは継承している。

UnityEngine.Objectクラスを継承しないことで、

  • Unityエディタとの統合
    ・インスペクタータブでの表示・編集
    ・プロジェクトタブでAssetとして管理
    ・ヒエラルキータブでの表示
  • 名前の設定
  • インスタンス化

などができなくなるが、
これらのクラスにとっては無用の長物であるため
省機能化され、パフォーマンスが最適化されている。

Debug.log()とか
Input.GetAxis(“Horizontal”)とか
今までよく分からずに書いてたコードの正体が分かってスッキリですね。
log()って静的関数だったんだーとか。

GameObjectクラスとは

シーン内のすべてのGameObject
インスタンス化によって生み出すクラス」のこと。

すべてのGameObjectはGameObjectクラスを持つ
と言い換えてもいい。

右の画像のように、

  • 空のGameObject
  • 2D Object(Square, Circle など)
  • 3D Object(Cube, Sphereなど)

はもちろん、

  • MainCamera
  • Directional Light

などの初めから設置されているものや

  • Canvas
  • ScrollView
  • Text
  • Button

などのUI要素もGameObject。

これらはすべて
GameObjectクラスが
インスタンス化したものだったのだ

先述のGameObject.Find()は
GameObjectがクラスで
Find()は静的関数だったりする。

気をつけて

GameObjectはスクリプトをアタッチすることではじめて
MonoBehaviourクラスを継承する

CustomClassとは

スクリプトにてユーザーが宣言するクラスのこと。
(”CustomClass”というキーワードがあるわけではない)

「ユーザー定義クラス」
「独自クラス」
と呼ばれることも。

MonoBehaviourを継承したものは
コンポーネントとしてアタッチできる特性を踏まえ
「カスタムコンポーネント」と呼ばれる。

なので、MonoBehaviourを継承していないクラスのことを
特にカスタムクラスと呼ぶことが多い。

TANKSチュートリアルの「TankManager」がこれに該当。

UserDefinedNamespaceとは

ユーザーが宣言した名前空間のこと。
(”UserDefinedNamespace”というキーワードがあるわけではない)

カスタムクラスをなんらかの名前空間に属させたいときは
これを宣言する

属する名前空間を特に決めていない場合、
カスタムクラスはグローバル名前空間に属する。

グローバル名前空間とは
名前空間が指定されていないときにデフォルトで属するところ。

その他参考リンク

デリゲートの認識

https://claude.ai/chat/59f475e1-304e-4371-9fec-ada34d758cd9

アドリスナーの認識

https://claude.ai/chat/b64271c9-04ac-4be4-b9e9-dd26e13a3e6f

おわりに

絶対カリキュラムから遅れていると思っていたが、
案外3~4日ほど進んでいた。
絵も描かないとな~と思ってるが
一気呵成で覚えたい気持ちが強い…

あわせて読みたい
Unityはじめてみた 30日目 立太子ボタンの制作(LAMP環境/PHP) こんにちは栞です。フェチを表現するための手段としてUnity触ってます。 今回は毛色が変わって、LAMP環境で「くじ引きのできるページ」を作ろうという課題がでました。...
次の記事
感想をおしえてね(メンションがつきます)

コメント

コメント一覧 (2件)

  • 初心者学習の中でdelegateが言及されることがあるのですね、驚きました。24日目でも触れられていたので後ほど触れたいと思います。

    >item変数を通してitemsリストを変更することはできない。

    itemがstringであるときは値型なのでitem変数が指しているのはコピーされた文字列であるためitems[0]”の指し示すメモリ上の文字列を改変することがないので変更されませんが、もしitemが参照型であるMyMotimono型(Itemだと紛らわしいので…)でpublic void SetName(string name)を持っていたとしたらitem.SetName(“改変された”);が呼べてしまい、itemsは全て名前が”改変された”になりますのでご注意ください。
    ーーー
    Claudeのチャット内容は私が登録してないので読めないため、重複したことを記載してしまうかもしれませんが、delegateはメソッドシグネチャだけを単体で宣言して名づけるものです。すなわちpublic void delegate MyDelegate(int a)とは、「int1つを引数とし戻り値がないという条件を満たす、任意個のメソッドへの参照」をMyDelegate型と名付けるということになります。ラムダ式はその場限りの無名なメソッドへの参照を生成するものであり、MyDelegate md = (int x) => Console.WriteLine(x * 2);というような連携ができます(md(5);を呼ぶと10がコンソール出力されます)。
    使い道としては
    – メソッドの中で得られた何らかの結果を教えてほしいと思っている関数(”コールバック”)が渡せるようにし、その時の関数の型を定めたいとき 例:HTTP通信をしてデータをインターネットから取得し、その後ファイルに書き込むメソッドの中で、途中のHTTP通信が終わったタイミングで何かを行いたいメソッドへの参照を渡す void FetchAndWrite(MyCallbackOnFetchDone callback) { FetchHttp(); callback(); WriteToFile(); }
    – メソッドに渡された関数を利用して動作をカスタムできるようにしたいとき 例:Listの並べ替えをするメソッドに「比較対象となる二者の文字数を比べて前後を判定する関数への参照」を渡すことで辞書順ではなく文字数順にする void SortListCustom(MyComparisonLogic comparisonLogic) { myList.Sort(comparisonLogic); }

    なお、後者をやりたい場合実際には最初からC#に用意されています。void Sort(Comparison)です。Comparisonはdelegate int(0なら序列同一、1ならxを後ろに回すべきという判定、-1はその逆)Comparsion(string x, string y)として定義されているわけですね。
    ほかにもFuncやAction型があるので、後者については実用上いちいちdelegateを定義しなくても、FuncやActionの引数を受けるようにすればよいです。

    前者についても、delegateを使用するのはFuncでは表現力が足りないと感じて「何に関するデリゲートを渡してほしいのか」を型として命名し、その辺の(シグネチャが一致するとしても)Funcを流用して持ってきてほしくはないときだけのような気がします(そしてそれはFuncが悪いのではなく汎用的な引数・戻り値型を使っているのが原因であることが多いです)。

    またマルチキャストデリゲートはイベントリスナーを非同期で呼びたかったり、個々のコールバックの戻り値が欲しかったりするときに都合が悪いので個人的には使用しないでいたいですね。

    • コメントありがとうございます!
      しばらくJSON~APIまわりの学習に集中しているので
      また目を通させていただきます!

コメントする

CAPTCHA


目次