チュートリアル作成日
2023年11月3日
難易度高めなので、「Unityでゲーム作るの初めて、プログラミングも初心者」という方は当サイトのヒットアンドブローとかツムツムとかやってからがいいです。
ゲームのルール
136種類の麻雀牌がタテ8×ヨコ17に並んでいる。同じ絵柄の二つの牌を選んで消していく。
消すことができるのは、(1)タテ、ヨコ隣り合っている場合、(2)空白マスをつないでタテヨコ3本以内の直線でつながる場合(2回曲がれる、外周も使える)。
(インターネットで無料で遊べる四川省(二角取りゲーム)があるので、実際に遊んでみてルールを理解するとゲームも作りやすいです。)
制作環境
Unity2022.3.11f1
C#
完成予想画像
(絵柄はいらすとやです。チュートリアルでは別の画像を使います)
遊び方
プレイヤーは麻雀牌をふたつタップして、条件が揃えば麻雀牌が消える。
目次
下準備
麻雀牌の作成
8×17にランダムで並べる(2番目に難しいところ)
クリックできるようにする(3番目に難しいところ)
ロジックを考える
マッチング判定プログラム(一番難しいところ)
手詰まりの判定
おまけ(ヒント機能など)
プロジェクトの新規作成
2D。(コアじゃない方で)
名前、保存場所はご自由に。
プラットフォームは何でもOK。PCでもAndroidでも。このチュートリアルではAndroidスマホヨコ型ゲームとして作っていきます。
AndroidへSwitch Platform
Unity > File > BuildSettings
Androidを選択して、Switch Platformをクリック。2分程度で処理が終わる。
Gameウィンドウにて、1920x1080 Landscapeにする。(ここはお好みで)
Sceneの名前を変更する。
Project > Sceneフォルダ > SampleSceneで右クリック。名前をGameに変更。
3つのフォルダを作る。
Project > Assetsにて右クリック Create > Folderを選択。名前をImagesにする。
同じ要領で、PrefabsとScriptsのフォルダも作成する。
麻雀牌の画像を入手する。
もちろん自作してくれて構いませんが、今回は以下のサイトからいただきました。
https://mj-dragon.com/yaku/haiga.html
画像は「麻雀の雀龍.com(ルール解説あり)」の無料麻雀牌画を利用しています。
ここのMサイズセットです。ヨコ30ピクセル、タテ40ピクセルです。この数字を後で使います。
画像をUnityへ取り込む。
Project > Imagesフォルダにて右クリック、Import New Assetで、シフトキーを押しながらずらーっと選ぶ。
使うのは、タテ型で
東南西北白撥中、一萬~九萬、一筒~九筒、一索~九索です。
フォルダ内の順番通りで大丈夫です。(途中に赤牌があるので飛ばしてください、使いません)
Project > Imagesですべての牌の画像を選択して、Inspectorにて以下のように設定。
Texture Type > Sprite (2D and UI)
Sprite Mode > Single (ここまではたぶんデフォルト)
Pixels Per Unit > 40 (ここを変えればOK)
最後に、Applyをクリック。
麻雀牌オブジェクトを作成する。
見た目の設定
Project > Imagesから、今作った麻雀牌のどれかひとつをHierarchyへドラッグアンドドロップ。
1.Hierarchyでそれを選んでおいて、Inspectorにて名前をMahjongTileに変更。
2.Add Componentで、Box Collider 2Dをつける。
Box Collider 2DのSizeを、X: 0.7、Y: 0.9にしておく。
中身の設定
Project > Scriptsフォルダで右クリック。
Create > C#Scriptを作成し、名前をMahjongTileScriptとする。
このスクリプトを、上で作ったMahjongTileオブジェクトへアタッチ。(Add Componentあたりにドラッグアンドドロップ)
次にタグを作る。
HierarchyでMahjongTileを選んでおいて、Tagの右下▼をクリック。
Add Tagで、タグを追加していく(プラスマークを押して作っていく)
タグは画像の名前に合わせて4つともう一つ。あわせて5個のタグを作る。
ji
man
pin
sou
そして、emptyというタグ。
コピーする。
MahjongTileオブジェクトを複製して、名前をEmptyTileにする。
プレハブにする
ここまで出来たら、HierarchyにあるMahjongTileをProject > Prefabsフォルダへドラッグアンドドロップ。プレハブにする。
EmptyTileもプレハブに。
プレハブは二つです。
そして、Hierarchyにあるものは削除。
スクリプトを書く。
さっき作ったMahjongTileScriptを開く。(Visual Studioなど)
これだけ。
using UnityEngine;
public class MahJongTileScript : MonoBehaviour
{
public int number;
}
Unityエディターに戻って、HierarchyにてCreate Empty。名前をGameBoardにしておく。
Project > Scripts にてC#Script、名前はBoardSetUpにする。
BoardSetUpスクリプトを開く。
まずは麻雀牌を必要なだけ生成するスクリプト
まずは以下の通り。あとでUnityでアタッチしていく。
public GameObject tilePrefab;
public GameObject emptyTile;
public Sprite[] MahjongTileImage;
public Transform board;
//リストを作成。シャッフルするときに使う。
private List<GameObject> mahjongTileList = new List<GameObject>();
GenerateTiles関数を作る。voidです。
麻雀牌はそれぞれ4セットずつ。萬子筒子索子1~9を4セット、字牌7つを4セット
とりあえず萬子のスクリプト
void GenerateTiles()
{
//generate manzu
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 9; j++)
{
GameObject mahjongTiles = Instantiate(tilePrefab, board.position, Quaternion.identity);
//画像、タグ、ナンバーを割り当てる処理を入れる。画像ナンバーは0~8
//麻雀牌プレハブのインスペクターや、アタッチしているスクリプトを書き替えていく。
mahjongTiles.GetComponent<SpriteRenderer>().sprite = MahjongTileImage[j]; //あとでいじります。
mahjongTiles.tag = "man";
mahjongTiles.GetComponent<MahJongTileScript>().number = j; //0~8の番号が当てられる
mahjongTileList.Add(mahjongTiles);
}
}
//以下、筒子、索子、字牌をつくる。字牌だけはfor 0 <= i < 4 の for 0 <= j < 7 になることに注意。
}
コメント部分を読めば何をやってるかわかるかと思います。
Unityエディターに戻って、プレハブや画像のアタッチをします。
Hierarchy > GameBoardを選んで、Inspectorにていろいろ作業。
1.まずはプレハブ二つをつける。
2.画像をセット。順番が大事です。スクリプトの通りに。
スクリプトを、萬子、筒子、索子、字牌の順で書いたなら、画像もman1~9、pin1~9、sou1~9、ji1~7の順にセット。
3.BoardにはGameBoardオブジェクトをアタッチ。
mahjongTileImageの順番を把握する。
上記の順番であれば、0~8が萬子、9~17が筒子、18~26が索子27~33が字牌となっているはず。
スクリプトへ戻って修正。
たとえば筒子の部分は、以下の通り(画像番号のところを変える)
(void GenerateTiles()の中身です)
//generate pinzu
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 9; j++)
{
GameObject mahjongTiles = Instantiate(tilePrefab, board.position, Quaternion.identity);
//画像、タグ、ナンバーを割り当てる処理を入れる。画像ナンバーは9~17
//麻雀牌プレハブのインスペクターや、アタッチしているスクリプトを書き替えていく。
mahjongTiles.GetComponent<SpriteRenderer>().sprite = MahjongPieImage[j+9]; // ここを変えたよ。j+9にしたよ。
mahjongTiles.tag = "pin";
mahjongTiles.GetComponent<MahJongPieScript>().number = j;
mahjongTileList.Add(mahjongTiles);
}
}
索子はj+18、字牌はj+27です。
ゲーム画面に配置するスクリプト
さあ、配列を使いますよ!
ここでプログラミングの配列についてちょっと大事な小話です。
私たちはX軸といったら横方向、Y軸と言ったら縦方向と思います。
そして(5,12)とあれば、X軸に5、Y軸に12行ったところだと思います。これはベクトルの考え方です。
しかし、プログラミングの配列の表示は(y、x)です。逆です。
配列で上に12行って、5右に行くというのは、(12,5)という表記になります。上下が1番目の要素で、左右が2番目の要素です。
このことを頭に入れておいてください。
さあ、8×17に並べましょう。8行17列です。行をrow、列をcolumnとします。
さてスクリプト。まずは、以下を冒頭に設定。
public int rows; //publicのものはUnityで設定しよう。あとでrows:10、columns:19にセット。8,17よりそれぞれ2つ多いです。
public int columns;
public GameObject[,] tileArray;
int sortingOrder = 1; // 初期のOrder in Layer
このsortingOrderは何かというと、画像を少し上下重ねて並べたいわけですが(画像の上端の黄色いところ、つまり牌の側面が見えないように)、上の行にあるものが上に表示されるようにしたいわけです。これをやらないと上の行にあるものが下にあるように見えたりするのです。
// Start is called before the first frame update
void Start()
{
GenerateTiles();
//ShuffleTiles(); // ※あとで作ります。
tileArray = new GameObject[rows, columns]; //配列の初期化。
SetupBoard();
}
void SetupBoard()
{
int count = 0;
for (int row = 0; row < rows; row++)
{
// Order in Layer を増加させて、上の列の牌が下の列の牌より上に表示されるようにする
sortingOrder++;
for (int column = 0; column < columns; column++)
{
// 外周の行と列にはemptyTileを入れておく。ここがミソ。
if (row == 0 || row == rows - 1 || column == 0 || column == columns - 1)
{
GameObject EmptyTile = Instantiate(emptyTile, board.position, Quaternion.identity);
EmptyTile.tag = "empty";
tileArray[row, column] = EmptyTile;//配列に入っていればいい。画面表示上の座標は無視している。
continue; // emptyTileの時は、以降の処理はスキップ
}
Vector2 tilePosition = new Vector2(column, row); // ※あとでいじります。
mahjongTileList[count].transform.position = tilePosition;
tileArray[row, column] = mahjongTileList[count]; //配列にゲームオブジェクトを入れる。
// SpriteRenderer コンポーネントを取得
SpriteRenderer spriteRenderer = mahjongTileList[count].GetComponent<SpriteRenderer>();
// Order in Layer を設定 上の行にあるものは、下の行にあるものよりひとつ上に表示される。
spriteRenderer.sortingOrder = sortingOrder;
count++;
}
}
}
Unityエディターに戻る。
GameBoardにて、rowsを10に、columnsを19に設定。
さあ、ここでテストプレイ!
そうすると、きれいにならべることができました・・・?
位置が変ですね。
そうです、カメラの位置を動かさないといけませんね。
それから、ちょっとスキマがあってイメージと違いますね。
ということで、テストプレイをやめて、プレハブをHierarchyに置いてみます。
ふたつくらい複製して、上下左右きれいに重なるようにマウスやInspectorでPositionの数値をいじります。
どうでしょう。横軸方向は、0.75くらいずらすのがよいでしょうか?
縦軸方向は0.9くらいがよいでしょうか?
さあ、スクリプトの該当箇所を修正します。
以下です。
Vector2 tilePosition = new Vector2(column * 0.75f, row * 0.9f); // 掛け算しておけばよいのです。
さあ、テストプレイしてみてください。
次にカメラの位置です。お好きな位置にずらしましょう。
Background ColorやSizeもちょうどいいところに変えましょう。
シャッフルさせるスクリプト。
このシャッフルスクリプトは定番です。カードゲームでも同じです。
void ShuffleTiles()
{
for (int i = 0; i < 136; i++) //麻雀牌は136個あります。ここはmahjongTileList.Countでもいいと思います。
{
int randomIndex = Random.Range(i, mahjongTileList.Count);
GameObject temp = mahjongTileList[i];
mahjongTileList[i] = mahjongTileList[randomIndex];
mahjongTileList[randomIndex] = temp;
}
}
i番目の牌とランダムなどこかの牌を交換し続ける、という関数ですね。tempが肝です。
Start関数のコメントアウトを外して、テストプレイです。
void Start()
{
GenerateTiles(); // 麻雀牌を生成して、
ShuffleTiles(); // シャッフルして、
tileArray = new GameObject[rows, columns]; //配列の初期化をしておいて、
SetupBoard(); // ゲーム画面に並べたり、配列にゲームオブジェクトを格納したりします。
}
ユーザーが麻雀牌をクリック(タップ)できるようにしていきます。
UnityエディターのHierarchyで右クリック、Create Emptyで名前をPlayerにしておきます。
Project > Scriptsにて右クリック、Create > C#Script、名前はPlayerScriptにしましょう。
PlayerScriptをPlayerオブジェクトにアタッチして、PlayerScriptを開きます。
これからスクリプトを書いていきますが、考え方は以下の通り。
1.プレイヤーが一つ目の牌をタップする。リストに格納。
2.二つ目の牌をタップする。リストに格納。
3.一つ目と二つ目を比較して、マッチング判定をする。
それでは、一つ目の麻雀牌をタップすると、その牌が半透明になるというところまで。
private List<GameObject> selectedTiles = new List<GameObject>();
private GameObject tile1;// 一つ目にピックアップした牌
private GameObject tile2;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(mousePosition, Vector2.zero);
if (hit.collider != null && (hit.collider.tag == "man" || hit.collider.tag =="pin"
|| hit.collider.tag == "sou" || hit.collider.tag == "ji")) // 括弧が二重ですから注意してください。
{
GameObject clickedTile = hit.collider.gameObject;
// クリックされた麻雀牌を識別し、クリック処理を実行
HandleTileClick(clickedTile);
}
}
}
// 麻雀牌を選んだ時の操作
void HandleTileClick(GameObject clickedTile)
{
if (selectedTiles.Contains(clickedTile))
{
// すでに選択された麻雀牌を再度クリックした場合、選択を解除
selectedTiles.Remove(clickedTile);
SetTileTransparency(clickedTile, 1.0f); // 元の色に戻す処理。1.0f は通常の透明度
}
else
{
// クリックされた麻雀牌をリストに追加
selectedTiles.Add(clickedTile);
// クリックされた麻雀牌を半透明にする。下に関数があります。
SetTileTransparency(clickedTile, 0.5f);
// リストに2つの麻雀牌がある場合、ペアのマッチングを試みる
if (selectedTiles.Count == 2)
{
// tile1,2をセッティング。
tile1 = selectedTiles[0];
tile2 = selectedTiles[1];
//それぞれのタグを取得しておく(あとで戻すときのために)
string tile1Tag = tile1.tag;
string tile2Tag = tile2.tag;
// 判定処理へ
if (IsMatchingPair(tile1, tile2)) // ※あとで作ります。
{
// ペアのマッチングが成立した場合の処理をここに記述
Debug.Log("Matching Pair!");
// 成立したペアを非表示にするなどのアクションを実行
// 手詰まりかどうかチェックして。
// 残りの牌を数えて。
// クリアしたのか、手詰まりなのか表示する処理
}
else
{
// ペアのマッチングが成立しなかった場合の処理をここに記述
Debug.Log("Not a Matching Pair");
// 選択した2つの麻雀牌を再び非選択状態に戻す。
// 元の色に戻す処理
foreach (var item in selectedTiles)
{
SetTileTransparency(item, 1.0f);
}
}
// 選択した麻雀牌をリストから削除
selectedTiles.Clear();
}
}
}
// 半透明にしたり戻したりする関数
void SetTileTransparency(GameObject tile, float alpha) //引数2つ
{
SpriteRenderer spriteRenderer = tile.GetComponent<SpriteRenderer>();
Color tileColor = spriteRenderer.color;
tileColor.a = alpha;
spriteRenderer.color = tileColor;
}
bool IsMatchingPair(GameObject tile1, GameObject tile2) //bool型です。trueかfalseを返すようにします。
{
return true; // ※とりあえず。
}
さて。ここまでのところでテストプレイすると、どうでしょう。半透明になりましたか?
長いので、続きは後半へ。マッチング判定のロジックとプログラミングです。このゲーム制作の一番難しいところです。