見出し画像

Unityで「しちならべ」を作る(3)

こんにちは「つけらっとゲームス」プログラム担当のとちです。

情報系専門学校で外部講師として「七並べ」のシステム設計を授業で扱ったこともあり解答例として設計書を作りながら、Unityで「しちならべ」を作ったお話です(前回分はコチラ ↓ )

「ゲームを作ってみよう!」と考えている方、「ゲーム作りってどんな感じ?」と興味がある方向けの記事にしたいと思っています。

ちなみに専門学校の外部講師としての記事はこんなのがあります。ご興味がございましたら以下もご覧くださいね。

今回は七並べのゲーム中に頻繁に使用する「どのカードをクリックしたか?」をどうやって処理しているかを具体的に説明してみますね。実際に使用しているスクリプトも用意しています!



どのカードをクリックしたか?

「七並べ」と言えば各プレイヤーは手札から場にカードを出してゲームを進めて行きます。

8や6を持っていて、それ以上以下のカードが他に無ければ敢えて場に出さずに止めておくという戦略もありますよね。

それなりに戦略性のあるゲームなので操作ミスはなるべく防げるようにしたいところです。そこで以下のように考えました。

  1. 場に出せないカードはクリックしても反応しない

  2. 場に出せるカードであっても2度クリックしないと場に出せない

  3. 1度目クリックの後、別カードを選択することも可能

これなら戦略上ミスったらダメな場面での操作ミスを防げると思います。
以下のスクリーンショットの通り、動作します。

手札で出せるカードを明るく表示
1度目クリックすると少し拡大、2度目クリックで場に出す

BoxCollider2D

では、これをUnityではどうやっているのか?
まずはカード(トランプ)のインスペクタを見てみましょう!
カードオブジェクトを選択するとこんな感じです。

手札オブジェクトのInspector

ここで大事なのがBoxCollider2Dです。
「コライダーとはなんぞや?」というのを説明すると…オブジェクトに物理判定を持たすコンポーネントだと思っていいです。

コライダーは「Box」以外にも「Circle」や「polygon」などいくつかの形がありますがトランプのカードは四角形、2Dで動くゲームですので、ここでは「BoxCollider2D」を選択しています。

もしオブジェクトにコライダーが見当たらない場合は「Add Component」をクリックして「BoxCollider2D」を選びましょう!

さて、手札は最大何枚のカードになるでしょう?
通常のカード(A~Kで4マーク)+ジョーカーで53枚、4人のプレイヤーがいるので、53÷4=13.25ですね。

ということは、手札は最大14枚になる可能性があります。
先程の手札オブジェクトが14個あれば大丈夫。

オブジェクト名の末尾を番号にして管理(0~13の14枚)

コピペでオブジェクトを増やしておくと良いでしょう。
プレハブにしてもいいですね、prefabについては別のタイミングで説明しましょ!

次はスクリプトでどうやっているのかを説明しますね。


Raycast使ってみる!

手札は最大14枚です。どのカードを場に出すのかが大事となる「七並べ」ですから、そのあたりはプログラムでしっかり区別しましょう。その手段として「Raycast」を使ってみましょう。

「そもそもRaycastってなんやねん?」というのを説明すると…

Raycast は例えるならば、空間上のある地点から特定方向へ発射されたセンサーのようなものです。センサーと接触したすべてのオブジェクトは検知され報告されます。

Unityスクリプトリファレンスより引用

とのことです。
つまり、クリックしたポイントからセンサーを画面奥に発射して、センサーと接触したオブジェクトが何なのかを取得できればいいんですね。

「クリックしたところに何があるか?」はゲームの色々な箇所で使用するので関数化しちゃいましょう。

「しちならべ」のプロジェクトファイルの中を見ると「CRaycastProc.cs」というファイルがあります。Raycast関係をまとめたファイルです。

using UnityEngine;
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//
//	Raycast関連
//
//	Author : toti
//
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
public class CRaycastProc : MonoBehaviour
{
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	//	RaycastAllRtn
	//
	//		クリックポイントにある全てのオブジェクト名を返答する
	//		オブジェクトが重なっている可能性がある場合はコチラを使用する
	//
	//	引数		クリックまたはタッチした場所
	//
	//	戻値		GameObject配列[10]
	//
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	public static GameObject[] RaycastAllRtn(Vector2 wMusPos)
	{
		Ray            wRayAct = Camera.main.ScreenPointToRay(wMusPos);
		RaycastHit2D[] wHitOBJ = (Physics2D.RaycastAll(wRayAct.origin, wRayAct.direction));
		int            wHitLen = wHitOBJ.Length;
		GameObject[]   wOBJ    = new GameObject[wHitLen];
		if(wHitLen >= 1)
		{
			int wOBJIx = 0;
			for(int ix = 0; ix < wHitLen; ix++)
			{
				//print("wHitLen:" + wHitLen + " wOBJIx:" + wOBJIx + " / ix:" + ix);
				wOBJ[wOBJIx] = wHitOBJ[ix].collider.gameObject;
				wOBJIx++;
			}
		}
		return wOBJ;
	}
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	//	RaycastSortingOrder
	//
	//		クリックポイントにある全てのオブジェクトの中で最もSortingOrderの大きいモノ(前面のモノ)を返答する
	//		オブジェクトが重なっている可能性がある場合はコチラを使用する
	//
	//	引数		wMusPos	クリックまたはタッチした場所
	//
	//	戻値		最もSortingOrderの大きいゲームオブジェクト
	//
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	public static GameObject RaycastSortingOrder(Vector2 wMusPos)
	{
		Ray            wRayAct  = Camera.main.ScreenPointToRay(wMusPos);
		RaycastHit2D[] wHitOBJ  = (Physics2D.RaycastAll(wRayAct.origin, wRayAct.direction));
		int            wHitLen  = wHitOBJ.Length;
		int            wSortMax = -999;
		GameObject     wRet     = null;
		if(wHitLen >= 1)
		{
			for(int ix = 0; ix < wHitLen; ix++)
			{
				GameObject wOBJ  = wHitOBJ[ix].collider.gameObject;
				string     wName = wOBJ.name;
				int        wSort = wOBJ.GetComponent<SpriteRenderer>().sortingOrder;
				if(wSortMax < wSort)
				{
					wSortMax = wSort;
					wRet     = wOBJ;
				}
			}
		}
		return wRet;
	}
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	//	RaycastFront
	//
	//		クリックポイントにある全てのオブジェクトの中で最もZ座標の小さいモノ(前面のモノ)を返答する
	//		オブジェクトが重なっている可能性がある場合はコチラを使用する
	//
	//	引数		wMusPos	クリックまたはタッチした場所
	//
	//	戻値		最もZ座標の小さいゲームオブジェクト
	//
	//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
	public static GameObject RaycastFront(Vector2 wMusPos)
	{
		Ray            wRayAct  = Camera.main.ScreenPointToRay(wMusPos);
		RaycastHit2D[] wHitOBJ  = (Physics2D.RaycastAll(wRayAct.origin, wRayAct.direction));
		int            wHitLen  = wHitOBJ.Length;
		float          wPosZMax = 999f;
		GameObject     wRet     = null;
		if(wHitLen >= 1)
		{
			for(int ix = 0; ix < wHitLen; ix++)
			{
				GameObject wOBJ  = wHitOBJ[ix].collider.gameObject;
				string     wName = wOBJ.name;
				Vector3    wPos  = wOBJ.transform.position;
				if(wPosZMax > wPos.z)
				{
					wPosZMax = wPos.z;
					wRet     = wOBJ;
				}
			}
		}
		return wRet;
	}
}

スクリプトファイルの中身を見るとコメントで分かれていますね。
これらを関数として他のスクリプトから呼び出しているんです。
引数はマウスポジションと全部同じですが戻値が異なります。

RaycastAllRtn

Rayを発射して触れたオブジェクトを全部返答します。
オブジェクトAとB両方に触れていた場合という処理が必要なら便利です!

RaycastSortingOrder

Rayを発射して触れたオブジェクトの中で最も「sortingOrder」の値が大きいオブジェクトを返答します。inspectorの「Order in layer」ですね。
この値は表示されている順番を表しています。なので一番手前に表示されているオブジェクトを取得しています。

RaycastFront

Rayを発射して触れたオブジェクトの中で最も「z座標」の値が小さいオブジェクトを返答します。

z座標が小さいモノほど手前に表示されるので「SortingOrder」と似ていますね。基本的に表示順は「Order in layer」で管理した方が良さそう、なのでコチラはあくまで補助的な関数となります。


1~2度目のクリック

1度目のクリックでは選択したカードを少し拡大しています。
オブジェクトサイズを変更するのもゲーム中の色々なところで使用しそう。そこでコチラも関数化しています。

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//	ObjectUPDscale	オブジェクトのスケールを目標値にする処理
//
// 引数		ObjUPD		変化対象のオブジェクト名
//				sclUPDx		スケールのx変化量
//				sclMAXx		スケールのx最大値(または最小値)>> 目標値
//				SclUPDy		スケールのy変化量
//				sclMAXx		スケールのy最大値(または最小値)>> 目標値
//
//	戻値		[0] 目標値に達していない
//				[1] 目標値を達している
//
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
static public int ObjectUPDscale(string ObjUPD,float sclUPDx,float sclMAXx,float sclUPDy,float sclMAXy)
{
	GameObject wkOBJ = GameObject.Find(ObjUPD);
	Vector3    wkSz  = wkOBJ.transform.localScale;
	int        wOKSW = 0;
	int        wRet  = 0;
	wkSz.x = wkSz.x + sclUPDx;
	if(sclUPDx > 0 && wkSz.x >= sclMAXx){wkSz.x = sclMAXx; wOKSW++;}
	if(sclUPDx < 0 && wkSz.x <= sclMAXx){wkSz.x = sclMAXx; wOKSW++;}
	wkSz.y = wkSz.y + sclUPDy;
	if(sclUPDy > 0 && wkSz.y >= sclMAXy){wkSz.y = sclMAXy; wOKSW++;}
	if(sclUPDy < 0 && wkSz.y <= sclMAXy){wkSz.y = sclMAXy; wOKSW++;}
	if(wOKSW >= 2){wRet++;}
	wkOBJ.transform.localScale = wkSz;																		// Objectにサイズを反映させる
	return wRet;																							// 戻値を返答する
}

1フレームでどれくらいの大きさが変化するのか?
変化した結果、目標としているサイズに達しているのか?
それらを解決するのが「ObjectUPDscale」です。

赤丸部分のScaleを変化させています

ゲーム中に1度クリックするとサイズを(0.9,0.9,1)にしていました。
(1,1,1)だと思っていたんですが、想像していたより大きかったんでしょうねぇ~。

目標サイズに到達すると「ObjectUPDscale」から「1」が返答されるので、それまで繰り返し処理させると徐々に大きくなるように見えます。

さて、2度目のクリックでカードを場に出す仕組み、そこも解決しましょう。普段は(0.8,0.8,1)で1度クリックすると(0.9,0.9,1)のハズ!

1度目のクリックでカードサイズが大きくなっているので、以前にクリックしているかどうかの判断は容易ですよね。


場に出せないカード

前述通り、どのカードをクリックしたかを判断するためにRayを発射して、それに触れたオブジェクト(カード)を取得してきました。

でも「七並べ」は「7」を中心に隣り合っている数字のカード以外は場に出せません。ゲームの中では出せないカードはグレイアウトされています。

つまり、クリックしても反応しないカードですね。
inspectorをもう一度確認してみましょう。

BoxCollider2Dが無効になってる!

スクショの場所にチェックマークが入っているとBoxCollider2Dが有効、入っていない場合は無効となります。これでクリック時にRayが接触するかどうかを切り替えているんですね。

この切替は凄く簡単。

PcardSv.Pobj[ix].GetComponent<BoxCollider2D>().enabled  = false;

PcardSv.Pobj[添え字]にRayと接触したオブジェクトが入っています。
コンポーネント<BoxCollider2D>のenabledをfalseにすると無効、trueにすると有効となります。


続きます!

今回は、どのオブジェクト(カード)をクリックしたかを解決しました。
しかし、グレイアウトしているカード(場に出せないカード)をどうやって判断しているのか、そのへんについては全く解決されていません。

次回以降は操作ではなく七並べのルール上、どういう考え方で動かせばいいのかを解決していきたいと思います。

今回は長くなったのでここまでです。

ちなみに外部講師以外では、普段はこんなゲームをいじっている人です。
もしよかったら以下の記事もご覧くださいませ~。

次回【Unityで「しちならべ」を作る(4)】はコチラです(↓)


この記事が参加している募集

ゲームの作り方

この記事が気に入ったらサポートをしてみませんか?