バーチャル3Dクリエイター神部まゆみです(*^_^*)
この記事はUnityでアイテム回収して拾う機能を実装したので、それについての記事です。
前回の記事はこちら。
やりたいこと:アイテムを拾う機能を実装する
これまででアイテムを捨てたり、フィールド上のアイテムを光らせて目立つようにする機能は実装できた。
まだ拾う機能が作れていないので実装したい。
ChatGPTパイセンに聞いてみる
とりあえず彼に聞けばなんとかなるだろう…。




次は拾う処理を実装したいです。
「拾う」とか分かりやすい文字やアイコンのようなUIを表示すると良いのでしょうか?後々「話す」「開ける」など共通のアクションで使うことになる可能性もありますが、後々のことも考えて汎用的なUI設計にしたほうがいいのでしょうか?


いい流れです。
そしてその質問が出てくる時点で、もう設計者の視点に入ってます 👍
結論から言うと――
最初から汎用UIとして作るのが正解
ただし「今は拾うだけ動けばOK」に絞るのがコツ
です。
結論(設計方針)
👍 おすすめ
- 1つの「インタラクトUI」
- 文言とアイコンを差し替えて使い回す
👎 避けたい
- 「拾う専用UI」「話す専用UI」を量産
後から必ず後悔します 😅
他ゲームでよく見る形(王道)
[ E ] 拾う
[ X ] 開ける
[ Y ] 話す
- 画面中央下 or クロスヘア付近
- 常に 1つだけ表示
- 対象が変われば中身が変わる
これを目指すと後が楽です。
推奨UI構成(汎用)
Canvas(常時表示)
InteractCanvas
└─ InteractPanel
├─ ActionText (TMP)
└─ Icon (Image) ← 任意
InteractPanel.cs
using TMPro;
using UnityEngine;
public class InteractPanel : MonoBehaviour
{
[SerializeField] private TMP_Text actionText;
void Awake()
{
Hide();
}
public void Show(string text)
{
actionText.text = text;
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
}
拾う処理の設計(汎用を意識)
インターフェースを使う(超おすすめ)
public interface IInteractable
{
string GetActionText();
void Interact();
}
DroppedItem を拾えるようにする
DroppedItem.cs
public class DroppedItem : MonoBehaviour, IInteractable
{
public ItemData itemData;
private Inventory inventory;
void Start()
{
inventory = FindObjectOfType<Inventory>();
}
public string GetActionText()
{
return "拾う";
}
public void Interact()
{
inventory.Add(itemData);
Destroy(gameObject);
}
}
※ Inventory.Add() は後で追加します
プレイヤー側(検出)
PlayerInteract.cs
using UnityEngine;
public class PlayerInteract : MonoBehaviour
{
[SerializeField] private float interactDistance = 2f;
[SerializeField] private InteractPanel panel;
void Update()
{
Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, interactDistance))
{
var interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
panel.Show(interactable.GetActionText());
if (Input.GetKeyDown(KeyCode.E))
{
interactable.Interact();
}
return;
}
}
panel.Hide();
}
}
Inventory に Add を追加
public void Add(ItemData item)
{
items.Add(item);
}
これでできること
- アイテムを見る → 「拾う」表示
- Eキーで拾う
- UIは 話す/開ける にも流用可能
なぜこの設計が良いか
- UIは1個だけ
- 処理は対象ごとに分離
- 文言変更だけで別アクション
後からの拡張がめちゃくちゃ楽です。
とりあえずUIで「拾う」とか表示してインベントリに追加するだけだから、そこまで難しくないかな?
実際に実装してみる
たぶんできるだろうと思うので実装してみます。
「拾う」表示用のUIキャンバスを追加
これは後々「話す」「開ける」など汎用的なインタラクト表示機能にする予定です。
↓みたいにキャンバスを配置。


キャンバス
ーパネル
ーテキスト(TMP)
ー画像アイコン(任意)
画像アイコンは一応作っておいたけど、今回は設定しないでとりあえずテキスト表示だけにしておきます。
↓とりあえずこんな感じで画面中央のエイムカーソルの上下あたりに表示されるようにしました。
ワールド空間にする場合はBillboardスクリプトを付けて常に同じ方向を向くようにする必要があるけど、今回はスクリーンスペースオーバーレイにします。
InteractPanel.csを追加する
以下のスクリプトをコピペしてInteractPanel.csを作ります。
using TMPro;
using UnityEngine;
public class InteractPanel : MonoBehaviour
{
[SerializeField] private TMP_Text actionText;
void Awake()
{
Hide();
}
public void Show(string text)
{
actionText.text = text;
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
}
パネルにアタッチし、子のテキストメッシュプロを指定します。


IInterractable.csを追加
これは作るだけでOKです。
public interface IInteractable
{
string GetActionText();
void Interact();
}
PlayerInteract.csを追加する
以下のコードをコピペし、PlayerInteract.csを作ります。
using UnityEngine;
public class PlayerInteract : MonoBehaviour
{
[Header("Camera")]
[SerializeField] private Camera playerCamera;
[Header("Interact")]
[SerializeField] private float interactDistance = 10;
[SerializeField] private InteractPanel panel;
private IInteractable currentInteractable;
void Update()
{
CheckInteractable();
if (currentInteractable != null && Input.GetKeyDown(KeyCode.E))
{
currentInteractable.Interact();
}
}
void CheckInteractable()
{
currentInteractable = null;
// TPS向けに少し下向き
Vector3 dir = playerCamera.transform.forward;
dir.y -= 0.1f;
dir.Normalize();
Ray ray = new Ray(playerCamera.transform.position, dir);
Debug.DrawRay(ray.origin, ray.direction * interactDistance, Color.red);
if (Physics.Raycast(ray, out RaycastHit hit, interactDistance))
{
// 子Collider対応
currentInteractable = hit.collider.GetComponentInParent<IInteractable>();
if (currentInteractable != null)
{
panel.Show(currentInteractable.GetActionText());
return;
}
}
panel.Hide();
}
}
プレイヤーにアタッチし、メインカメラとInteractのパネルを指定します。
Interact Distanceはレイキャストの長さなので短すぎるとインタラクト判定が当たりませんが、とりあえず10mくらいあればOK。


カメラはメインカメラを指定するけど、ちゃんとタグがメインカメラになってるか注意。
デフォルトではなってるはずだけど。
Inventory.csを書き換える
今のところアイテムスロットが8個のため、上限いっぱいだったら追加せずデバッグログを出すようにしています。
コードを以下のように書き換えればインベントリに追加できる。
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
[SerializeField] private int maxSlots = 8;
public List<ItemData> items = new List<ItemData>();
public bool Add(ItemData item)
{
if (items.Count >= maxSlots)
{
Debug.Log("Inventory is full");
return false;
}
items.Add(item);
return true;
}
public void Remove(ItemData item)
{
items.Remove(item);
}
}
あとでUIに「持ち物がいっぱいです」と表示させようかな?
DroppedItem.csを書き換える
以下のように書き換えます。
using UnityEngine;
public class DroppedItem : MonoBehaviour, IInteractable
{
public ItemData itemData;
private Inventory inventory;
void Start()
{
inventory = Object.FindFirstObjectByType<Inventory>();
}
public string GetActionText()
{
return "拾う";
}
public void Interact()
{
inventory.Add(itemData);
Destroy(gameObject);
}
public void Setup(ItemData data)
{
itemData = data;
}
}
アイテムプレハブのコライダーを設定する
インタラクトの判定はコライダーでするんだけど、カメラの位置次第でレイキャストが当たらない可能性がある。
当たり判定用に大き目なコライダーを設定し、「トリガーにする」にチェックを入れておくとレイが当たりやすくなります。


目立つように子に小さいCylinderを追加したけど、ここにもコライダーが付いていてレイキャストが誤判定する可能性がある。
けどレイヤーをIgnore Raycastにすればコライダーがついていてもレイキャストが無視されます。


動かない場合はだいたいレイが当たってないことが多いので、このあたりの設定を見直すと良いと思います。
これでダメなら単純に接触して拾う感じにするしかないかな(^_^;)
これで無事拾えた!
これで無事に拾えましたね。
今回はまだアクションインプットの設定をしていないので、拾うのはEキーです。
つづく?
無事に拾う機能が実装できて良かった。
アイテム関係のコアな部分はだいたいできてきたかな?
また続きが書けたら追記します(*^_^*)





