init
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using VOID.Generic.Dict;
|
||||
|
||||
namespace VOID.Generic.World
|
||||
{
|
||||
public class Area : MonoBehaviour
|
||||
{
|
||||
#region GIZMOS
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
var defaultColor = Gizmos.color;
|
||||
var forward = transform.forward;
|
||||
var right = transform.right;
|
||||
var back = -forward;
|
||||
var left = -right;
|
||||
|
||||
if (forwardConnection.type != AreaConnectionType.Inner)
|
||||
{
|
||||
GizmosDrawBorder(left, forward, right, forwardConnection);
|
||||
GizmosDrawPassage(left, forward, right, forwardConnection);
|
||||
}
|
||||
|
||||
if (rightConnection.type != AreaConnectionType.Inner)
|
||||
{
|
||||
GizmosDrawBorder(forward, right, back, rightConnection);
|
||||
GizmosDrawPassage(forward, right, back, rightConnection);
|
||||
}
|
||||
|
||||
if (backConnection.type != AreaConnectionType.Inner)
|
||||
{
|
||||
GizmosDrawBorder(right, back, left, backConnection);
|
||||
GizmosDrawPassage(right, back, left, backConnection);
|
||||
}
|
||||
|
||||
if (leftConnection.type != AreaConnectionType.Inner)
|
||||
{
|
||||
GizmosDrawBorder(back, left, forward, leftConnection);
|
||||
GizmosDrawPassage(back, left, forward, leftConnection);
|
||||
}
|
||||
Gizmos.color = defaultColor;
|
||||
}
|
||||
|
||||
private void GizmosDrawBorder(Vector3 left, Vector3 forward, Vector3 right, AreaConnection connection)
|
||||
{
|
||||
if (connection.type == AreaConnectionType.Inner) return;
|
||||
var areaPos = transform.position;
|
||||
var floor = Vector3.up * HeightPerFloor * connection.floor;
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(
|
||||
areaPos + floor + forward * Size / 2 + left * Size / 2,
|
||||
areaPos + floor + forward * Size / 2 + right * Size / 2
|
||||
);
|
||||
}
|
||||
|
||||
private void GizmosDrawPassage(Vector3 left, Vector3 forward, Vector3 right, AreaConnection connection)
|
||||
{
|
||||
float passageWidth;
|
||||
switch (connection.type)
|
||||
{
|
||||
case AreaConnectionType.Tunnel: passageWidth = 5f; break;
|
||||
case AreaConnectionType.Whole: passageWidth = Size - 1f; break;
|
||||
case AreaConnectionType.None:
|
||||
case AreaConnectionType.Inner:
|
||||
default: return;
|
||||
}
|
||||
var areaPos = transform.position;
|
||||
var floor = Vector3.up * HeightPerFloor * connection.floor;
|
||||
var passageHeight = Vector3.up * 5f;
|
||||
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLineStrip(new []{
|
||||
areaPos + floor + forward * Size / 2 + left * passageWidth / 2,
|
||||
areaPos + floor + forward * Size / 2 + right * passageWidth / 2,
|
||||
areaPos + floor + passageHeight + forward * Size / 2 + right * passageWidth / 2,
|
||||
areaPos + floor + passageHeight + forward * Size / 2 + left * passageWidth / 2
|
||||
}, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[BoxGroup("Constant Variables")]
|
||||
[HorizontalGroup("Constant Variables/Row")]
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[InfoBox("Constant size of single square area. Content of this area SHOULD NOT leave given bounds.")]
|
||||
public const float Size = 50f;
|
||||
|
||||
[BoxGroup("Constant Variables")]
|
||||
[HorizontalGroup("Constant Variables/Row")]
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[InfoBox("Constant height of floor. Every floor supposed to be dividable by that value.")]
|
||||
public const float HeightPerFloor = 3f;
|
||||
|
||||
[BoxGroup("Complex Area")]
|
||||
[HorizontalGroup("Complex Area/Row")]
|
||||
[ReadOnly]
|
||||
[InfoBox(nameof(ComplexArea) + " which is current " + nameof(Area) + "'s parent. It is filled by button in " + nameof(ComplexArea))]
|
||||
[DontValidate]
|
||||
public ComplexArea complexArea;
|
||||
|
||||
[BoxGroup("Complex Area")]
|
||||
[HorizontalGroup("Complex Area/Row")]
|
||||
[ReadOnly]
|
||||
[InfoBox("Position relative to the " + nameof(ComplexArea) + ". Usable only from " + nameof(ComplexArea))]
|
||||
public Vector2Int position = Vector2Int.zero;
|
||||
|
||||
[InfoBox("Connections to other areas."
|
||||
+"\nInner connection cant be edited here, check " + nameof(ComplexArea) + "."
|
||||
+"\nConnection only with matching tags can be connected.")]
|
||||
[BoxGroup("Connections")]
|
||||
public AreaConnection forwardConnection = new();
|
||||
[BoxGroup("Connections")]
|
||||
public AreaConnection rightConnection = new();
|
||||
[BoxGroup("Connections")]
|
||||
public AreaConnection backConnection = new();
|
||||
[BoxGroup("Connections")]
|
||||
public AreaConnection leftConnection = new();
|
||||
|
||||
public List<AreaConnection> GetConnections() =>
|
||||
new() { forwardConnection, rightConnection, backConnection, leftConnection };
|
||||
|
||||
public AreaConnection GetConnection(Direction2D direction) => direction switch
|
||||
{
|
||||
Direction2D.Forward => forwardConnection,
|
||||
Direction2D.Right => rightConnection,
|
||||
Direction2D.Back => backConnection,
|
||||
Direction2D.Left => leftConnection,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65340696e7c14423a76bb28ac0d25d28
|
||||
timeCreated: 1706379745
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using VOID.Generic.Dict;
|
||||
using VOID.ScriptableObjects.World;
|
||||
|
||||
namespace VOID.Generic.World
|
||||
{
|
||||
[Serializable]
|
||||
public class AreaConnection
|
||||
{
|
||||
[HorizontalGroup("Row1")]
|
||||
[ReadOnly]
|
||||
public Direction2D direction;
|
||||
|
||||
[HorizontalGroup("Row1")]
|
||||
[DisableIf(nameof(type), AreaConnectionType.Inner)]
|
||||
[OnValueChanged(nameof(OnConnectionTypeChange))]
|
||||
[ValueDropdown(nameof(GetConnectionTypes))]
|
||||
public AreaConnectionType type;
|
||||
|
||||
[HorizontalGroup("Row2")]
|
||||
[DisableIf(nameof(IsNotEditable))]
|
||||
public AreaConnectionTag tag;
|
||||
|
||||
[HorizontalGroup("Row2")]
|
||||
[DisableIf(nameof(IsNotEditable))]
|
||||
[MinValue(0)]
|
||||
public int floor;
|
||||
|
||||
private void OnConnectionTypeChange()
|
||||
{
|
||||
if (IsNotEditable())
|
||||
{
|
||||
tag = null;
|
||||
floor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNotEditable() => type is AreaConnectionType.Inner or AreaConnectionType.None;
|
||||
|
||||
private IEnumerable GetConnectionTypes() => Enum.GetValues(typeof(AreaConnectionType)).Cast<AreaConnectionType>()
|
||||
.Where(ct => ct != AreaConnectionType.Inner)
|
||||
.Select(ct => new ValueDropdownItem(Enum.GetName(typeof(AreaConnectionType), ct), (int)ct));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26c30f13520f4417a951bbdf9f0f16a4
|
||||
timeCreated: 1706478041
|
||||
@@ -0,0 +1,199 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using Sirenix.Utilities;
|
||||
using UnityEngine;
|
||||
using VOID.Generic.Dict;
|
||||
|
||||
namespace VOID.Generic.World
|
||||
{
|
||||
public class ComplexArea : SerializedMonoBehaviour
|
||||
{
|
||||
[SerializeField] [ReadOnly] public List<Area> areas;
|
||||
|
||||
[ReadOnly] [BoxGroup("variant", false)]
|
||||
public bool isVariant;
|
||||
|
||||
[ReadOnly] [HideIf(nameof(isVariant))] [BoxGroup("variant", false)]
|
||||
public List<ComplexArea> variants = new();
|
||||
|
||||
[ReadOnly] [ShowIf(nameof(isVariant))] [BoxGroup("variant", false)]
|
||||
public ComplexArea original;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private GameObject GetGameObjectForAreas()
|
||||
{
|
||||
const string areasGameObjectName = "_areas";
|
||||
var areasGameObject = gameObject.transform.Find(areasGameObjectName)?.gameObject;
|
||||
if (areasGameObject) return areasGameObject;
|
||||
areasGameObject = new GameObject(areasGameObjectName);
|
||||
areasGameObject.transform.position = Vector3.zero;
|
||||
areasGameObject.transform.parent = gameObject.transform;
|
||||
areasGameObject.transform.SetAsFirstSibling();
|
||||
return areasGameObject;
|
||||
}
|
||||
|
||||
[Title("Generate")]
|
||||
[Button]
|
||||
private void GenerateAreas()
|
||||
{
|
||||
areas.ForEach(area => DestroyImmediate(area.gameObject));
|
||||
areas.Clear();
|
||||
GenerateMissingAreas();
|
||||
}
|
||||
|
||||
[Button]
|
||||
private void GenerateMissingAreas()
|
||||
{
|
||||
// Bounds thats contains everything in this ComplexArea Prefab
|
||||
var complexAreaBounds = new Bounds();
|
||||
GetComponentsInChildren<Collider>()
|
||||
.ForEach(collider => complexAreaBounds.Encapsulate(collider.bounds));
|
||||
GetComponentsInChildren<MeshRenderer>()
|
||||
.ForEach(meshRenderer => complexAreaBounds.Encapsulate(meshRenderer.bounds));
|
||||
|
||||
// To calculate position index we need only width (x) and length (z)
|
||||
var startIndex = new Vector2Int(
|
||||
Mathf.RoundToInt(complexAreaBounds.min.x / Area.Size),
|
||||
Mathf.RoundToInt(complexAreaBounds.min.z / Area.Size)
|
||||
);
|
||||
var endIndex = new Vector2Int(
|
||||
Mathf.RoundToInt(complexAreaBounds.max.x / Area.Size),
|
||||
Mathf.RoundToInt(complexAreaBounds.max.z / Area.Size)
|
||||
);
|
||||
|
||||
var areasGameObject = GetGameObjectForAreas();
|
||||
|
||||
// Create areas for everything inside bounds
|
||||
for (var x = startIndex.x; x <= endIndex.x; x++)
|
||||
{
|
||||
for (var y = startIndex.y; y <= endIndex.y; y++)
|
||||
{
|
||||
if (areas.Any(area => area.position.x == x && area.position.y == y)) continue;
|
||||
|
||||
var areaGameObject = new GameObject();
|
||||
areaGameObject.AddComponent<Area>();
|
||||
areaGameObject.transform.parent = areasGameObject.transform;
|
||||
areaGameObject.transform.position = new Vector3(x * Area.Size, 0, y * Area.Size);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
[Button]
|
||||
private void GenerateVariants()
|
||||
{
|
||||
Editor.EditorWindow.World.ComplexAreaRotateVariantGenerator.GenerateRotatedVariants(this);
|
||||
}
|
||||
|
||||
[Title("Update")]
|
||||
[Button(ButtonSizes.Large)]
|
||||
public void UpdateAll()
|
||||
{
|
||||
UpdateAreaList();
|
||||
UpdatePositionIndexes();
|
||||
UpdateNames();
|
||||
UpdateConnections();
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("Update")]
|
||||
[Button]
|
||||
private void UpdateAreaList()
|
||||
{
|
||||
areas = gameObject.GetComponentsInChildren<Area>().ToList();
|
||||
areas.ForEach(area => area.complexArea = this);
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("Update")]
|
||||
[Button]
|
||||
private void UpdatePositionIndexes()
|
||||
{
|
||||
areas.ForEach(area =>
|
||||
{
|
||||
var position = area.transform.position;
|
||||
area.position.x = Mathf.RoundToInt(position.x / Area.Size);
|
||||
area.position.y = Mathf.RoundToInt(position.z / Area.Size);
|
||||
});
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("Update")]
|
||||
[Button]
|
||||
private void UpdateNames()
|
||||
{
|
||||
UpdatePositionIndexes();
|
||||
areas.ForEach(area => area.name = $"{nameof(Area)} ({area.position.x},{area.position.y})");
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("Update")]
|
||||
[Button]
|
||||
private void UpdateConnections()
|
||||
{
|
||||
UpdatePositionIndexes();
|
||||
areas.ForEach(area =>
|
||||
{
|
||||
var forwardArea = areas.Find(subArea => (area.position + Vector2Int.up).Equals(subArea.position));
|
||||
var rightArea = areas.Find(subArea => (area.position + Vector2Int.right).Equals(subArea.position));
|
||||
var backArea = areas.Find(subArea => (area.position + Vector2Int.down).Equals(subArea.position));
|
||||
var leftArea = areas.Find(subArea => (area.position + Vector2Int.left).Equals(subArea.position));
|
||||
|
||||
area.forwardConnection.direction = Direction2D.Forward;
|
||||
area.rightConnection.direction = Direction2D.Right;
|
||||
area.backConnection.direction = Direction2D.Back;
|
||||
area.leftConnection.direction = Direction2D.Left;
|
||||
|
||||
if (forwardArea)
|
||||
area.forwardConnection.type = AreaConnectionType.Inner;
|
||||
else if (area.forwardConnection.type == AreaConnectionType.Inner)
|
||||
area.forwardConnection.type = AreaConnectionType.None;
|
||||
|
||||
if (rightArea)
|
||||
area.rightConnection.type = AreaConnectionType.Inner;
|
||||
else if (area.rightConnection.type == AreaConnectionType.Inner)
|
||||
area.rightConnection.type = AreaConnectionType.None;
|
||||
|
||||
if (backArea)
|
||||
area.backConnection.type = AreaConnectionType.Inner;
|
||||
else if (area.backConnection.type == AreaConnectionType.Inner)
|
||||
area.backConnection.type = AreaConnectionType.None;
|
||||
|
||||
if (leftArea)
|
||||
area.leftConnection.type = AreaConnectionType.Inner;
|
||||
else if (area.leftConnection.type == AreaConnectionType.Inner)
|
||||
area.leftConnection.type = AreaConnectionType.None;
|
||||
});
|
||||
}
|
||||
|
||||
[Title("Auto-Fix")]
|
||||
[Button(ButtonSizes.Large)]
|
||||
public void AutoFixAll()
|
||||
{
|
||||
AutoFixAreaTransforms();
|
||||
AutoFixAreaHierarchy();
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("AutoFix")]
|
||||
[Button]
|
||||
private void AutoFixAreaTransforms()
|
||||
{
|
||||
areas.ForEach(area =>
|
||||
{
|
||||
var position = area.transform.localPosition;
|
||||
position.x = Mathf.RoundToInt(position.x / Area.Size) * (int)Area.Size;
|
||||
position.y = Mathf.RoundToInt(position.y / Area.Size) * (int)Area.Size;
|
||||
position.z = Mathf.RoundToInt(position.z / Area.Size) * (int)Area.Size;
|
||||
area.transform.localPosition = position;
|
||||
});
|
||||
}
|
||||
|
||||
[ResponsiveButtonGroup("AutoFix")]
|
||||
[Button]
|
||||
private void AutoFixAreaHierarchy()
|
||||
{
|
||||
var areasGameObject = GetGameObjectForAreas();
|
||||
GetComponentsInChildren<Area>()
|
||||
.ForEach(area => area.gameObject.transform.parent = areasGameObject.transform);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee7cf15233694c29a09cfdb2fb350d2b
|
||||
timeCreated: 1706377594
|
||||
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using Sirenix.Utilities;
|
||||
using UnityEngine;
|
||||
using VOID.Generic.Dict;
|
||||
using VOID.Generic.Util;
|
||||
using VOID.ScriptableObjects.World;
|
||||
|
||||
namespace VOID.Generic.World
|
||||
{
|
||||
public class WorldGeneratorManager : MonoBehaviour
|
||||
{
|
||||
public static WorldGeneratorManager current { get; private set; }
|
||||
|
||||
[InlineEditor] public WorldGeneratorSettings worldSettings;
|
||||
|
||||
// Temp data used for generation
|
||||
private readonly Dictionary<Vector2Int, Area> _mapArea = new();
|
||||
private readonly Dictionary<Vector2Int, ComplexArea> _mapComplexArea = new();
|
||||
private readonly Dictionary<Vector2Int, List<AreaConnection>> _mapOpenConnections = new();
|
||||
private readonly Dictionary<WorldAreaGeneratorSettings, int> _groupCount = new();
|
||||
private readonly Dictionary<Vector2Int, int> _distanceToArea = new();
|
||||
|
||||
// Config
|
||||
[SerializeField] private Material wallMaterial;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
current = this;
|
||||
}
|
||||
|
||||
[Button]
|
||||
public void Generate(int minAreas = 150, int seed = 2137, bool debugWalls = false, bool debugDistance = false)
|
||||
{
|
||||
if (worldSettings == null) return;
|
||||
|
||||
RandomUtil.InitState(seed);
|
||||
|
||||
// Clear previous data
|
||||
Clear();
|
||||
|
||||
GenerateStartingArea();
|
||||
GenerateAreas(minAreas);
|
||||
|
||||
CreateComplexAreas();
|
||||
if (debugWalls) CreateWalls();
|
||||
if (debugDistance) CreateAreaDistanceText();
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
_mapArea.Clear();
|
||||
_mapComplexArea.Clear();
|
||||
_mapOpenConnections.Clear();
|
||||
_distanceToArea.Clear();
|
||||
|
||||
worldSettings.list.ForEach(areaSettings => _groupCount[areaSettings] = 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all prepared <see cref="ComplexArea"/>
|
||||
/// </summary>
|
||||
private void CreateComplexAreas()
|
||||
{
|
||||
var areasParentGameObject = new GameObject("areas parent");
|
||||
_mapComplexArea.Keys.ForEach(posIndex =>
|
||||
{
|
||||
var complexArea = _mapComplexArea[posIndex];
|
||||
var position = new Vector3(posIndex.x * Area.Size, 0, posIndex.y * Area.Size);
|
||||
var areaGameObject = Instantiate(complexArea, position, Quaternion.identity);
|
||||
areaGameObject.transform.parent = areasParentGameObject.transform;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: DEBUG - creates temporary walls :D
|
||||
/// </summary>
|
||||
private void CreateWalls()
|
||||
{
|
||||
var walledPosIndexes = new List<Vector2Int>();
|
||||
var wallsParentGameObject = new GameObject("walls parent");
|
||||
|
||||
_mapArea.Keys.ForEach(posIndex =>
|
||||
{
|
||||
_mapArea[posIndex].GetConnections().ForEach(connection =>
|
||||
{
|
||||
var otherPosIndex = GetPosIndexInDirection(posIndex, connection.direction);
|
||||
if (walledPosIndexes.Contains(otherPosIndex)) return;
|
||||
if (connection.type is not AreaConnectionType.None && _mapArea.ContainsKey(otherPosIndex)) return;
|
||||
var wallPosition = Vector3.Lerp(
|
||||
new Vector3(posIndex.x * Area.Size, 0, posIndex.y * Area.Size),
|
||||
new Vector3(otherPosIndex.x * Area.Size, 0, otherPosIndex.y * Area.Size),
|
||||
0.5f);
|
||||
var wallScale = new Vector3(Area.Size, 10, 1);
|
||||
var isVertical = connection.direction is Direction2D.Back or Direction2D.Forward;
|
||||
var wallRotation = Quaternion.Euler(0, isVertical ? 0 : 90, 0);
|
||||
var wallGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
wallGameObject.transform.position = wallPosition;
|
||||
wallGameObject.transform.localScale = wallScale;
|
||||
wallGameObject.transform.rotation = wallRotation;
|
||||
wallGameObject.GetComponent<MeshRenderer>().material = wallMaterial;
|
||||
wallGameObject.transform.parent = wallsParentGameObject.transform;
|
||||
wallGameObject.layer = LayerManager.StaticIndex;
|
||||
});
|
||||
|
||||
walledPosIndexes.Add(posIndex);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: DEBUG - area distance from starting area
|
||||
/// </summary>
|
||||
private void CreateAreaDistanceText()
|
||||
{
|
||||
var distanceParentGameObject = new GameObject("distance to areas");
|
||||
_mapArea.Keys.ForEach(posIndex =>
|
||||
{
|
||||
var position = new Vector3(posIndex.x * Area.Size, 25, posIndex.y * Area.Size);
|
||||
var rotation = new Vector3(90, 0, 0);
|
||||
|
||||
var textGameObject = new GameObject($"{_distanceToArea} ({posIndex})");
|
||||
textGameObject.transform.parent = distanceParentGameObject.transform;
|
||||
textGameObject.transform.position = position;
|
||||
textGameObject.transform.rotation = Quaternion.Euler(rotation);
|
||||
var textMesh = textGameObject.AddComponent<TextMesh>();
|
||||
textMesh.color = Color.red;
|
||||
textMesh.text = _distanceToArea[posIndex].ToString();
|
||||
textMesh.fontSize = 100;
|
||||
textMesh.alignment = TextAlignment.Center;
|
||||
textMesh.anchor = TextAnchor.MiddleCenter;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects random starting area at position 0,0.
|
||||
/// </summary>
|
||||
private void GenerateStartingArea()
|
||||
{
|
||||
var startingArea = RandomUtil.RandomElement(WithVariants(worldSettings.startingComplexAreas));
|
||||
UpdateMap(startingArea, Vector2Int.zero);
|
||||
RecreateAreasDistance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alias to <see cref="AddRandomValidComplexArea(Vector2Int, Direction2D)"/>, but runs it multiple times
|
||||
/// </summary>
|
||||
private void GenerateAreas(int count)
|
||||
{
|
||||
while (_mapArea.Count <= count && _mapOpenConnections.Count > 0)
|
||||
{
|
||||
FindClosestConnection(out var posIndex, out var direction);
|
||||
AddRandomValidComplexArea(posIndex, direction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns in out parameters closest and open connection at position index and direction
|
||||
/// </summary>
|
||||
/// <param name="posIndex">Position index where open connection exists</param>
|
||||
/// <param name="direction">In which direction connection is open</param>
|
||||
private void FindClosestConnection(out Vector2Int posIndex, out Direction2D direction)
|
||||
{
|
||||
posIndex = default;
|
||||
direction = default;
|
||||
var minDistance = float.MaxValue;
|
||||
foreach (var pair in _mapOpenConnections)
|
||||
{
|
||||
var distance = Vector2Int.Distance(Vector2Int.zero, pair.Key);
|
||||
if (!(distance < minDistance)) continue;
|
||||
minDistance = distance;
|
||||
posIndex = pair.Key;
|
||||
direction = RandomUtil.RandomElement(pair.Value).direction;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select random <see cref="ComplexArea"/> from given list that can be connected to given position.
|
||||
/// </summary>
|
||||
/// <param name="thisPosIndex">To which position new area will be connected</param>
|
||||
/// <param name="thisDir">From which direction new area will be connected</param>
|
||||
private void AddRandomValidComplexArea(Vector2Int thisPosIndex, Direction2D thisDir)
|
||||
{
|
||||
var otherPosIndex = GetPosIndexInDirection(thisPosIndex, thisDir);
|
||||
var otherPosDistance = _distanceToArea[thisPosIndex] + 1;
|
||||
|
||||
// 1. Get all Area that can be placed
|
||||
// - Check group limits
|
||||
// - Check if group can be placed in this distance
|
||||
// - Include rotated variants if needed
|
||||
// - Check if it would collide others
|
||||
var validAreas = worldSettings.list
|
||||
.Where(areaSettings => _groupCount[areaSettings] < areaSettings.limit)
|
||||
.Where(areaSettings => !areaSettings.requireDistance || (otherPosDistance >= areaSettings.distance.x && otherPosDistance <= areaSettings.distance.y))
|
||||
.SelectMany(areaSettings => WithVariants(areaSettings.group, areaSettings.useRotatedVariants))
|
||||
.SelectMany(complexArea => complexArea.areas)
|
||||
.Where(area => IsComplexAreaValid(area, otherPosIndex)).ToList();
|
||||
|
||||
// 2. No available Area can be placed here!
|
||||
if (!validAreas.Any())
|
||||
{
|
||||
// No valid areas found
|
||||
CloseConnection(thisPosIndex, _mapArea[thisPosIndex].GetConnection(thisDir));
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Get all ComplexArea, by found validated Area
|
||||
var validComplexAreas = validAreas.Select(area => area.complexArea).Distinct().ToList();
|
||||
|
||||
// 4. Get all groups from WorldGeneratorSettings that contain ANY validated ComplexArea
|
||||
// - Include rotated variants if needed
|
||||
var validWorldAreaSettings = worldSettings.list
|
||||
.Where(areaSettings => validComplexAreas.Intersect(WithVariants(areaSettings.group)).Any()).ToList();
|
||||
|
||||
// 5. Get random group
|
||||
var worldAreaSettingsToAdd = RandomUtil.RandomElementByWeight(validWorldAreaSettings, wags => wags.weight);
|
||||
|
||||
// 6. Randomized group still can contain few ComplexArea, so we need to get only valid ones
|
||||
var areasToAdd = WithVariants(worldAreaSettingsToAdd.group, worldAreaSettingsToAdd.useRotatedVariants)
|
||||
.Where(complexArea => validComplexAreas.Contains(complexArea))
|
||||
.SelectMany(complexArea => complexArea.areas)
|
||||
.Where(area => validAreas.Contains(area)).ToList();
|
||||
|
||||
// 7. Randomize again to get final ComplexArea from group
|
||||
var areaToAdd = RandomUtil.RandomElement(areasToAdd);
|
||||
|
||||
// 8. Add ComplexArea with correct offset to its Area
|
||||
UpdateMap(areaToAdd.complexArea, otherPosIndex - areaToAdd.position);
|
||||
_groupCount[worldAreaSettingsToAdd]++;
|
||||
|
||||
// 9. Recreate Distances to areas
|
||||
RecreateAreasDistance();
|
||||
}
|
||||
|
||||
private List<ComplexArea> WithVariants(List<ComplexArea> complexAreas, bool includeRotatedVariants = true)
|
||||
{
|
||||
if (!includeRotatedVariants) return complexAreas;
|
||||
return complexAreas.Concat(complexAreas.SelectMany(complexArea => complexArea.variants)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if whole <see cref="ComplexArea"/> which given <see cref="Area"/> belongs to can be placed at given position index
|
||||
/// </summary>
|
||||
private bool IsComplexAreaValid(Area area, Vector2Int posIndex)
|
||||
{
|
||||
return IsComplexAreaValid(area.complexArea, posIndex - area.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if whole <see cref="ComplexArea"/> can be placed at given position index
|
||||
/// </summary>
|
||||
private bool IsComplexAreaValid(ComplexArea complexArea, Vector2Int posIndex)
|
||||
{
|
||||
return complexArea.areas.All(area => IsAreaValid(area, posIndex + area.position));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if given <see cref="Area"/> can be placed at position index
|
||||
/// </summary>
|
||||
private bool IsAreaValid(Area area, Vector2Int posIndex)
|
||||
{
|
||||
// Position already occupied
|
||||
if (_mapArea.ContainsKey(posIndex)) return false;
|
||||
|
||||
return area.GetConnections().All(connection => IsConnectionValid(connection, posIndex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if <see cref="AreaConnection"/> can be placed at given position index
|
||||
/// Its valid when:
|
||||
/// - Neighbor is empty
|
||||
/// - Neighbor connection is also open AND have identical tag
|
||||
/// </summary>
|
||||
private bool IsConnectionValid(AreaConnection thisConnection, Vector2Int thisPosIndex)
|
||||
{
|
||||
var otherPosIndex = GetPosIndexInDirection(thisPosIndex, thisConnection.direction);
|
||||
var otherConnection = _mapArea.GetValueOrDefault(otherPosIndex)?.GetConnection(GetReverseDirection(thisConnection.direction));
|
||||
|
||||
// No area in that direction - VALID
|
||||
if (otherConnection == null) return true;
|
||||
|
||||
// Open connection leads to closed connection - INVALID
|
||||
if (IsConnectionOpen(thisConnection) != IsConnectionOpen(otherConnection)) return false;
|
||||
|
||||
// Connections have mismatched tags - INVALID
|
||||
if (thisConnection.tag != otherConnection.tag) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateMap(ComplexArea complexArea, Vector2Int posIndex)
|
||||
{
|
||||
_mapComplexArea.Add(posIndex, complexArea);
|
||||
complexArea.areas.ForEach(area =>
|
||||
{
|
||||
var localPosIndex = posIndex + area.position;
|
||||
var connections = area.GetConnections().Where(IsConnectionOpen).ToList();
|
||||
_mapArea.Add(localPosIndex, area);
|
||||
_mapOpenConnections.Add(localPosIndex, connections);
|
||||
UpdateMapConnections(localPosIndex);
|
||||
});
|
||||
}
|
||||
|
||||
private void RecreateAreasDistance()
|
||||
{
|
||||
// Clear all distances and start from beginning
|
||||
// Just to be sure that connected branches of generated world will have correct distance from start
|
||||
_distanceToArea.Clear();
|
||||
_distanceToArea.Add(Vector2Int.zero, 0);
|
||||
|
||||
var toUpdate = new List<Vector2Int> { Vector2Int.zero };
|
||||
var distance = 0;
|
||||
|
||||
while (toUpdate.Count > 0)
|
||||
{
|
||||
distance++;
|
||||
toUpdate = toUpdate
|
||||
// Get all neighbours...
|
||||
.SelectMany(GetNeighbourPositions)
|
||||
// ...but only those without distance calculated already
|
||||
.Where(posIndex => !_distanceToArea.ContainsKey(posIndex))
|
||||
.ToList();
|
||||
toUpdate.ForEach(posIndex => _distanceToArea.TryAdd(posIndex, distance));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all index positions of existing and connected neighbours
|
||||
/// </summary>
|
||||
private List<Vector2Int> GetNeighbourPositions(Vector2Int posIndex)
|
||||
{
|
||||
return Enum.GetValues(typeof(Direction2D)).Cast<Direction2D>()
|
||||
// Only directions which area is open to
|
||||
.Where(direction => IsConnectionAny(_mapArea[posIndex].GetConnection(direction)))
|
||||
// Get neighbour index positions
|
||||
.Select(direction => GetPosIndexInDirection(posIndex, direction))
|
||||
// Neighbour need to exists
|
||||
.Where(otherPosIndex => _mapArea.ContainsKey(otherPosIndex))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void UpdateMapConnections(Vector2Int thisPosIndex)
|
||||
{
|
||||
if (!_mapOpenConnections.ContainsKey(thisPosIndex)) return;
|
||||
|
||||
if (_mapOpenConnections[thisPosIndex].IsNullOrEmpty())
|
||||
{
|
||||
_mapOpenConnections.Remove(thisPosIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
_mapOpenConnections[thisPosIndex].ToList().ForEach(thisConnection =>
|
||||
{
|
||||
var otherPosIndex = GetPosIndexInDirection(thisPosIndex, thisConnection.direction);
|
||||
var otherConnection = _mapArea.GetValueOrDefault(otherPosIndex)?.GetConnection(GetReverseDirection(thisConnection.direction));
|
||||
|
||||
if (otherConnection == null) return;
|
||||
CloseConnection(thisPosIndex, thisConnection);
|
||||
CloseConnection(otherPosIndex, otherConnection);
|
||||
});
|
||||
}
|
||||
|
||||
private void CloseConnection(Vector2Int posIndex, AreaConnection connection)
|
||||
{
|
||||
var connections = _mapOpenConnections.GetValueOrDefault(posIndex);
|
||||
connections?.Remove(connection);
|
||||
if (connections.IsNullOrEmpty()) _mapOpenConnections.Remove(posIndex);
|
||||
}
|
||||
|
||||
private Vector2Int GetPosIndexInDirection(Vector2Int fromPosIndex, Direction2D direction)
|
||||
{
|
||||
return direction switch
|
||||
{
|
||||
Direction2D.Forward => fromPosIndex + Vector2Int.up,
|
||||
Direction2D.Right => fromPosIndex + Vector2Int.right,
|
||||
Direction2D.Back => fromPosIndex + Vector2Int.down,
|
||||
Direction2D.Left => fromPosIndex + Vector2Int.left,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
|
||||
};
|
||||
}
|
||||
|
||||
private Direction2D GetReverseDirection(Direction2D direction)
|
||||
{
|
||||
return direction switch
|
||||
{
|
||||
Direction2D.Forward => Direction2D.Back,
|
||||
Direction2D.Right => Direction2D.Left,
|
||||
Direction2D.Back => Direction2D.Forward,
|
||||
Direction2D.Left => Direction2D.Right,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsConnectionOpen(AreaConnection connection)
|
||||
{
|
||||
return connection.type is AreaConnectionType.Tunnel or AreaConnectionType.Whole;
|
||||
}
|
||||
|
||||
private bool IsConnectionAny(AreaConnection connection)
|
||||
{
|
||||
return connection.type is not AreaConnectionType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dcdca6249d045f087d0a559562176c9
|
||||
timeCreated: 1706975199
|
||||
Reference in New Issue
Block a user