This commit is contained in:
2026-07-09 21:33:33 +02:00
commit 84a2a365f3
2364 changed files with 950134 additions and 0 deletions
+136
View File
@@ -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