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 stack = new Stack(); 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(); 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(); 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> GetNeighbourNotVisited(int cx, int cy) { List> neighbours = new List>(); 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.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.RIGHT, nodes[x, y])); } } break; case MazeNode.Directions.BOTTOM: if (y > 0) { --y; if (!nodes[x, y].visited) { neighbours.Add(new Tuple(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.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()?.MoveToStart(); PlayerController pc = player.GetComponent(); 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 GetAllNodesAsList() { List allNodes = new List(); 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 GetValidNodeObjects() { List validNodes = new List(); 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(); } } } */ }