五目並べアプリを作るにあたって一番最初に用意しなければならないのは間違えなく盤だ。五目並べは、囲碁盤(19×19)や、連珠盤(15×15)でよく打たれるらしいですね。ただ今回は、盤の目を自由に選べるようにしていきたいと思います。
先に目次から「ここまでの成果」を見ておくと理解しながらすすめるかも。
画像の用意
3つの画像が必要になってきます。
- 選択されていないマス
- プレイヤー1に選択されたマス
- プレイヤー2に選択されたマス
適当に作っておいたので右クリックで保存してunityのAssets>Prefabsに保存してください。
まだPrefabsを作ってない方はこちらを先に見てください。
必要になるゲームオブジェクトを追加
最終的なGameシーンの中身はこんな感じのゲームオブジェクトで構成されています。

GameMaster
これは空のオブジェクトです。名前をGameMasterにします。この中にこれから作るゲームオブジェクトは入れてください。
ヒエラルキー右クリックで空のオブジェクト。
僕は何となくMain Cameraもこの中に入れておきました。
Canvas
これはキャンバスです。UIを使ったりするときに便利です。これを使うとユーザーの画面にあった配置を簡単に実現できます。
ヒエラルキーを右クリックでUI>キャンバスグループ で追加できます。
キャンバスを追加すると自動的にEventSystemも追加されます。
BoardViewport
これは空のオブジェクトです。Canvasの中に入れてください。あとで説明するScroll Rectに使います。
Board
これも空のオブジェクトです。BoardViewportの中に入れてください。これもあとで説明するScroll Rectに使います。後で、この中に100個のパネル(10 x 10 の盤の場合)を自動的に作るスクリプトを書きます。
0
これは画像です。あとで100個複製するときの元になります。
ヒエラルキー右クリックで、UI>画像
ゲームオブジェクトにコンポーネントを追加
BoardViewport
このオブジェクトにScroll RectとRect Mask2D コンポーネントを追加してください。
追加した後、Scroll Rect のコンテンツにBoardオブジェクトを選んでください。

同じ方法で、ビューポートにはBoardViewportオブジェクトを選択。
さらに移動タイプは「クランプ済」、慣性をオフ、スクロール感度を1にします。
最終的にこんな感じになってます。

Board
これにはGridLayoutGroup コンポーネントを追加します。
変えるところは制約をFixed Column Countにして、制約数はとりあえず10にしておきましょう。

スクリプトを書く
Assets>Scriptsに、Board, Game, UIOrganize というc#スクリプトを作ります。
BoardはなぜかMonoBehaviourの継承を外しました。後から考えるとあったままでもいいきが。。。
Board
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
using System.Collections.Generic; public class Board { public int size; private List<int> board = new List<int>(); /* * -1 と 1 はユーザーID * 0 は空のマス */ public void setSize(int param) { size = param; } public void initialize() { board.Clear(); for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { board.Add(0); } } } public List<int> getDeepBoard() { //ディープコピー List<int> temp = new List<int>(); int index = 0; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { temp.Add(board[index]); index++; } } return temp; } public List<int> getShallowBoard() { //シャローコピー return board; } public List<int> intput(int place,int ID) { board[place] = ID; return board; } } |
Boardクラスは2つの重要な変数を持っています。
sizeは盤の一辺の長さで、boardは今の盤の状態が保存されているList<int>です。(5-6行目)
setSizeはパラメーターでもらったparamをsizeに収納。(12-16行目)
initializeはboardを空にして、もう一回 size x sizeの分だけ0をboardに追加する。いわゆるboardの初期化。(17-27行目)
getDeepBoardはboardのディープコピー。オンライン対戦なんかやるとき必要になりそう。。。(28-43行目)
getShallowBoardはboardのシャローコピー。(44-49行目)
inputはパラメーターから場所と今のプレイヤーのIDを受け取って、それをもとにboardを操作する。(50-54行目)
Game
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public class Game : MonoBehaviour { public Sprite SpritePlayer1; public Sprite SpritePlayer_1; public Sprite Empty; Vector3 lastPositon = new Vector3(); Boolean is_selecting = false; private Board board; public int size = 15; public int ID = -1; // Start is called before the first frame update void Start() { board = new Board(); board.setSize(size); board.initialize(); GetComponent<UIOrganize>().positionBoard(size); drawBoard(size); } // Update is called once per frame void Update() { int index = GetTargetOnClick(); if (index != -1) { input(index); } } public void drawBoard(int size) { //初期化の時用 GameObject tempItem = transform.Find("Canvas").transform.Find("BoardViewport").transform.Find("Board").GetChild(0).gameObject; GameObject parent = transform.Find("Canvas").transform.Find("BoardViewport").transform.Find("Board").gameObject; int counter = 0; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { GameObject targetItem = Instantiate(tempItem,new Vector3(0f,0f,0f), Quaternion.identity); targetItem.transform.SetParent(parent.transform); targetItem.transform.name = counter.ToString(); counter++; } } Destroy(tempItem); } public void updatePanel(int placement,int ID) { UnityEngine.UI.Image image = transform.Find("Canvas").transform.Find("BoardViewport").transform.Find("Board").GetChild(placement).GetComponent<UnityEngine.UI.Image>(); switch (ID) { case 1: image.sprite = SpritePlayer1; break; case -1: image.sprite = SpritePlayer_1; break; default: image.sprite = Empty; break; } } public void input(int place) { board.intput(place,ID); updatePanel(place, ID); nextPlayer(); } public void nextPlayer() { ID = ID * -1; } int GetTargetOnClick() { //set true when user press down //set false when user moves position //then check if it is true when user lifts finger. if (Input.GetMouseButtonDown(0)) { lastPositon = Input.mousePosition; is_selecting = true; } if (Input.GetMouseButton(0)) { Vector3 currentPositon = Input.mousePosition; float deltaPositon = Vector3.Distance(currentPositon, lastPositon); if (deltaPositon > 10) { //the user decided not to click here is_selecting = false; } } if (Input.GetMouseButtonUp(0) && is_selecting) { PointerEventData pointer = new PointerEventData(EventSystem.current); pointer.position = Input.mousePosition; List<RaycastResult> result = new List<RaycastResult>(); EventSystem.current.RaycastAll(pointer, result); foreach (RaycastResult raycastresult in result) { if (raycastresult.gameObject.transform.parent.transform.name == "Board") { return int.Parse(raycastresult.gameObject.transform.name); } } } if (Input.touchCount > 0) { // タッチ情報の取得 Touch touch = Input.GetTouch(0); if (touch.phase == TouchPhase.Began) { lastPositon = Input.mousePosition; is_selecting = true; } if (touch.phase == TouchPhase.Moved) { is_selecting = false; } if (touch.phase == TouchPhase.Ended && is_selecting) { PointerEventData pointer = new PointerEventData(EventSystem.current); pointer.position = touch.position; List<RaycastResult> result = new List<RaycastResult>(); EventSystem.current.RaycastAll(pointer, result); foreach (RaycastResult raycastresult in result) { Debug.Log(raycastresult.gameObject.transform.parent.transform.name); if (raycastresult.gameObject.transform.parent.transform.name == "Board") { return int.Parse(raycastresult.gameObject.transform.name); } } } } return -1; } } |
19-28行目: Boardクラスのインスタンスを作ってsetSize とinitializeを Start()の中でする。 ゲームが始まるとこれが一回呼ばれる。
31-38行目: GetTargetOnClick()はクリックされたオブジェクトの名前をintに直して返す。10 x 10 の場合はindex 0 – 99。この関数はどこもタッチされていないと-1が返る。-1じゃなかったらそのインデックスでboardに入力。
40-59行目: drawBoard()は初期化したときとかに使う。この関数はBoard(オブジェクト)に初めから入っている0(オブジェクト)を100こ複製する。Instantiate は便利なので使い方ググってみてください。
65-75行目: updatePanel()はマスの画像を変える。例えば、インデックス5にプレイヤー1が入力した場合、5(オブジェクト)をplayer1用の画像に変える。
77-82行目: input()はBoardのinput()とGameのupdatePanel()を実行した後に次のプレイヤーの番にする。
83-86行目:nextPlayer()は次のプレイヤーの番にする。ID -1が一人目、1が二人目だから、毎回 IDに-1を掛けると次のプレイヤーIDにできる。
94-125行目:これはデバッグ用。というのもアンドロイドアプリを作っているのでマウスのクリックは判定しなくていいはず。ただ毎回テスト用に自分の端末にインストールしてデバッグをするのがめんどくさいのでエディタ用にクリックの処理をしている。やっていることは下記の128-158行目と同じ。
128-158行目: ここではタッチスクリーンの処理をしている。もしも指が動いたら、その指はマスを選択したわけではなく、マスの画面を動かしたいということ。なのでis_selecting = false。is_selectingがtrueの場合、RayCastを使って当たったオブジェクトの名前を整数に直して返す。
UIOrganize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIOrganize : MonoBehaviour { public void positionBoard(int size) { RectTransform rectTransformContent = transform.Find("Canvas").transform.Find("BoardViewport").transform.Find("Board").transform.gameObject.GetComponent<RectTransform>(); rectTransformContent.sizeDelta = new Vector2(size*100, size * 100); GridLayoutGroup gridLayoutGroupContent = transform.Find("Canvas").transform.Find("BoardViewport").transform.Find("Board").transform.gameObject.GetComponent<GridLayoutGroup>(); gridLayoutGroupContent.constraint = GridLayoutGroup.Constraint.FixedColumnCount; gridLayoutGroupContent.constraintCount = size; } } |
ここではCanvas>BoardViewport>Board(オブジェクト)のサイズを盤の目の数に合わせて変え、同じくCanvas>BoardViewport>Board(オブジェクト)のコンポーネントとして追加したGridLayoutGroupの制約数を変更する。こうすることでBoardが正方形になる。
ここまでの成果
このままではプレイヤーがどのマスでもクリックできるようになってしまいます。次の記事では五目並べのルールを追加していきます。
ちなみにWebGL版でもビルドしてみました。