MIF_E31221357/Assets/Scripts/MazeGenerator.cs

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();
}
}
}
*/
}