Unity1Week GameJam お題「Space」に参加しました


Unity1Week GameJam お題「Space」に、「FIND SPACE」を応募しました。

以前からTwitterで「Unity1Week GameJam」の存在は知っていたんですが、1週間でゲームを作るなんて自分には(ヾノ・∀・`)ムリムリ、と思っていました。しかし、どこかで決断しないと、ということで今回初めて参加してみました。
ところが本業の仕事が忙しく、お題が発表された日曜夜から金曜日までは全く時間が取れず・・・。「Space」のテーマに合うゲームを寝る前に考えようとしてそのまま寝る、みたいな日々が続きました。
「Space」というと真っ先に宇宙が思い浮かびますが、今回は「空間」「領域」のイメージで考えて、シンプルにUnityの2Dで作ることにしました。
初めはテトリスのように、空いているスペースにぴったりのブロックを入れるような落下パズルを考えていたのですが、試行錯誤の上、ブロックをぴったりのスペースにドラッグ&ドロップする、という内容に落ち着きました。
金曜の夜にUnityプロジェクトを作り、土曜日に基本骨子を作り、日曜日にxRTechTokyoに参加しながら調整とWebGLへの書き出しを行い、帰宅後の締め切りの20時の少し手前でアップロード完了、というドタバタスケジュールでしたが何とか応募に間に合いました。

以下、メモも兼ねてポイントを記載します。

ブロックの生成

お題または選択肢となるブロックですが、1辺のブロックの数blockSize(可変にしていましたが今回は3で固定)を使って、下記のメソッドで生成を行っています。
ブロックの部品をparentObjectの子オブジェクトとしてまとめると同時に、配置の情報をintの二次元配列に格納しています。これは、ゲームの制約上、作成した選択肢は必ずユニークでなければならないため、比較をしやすくするためです。また、3×3すべてが空白のブロックは使いたくなかったため、isEmptyというチェック用メソッドを用意して、do~whileでチェックを通過するまでループする構造になっています。

GameObject createBlock(string name,out int[,] retMap)
{
	int[,] map = new int[blockSize,blockSize];
	GameObject parentObject = new GameObject (name);
	parentObject.transform.localScale = new Vector3 (blockSize, blockSize, 0);
	do {
		//ループした場合に前にInstantiateしたオブジェクトを一旦消去する
		foreach(Transform tsf in parentObject.transform)
		{
			Destroy(tsf.gameObject);
		}
		for (int i = 0; i < blockSize; i++) {
			for (int j = 0; j < blockSize; j++) {
				int rand = Random.Range (0, 2);
				if (rand == 0) {
					GameObject t = (GameObject)Instantiate (tile, new Vector2 (i, j), Quaternion.identity);
					t.transform.SetParent (parentObject.transform);
					map [i, j] = 1;
				}
				else
				{
					GameObject t = (GameObject)Instantiate (blackTilePrefab, new Vector2 (i, j), Quaternion.identity);
					t.transform.SetParent (parentObject.transform);
					map [i, j] = 0;
				}
			}
		}
	} while(isEmptyMap (map));
	float offset = (float)(blockSize - 1) / (blockSize * 2f);	//がんばった計算式
	parentObject.AddComponent<boxCollider2D> ();
	parentObject.GetComponent<boxCollider2D> ().offset = new Vector2 (offset, offset);
	retMap = map;
	return parentObject;
}

しかし、ここで一番苦労したのは、BoxCollider2Dのサイズと位置の制御についてです。
親オブジェクトにBoxCollier2Dをアタッチして、そのサイズを子オブジェクトの3×3の領域と全く同じにするのにいろいろ考えて、上記の計算式の結果のoffsetをBoxCollider2Dにセットすることにより位置合わせを行いました。
また、単純ですが見落としがちなこととして、Collider2D同士のあたり判定を行う場合には、少なくとも片方にRigidBody2Dがアタッチされていなければなりません。

ブロックのドラッグ&ドロップ

小さいオブジェクトなら、マウスカーソルの位置をワールド座標に変換して、オブジェクトのpositionにすればドラッグでの移動は簡単に作れるのですが、今回のように大きいオブジェクトだと、オブジェクトのどこをクリックしてドラッグを開始したのかを考えないと、オブジェクトがワープしてしまいます。(オブジェクトの中心点がカーソルの位置になってしまうため)
なので、オブジェクトをクリックした位置と、オブジェクトの中心点の差分のベクトルを保存しておき、移動の際にその値を加算しました。

Vector3 diff = Vector3.zero;
void OnMouseDown()
{
	Vector3 clickPointScreen = Input.mousePosition;
	Vector3 clickPointWorld = Camera.main.ScreenToWorldPoint (clickPointScreen);
	diff = gameObject.transform.position - clickPointWorld;
}
void OnMouseDrag()
{
	Vector3 mousePointScreen = Input.mousePosition;
	Vector3 targetPointWorld = Camera.main.ScreenToWorldPoint (mousePointScreen);
	targetPointWorld = targetPointWorld + diff;
	targetPointWorld.z = 0;
	this.transform.position = targetPointWorld;
}
ブロックの正誤判定

お題のブロックを選択肢のブロックにドラッグ&ドロップした際に、正しいブロックなのか間違いのブロックなのか判定する必要があります。判定はColliderを使用するのですが、ドロップ=OnMouseUpした瞬間にどのColliderなのかを判定することはできなさそうです(OnTriggerやOnStayなどのイベントで制御されているため)
なので、ドラッグされてOnTriggerStay2Dが発生しているときにColliderで判別してグローバル変数に入れておき、OnMouseUpが発生したときにその値を使用して判定を行っています。

enum CollisionStatus
{
	None = -1,
	Target,
	Other
}
CollisionStatus colStatus;
void OnTriggerStay2D(Collider2D col){
	if (GameManager.Instance.targetBlockName == col.gameObject.name) {
		colStatus = CollisionStatus.Target;
	} else {
		colStatus = CollisionStatus.Other;
	}
}
void OnMouseUp()
{
	if (colStatus == CollisionStatus.Target) {
		Debug.Log ("Collect!!");
		GameManager.Instance.OnBlockDrop (true);
	} else if (colStatus == CollisionStatus.Other) {
		Debug.Log ("Miss!!");
		GameManager.Instance.OnBlockDrop (false);
	}
}
WebGLへの書き出し

まず、WebGLへの書き出しはかなり時間がかかります。途中でプログレスバーが進まなくなってフリーズしたかと思うほどなのですが、気長に数分以上は待たないとだめそうです。@naichilabさんに教えてもらって助かりました。
そして書き出したhtmlで確認すると、画面の比率が大幅に違っていて焦りましたが、画面のサイズはPlayer Settings > Default Screen Height/Width で設定できます。これはhtml側の設定の話となります。
また、UnityRoomにアップロードする際にもHeight/Widthを設定できますので、そこで正しい値を入れれば基本的には問題なく動くはずです。

AssetStoreの利用

短い開発期間だったので、AssetStoreの素材を使わせていただきました。(いずれも無料Asset)

Elevate your workflow with the Free 8-Bit Pixel Pack asset from Super Icon Ltd. Find this & more Environments on the Unity Asset Store.
使いやすいファミコン風のテクスチャのセットです。スプライト化されているのですぐに使用できます。
Layer in the sounds of The 8-bit Jukebox Lite - Music Pack from Cyberleaf Studio for your next project. Browse all audio options on the Unity Asset Store.
チップチューンのBGMが多数含まれたセットです。どれもクオリティが高い!BGMとして使用させてもらいました。
Layer in the sounds of Free 8bit Game Sound Effect Package from StrangePulseStudio for your next project. Browse all audio options on the Unity Asset Store.
ファミコンサウンド風の効果音のセットです。ベーシックなものが含まれています。

Unity1Week GameJamの意義

企画してくださっている@naichlabさん、ありがとうございます!
Unityを勉強して使えるようになってきても、1つの作品として完成・公開するには相当なパワーが必要になります。コアのゲーム部分だけを作るのは楽しいですが、例えばステージクリア処理、ゲームオーバー処理、ハイスコアの記録、ゲームバランスの調整、デバッグなど、ゲームのコア部分以外の作業が必ず必要になってきます。
Unity1Week GameJamは、期限を1週間に制限することにより、1つのゲームとして完成させるためのリソースの割り当て方を嫌が応にも勉強させてくれます。自作にこだわらないでAssetStoreを使ったり、本当ならもっと綺麗に書きたいプログラムをひとまずは動かす方法で、という割り切りができたり、人により様々だと思いますが、リソースを有効に活用する(せざるを得ない)経験というのはとても重要だと思います。
さて、次回は丸々一週間使えるように本業を終わらせておこう・・・。

スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク