こんにちは、Unityエンジニアのオオバです。
UI開発の悩みポイントの1つは「ボタンのつくり方」です。なにが正解なのか迷います。しかもボタンは ゲームの中で大量に存在するもの。 はじめの一歩をまちがうと、あとで大きな修正が発生するかも...と思いますよね?
はい、大規模改修が待っています(何度も経験済み)。
「なんとなく動くから大丈夫だろう」という考えだと後で大変なことになります。
「じゃあ、どうすればよいの?」
本記事では 「おすすめしないボタンのつくり方」 を解説しつつ、解決方法の1例を紹介します。ボタンは一歩まちがうと沼。沼と言うか地獄。一度ハマってしまうと、 本来クオリティを上げるために使う時間がボタンの修正作業時間になってしまいます。
ここまで聞いてエンジニア向けの話っぽいですが、 実はUIデザイナーにも知っておいてもらいたい知識 です。エンジニアとUIデザイナーが共通認識を持っておかねばならない部分ですので、ぜひ最後まで読んでみてください。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
Unity標準ボタンの使用禁止する5つの理由
結論から話すと、おすすめしないボタンのつくり方とは 「Unity標準ボタンの使用すること」 です。つまりUnity標準ボタンの使用は禁止です。標準ボタンとは メニューGameObject > UI > Button - TextMeshPro
のこと。
禁止する理由は5点あります。
①機能不足
②機能拡張しづらい
③ボタンアニメーションを使い回せない
④ボタン処理を一括修正ができない
⑤Inspectorウィンドウから関数をセットできてしまう
なぜここまでUnity標準ボタンの使用をおすすめしないかというと、オオバは 過去何度か途中参加したプロジェクトで標準ボタンを置き換える作業をした経験があるからです。 非常に辛い作業です。なぜなら標準ボタンはUnity内のボタンクラスであるため簡単に参照できないのです。つまり どこで使われているか把握しづらいため時間がかかります。
しかもプロジェクトが後半に進むにつれてボタンは膨大な数に膨れ上がります。そうなる前に対処すべき案件なのです。
理由①標準ボタンは機能不足
標準ボタンにはスマホゲームを作るにあたって機能がありません。スマホゲームで使うボタン機能は最低でも約5種類です。
- タップ判定
- 長押し判定
- タップしても反応しない状態
- タップアニメーション
- タップ音の出し分け(OK、キャンセルなど)
一方Unity標準ボタンの機能は約3種類です。
- タップ判定
- タップしても反応しない状態
- タップアニメーション
「長押し判定」「タップ音の出し分け」この2種類が標準ボタンには実装されていません。 スマホ向けUnity開発12年の経験で、長押しとタップ音が不要なプロジェクトに出会ったことはありません。
すべてのボタンに長押し判定は不要ですが、ゲーム開発は仕様変更がよく発生します。 「このボタンは長押しで詳細ダイアログを表示させてみよう」 とサクッと仕様追加されることは多いです。プロジェクトが進むに連れて標準ボタンだけではどうしても対応できない部分が多くなるのです。
理由②標準ボタンは機能拡張しづらい
機能拡張しづらい点がUnity標準ボタンを禁止する理由の1つです。
標準ボタンを機能拡張する場合は標準ボタンをラップします。例として ButtonWrapper というコンポーネントを用意しました。ButtonWrapperがButtonを操作し、Buttonに足りない機能を追加する設計です。以下イメージ図。
一見良さそうな設計ですが、欠点は、標準ボタンとは 別のコンポーネント であることです。既存の標準ボタンの使用箇所を探し、該当するすべてのGameObjectにButtonWrapperをアタッチする必要があります。また、標準ボタンを内包するため、開発するゲームにとっては不要な機能をずっと保持するのも健全ではありません。
標準ボタンを拡張できないわけではありませんが、 わざわざ拡張する ”うまみ” は無い ため、標準ボタンの使用はおすすめできません。
理由③ボタンアニメーションを使い回せない
ボタンアニメーションを再利用できないこと も標準ボタンを採用したくない理由の1つです。Unity標準ボタンで複雑な演出をつくる場合はAnimationClipを使います。AnimationClipとはUnity標準のタイムライン形式アニメーションツール です。
ButtonコンポーネントのTransitionを「Animation」にすることでAnimationClipを使ってボタンアニメーションを作れます。
オオバも凝った演出をつくるときにはAnimationClipをよく使います。ただ落とし穴が1点。AnimationClipを使い回す場合、 GameObjectの階層を合わせる必要があるということです。
AnimationClipについてはこちらの記事を参考にしてみてください。
構造の異なるボタンはAnimationClipを再利用できない
次のような2つのボタン「RewardButton」と「Button_Ranking」を同じAnimationClipで再生することはできません。
異なる構造のボタンに対して同じアニメーションを適用したい場合は、演出は同じだけど「別のAnimationClip」を作る必要があるのです。
この制限はかなり大きいです。1つのゲームの中でさまざまなボタンをつくることになると思いますが、 全てのボタンを共通の構造にすることは難しいから です。
つまりUnity標準ボタンを使うと、大量に存在するボタンに対して共通アニメーションデータを使い回せない制限が生まれます。この制限は 修正工数とテスト工数の爆発につながります。
極端な例として、100個のボタンに対して100個のAnimationClipが存在するとします。ボタンアニメーションに修正が入るとどうなるでしょう。修正のたびに100個のAnimationClipを変更することになるのです。 非常に辛い。 大量のボタンアニメーションをひとつずつ作っていられないです。
アニメーションの共通化はUI開発では必須です。ゆえに標準ボタンの採用は難しいのです。
理由④ボタン処理を一括修正ができない
標準ボタンは処理を一括修正できません。なぜなら機能を拡張できないからです。UI開発は後半に入るにつれて微調整が多く発生します。前述の「理由③ボタンアニメーションを使い回せない」でも解説しましたが、 大量に存在するUIは処理の共通化が必須です。
例えば 「ボタンタップのタイミングで特定のタップエフェクトを画面に表示させたい」 という仕様を追加するとどうなるでしょうか。
標準ボタンはボタンごとにタップ関数を記述することになります。以下のような処理が至るところに存在するということです。
var clickEvent = new Button.ButtonClickedEvent();
clickEvent.AddListener(() =>
{
Debug.Log("タップ処理");
});
button.onClick = clickEvent;
仕様を満たすためには、すべてのタップ処理を検索して、タップエフェクト処理を記述していくことになるのです。
一方独自ボタンを作っていた場合は、タップ処理の中身を書き換えることで、すべてのボタンに適用することが可能です。つまりとても修正がとても簡単です。
💻ソースコード : 独自ボタンのタップ処理サンプル
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("タップ処理");
// タップエフェクトを表示する処理
}
時間さえかければ標準ボタンでも対応は可能ですが 仕様変更に弱い設計 と言えます。独自ボタンなら一箇所修正するだけなのに対し、標準ボタンは全箇所検索して検索してソースコードの修正です。テスト工数も膨れ上がります。
本来クオリティアップに使いたかった時間を生産性のない作業に使ってしまうのはもったいないため、標準ボタンの利用はおすすめできません。
理由⑤Inspectorウィンドウから関数をセットできてしまう
最後の理由は標準ボタンのInspectorウィンドウから関数をセットできてしまうことです。具体的には以下の図。Buttonコンポーネント(標準ボタン)のOnClickにpublic関数を指定できてしまうことです。
Inspectorウィンドウから関数をセットできることは、一見便利そうに感じますが実はこれ、「ヤバい」のです。なぜなら プログラムから読み取ることができないからです。
「なぜかタップした時の処理がおかしい」「プログラムは正しい」「なぜだ......」 こんな感じでデバッグの沼にハマっていきます。フタを開ければButtonのInspectorウィンドウから関数がセットされていたというオチ。
特にチーム開発の場合、 プログラムから読み取れない処理を作るのはやめたほうが無難 です。標準ボタンのOnClickもその1つ。標準ボタンを使うということは、InspectorウィンドウのOnClickが使われている可能性を考慮する必要が出てきます。エンジニアにとっては無駄な思考は生産性の低下につながります。
予期せぬ事故を防ぐ、生産性を上げるためにも標準ボタンの使用は避けたいのです。
独自ボタンを作る2つの方法
本章では標準ボタンではなく、ゲーム独自のボタンを作る方法を紹介します。2つほど紹介しますが、おすすめは後者の「方法②フルスクラッチ(おすすめ)」です。
①標準ボタンをラップして独自ボタンを作成
②フルスクラッチで独自ボタンを作成
方法①標準ボタンをラップ
「理由②標準ボタンは機能拡張しづらい」で登場した標準ボタンをラップした拡張方法です。下の図のようにButtonコンポーネントのラッパーがButtonを操作します。
ただし、「理由②標準ボタンは機能拡張しづらい」で解説したとおり、標準ボタンの機能に引きづられるため、あまりおすすめできる方法ではありません。
方法②フルスクラッチ(おすすめ)
独自ボタンはフルスクラッチで書いてしまっても問題ないです。
具体的には、MonoBehaviourを継承したクラスに、「IPointerClickHandler」「IPointerDownHandler」「IPointerUpHandler」を実装します。この3つのインターフェースを実装することで、クリック(タップ)、タップダウン(タップしたまま)、タップアップ(タップした状態から離した)を持つ独自ボタンを作ることができます。
public class CustomButton : MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerUpHandler
{
// タップ
public void OnPointerClick(PointerEventData eventData){}
// タップダウン
public void OnPointerDown(PointerEventData eventData){}
// タップアップ
public void OnPointerUp(PointerEventData eventData){}
}
CustomButtonクラスに先のインターフェースを実装した状態です。次の3メソッド 「OnPointerClick」「OnPointerDown」「OnPointerUp」 に独自の処理を記述していくのです。
ボタンの使い方として、外部からタップした時の処理をセットしたいもの。それを実現するためにpublicのAction変数を宣言し、OnPointerClickで実行します。(下記コード参考)
public System.Action onClickCallback;
public void OnPointerClick(PointerEventData eventData)
{
onClickCallback?.Invoke();
}
以下のような感じでボタンの外部から処理をセットします。
CustomButton button;
button.onClickCallback = ()=> {
Debug.Log("タップした時の処理");
};
サンプルとして、タップすると 縮小してボタンの色が少し透明になる独自ボタン を作ってみました。
ソースコード全文は以下です。アニメーションの処理に「DOTween」という 無料のアニメーションライブラリ を使っています。
👉 【Unity】DOTweenで解決!スクリプトでアニメーション実装
💻ソースコード : 独自UIボタンの一例
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
public class CustomButton : MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerUpHandler
{
public System.Action onClickCallback;
[SerializeField] private CanvasGroup _canvasGroup;
public void OnPointerClick(PointerEventData eventData)
{
onClickCallback?.Invoke();
}
public void OnPointerDown(PointerEventData eventData)
{
transform.DOScale(0.95f, 0.24f).SetEase(Ease.OutCubic);
_canvasGroup.DOFade(0.8f, 0.24f).SetEase(Ease.OutCubic);
}
public void OnPointerUp(PointerEventData eventData)
{
transform.DOScale(1f, 0.24f).SetEase(Ease.OutCubic);
_canvasGroup.DOFade(1f, 0.24f).SetEase(Ease.OutCubic);
}
}
CustomButtonのInspectorウィンドウは以下のような感じです。ImageやRawImageといったRaycastTargetを持つGraphicコンポーネントを同階層にアタッチしておくのを忘れずに。
このように独自ボタンを作っておけば、後から修正が入ったとしても 独自ボタン内部の処理を書き換えるだけで対応が可能 となるのです。少し難易度は上がるかもしれませんが、ゲーム開発の最初から独自ボタンでUI開発することをおすすめします。
Unity標準ボタンを大規模改修することになる2つのケース
本章では Unity標準ボタンの大規模改修になりうる開発のケース を2つ紹介します。
プロジェクト後半、UIデザインをクオリティアップさせる場面で、 大量のUnity標準ボタンを独自ボタンに置き換えた経験が何度かあるのです。
この記事を読んでいるあなたが同じ経験をしてほしくないため共有します。
①モック開発中に標準ボタンを採用してしまうケース
モック開発中にUnity標準ボタンを使っていないでしょうか。使っていたら 黄色信号 です。なぜなら モック開発を継続してそのままリリースまで進むパターン もあり得るからです。もちろん本番開発時にモック開発のソースコードを全部捨てるなら話は別ですが、プロジェクトの状況がそれを許さない場合もあります。
もし、モック開発を継続して本番開発まで進めてしまった場合、プロジェクト中盤から後半にかけて大量の標準ボタンから独自ボタンに置き換えることになります。 貴重な後半の時間を捨ててしまうことになる ため、最初から最低限の機能を実装した独自ボタンを使う方が安全です。(最低限の機能とはタップ判定だけでもOK)
ノーコード(プログラミングなし)でUIボタンを実装する方法をこちらの記事で紹介しています。Visual Scriptingに興味ある方はチェックしてみてください。
②UIデザイナー主導でPrefabを構築するケース
プログラミング知識をあまりもっていないUIデザイナーの場合、 「Unityが提供するボタンだから大丈夫」 と思いがちです。それは仕方ないことだと思います。しかし、今まで解説してきたとおり標準ボタンの利用は危険です。超小規模なプロジェクトなら良いかもしれませんが、 何億も予算をかけて作るゲームの場合は、エンジニアに相談しましょう。
冒頭で伝えたとおり、UIデザイナーの方にも読んでほしかったのはまさにこの部分です。 気軽に標準ボタンに手を出さないほうが無難です。

UnityのPrefab(プレハブ)とは?使い方まで徹底解説
Unity開発する上で基礎中の基礎「Prefab(プレハブ)」本記事を通してPrefabの正しい使い方を学ぶことができます。
Unity標準ボタンはおすすめしない5つの理由まとめ
この記事はUnityの標準ボタンをおすすめしない理由を解説してきました。内容を簡単にまとめます。
①標準ボタンは機能不足
②標準ボタンは機能拡張しづらい
③標準ボタンはアニメーションを使い回せない
④標準ボタンはタップなどの処理を一括修正ができない
⑤標準ボタンはInspectorウィンドウから関数をセットできてしまう危険性
こんな感じです。ゲーム開発する上では標準ボタンを使う理由はなく、独自ボタン一択です。しかし勘違いしてほしくないのは Unity標準ボタンをディスっているわけではありません。
関数を外からセットしたり、短時間にボタンを作れるのは簡単な検証やデバッグメニューをつくるときに重宝します。 わざわざコードを書いてボタンを作るより標準ボタンを使う方が何倍も時短できるケースはあるのです。
あくまで本記事の趣旨は 「プロダクトに標準ボタンは使えない」 という話です。この記事を読んでくださったみなさまが、安全にUIボタンを開発できることを祈っております。

筆者のXをフォローしよう
「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!
最後まで読んでいただきありがとうございました!
すばらしいUnityライフをお過ごしください。
- Unity2020.3.30f1