【Unity】TextMeshProにハイパーリンクタグを実装する方法【なるだけ優しく解説】

Unity

このブログで使用しているVer
・Unity 2021.3.0f1
・TextMeshPro Version 3.0.6

こんにちは!Unityでゲーム制作しています、えきふるです!
ここではUnityのTextMeshProの文字列にハイパーリンクを設定する方法を解説します。

えきふる
えきふる

なるべく詳細に解説しますので安心してね!

はじめに

もし、そもそもTextMeshProへのリッチテキストタグの付け方を知らない
という方がいたら下記記事の最初の方に詳しく記載しているので、確認してみてください。

えきふる
えきふる

ほんなら見ていきましょ〜!

実装手順

TextMeshProにハイパーリンクを実装する方法は、大きく分けて以下の3STEPで制作します!

  1. TextMeshPro(TMP)のテキストコンポーネントにハイパーリンク用のタグを記載する
  2. TMPをクリックできるようにUnity側の設定をする
  3. クリックしてリンクをブラウザで開けるようにスクリプトを作成してTMPにスクリプトをアタッチして終了!
えきふる
えきふる

それでは順番に見ていきましょ〜!

1.テキストにハイパーリンク用のタグを記載する

まず最初にUnityのTextMesuProにハイパーリンク用のタグをつけます。

テキストリンクのタグ記述は
<link=”URL”>テキスト</link>
になります。

サンプルとして下記を確認して下さい。(このHPのトップへのリンクです。)

<link="https://ekifurulab.com/">えきふるゲームラボ</link>
UnityゲームエンジンのInsepector
Unityゲームエンジン内のゲーム画面

他の種類のタグ(太字や色変え等)ではUnity上のTextInput内での記載だけで機能していましたが、
今回はこのタグだけでは機能しません。
現状は「テキスト内にリンク用のURL情報IDが入っているだけ」になっています。

この後に、
・テキストがクリックされたかを判別する。
・クリックした場所にURL情報があればその情報を取得して、ブラウザで開く。
という処理を用意していきます。

2.クリックできるようにUnity側の設定をする

次に「クリックをした」とUnityが認識できるように設定していきます。
UnityのHierarchy内で「EventSystem」を作成してください。
作成の仕方はHierarchy内で右クリック→UI→EventSystemで作成します。

UnityゲームエンジンのHierarchy

このEventSystemはUnity内でのクリックやドラッグアンドドロップを検知してくれます。


次の項目で詳しく見ていきますが、
今回はテキストクリック時に処理を扱うのでスクリプト側で扱えるように

using UnityEngine.EventSystems;

の記載と
インターフェースである「IPointerClickHandler」をスクリプトに記載して実装する必要があります。
インターフェースは下記の構文で実装されます。

class クラス名 : インターフェース名
{
  クラスの定義
}

※インターフェースの意味に関しては下でTIPSに記載しました。

「IPointerClickHandler」を実装すると定義されている「OnPointerClick」メソッド(クリックしたらこのメソッドを処理する)を呼び出す事ができるようになります。

TIPS:クリックを判定する設定に関して

UnityのUIはスクリーン座標に位置しています。
ですので今回Unity側の設定はEventSystemを用意するだけで大丈夫ですが、
もし3Dオブジェクトなどのワールド座標に配置されているオブジェクトのクリックを感知したい場合はカメラにPhisicsRaycasterコンポーネントをアタッチする必要があります。
これによりカメラからRay(光線)を飛ばしてワールド座標空間でオブジェクトがあるかどうかを判別してくれます。

TIPS:インターフェースとは何か?

インターフェースとはメソッドの呼び出し方を定義しているものです。
実際の処理は実装したクラス側で定義して行います。
これはテレビとリモコンの関係のようなもので、テレビがクラスリモコンがインターフェースのイメージになります。

例えばインターフェースであるリモコンには電源のON /OFFを呼び出すボタン(メソッド)がありますが、そのON/OFFボタンを押した時の処理(電源ONメソッドが呼ばれた時に中身はどう処理が実行されるか)はクラスであるテレビ側によって定義されています。

このリモコンというインターフェースによって、私たちはテレビ個別の内部処理を気にする事無く、全て同じように操作ができるようになります。

つまりのメソッドは何をすると呼び出されるのか?」だけを定めているという事です。

実はUnityでも普段、この決められた呼び出しルールの中でメソッドの処理を定義していますよね。

Unityでは「MonoBehaviour」が基本インターフェースとしてクラスに実装されています。
「MonoBehaviour」を実装していることで「void Start ()」メソッドがスクリプトを実行された時に最初に呼び出されるというルールで動いてくれるんですね。

public class Test : MonoBehaviour
{
void Start () {
	}
}
えきふる
えきふる

「MonoBehaviour」インターフェースによってvoid Start ()メソッドは実行スタート時に呼び出されると定義されているんだね

3.クリックしてリンクをブラウザで開けるようにスクリプトを作成

ここまでの準備ができたら、いよいよ
クリックしたら指定したリンクをWebブラウザで開くようにスクリプトを記述します。

私は下記のようにスクリプトを用意しました。
処理のコメンアウトに数字を振ってあるので、後ほど内容を1つずつ順番に見ていきましょう。

※コメントアウトが長いので文字を小さくしています。

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

//1.インターフェースに「IPointerClickHandler」を追加します
public class HyperLink : MonoBehaviour, IPointerClickHandler
{
    //2.「IPointerClickHandler」のルールで「OnPointerClick」メソッドはクリックすると呼ばれます。
    public void OnPointerClick(PointerEventData eventData)
    {
        //3.クリックした時のマウスカーソルの位置を取得します。
        Vector2 pos = Input.mousePosition;

        //4.このスクリプトのついたゲームオブジェクトから「TextMeshProUGUI」コンポーネントを取得し、text変数に代入。
        TextMeshProUGUI text = GetComponent<TextMeshProUGUI>();

        //5.text=TextMeshProUGUIコンポーネントのルートにもっとも近いCanvasオブジェクトを代入。
        Canvas canvas = text.canvas;

        //6.カメラの変数を生成して、canvasのrenderModeを三項条件演算子で評価してカメラを代入。
        Camera camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;

        //7.FindIntersectingLink(TMP_Text, Vector3, Camera)で指定された位置にのリンクのインデックスを返します。(存在する場合)
        int index = TMP_TextUtilities.FindIntersectingLink(text, pos, camera);

        //8.インデックスに要素が存在しない場合は-1を返すので-1では無い場合に処理を続行します。
        if (index != -1)
        {
            //9.テキストオブジェクトにリンク情報IDを取得します。
            TMP_LinkInfo linkUrlInfo = text.textInfo.linkInfo[index];

            //10.文字列urlにGetLinkIDで取得したリンクIDを文字列として返して代入。
            string url = linkUrlInfo.GetLinkID();

            //11.Web ブラウザーで url の場所を開きます。
            Application.OpenURL(url);
        }
    }
}

あとは上記のスクリプトをタグを記載したTMPにアタッチしてあげれば完了です。

UnityゲームエンジンのInsepector

スクリプトの詳細説明

それでは順番にスクリプトを見ていきましょう。

■最初に下記のusingディレクティブの記述をしてください。

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

「TMPro」でTextMeshPro、
「UnityEngine.EventSystems」でUnityのイベントシステム、
それぞれで用意されているクラスを使用する時にネームスペースの記述が不要になります。
(要するにTextMeshProとイベントシステムの型が使えるようになるという感じです。)

ネームスペースに関しては下記でも説明しています!


■1番目の部分になります。

public class HyperLink : MonoBehaviour, IPointerClickHandler

まず「IPointerClickHandler」というインターフェースをこのクラスに追加実装します。
※私は「HyperLink」という名前でこのクラスを作成しました。
これは2番目にある「OnPointerClick」というメソッドを使えるようにする為です。

えきふる
えきふる

インターフェースに関しては上のTIPSに記載したよ


■2番目です。

public void OnPointerClick(PointerEventData eventData)

OnPointerClickメソッドを作成します。先程のインターフェースの実装のおかげでこのメソッドは
「クリックすると呼び出されるメソッド」になっています。
(シーンにEventSystemがあることが必要です。)


■3.4.5番目は簡単なので一気にいきましょう。

      Vector2 pos = Input.mousePosition;
  
      TextMeshProUGUI text = GetComponent<TextMeshProUGUI>();

      Canvas canvas = text.canvas;

・3番ではVector変数に現在のマウスカーソルの位置を代入しています。このメソッド全体はクリックした時に呼ばれているので、クリック時のマウスカーソルの位置を取得。

・4番ではこのスクリプトがアタッチされたゲームオブジェクトのTextMeshProUGUIコンポーネントを取得して、以降TMPの各要素にアクセスできるようにします。

・5番では先程のTextMeshProUGUIに最も近いCanvasを取得しています。後で使用します。

参考:unityドキュメント「Graphic.canvas」の項目https://docs.unity3d.com/ja/2019.2/ScriptReference/UI.Graphic-canvas.html


■6番目です。

ここではCamera変数を作成した後に5番で取得したcanvasのレンダーモードによって代入するものをnullかworldCameraで条件を分けて代入しています。

        Camera camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;

条件を分けるのに三項条件演算子(条件演算子 「?:」)というものを使用しています。
三項条件演算子は下記のように記述をします。

condition ? consequent : alternative

この演算子ではconditionの部分が
「true」ならconsequentの式が返され、
「false」ならalternativeの方の式が返されます。

今回の部分で言うと、キャンバスのレンダーモードがスクリーンスペースならnull、そうでないならワールド座標系のカメラを代入してね、と言うことですね。

canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;

参考:unityドキュメント「Canvas.worldCamera」の項目https://docs.unity3d.com/ScriptReference/Canvas-worldCamera.html


■7番目です。

        int index = TMP_TextUtilities.FindIntersectingLink(text, pos, camera);

FindIntersectingLink(TMP_Text, Vector3, Camera)というクラスでCameraから見たVector3の位置にあるTMPリンクのインデックスを返しています。
※リンクタグの入ったテキスト群は上から順に0.1.2…とインデックスが振られるようです。
こうすることでテキストリンクがあるかどうかを確認します。


■もう少しです!8番目から最後の11番目まで一気にいきましょう!

        if (index != -1)
        {
            TMP_LinkInfo linkUrlInfo = text.textInfo.linkInfo[index];

            string url = linkUrlInfo.GetLinkID();

            Application.OpenURL(url);
        }

・8番目ではインデックスに要素がある時にその中の処理を実行しています。
要素がない=(この場合は)クリック箇所TMPにリンクタグがない場合、インデックスは-1を返します。
インデックスが-1でなければif文の中の処理をするという内容です。

別パターンとしてここの記述はif(index == -1) return;とかで-1だったら処理を終了する、と記述し後ろの行に続きの処理を記述しても大丈夫です。

・9番目ではクリックした時に返ってきたインデックス番号のリンク情報を取得しています。
TMP_LinkInfoはテキストオブジェクトに含まれる個々のリンク情報を含む構造体です。
※構造体に関しては後にTIPSで説明。

この構造体の変数linkUrlInfoに変数textのtextinfo(テキスト情報)の中の(index)番のlinkinfo(リンク情報)を代入しています。
簡単に言うとTMPのリンク情報はtext.textInfo.linkInfo[index番号]に格納されていると言うことですね。

・10番目では9番で取得したリンク情報を文字列に変換しています。
GetLinkID();メソッドはリンクIDを文字列として返す関数ですので、
linkInfoで取得したリンク情報をstring型の文字列にして変数urlに入れています。

・11番目では
Application.OpenURL(url);でWebブラウザーでurlの場所を開きます。
Application.OpenURL(string)はstringをURLとして開くクラスになります。

TIPS:構造体とは

構造体とはクラスの特殊版という感じのものです。
クラスと同様に扱うことができますが値型(string型とobject型以外の型)しか持つことができません。
クラスのように全ての型を持てない分、動作が早いという特徴があるようです。

おわり

えきふる
えきふる

もしよく分からない所や間違いがあればお気軽にコメントくださいね!

※このブログは、UnityTechnologiesまたはその関連会社が後援または提携しているものではありません。「Unity」は、UnityTechnologiesまたはその関連会社の米国およびその他の国における商標または登録商標です。

コメント

タイトルとURLをコピーしました