Unityで「しちならべ」を作る(3)
こんにちは「つけらっとゲームス」プログラム担当のとちです。
情報系専門学校で外部講師として「七並べ」のシステム設計を授業で扱ったこともあり解答例として設計書を作りながら、Unityで「しちならべ」を作ったお話です(前回分はコチラ ↓ )
「ゲームを作ってみよう!」と考えている方、「ゲーム作りってどんな感じ?」と興味がある方向けの記事にしたいと思っています。
ちなみに専門学校の外部講師としての記事はこんなのがあります。ご興味がございましたら以下もご覧くださいね。
今回は七並べのゲーム中に頻繁に使用する「どのカードをクリックしたか?」をどうやって処理しているかを具体的に説明してみますね。実際に使用しているスクリプトも用意しています!
どのカードをクリックしたか?
「七並べ」と言えば各プレイヤーは手札から場にカードを出してゲームを進めて行きます。
8や6を持っていて、それ以上以下のカードが他に無ければ敢えて場に出さずに止めておくという戦略もありますよね。
それなりに戦略性のあるゲームなので操作ミスはなるべく防げるようにしたいところです。そこで以下のように考えました。
場に出せないカードはクリックしても反応しない
場に出せるカードであっても2度クリックしないと場に出せない
1度目クリックの後、別カードを選択することも可能
これなら戦略上ミスったらダメな場面での操作ミスを防げると思います。
以下のスクリーンショットの通り、動作します。
BoxCollider2D
では、これをUnityではどうやっているのか?
まずはカード(トランプ)のインスペクタを見てみましょう!
カードオブジェクトを選択するとこんな感じです。
ここで大事なのがBoxCollider2Dです。
「コライダーとはなんぞや?」というのを説明すると…オブジェクトに物理判定を持たすコンポーネントだと思っていいです。
コライダーは「Box」以外にも「Circle」や「polygon」などいくつかの形がありますがトランプのカードは四角形、2Dで動くゲームですので、ここでは「BoxCollider2D」を選択しています。
もしオブジェクトにコライダーが見当たらない場合は「Add Component」をクリックして「BoxCollider2D」を選びましょう!
さて、手札は最大何枚のカードになるでしょう?
通常のカード(A~Kで4マーク)+ジョーカーで53枚、4人のプレイヤーがいるので、53÷4=13.25ですね。
ということは、手札は最大14枚になる可能性があります。
先程の手札オブジェクトが14個あれば大丈夫。
コピペでオブジェクトを増やしておくと良いでしょう。
プレハブにしてもいいですね、prefabについては別のタイミングで説明しましょ!
次はスクリプトでどうやっているのかを説明しますね。
Raycast使ってみる!
手札は最大14枚です。どのカードを場に出すのかが大事となる「七並べ」ですから、そのあたりはプログラムでしっかり区別しましょう。その手段として「Raycast」を使ってみましょう。
「そもそもRaycastってなんやねん?」というのを説明すると…
とのことです。
つまり、クリックしたポイントからセンサーを画面奥に発射して、センサーと接触したオブジェクトが何なのかを取得できればいいんですね。
「クリックしたところに何があるか?」はゲームの色々な箇所で使用するので関数化しちゃいましょう。
「しちならべ」のプロジェクトファイルの中を見ると「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」です。
ゲーム中に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が有効、入っていない場合は無効となります。これでクリック時にRayが接触するかどうかを切り替えているんですね。
この切替は凄く簡単。
PcardSv.Pobj[ix].GetComponent<BoxCollider2D>().enabled = false;
PcardSv.Pobj[添え字]にRayと接触したオブジェクトが入っています。
コンポーネント<BoxCollider2D>のenabledをfalseにすると無効、trueにすると有効となります。
続きます!
今回は、どのオブジェクト(カード)をクリックしたかを解決しました。
しかし、グレイアウトしているカード(場に出せないカード)をどうやって判断しているのか、そのへんについては全く解決されていません。
次回以降は操作ではなく七並べのルール上、どういう考え方で動かせばいいのかを解決していきたいと思います。
今回は長くなったのでここまでです。
ちなみに外部講師以外では、普段はこんなゲームをいじっている人です。
もしよかったら以下の記事もご覧くださいませ~。
次回【Unityで「しちならべ」を作る(4)】はコチラです(↓)
この記事が気に入ったらサポートをしてみませんか?