Skip to content

Commit

Permalink
feat(map): implement tilemap culling
Browse files Browse the repository at this point in the history
  • Loading branch information
mifiamigahna authored Nov 15, 2022
1 parent c442bfa commit 9808729
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 94 deletions.
5 changes: 4 additions & 1 deletion WorkingTitle/Assets/GameAssets/MapAssets/map_base.asset
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ MonoBehaviour:
SerializationNodes:
- Name: ChunkSize
Entry: 3
Data: 21
Data: 22
- Name: ViewDistance
Entry: 3
Data: 25
- Name: ChunkPrefabs
Entry: 7
Data: 0|System.Collections.Generic.List`1[[UnityEngine.GameObject, UnityEngine.CoreModule]],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ MonoBehaviour:
SerializationNodes:
- Name: Speed
Entry: 4
Data: 50
Data: 150
- Name: SpeedBoostModifier
Entry: 4
Data: 1.5
Expand Down
3 changes: 3 additions & 0 deletions WorkingTitle/Assets/WorkingTitle.Unity/Assets/MapAsset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class MapAsset : SerializedScriptableObject
{
[OdinSerialize]
public int ChunkSize { get; set; }

[OdinSerialize]
public int ViewDistance { get; set; }

[OdinSerialize]
[ValidateInput(nameof(IsAtLeastOneChunk), "There must be at least one chunk")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ void UpdateTarget()
IsWithinTargetDistanceThreshold = TargetDistance < AiAsset.TargetDistanceThreshold;
}

void OnCellPositionChanged(object sender, Vector2Int position)
void OnCellPositionChanged(object sender, CellPositionChangedEventArgs e)
{
if (!PathfindingComponent) return;

CurrentCell = PathfindingComponent.GetCell(EntityComponent.CellPosition);
CurrentCell = PathfindingComponent.GetCell(e.NewCellPosition);

if (CurrentCell is null) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ void Awake()
var playerComponent = FindObjectOfType<PlayerComponent>();

MapObject = mapComponent ? mapComponent.gameObject : Instantiate(GameAsset.MapPrefab, transform);
mapComponent = MapObject.GetComponent<MapComponent>();

var centerPosition = new Vector2(mapComponent.MapAsset.ChunkSize / 2f, mapComponent.MapAsset.ChunkSize / 2f);
PlayerObject = playerComponent ? playerComponent.gameObject : Instantiate(GameAsset.PlayerPrefab, centerPosition, Quaternion.identity, MapObject.transform);
PlayerObject = playerComponent ? playerComponent.gameObject : Instantiate(GameAsset.PlayerPrefab, MapObject.transform);
}
}
}
193 changes: 133 additions & 60 deletions WorkingTitle/Assets/WorkingTitle.Unity/Components/Map/MapComponent.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
using UnityEngine.Experimental.GlobalIllumination;
using UnityEngine.Tilemaps;
using WorkingTitle.Lib.Extensions;
using WorkingTitle.Lib.Pathfinding;
using WorkingTitle.Unity.Assets;
using WorkingTitle.Unity.Components.Physics;
Expand All @@ -30,19 +33,28 @@ public class MapComponent : SerializedMonoBehaviour

[ShowInInspector]
[ReadOnly]
public BoundsInt CenterChunkBounds { get; set; }
public BoundsInt ChunkBounds { get; set; }

[ShowInInspector]
[ReadOnly]
Vector2Int CenterChunkPosition { get; set; }
Vector2Int ChunkIndex { get; set; }

[ShowInInspector]
[ReadOnly]
public Vector2Int ChunkPosition { get; set; }

List<Dictionary<TilemapType, TileBase[]>> ChunkTiles { get; } = new();
Dictionary<Vector2Int, Dictionary<TilemapType, TileBase[]>> Chunks { get; } = new();

Grid Grid { get; set; }
EntityComponent PlayerEntityComponent { get; set; }

void Awake()
{
Grid = GetComponent<Grid>();
MapSize = new Vector2Int(3 * MapAsset.ChunkSize, 3 * MapAsset.ChunkSize);
MapSize = new Vector2Int(MapAsset.ViewDistance * 2, MapAsset.ViewDistance * 2);

CacheChunks();
}

void Start()
Expand All @@ -54,99 +66,160 @@ void Start()
if (PlayerEntityComponent)
{
PlayerEntityComponent.ChunkChanged += OnChunkChanged;
PlayerEntityComponent.CellPositionChanged += OnCellPositionChanged;
}

SpawnChunks(DirectionExtensions.All);
PlayerEntityComponent.SetPosition(new Vector2Int(MapAsset.ChunkSize / 2, MapAsset.ChunkSize / 2));

UpdateMap(true);
UpdateBounds();
}

void SpawnChunk(Direction direction)
void CacheChunks()
{
var randomChunkIndex = Random.Range(0, MapAsset.ChunkPrefabs.Count);
var randomChunk = MapAsset.ChunkPrefabs[randomChunkIndex];
var chunkComponent = randomChunk.GetComponent<ChunkComponent>();

var chunkPosition = (Vector3Int)(CenterChunkPosition + direction.ToVector2Int() * MapAsset.ChunkSize);
var fromBounds = new BoundsInt(Vector3Int.zero, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));
var toBounds = new BoundsInt(chunkPosition, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));
var bounds = new BoundsInt(Vector3Int.zero, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));

foreach (var (sourceTilemapType, sourceTilemap) in chunkComponent.Tilemaps)
foreach (var chunkPrefab in MapAsset.ChunkPrefabs)
{
if (!Tilemaps.Keys.Contains(sourceTilemapType)) continue;

var destinationTilemap = Tilemaps[sourceTilemapType];
var chunkComponent = chunkPrefab.GetComponent<ChunkComponent>();
var chunkTilesEntry = new Dictionary<TilemapType, TileBase[]>();

var tiles = sourceTilemap.GetTilesBlock(fromBounds);
destinationTilemap.SetTilesBlock(toBounds, tiles);
foreach (var tilemapType in Tilemaps.Keys)
{
if (!chunkComponent.Tilemaps.ContainsKey(tilemapType)) continue;

var tilemap = chunkComponent.Tilemaps[tilemapType];
var chunkTiles = new TileBase[MapAsset.ChunkSize * MapAsset.ChunkSize];
tilemap.GetTilesBlockNonAlloc(bounds, chunkTiles);

chunkTilesEntry.Add(tilemapType, chunkTiles);
}

destinationTilemap.CompressBounds();
ChunkTiles.Add(chunkTilesEntry);
}
}

void SpawnChunks(IEnumerable<Direction> directions)
void ChooseChunk(Vector2Int chunkIndex)
{
foreach (var direction in directions)
{
SpawnChunk(direction);
}
if (Chunks.ContainsKey(chunkIndex)) return;

var randomChunkIndex = Random.Range(0, ChunkTiles.Count);
var randomChunkTiles = ChunkTiles[randomChunkIndex];

Chunks.Add(chunkIndex, randomChunkTiles);
}

void DeleteChunk(Direction direction)
Vector2Int PositionToChunkIndex(Vector3Int position)
{
var chunkPosition = (Vector3Int)(CenterChunkPosition + direction.ToVector2Int() * MapAsset.ChunkSize);
var bounds = new BoundsInt(chunkPosition, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));
var tiles = new TileBase[MapAsset.ChunkSize * MapAsset.ChunkSize];

foreach (var (_, tilemap) in Tilemaps)
{
tilemap.SetTilesBlock(bounds, tiles);
tilemap.CompressBounds();
}
return new Vector2Int(
Mathf.FloorToInt(position.x / (float) MapAsset.ChunkSize),
Mathf.FloorToInt(position.y / (float) MapAsset.ChunkSize)
);
}

void DeleteChunks(IEnumerable<Direction> directions)
int Modulo(int x, int m)
{
foreach (var direction in directions)
{
DeleteChunk(direction);
}
return (x % m + m) % m;
}

void OnChunkChanged(object sender, Direction direction)
{
if (direction == Direction.None) return;

var directionsToAdd = direction switch
{
_ when DirectionExtensions.Upwards.Contains(direction) => DirectionExtensions.Upwards.ToList(),
_ when DirectionExtensions.Leftwards.Contains(direction) => DirectionExtensions.Leftwards.ToList(),
_ when DirectionExtensions.Downwards.Contains(direction) => DirectionExtensions.Downwards.ToList(),
_ when DirectionExtensions.Rightwards.Contains(direction) => DirectionExtensions.Rightwards.ToList(),
_ => new List<Direction>()
};

var directionsToDelete = directionsToAdd.ToOpposite();
ChunkIndex += direction.ToVector2Int();
ChunkPosition += direction.ToVector2Int() * MapAsset.ChunkSize;
}

DeleteChunks(directionsToDelete);
CenterChunkPosition += direction.ToVector2Int() * MapAsset.ChunkSize;
void OnCellPositionChanged(object sender, CellPositionChangedEventArgs e)
{
UpdateMap();

SpawnChunks(directionsToAdd);
UpdateBounds();
}

void UpdateMap(bool ignoreMinDistance = false)
{
var tilesToSpawn = new List<TileBase>();
var tilePositionsToSpawn = new List<Vector3Int>();

foreach (var (tilemapType, destinationTilemap) in Tilemaps)
{
for (int x = -MapAsset.ViewDistance - 1; x < MapAsset.ViewDistance + 2; x++)
{
for (int y = -MapAsset.ViewDistance - 1; y < MapAsset.ViewDistance + 2; y++)
{
var relativePosition = new Vector3Int(x, y);

var absoluteX = Mathf.Abs(relativePosition.x);
var absoluteY = Mathf.Abs(relativePosition.y);

if ((absoluteX < MapAsset.ViewDistance - 1 &&
absoluteY < MapAsset.ViewDistance - 1) &&
!ignoreMinDistance)
{
continue;
}

if (absoluteX > MapAsset.ViewDistance + 1 ||
absoluteY > MapAsset.ViewDistance + 1)
{
continue;
}

var position = (Vector3Int)PlayerEntityComponent.CellPosition;
var cellPosition = position + relativePosition;
var hasTile = destinationTilemap.HasTile(cellPosition);

if (absoluteX > MapAsset.ViewDistance ||
absoluteY > MapAsset.ViewDistance)
{
if (!hasTile) continue;

tilesToSpawn.Add(null);
tilePositionsToSpawn.Add(cellPosition);
continue;
}

if (hasTile) continue;

var relativeChunkPosition = new Vector2Int(
Modulo(cellPosition.x, MapAsset.ChunkSize),
Modulo(cellPosition.y, MapAsset.ChunkSize));

var chunkIndex = PositionToChunkIndex(cellPosition);

if (!Chunks.ContainsKey(chunkIndex)) ChooseChunk(chunkIndex);

var chunk = Chunks[chunkIndex];
var tileIndex = relativeChunkPosition.y * MapAsset.ChunkSize + relativeChunkPosition.x;
var tile = chunk[tilemapType][tileIndex];

if (!tile) continue;

tilesToSpawn.Add(tile);
tilePositionsToSpawn.Add(cellPosition);
}
}

destinationTilemap.SetTiles(tilePositionsToSpawn.ToArray(), tilesToSpawn.ToArray());
destinationTilemap.CompressBounds();

tilesToSpawn.Clear();
tilePositionsToSpawn.Clear();
}
}

void UpdateBounds()
{
var mapBounds = new BoundsInt(
(Vector3Int)CenterChunkPosition - new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize),
new Vector3Int(MapAsset.ChunkSize * 3, MapAsset.ChunkSize * 3, 1));
(Vector3Int)PlayerEntityComponent.CellPosition - new Vector3Int(MapAsset.ViewDistance, MapAsset.ViewDistance),
new Vector3Int(MapAsset.ViewDistance * 2, MapAsset.ViewDistance * 2, 1));
mapBounds.ClampToBounds(mapBounds);
MapBounds = mapBounds;

var centerBounds = new BoundsInt((Vector3Int)CenterChunkPosition, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));
centerBounds.ClampToBounds(centerBounds);
CenterChunkBounds = centerBounds;
var chunkBounds = new BoundsInt((Vector3Int)ChunkPosition, new Vector3Int(MapAsset.ChunkSize, MapAsset.ChunkSize, 1));
chunkBounds.ClampToBounds(chunkBounds);
ChunkBounds = chunkBounds;
}

IEnumerable<Tilemap> GetWalkableTilemaps() => MapAsset.TilemapIsWalkable
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void Start()

var obstaclePositions = MapComponent
.GetObstacleTilemaps()
.GetTilePositions()
.GetTilePositions(MapComponent.MapBounds)
.ToPositive(MapComponent.MapBounds)
.ToList();

Expand All @@ -59,7 +59,7 @@ IEnumerator UpdateFlowField()
{
var obstaclePositions = MapComponent
.GetObstacleTilemaps()
.GetTilePositions()
.GetTilePositions(MapComponent.MapBounds)
.ToPositive(MapComponent.MapBounds)
.ToList();

Expand Down Expand Up @@ -101,7 +101,7 @@ public PathfindingCell GetCell(Vector2Int position)
return FlowField.Cells[positivePosition.x][positivePosition.y];
}

void OnPlayerCellPositionChanged(object sender, Vector2Int position)
void OnPlayerCellPositionChanged(object sender, CellPositionChangedEventArgs e)
{
HasTargetPositionChanged = true;
}
Expand All @@ -128,5 +128,20 @@ FlowField CalcFlowField(List<Vector2Int> obstaclePositions)

return flowField;
}

#if UNITY_EDITOR

void OnDrawGizmos()
{
foreach (var cells in FlowField.Cells)
{
foreach (var cell in cells)
{
Debug.DrawRay((Vector3)(Vector2)cell.Position + MapComponent.MapBounds.position, cell.Direction * 0.5f, Color.red);
}
}
}

#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using UnityEngine;

namespace WorkingTitle.Unity.Components.Physics
{
public class CellPositionChangedEventArgs : EventArgs
{
public Vector2Int NewCellPosition { get; }
public Vector2Int OldCellPosition { get; }

public CellPositionChangedEventArgs(Vector2Int newCellPosition, Vector2Int oldCellPosition)
{
NewCellPosition = newCellPosition;
OldCellPosition = oldCellPosition;
}
}
}
Loading

0 comments on commit 9808729

Please sign in to comment.