![見出し画像](https://assets.st-note.com/production/uploads/images/70727001/rectangle_large_type_2_84a415d57dbfce6f5c7a5e07fd73d3c7.png?width=1200)
Photo by
takataok
[Unity Blocks 12]ランダムにピースを置く
次にピースがこれ以上置けるか、置けないかの判定をし、どこにどのピースがどの向きで置けるかの情報リストを取得、ランダムに実行をしていきます。
置けるピースをとりあえず全探索する
やることとしてはまず、ボードの全ての点において、順に各ピースを当てはめることができるかを見ていく、全探索でやっていきます。
各ピースを当てはめていきますが、本当に置いてしまったり、試している途中でピースの回転があっても面倒なので、少しピースのスクリプトを変えます。(またあとで変わるんですけどね)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Piece : MonoBehaviour
{
public List<int[]> Design{get; set;}
public Vector3 WaitPoint{get; set;} //ピースの待機場所
List<int[]> StartDesign;
int RotateAngle;
int ReverseAngle;
public bool IsSet; //置かれた後のピースか判断 あまり必要ないかも
void Start()
{
Design = PieceDesign.ReturnDesign(this.gameObject);
StartDesign = CopyPieceDesign(Design);
}
void Update()
{
}
//ボードで設定されているピース待機場所に戻る
public void ReturnWaitPoint()
{
DebugLogPieceList(StartDesign);
Design = CopyPieceDesign(StartDesign);
RotateAngle = 0;
ReverseAngle = 0;
transform.rotation = Quaternion.AngleAxis(0, new Vector3(0, 1, 0));
this.transform.position = WaitPoint;
}
//ピースの回転。反転中は回転方向を逆にしています。回転方向は引数の±で判断
public void Rotate(int Dir)
{
PieceDesign.Rotate(Design, Dir);
if (ReverseAngle != 0)
Dir *= -1;
RotateAngle += 90 * Dir;
Quaternion TmpQ;
TmpQ = Quaternion.AngleAxis(RotateAngle, new Vector3(0, 1, 0));
transform.rotation = Quaternion.AngleAxis(ReverseAngle, new Vector3(1, 0, 0)) * TmpQ;
if (RotateAngle >= 360 || RotateAngle <= -360)
RotateAngle = 0;
}
//ピースの反転
public void Reverse()
{
PieceDesign.Reverse(Design);
ReverseAngle += 180;
Quaternion TmpQ;
TmpQ = Quaternion.AngleAxis(RotateAngle, new Vector3(0, 1, 0));
transform.rotation = Quaternion.AngleAxis(ReverseAngle, new Vector3(1, 0, 0)) * TmpQ;
if (ReverseAngle >= 360)
ReverseAngle = 0;
}
//Designの配列のディープコピーを作成
public List<int[]> CopyPieceDesign(List<int[]> Design)
{
List<int[]> Res = new List<int[]>();
foreach (int[] Piece in Design)
{
int Len = Piece.Length;
int[] TmpPoint = new int[Len];
for (int Index = 0; Index < Len; Index++)
{
TmpPoint[Index] = Piece[Index];
}
Res.Add(TmpPoint);
}
return Res;
}
//デバッグ用
public void DebugLogPieceList()
{
Debug.Log("====");
foreach (int[] A in Design)
{
Debug.Log(A[0] + " " + A[1]);
}
}
public void DebugLogPieceList(List<int[]> Design)
{
Debug.Log("====");
foreach (int[] A in Design)
{
Debug.Log(A[0] + " " + A[1]);
}
}
}
CopyPieceDesignによりピースの形の情報をディープコピーできるようにしています。また、回転や、反転といった動作はピースのインスタンスの情報は特に必要なかったので、ほかのstaticなクラスに移動させました。
これでクラス外からこのピースの情報をコピーし、回転などのシミュレーションができるようになりました。
移動先はStart関数でも呼んでいるPieceDesignです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class PieceDesign
{
static Dictionary<string, int[,]> DesignDict;
public static List<int[]> ReturnDesign(GameObject ParentObj)
{
Vector3 Pivot = Vector3.zero;
List<int[]> res = new List<int[]>();
res.Add(new int[]{0,0});
foreach (Transform ChildObj in ParentObj.transform)
{
if (ChildObj.gameObject.name == "Piece")
Pivot = ChildObj.transform.position;
break;
}
foreach (Transform ChildObj in ParentObj.transform)
{
if (ChildObj.gameObject.name == "Piece")
continue;
Vector3 Pos = (ChildObj.transform.position - Pivot) * 100;
int[] ChildPoint = new int[2];
ChildPoint[0] = -(int)(Mathf.Round(Pos.z));
ChildPoint[1] = (int)(Mathf.Round(Pos.x));
res.Add(ChildPoint);
}
return res;
}
public static List<int[]> Reverse(List<int[]> Design)
{
foreach (int[] Piece in Design)
{
Piece[0] = -Piece[0];
}
return Design;
}
public static List<int[]> Rotate(List<int[]> Design, int Dir)
{
foreach (int[] Piece in Design)
{
int[] Tmp = new int[]{Piece[0], Piece[1]};
Piece[1] = -1 * Dir *Tmp[0];
Piece[0] = 1 * Dir *Tmp[1];
}
return Design;
}
}
実際に探索を行う関数はPieceControllerに入れています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PieceController : MonoBehaviour
{
[SerializeField]
Material[] Materials;
Vector3 SpawnPoint;
Vector3 DotPoint;
int PlayerNum;
const int MaxPlayer = 2;
List<List<GameObject>> AllPieceList; //ピースオブジェクトのリスト
int PieceCount = 1;
GameObject ControlPiece; //操作中のピース
Board BoardScript;
int[] PiecePivot; //現在操作中のピースの原点の位置
//スタート時に動く関数
void Start()
{
BoardScript = GameObject.Find("Board").GetComponent<Board>();
PiecePivot = new int[]{0, 0};
AllPieceList = new List<List<GameObject>>();
SpawnPoint = new Vector3(-0.065f, 0.05f, 0.065f);
DotPoint = this.transform.position;
CreatePieces(new Vector3(0.12f, 0.1f, 0.15f), 0);
CreatePieces(new Vector3(-0.36f, 0.1f, 0.15f), 1);
ChangeControlPiece(-1);
}
//プレイヤー分のピースを生成する。初期状態では重力無し
//AllPieceListに生成したピースのオブジェクトを保管しておく
//PieceWaitPoint:ピースの生成位置。基本的にここでピースは待機する
void CreatePieces(Vector3 PieceWaitPoint, int MaterialNum)
{
List<GameObject> PlayerPieceList = new List<GameObject>();
foreach(string PieceName in ConstList.PieceList)
{
GameObject NewPiece = Instantiate((GameObject)Resources.Load(PieceName));
NewPiece.GetComponent<Rigidbody>().useGravity = false;
NewPiece.transform.position = PieceWaitPoint;
NewPiece.GetComponent<Piece>().WaitPoint = PieceWaitPoint;
PlayerPieceList.Add(NewPiece);
PieceWaitPoint = StepPieceWaitPoint(PieceWaitPoint);
SetMaterialToChild(NewPiece, Materials[MaterialNum]);
}
AllPieceList.Add(PlayerPieceList);
}
//ピース生成中、ピース間の距離分移動
Vector3 StepPieceWaitPoint(Vector3 WaitPoint)
{
WaitPoint += new Vector3(0f, 0f, -0.06f);
if (WaitPoint.z < -0.15f)
{
WaitPoint.x += 0.06f;
WaitPoint.z = 0.15f;
}
return WaitPoint;
}
//F,Gでピース変更。スペースでピース設置
//WASDでピース移動
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
ChangeControlPiece(1);
if (Input.GetKeyDown(KeyCode.G))
ChangeControlPiece(-1);
if (Input.GetKeyDown(KeyCode.Space))
{
SetPiece();
}
if (Input.GetKeyDown(KeyCode.R))
PieceReverse();
if (Input.GetKeyDown(KeyCode.E))
PieceRotate(1);
if (Input.GetKeyDown(KeyCode.Q))
PieceRotate(-1);
if (Input.GetKeyDown(KeyCode.W))
MoveSpawnPoint(new Vector3(0f, 0f, 0.01f));
if (Input.GetKeyDown(KeyCode.S))
MoveSpawnPoint(new Vector3(0f, 0f, -0.01f));
if (Input.GetKeyDown(KeyCode.D))
MoveSpawnPoint(new Vector3(0.01f, 0f, 0f));
if (Input.GetKeyDown(KeyCode.A))
MoveSpawnPoint(new Vector3(-0.01f, 0f, 0f));
if (Input.GetKeyDown(KeyCode.Z))
ShowPieceInfo();
if (Input.GetKeyDown(KeyCode.X))
IsPossibleAnySetPiece();
}
//ピースを置く。重力はオンにする。
//置いたピースはAllPieceListから削除する
//PieceCountはリセット。その後ChangeControlPieceで一番最初のピースを持たせる
public bool SetPiece()
{
Piece ControlPieceScript;
if (ControlPiece == null)
return false;
ControlPieceScript = ControlPiece.GetComponent<Piece>();
bool IsSpace = BoardScript.IsPossibleSetPiece(ControlPieceScript.Design, PiecePivot, PlayerNum);
if (!IsSpace)
return false;
BoardScript.SetPiece(ControlPieceScript.Design, PiecePivot, PlayerNum);
ControlPiece.GetComponent<Rigidbody>().useGravity = true;
ControlPieceScript.IsSet = true;
AllPieceList[PlayerNum].Remove(ControlPiece);
IncreasePlayerNum();
ControlPiece = null;
PieceCount = 1;
ChangeControlPiece(-1);
return true;
}
//操作するピースを変更する。引数で次のピースか前のピースか決めている。
//操作から離れたピースは最初の生成位置に戻る
public void ChangeControlPiece(int Dir)
{
int MaxPiece = (AllPieceList[PlayerNum].Count);
if (MaxPiece == 0)
return;
if (ControlPiece != null)
ControlPiece.GetComponent<Piece>().ReturnWaitPoint();
PieceCount += Dir;
if (PieceCount >= MaxPiece)
PieceCount = 0;
if (PieceCount <= -1)
PieceCount = MaxPiece - 1;
ControlPiece = AllPieceList[PlayerNum][PieceCount];
ControlPiece.transform.position = this.SpawnPoint;
}
public void IncreasePlayerNum()
{
PlayerNum += 1;
if (PlayerNum == MaxPlayer)
PlayerNum = 0;
}
public void PieceReverse()
{
ControlPiece.GetComponent<Piece>().Reverse();
}
public void PieceRotate(int Dir)
{
ControlPiece.GetComponent<Piece>().Rotate(Dir);
}
public void MoveSpawnPoint(Vector3 diff)
{
if (ControlPiece == null)
return ;
int PointY = PiecePivot[0] - (int)(Mathf.Round(diff.z * 100));
int PointX = PiecePivot[1] + (int)(Mathf.Round(diff.x * 100));
if (PointX < 0 || ConstList.BoardSize <= PointX ||
PointY < 0 || ConstList.BoardSize <= PointY)
return;
this.transform.position += diff;
SpawnPoint += diff;
diff *= 100;
PiecePivot[0] -= (int)(Mathf.Round(diff.z));
PiecePivot[1] += (int)(Mathf.Round(diff.x));
ControlPiece.transform.position = this.SpawnPoint;
}
public void ShowPieceInfo()
{
ControlPiece.GetComponent<Piece>().DebugLogPieceList();
}
public void SetMaterialToChild(GameObject Obj, Material Material)
{
foreach (Transform Child in Obj.transform)
{
Child.GetComponent<Renderer>().material = Material;
}
}
//置けるピースがあるか検索を行う。
//ピースの種類(先頭から何番目のピースか)
// 反転
// 回転
// Y軸の位置
// X軸の位置
// の順でループを行っている。置ける場合にはループの情報を引き渡す
public object[] IsPossibleAnySetPiece()
{
List<GameObject> PieceList = AllPieceList[PlayerNum];
int PieceIndex = 0;
foreach (GameObject Piece in PieceList)
{
Piece ControlPieceScript = Piece.GetComponent<Piece>();
List<int[]> CurrentPieceDesign = ControlPieceScript.CopyPieceDesign(ControlPieceScript.Design);
for (int ReverseCount = 0; ReverseCount < 2; ReverseCount++)
{
for (int RotateCount = 0; RotateCount < 4; RotateCount++)
{
for (int IndexY = 0; IndexY < ConstList.BoardSize; IndexY++)
{
for (int IndexX = 0; IndexX < ConstList.BoardSize; IndexX++)
{
bool IsSpace = BoardScript.IsPossibleSetPiece(CurrentPieceDesign, new int[]{IndexY, IndexX}, PlayerNum);
if (IsSpace)
{
return new object[]
{
PieceIndex,
ReverseCount,
RotateCount,
IndexY,
IndexX,
};
}
}
}
PieceDesign.Rotate(CurrentPieceDesign, 1);
}
PieceDesign.Reverse(CurrentPieceDesign);
}
PieceIndex++;
}
return null;
}
}
IsPossibleAnySetPieceで判定を行っています。
・どのピースが
・何回反転し
・何回回転し
・Y軸にいくつ移動し
・X軸にいくつ移動する
の形でピースを置けるかの情報が帰ってきます。
IsPossibleAnySetPieceの情報をもとに実行する
ついでにExecInstructionを追加して、実際にその処理をやってしまいます。
//一旦原点まで基点を戻し、Instructionの指示に沿って回転や移動を行い、SetPieceを行う。
void ExecInstruction(object[] Instruction)
{
int PieceIndex = (int)Instruction[0];
int ReverseCount = (int)Instruction[1];
int RotateCount = (int)Instruction[2];
int IndexY = (int)Instruction[3];
int IndexX = (int)Instruction[4];
for (int Count = 0; Count < ConstList.BoardSize; Count++)
MoveSpawnPoint(new Vector3(0f, 0f, 0.01f));
for (int Count = 0; Count < ConstList.BoardSize; Count++)
MoveSpawnPoint(new Vector3(-0.01f, 0f, 0f));
for (int Count = 0; Count < PieceIndex; Count++)
ChangeControlPiece(1);
for (int Count = 0; Count < ReverseCount; Count++)
PieceReverse();
for (int Count = 0; Count < RotateCount; Count++)
PieceRotate(1);
for (int Count = 0; Count < IndexY; Count++)
MoveSpawnPoint(new Vector3(0f, 0f, -0.01f));
for (int Count = 0; Count < IndexX; Count++)
MoveSpawnPoint(new Vector3(0.01f, 0f, 0f));
SetPiece();
}
動かしてみる
スタートから置けなくなるまでやらせてみると
こんな感じでボタン一つ押すだけでここままで埋まりました。
ランダムに実行する
IsPossibleAnySetPieceで見つけてすぐにreturnを行わず、List<object[]>で指示を蓄えておきます(Allinstructionsとします)。最後に
object[] Instruction = AllInstructions[UnityEngine.Random.Range(0, AllInstructions.Count - 1)];
こんな感じでランダムに取り出して実行させるだけです。