using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.Events;
public class WfcGenerator : MonoBehaviour
{
[HideInInspector]
public List cells = new List ();
private List candidateCells = new List();
public CellSO Cell; //.......................A CellSO reference with modules defined.
public int _width;
public int _length;
[SerializeField] private int _moduleSize;
private int firstCollapse; //................The index of the first cell to be created in the list.
Quaternion prefabRotation;
int _row;
int _col;
int lowestEntropyValue; //...................Lowest usage value of modules among cells to Find_Lowest_Entropy and to Get_Definite_State.
int randomModule; //.........................Selected cell's random module index to Get_Definite_State.
[HideInInspector]
public GameObject gridHolder; //.............Game object for centering the position of the grid.
[SerializeField]
private GameObject emptyObject;
[HideInInspector] public List rotatableObjectTs = new();
public List moduleObjects = new();
ModuleObject moduleObject;
[HideInInspector] public static UnityEvent OnMapReady = new();
[HideInInspector] public static UnityEvent OnMapSolve = new();
void OnEnable()
{
EventManager.OnLevelStart.AddListener(GenerateWFC);
EventManager.OnLevelInitialize.AddListener(RecreateLevel);
}
void OnDisable()
{
EventManager.OnLevelStart.RemoveListener(GenerateWFC);
EventManager.OnLevelInitialize.RemoveListener(RecreateLevel);
}
private void RecreateLevel()
{
cells.Clear();
candidateCells.Clear();
rotatableObjectTs.Clear();
moduleObjects.Clear();
Destroy(gridHolder);
LevelManager.Instance.StartLevel();
}
public void GenerateWFC()
{
Generate();
StartWave();
CollapseGrid();
}
private void StartWave()
{
firstCollapse = cells.Count / 2;
cells[firstCollapse].isCollapsed = true;
int starterModule = Random.Range(0, cells[firstCollapse].modules.Count);
cells[firstCollapse].modules.RemoveAll(module => module !=cells[firstCollapse].modules[starterModule]);
cells[firstCollapse].modules[0].moduleUsageCount ++;
prefabRotation = cells[firstCollapse].modules[0].modulePrefab.transform.rotation;
GameObject obj = (GameObject)Instantiate(cells[firstCollapse].modules[0].modulePrefab, cells[firstCollapse].cellPos, prefabRotation);
moduleObject = obj.GetComponent();
if (moduleObject != null)
{
moduleObject.Row = cells[firstCollapse].Row;
moduleObject.Column = cells[firstCollapse].Column;
moduleObjects.Add(moduleObject);
}
ListRotatableMOTransforms(cells[firstCollapse].modules[0], obj.transform);
gridHolder = (GameObject)Instantiate(emptyObject);
gridHolder.transform.position = obj.transform.position;
obj.transform.SetParent(gridHolder.transform);
}
private void Generate()
{
CellSO originalCell = Cell;
for (int row = 0; row < _width; row++)
{
for (int col = 0; col < _length; col++)
{
CellSO cell = ScriptableObject.CreateInstance();
cell.modules = new List(originalCell.modules);
cell.cellPos = new Vector3(row * _moduleSize, 0, col * _moduleSize * -1);
cell.Row = row;
cell.Column = col;
cells.Add(cell);
}
}
}
private void FindNeighbors(CellSO cell)
{
_row = cell.Row;
_col = cell.Column;
// North
if (_col > 0 )
{
CellSO northNeighbor = cells.Find(c => c.Column == _col - 1 && c.Row == _row && !c.isCollapsed);
if (northNeighbor != null)
{
UpdateCell(0, northNeighbor, cell);
if (!candidateCells.Contains(northNeighbor))
{
candidateCells.Add(northNeighbor);
}
}
}
// South
if (_col < _length - 1 )
{
CellSO southNeighbor = cells.Find(c => c.Column == _col + 1 && c.Row == _row && !c.isCollapsed);
if (southNeighbor != null)
{
UpdateCell(1, southNeighbor, cell);
if (!candidateCells.Contains(southNeighbor))
{
candidateCells.Add(southNeighbor);
}
}
}
// East
if (_row < _width - 1 )
{
CellSO eastNeighbor = cells.Find(c => c.Column == _col && c.Row == _row + 1 && !c.isCollapsed);
if (eastNeighbor != null)
{
UpdateCell(2, eastNeighbor, cell);
if (!candidateCells.Contains(eastNeighbor))
{
candidateCells.Add(eastNeighbor);
}
}
}
// West
if (_row > 0)
{
CellSO westNeighbor = cells.Find(c => c.Column == _col && c.Row == _row - 1 && !c.isCollapsed);
if (westNeighbor != null)
{
UpdateCell(3, westNeighbor, cell);
if (!candidateCells.Contains(westNeighbor))
{
candidateCells.Add(westNeighbor);
}
}
}
}
private CellSO FindLowestEntropy()
{
int lowestModuleCount = candidateCells.Min(list => list.modules.Count);
var lowestEntropies = candidateCells.Where(num => num.modules.Count == lowestModuleCount).ToList();
if(lowestEntropies.Count > 1)
{
lowestEntropyValue = lowestEntropies.Min(x => x.entropy);
var lowestEntropyValues = lowestEntropies.Where(entropyValue => entropyValue.entropy == lowestEntropyValue).ToList();
if(lowestEntropyValues.Count > 1) return lowestEntropyValues[Random.Range(0, lowestEntropyValues.Count)];
else return lowestEntropies.Find(y => y.entropy == lowestEntropyValue); // or lowestEntropyValues[0] ...first and only element.
}
else
{
return lowestEntropies[0];
}
}
private void UpdateCell(int direction, CellSO neighborCell, CellSO cell)
{
// 0=north, 1=south, 2=east, 3=west
neighborCell.modules.RemoveAll(possibleModule => !IsMatching(direction, possibleModule, cell.modules[0])); //cell.modules[0] represents the last remaining cell.
neighborCell.entropy = neighborCell.modules.Sum(x => x.moduleUsageCount);
}
private bool IsMatching(int direction, ModuleSO neighborModule, ModuleSO cellModule)
{
if (direction == 0) // North
return neighborModule.south == cellModule.north;
if (direction == 1) // South
return neighborModule.north == cellModule.south;
if (direction == 2) // East
return neighborModule.west == cellModule.east;
if (direction == 3) // West
return neighborModule.east == cellModule.west;
return false;
}
private GameObject GetDefiniteState(CellSO currentCell)
{
if (currentCell.modules.Count > 0)
{
ModuleSO selectedModule;
if (currentCell.modules.Where(x => x.moduleUsageCount == lowestEntropyValue).Any())
{
selectedModule = currentCell.modules.Find(x => x.moduleUsageCount == lowestEntropyValue);
}
else
{
randomModule = Random.Range(0, currentCell.modules.Count);
selectedModule = currentCell.modules[randomModule];
}
currentCell.modules.RemoveAll(module => module !=selectedModule);
if (selectedModule != null)
{
GameObject modulePrefab = selectedModule.modulePrefab;
if (modulePrefab != null)
{
selectedModule.moduleUsageCount ++;
prefabRotation = modulePrefab.transform.rotation;
return modulePrefab;
}
else
{
Debug.LogWarning("modulePrefab is not assigned in the selected ModuleSO.");
return null; // or return a default GameObject if you have one.
}
}
else
{
Debug.LogWarning("Selected ModuleSO is null.");
return null; // or return a default GameObject if you have one.
}
}
else
{
Debug.LogWarning("No modules attached to the current cell.");
return null; // or return a default GameObject if you have one.
}
}
private void CollapseCell()
{
CellSO nextCell;
nextCell = FindLowestEntropy();
nextCell.isCollapsed = true;
GameObject obj = (GameObject)Instantiate(GetDefiniteState(nextCell), nextCell.cellPos, prefabRotation);
moduleObject = obj.GetComponent();
if (moduleObject != null)
{
moduleObject.Row = nextCell.Row;
moduleObject.Column = nextCell.Column;
moduleObjects.Add(moduleObject);
}
ListRotatableMOTransforms(nextCell.modules[0], obj.transform);
obj.transform.SetParent(gridHolder.transform);
candidateCells.Remove(nextCell);
FindNeighbors(nextCell);
}
private void CollapseGrid()
{
if(cells.Where(x => x.isCollapsed).Any())
{
var cell = cells.Find(x => x.isCollapsed);
FindNeighbors(cell);
}
while (cells.Where(x => !x.isCollapsed).Any())
{
if (candidateCells.Count > 0)
{
CollapseCell();
}
else
{
break;
}
}
gridHolder.transform.position = Vector3.zero;
Invoke("AnnounceMapReady", 2f);
}
#region Utility Methods
void ListRotatableMOTransforms(ModuleSO module, Transform moduleTransform)
{
if (module.north == module.south && module.south == module.east && module.east == module.west)
{
}
else
{
rotatableObjectTs.Add(moduleTransform);
}
}
void AnnounceMapReady()
{
OnMapReady.Invoke();
}
#endregion
}