404 lines
11 KiB
C#
404 lines
11 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
public class MazeGenerator : MonoBehaviour
|
|
{
|
|
[SerializeField] GameObject mazePrefab;
|
|
//[SerializeField] Transform mazeParent;
|
|
|
|
|
|
MazeNode[,] nodes = null;
|
|
|
|
[SerializeField] int numX = 10;
|
|
[SerializeField] int numY = 10;
|
|
|
|
[Header("Player")]
|
|
[SerializeField] GameObject player;
|
|
|
|
float nodeWidth;
|
|
float nodeHeight;
|
|
|
|
Stack<MazeNode> stack = new Stack<MazeNode>();
|
|
|
|
bool generate = false;
|
|
|
|
[SerializeField] ItemQuest itemQuest;
|
|
[SerializeField] int numberOfItems = 5;
|
|
[SerializeField] private GameObject finishPrefab;
|
|
|
|
public GameObject startNode { get; private set; }
|
|
public GameObject finishNode { get; private set; }
|
|
|
|
|
|
private void GetNodeSize()
|
|
{
|
|
SpriteRenderer[] spriteRenderers = mazePrefab.GetComponentsInChildren<SpriteRenderer>();
|
|
|
|
Vector3 minBounds = Vector3.positiveInfinity;
|
|
Vector3 maxBounds = Vector3.negativeInfinity;
|
|
|
|
foreach (SpriteRenderer ren in spriteRenderers)
|
|
{
|
|
minBounds = Vector3.Min(minBounds, ren.bounds.min);
|
|
maxBounds = Vector3.Max(maxBounds, ren.bounds.max);
|
|
}
|
|
|
|
nodeWidth = maxBounds.x - minBounds.x;
|
|
nodeHeight = maxBounds.y - minBounds.y;
|
|
}
|
|
|
|
private void SetCamera()
|
|
{
|
|
Camera.main.transform.position = new Vector3(numX * (nodeWidth - 1) / 2, numY * (nodeHeight - 1) / 2, -100.0f);
|
|
|
|
float min_value = Mathf.Min(numX * (nodeWidth - 1), numY * (nodeHeight - 1));
|
|
Camera.main.orthographicSize = min_value * 0.75f;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
string currentSceneName = SceneManager.GetActiveScene().name;
|
|
GameSession.Instance?.StartLevel(currentSceneName);
|
|
|
|
GetNodeSize();
|
|
|
|
nodes = new MazeNode[numX, numY];
|
|
|
|
float mazeWidth = (numX - 1) * nodeWidth;
|
|
float mazeHeight = (numY - 1) * nodeHeight;
|
|
|
|
// Hitung offset agar maze terletak di tengah
|
|
Vector3 centerOffset = new Vector3(mazeWidth / 2f, mazeHeight / 2f, 0f);
|
|
|
|
for (int i = 0; i < numX; ++i)
|
|
{
|
|
for (int j = 0; j < numY; ++j)
|
|
{
|
|
Vector3 nodePos = new Vector3(i * nodeWidth, j * nodeHeight, 0f) - centerOffset;
|
|
|
|
GameObject node = Instantiate(mazePrefab, Vector3.zero, Quaternion.identity, this.transform);
|
|
|
|
node.name = "nodes_" + i.ToString() + "_" + j.ToString();
|
|
node.transform.localPosition = nodePos;
|
|
node.transform.localScale = new Vector3(1f, 1f, 1f);
|
|
nodes[i, j] = node.GetComponent<MazeNode>();
|
|
nodes[i, j].Index = new Vector2Int(i, j);
|
|
|
|
}
|
|
}
|
|
this.transform.localScale = new Vector3(7f, 7f, 0f);
|
|
|
|
SetCamera();
|
|
|
|
GenerateInstantMaze();
|
|
|
|
//GameSession.Instance.SaveCheckpointScore();
|
|
GameSession.Instance.SaveLevelScore();
|
|
|
|
|
|
//Instantiate(finishPrefab, new Vector3(0, 0, -1), Quaternion.identity);
|
|
}
|
|
|
|
private void RemoveNodeWalls (int x, int y, MazeNode.Directions dir)
|
|
{
|
|
if(dir != MazeNode.Directions.NONE)
|
|
{
|
|
nodes[x, y].SetDirFlag(dir, false);
|
|
}
|
|
|
|
MazeNode.Directions opp = MazeNode.Directions.NONE;
|
|
switch (dir)
|
|
{
|
|
case MazeNode.Directions.TOP:
|
|
if(y < numY - 1)
|
|
{
|
|
opp = MazeNode.Directions.BOTTOM;
|
|
++y;
|
|
}
|
|
break;
|
|
case MazeNode.Directions.RIGHT:
|
|
if(x < numX - 1)
|
|
{
|
|
opp = MazeNode.Directions.LEFT;
|
|
++x ;
|
|
}
|
|
break;
|
|
case MazeNode.Directions.BOTTOM:
|
|
if (y > 0)
|
|
{
|
|
opp = MazeNode.Directions.TOP;
|
|
--y;
|
|
}
|
|
break;
|
|
case MazeNode.Directions.LEFT:
|
|
if (x > 0)
|
|
{
|
|
opp = MazeNode.Directions.RIGHT;
|
|
--x;
|
|
}
|
|
break;
|
|
}
|
|
if(opp != MazeNode.Directions.NONE)
|
|
{
|
|
nodes[x, y].SetDirFlag(opp, false);
|
|
}
|
|
}
|
|
|
|
private Vector3 GetNodeWorldCenter(int x, int y)
|
|
{
|
|
//return nodes[x, y].transform.position;
|
|
Vector3 centerOffset = new Vector3(numX * nodeWidth / 2f, numY * nodeHeight / 2f, 0f);
|
|
return new Vector3(x * nodeWidth, y * nodeHeight, 0f) - centerOffset;
|
|
}
|
|
|
|
public List<Tuple<MazeNode.Directions, MazeNode>> GetNeighbourNotVisited(int cx, int cy)
|
|
{
|
|
List<Tuple<MazeNode.Directions, MazeNode>> neighbours = new List<Tuple<MazeNode.Directions, MazeNode>>();
|
|
|
|
foreach (MazeNode.Directions dir in Enum.GetValues(typeof(MazeNode.Directions)))
|
|
{
|
|
int x = cx;
|
|
int y = cy;
|
|
|
|
switch (dir)
|
|
{
|
|
case MazeNode.Directions.TOP:
|
|
if (y < numY - 1)
|
|
{
|
|
++y;
|
|
if (!nodes[x, y].visited)
|
|
{
|
|
neighbours.Add(new Tuple<MazeNode.Directions, MazeNode>(MazeNode.Directions.TOP, nodes[x, y]));
|
|
}
|
|
}
|
|
break;
|
|
case MazeNode.Directions.RIGHT:
|
|
if (x < numX - 1)
|
|
{
|
|
++x;
|
|
if (!nodes[x, y].visited)
|
|
{
|
|
neighbours.Add(new Tuple<MazeNode.Directions, MazeNode>(MazeNode.Directions.RIGHT, nodes[x, y]));
|
|
}
|
|
}
|
|
break;
|
|
case MazeNode.Directions.BOTTOM:
|
|
if (y > 0)
|
|
{
|
|
--y;
|
|
if (!nodes[x, y].visited)
|
|
{
|
|
neighbours.Add(new Tuple<MazeNode.Directions, MazeNode>(MazeNode.Directions.BOTTOM, nodes[x, y]));
|
|
}
|
|
}
|
|
break;
|
|
case MazeNode.Directions.LEFT:
|
|
if (x > 0)
|
|
{
|
|
--x;
|
|
if (!nodes[x, y].visited)
|
|
{
|
|
neighbours.Add(new Tuple<MazeNode.Directions, MazeNode>(MazeNode.Directions.LEFT, nodes[x, y]));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
return neighbours;
|
|
}
|
|
|
|
private bool GenerateStep()
|
|
{
|
|
if(stack.Count == 0) return true;
|
|
|
|
MazeNode r = stack.Peek();
|
|
var neighbours = GetNeighbourNotVisited(r.Index.x, r.Index.y);
|
|
|
|
if(neighbours.Count != 0)
|
|
{
|
|
var index = 0;
|
|
if (neighbours.Count > 1)
|
|
{
|
|
index = UnityEngine.Random.Range(0, neighbours.Count);
|
|
}
|
|
|
|
var item = neighbours[index];
|
|
MazeNode neighbour = item.Item2;
|
|
neighbour.visited = true;
|
|
RemoveNodeWalls(r.Index.x, r.Index.y, item.Item1);
|
|
|
|
stack.Push(neighbour);
|
|
}
|
|
else
|
|
{
|
|
stack.Pop();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void CreateMaze()
|
|
{
|
|
if (generate) return;
|
|
|
|
Reset();
|
|
|
|
RemoveNodeWalls(0, 0, MazeNode.Directions.BOTTOM);
|
|
|
|
RemoveNodeWalls(numX -1, numY - 1, MazeNode.Directions.RIGHT);
|
|
|
|
stack.Push(nodes[0,0]);
|
|
|
|
StartCoroutine(Coroutine_Generate());
|
|
}
|
|
|
|
IEnumerator Coroutine_Generate()
|
|
{
|
|
generate = true;
|
|
bool flag = false;
|
|
while(!flag)
|
|
{
|
|
flag = GenerateStep();
|
|
yield return new WaitForSeconds(0.05f);
|
|
}
|
|
|
|
generate = false;
|
|
|
|
if (player != null)
|
|
{
|
|
player.transform.position = GetNodeWorldCenter(0, 0) + new Vector3(0, 0, -1);
|
|
}
|
|
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
for (int i = 0; i < numX; ++i)
|
|
{
|
|
for(int j = 0; j < numY; ++j)
|
|
{
|
|
nodes[i, j].SetDirFlag(MazeNode.Directions.TOP, true);
|
|
nodes[i, j].SetDirFlag(MazeNode.Directions.RIGHT, true);
|
|
nodes[i, j].SetDirFlag(MazeNode.Directions.BOTTOM, true);
|
|
nodes[i, j].SetDirFlag(MazeNode.Directions.LEFT, true);
|
|
nodes[i, j].visited = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GenerateInstantMaze()
|
|
{
|
|
generate = true;
|
|
|
|
Reset();
|
|
|
|
//RemoveNodeWalls(0, 0, MazeNode.Directions.BOTTOM);
|
|
//RemoveNodeWalls(numX - 1, numY - 1, MazeNode.Directions.RIGHT);
|
|
|
|
MazeNode start = nodes[0, 0];
|
|
start.visited = true;
|
|
stack.Push(start);
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
GenerateStep();
|
|
}
|
|
|
|
generate = false;
|
|
|
|
if (player != null && nodes[0, 0] != null)
|
|
{
|
|
startNode = nodes[0, 0].gameObject;
|
|
Vector3 nodeCenter = startNode.transform.position;
|
|
player.transform.position = nodeCenter + new Vector3(0, 0, 0);
|
|
|
|
|
|
player.GetComponent<PlayerController>()?.MoveToStart();
|
|
|
|
PlayerController pc = player.GetComponent<PlayerController>();
|
|
if (pc != null)
|
|
{
|
|
pc.InitMaze(this);
|
|
}
|
|
}
|
|
|
|
if (itemQuest != null)
|
|
{
|
|
itemQuest.SpawnItems(GetValidNodeObjects(), numberOfItems, startNode);
|
|
}
|
|
|
|
int finishX = numX - 1;
|
|
int finishY = numY - 1;
|
|
|
|
finishNode = nodes[finishX, finishY].gameObject;
|
|
|
|
if (finishPrefab != null)
|
|
{
|
|
//Instantiate(finishPrefab, finishNode.transform.position + new Vector3(0, 0, -10), Quaternion.identity, finishNode.transform);
|
|
Instantiate(finishPrefab, finishNode.transform.position, Quaternion.identity, finishNode.transform);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private List<MazeNode> GetAllNodesAsList()
|
|
{
|
|
List<MazeNode> allNodes = new List<MazeNode>();
|
|
for (int i = 0; i < numX; i++)
|
|
{
|
|
for (int j = 0; j < numY; j++)
|
|
{
|
|
allNodes.Add(nodes[i, j]);
|
|
}
|
|
}
|
|
return allNodes;
|
|
}
|
|
|
|
public MazeNode[,] GetNodes()
|
|
{
|
|
return nodes;
|
|
}
|
|
|
|
public Vector3 GetNodeWorldPosition(int x, int y)
|
|
{
|
|
return GetNodeWorldCenter(x, y) + new Vector3(0, 0, -1);
|
|
}
|
|
|
|
//fungsi get start node dan finish node
|
|
public List<GameObject> GetValidNodeObjects()
|
|
{
|
|
List<GameObject> validNodes = new List<GameObject>();
|
|
|
|
for (int i = 0; i < numX; i++)
|
|
{
|
|
for (int j = 0; j < numY; j++)
|
|
{
|
|
if (nodes[i, j].visited)
|
|
{
|
|
validNodes.Add(nodes[i, j].gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
return validNodes;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*private void Update()
|
|
{
|
|
if(Input.GetKeyDown(KeyCode.Space))
|
|
{
|
|
if(!generate)
|
|
{
|
|
CreateMaze();
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|