【Unity】数字選択ボタン

数字選択ボタン (ImageType: Simple Ver)

宝くじの選択画面のような複数の数字を選べるボタンを実装。
エディタ拡張により、ワンボタンで設置可能。

設置で一時的にGridLayoutGroupを利用するので、設置方向や一列の個数の指定も出来ます。

サンプル動画

  • 前半: 通常パターン
  • 後半: 角丸が際立つImageType: Slicedパターン

使用例

ドロップダウンと組み合わせ、戦術ゲームでの船団への命令に使用。
(縦画面)

*(FはFlagship)

数字選択ボタン使用例001

数字選択ボタン使用例002

画像素材

  • ライセンス: CC0

角丸白ボタンの画像素材

インポート手順

  • (2Dモードの場合は、そのままでOk)
  1. 3Dモードの場合は、D&Dしてインポートした画像をProjectウィンドウで選択。
  2. インスペクター -> Texture Type: Sprite (2D and UI)に変更。
  3. Apply

コード

NumberSelecterEditor

NumberSelecterのインスペクターにCreateボタンを出す為のエディタ拡張。

  • 新規スクリプトを作成、「NumberSelecterEditor」と命名。
    (エディタ拡張なので、Editorフォルダに入れておく)
  • 以下のコードをコピペ。


using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(NumberSelecter))]
public class NumberSelecterEditor : Editor
{
	NumberSelecter numberSelecter;

	public override void OnInspectorGUI()
	{
		base.OnInspectorGUI();

		if (numberSelecter == null)
			numberSelecter = target as NumberSelecter;


		if (GUILayout.Button("Create")){
			numberSelecter.CreateItems();
		}
	}
}

NumberSelecter

  1. 先にTextMeshProをインポートしておく。
  2. 新規スクリプトを作成、「NumberSelecter」と命名。
  3. 以下のコードをコピペ。
  4. 適当なCanvas下に空のオブジェクトを作成し、NumberSelecterをアタッチ。
  5. インポートした画像素材を、インスペクターからbtnSpriteへ紐付け。
  6. 各種項目を調整し、Createボタンをクリック。
    (フォントは黒のアウトラインがある設定を推奨)
  7. selectedItemIndexesから選択された数字ボタンのインデックスが読み取れる。

ImageType: Slicedにする場合 (より角丸になる)

  1. インポートした画像をProjectウィンドウで選択。
  2. インスペクター -> Sprite Editorを選択。
    (3Dモードの場合は、PackageManagerで2D Spriteをインストールする必要あり)
  3. Border -> L, T, R, B: 25程度
  4. Apply


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class NumberSelecter : MonoBehaviour
{

	[Header("数字ボタン数")]
	[Range(1, 100)]
	public int itemMax = 10;

	[Header("ボタン用の画像をインポートして紐付けしておく")]
	[SerializeField]
	Sprite btnSprite;

	[HideInInspector]
	[SerializeField]
	Button[] btns;

	[HideInInspector]
	[SerializeField]
	Image[] imgs;

	[HideInInspector]
	[SerializeField]
	TextMeshProUGUI[] tmps;

	[Header("数字ボタンが選択されている時の色")]
	[SerializeField]
	Color32 enabledColor = new Color32(255, 255, 255, 255);

	[Header("数字ボタンが選択されていない時(デフォルト状態)の色")]
	[SerializeField]
	Color32 disabledColor = new Color32(200, 200, 200, 128);

//現在選択している数字ボタンのインデックス集。
	[HideInInspector]
	public List<int> selectedItemIndexes;

	[Header("数字ボタンの文字のフォント")]
	[SerializeField]
	TMP_FontAsset font;

	[Header("数字ボタンの文字の大きさ")]
	[SerializeField]
	[Range(32, 128)]
	int fontSize = 64;

//数字を扱うのみなら、index + 1をStringに変換するパターンにしても良い。
	[Header("各数字ボタンの文字")]
	[SerializeField]
	string[] tmpTexts = new string[] {
		"1",
		"2",
		"3",
		"4",
		"5",
		"6",
		"7",
		"8",
		"9",
		"10",
	};

	[Header("幅を固定する方向 (Column: 縦, Row: 横)")]
	[SerializeField]
	Constraint constraint = Constraint.FixedRowCount;

	enum Constraint {
		FixedColumnCount,
		FixedRowCount,
	}

	[Header("幅を固定する値 (1列の個数)")]
	[SerializeField]
	[Range(1, 10)]
	int constraintCount = 5;

//SpriteEditorでボーダーを適切に設定する必要があるが、Slicedにすれば、より角が丸くなる。
	[Header("画像タイプ")]
	[SerializeField]
	ImageType imageType;

	enum ImageType {
		Simple,
		Sliced,
	}

	[Header("数字ボタンのサイズ")]
	[SerializeField]
	Vector2 size = new Vector2(150, 150);

	[Header("数字ボタン間のマージン")]
	[SerializeField]
	Vector2 spacing = new Vector2(10, 10);



//一時的に使用。    
	GameObject tempGo;
	GameObject tempGo2;
	TextMeshProUGUI tempTmp;
	ContentSizeFitter tempContentSizeFitter;
	GridLayoutGroup tempGridLayoutGroup;
	RectTransform tempRt;



	void Awake()
	{
//引数ありのボタンリスナー登録は事前に出来ないので、初期化時に行う。
		for (int i = 0; i < itemMax; i++) {
//リスナーの仕様上、ローカル変数を噛まさないとバグる。
			int index = i;
			btns[index].onClick.AddListener(() => ButtonListenerSelectedItem(index));
		}
	}

//全選択を解除したい時に呼ぶ。
	public void Reset()
	{
		for (int i = 0; i < selectedItemIndexes.Count; i++) {
			imgs[selectedItemIndexes[i]].color = disabledColor;
			tmps[selectedItemIndexes[i]].color = disabledColor;
		}

		selectedItemIndexes.Clear();
	}

//ボタンリスナー用メソッド。
	void ButtonListenerSelectedItem(int index)
	{
		if (!selectedItemIndexes.Contains(index)) {
			selectedItemIndexes.Add(index);
			imgs[index].color = enabledColor;
			tmps[index].color = enabledColor;
		} else {
			selectedItemIndexes.Remove(index);
			imgs[index].color = disabledColor;
			tmps[index].color = disabledColor;
		}
	}

//Createボタンを押した時に実行される、数字ボタンの生成。
	public void CreateItems()
	{
		selectedItemIndexes = new List<int>(itemMax);

		btns = new Button[itemMax];
		tmps = new TextMeshProUGUI[itemMax];
		imgs = new Image[itemMax];


		tempContentSizeFitter = gameObject.AddComponent<ContentSizeFitter>();
		tempContentSizeFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
		tempContentSizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;

		tempGridLayoutGroup = gameObject.AddComponent<GridLayoutGroup>();
		tempGridLayoutGroup.cellSize = size;
		tempGridLayoutGroup.spacing = spacing;

		if (constraint == Constraint.FixedColumnCount) {
			tempGridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
		} else {
			tempGridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
		}



		for (int i = 0; i < itemMax; i++) {
			tempGo = new GameObject(System.String.Format("NumberSelecterItem{0}", i + 1));
			tempGo.transform.parent = transform;
			tempGo.transform.localScale = Vector3.one;
			tempGo.AddComponent<RectTransform>();
			imgs[i] = tempGo.AddComponent<Image>();
			imgs[i].color = disabledColor;
			imgs[i].sprite = btnSprite;

			if (imageType == ImageType.Simple) {
				imgs[i].type = Image.Type.Simple;
			} else {
				imgs[i].type = Image.Type.Sliced;
			}

			btns[i] = tempGo.AddComponent<Button>();

			tempGo2 = new GameObject("Tmp");
			tempGo2.transform.parent = tempGo.transform;
			tempGo2.transform.localScale = Vector3.one;
			tempGo2.AddComponent<RectTransform>();
			tmps[i] = tempGo2.AddComponent<TextMeshProUGUI>();
			if (font != null)
				tmps[i].font = font;
			tmps[i].fontSize = fontSize;
			tmps[i].enableWordWrapping = false;
			tmps[i].alignment = TextAlignmentOptions.Center;
			tmps[i].color = disabledColor;
			tmps[i].text = tmpTexts[i];

			tmps[i].raycastTarget = false;
			tmps[i].richText = false;
		}

		StartCoroutine(DestroyLayoutGroup());
	}

//ContentSizeFitterとGridLayoutGroupを削除するタイミングが早過ぎて、sizeDeltaが0になってしまうのを防ぐ為のディレイ。
	IEnumerator DestroyLayoutGroup()
	{
		yield return null;
		yield return null;
		DestroyImmediate(tempContentSizeFitter);
		DestroyImmediate(tempGridLayoutGroup);
	}
}

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