riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IRoadFactory
{
void CreateRoad(int2 startTile, int2 endTile);
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IRoadFactory))]
public class RoadManagerSystem : GameSystem, IInitializable, IDisposable, IUpdatable, IRoadFactory
{
[InjectService]
protected GameConfig _config;
[InjectService]
private World _world;
[InjectService]
private ISignalBus _signalBus;
private readonly List<List<int2>> _pendingTiles = new();
private float _placeTileTimer;
public RoadManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteToolSignal);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteToolSignal);
}
public void Update()
{
PlaceTiles();
}
private void PlaceTiles()
{
if (_pendingTiles.Count == 0)
{
_placeTileTimer = 0;
return;
}
_placeTileTimer += Time.unscaledDeltaTime;
if (_placeTileTimer < _config.Roads.PlaceTileInterval) return;
for (var i = _pendingTiles.Count - 1; i >= 0; i--)
{
var tileQueue = _pendingTiles[i];
while (tileQueue.Count > 0)
{
var tile = tileQueue[0];
tileQueue.RemoveAt(0);
if (_world.RoadNetwork.IsRoadTile(tile)) continue;
_world.RoadNetwork.AddRoadTile(tile);
UpdateTilesAround(tile);
_signalBus.Raise(new RoadTileUpdatedSignal(tile, true));
_placeTileTimer = 0;
break;
}
if (tileQueue.Count == 0)
{
_pendingTiles.RemoveAt(i);
ListPool<int2>.Release(tileQueue);
}
}
}
private void OnDoDeleteToolSignal(DoDeleteToolSignal signal)
{
using var deletedTilesScope = ListPool<int2>.Get(out var deletedTiles);
foreach (var tile in TileRange.From(signal.Rect))
{
if (!_world.RoadNetwork.IsRoadTile(tile)) continue;
_world.RoadNetwork.RemoveRoadTile(tile);
UpdateTilesAround(tile);
deletedTiles.Add(tile);
}
foreach (var tile in deletedTiles) _signalBus.Raise(new RoadTileUpdatedSignal(tile, false));
}
private void UpdateTilesAround(int2 tile)
{
if (tile.x > 0) _world.RoadNetwork.UpdateRoadTile(tile + new int2(-1, 0));
if (tile.y < _world.Size.x - 1) _world.RoadNetwork.UpdateRoadTile(tile + new int2(1, 0));
if (tile.y > 0) _world.RoadNetwork.UpdateRoadTile(tile + new int2(0, -1));
if (tile.y < _world.Size.y - 1) _world.RoadNetwork.UpdateRoadTile(tile + new int2(0, 1));
}
public void CreateRoad(int2 startTile, int2 endTile)
{
var tileQueue = ListPool<int2>.Get();
_pendingTiles.Add(tileQueue);
TileMath.WalkLine(startTile, endTile, tileQueue.Add);
}
}
}

View File

@@ -0,0 +1,60 @@
using Unity.Collections;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class RoadNetwork : NativeGrid<ushort>
{
/// <summary>
/// Indicates that the tile is occupied by a road.
/// </summary>
private const int TileOccupiedBit = 1 << 15;
/// <summary>
/// Mask selecting the bits indicating in which directions a road tile is connected to another road tile.
/// </summary>
private const int ConnectivityBits = 0xFF;
public RoadNetwork(int2 size) : base(size, Allocator.Persistent)
{
}
public void AddRoadTile(int2 tile)
{
SetValue(tile, TileOccupiedBit);
UpdateRoadTile(tile);
}
public void UpdateRoadTile(int2 tile)
{
if (!IsRoadTile(tile)) return;
ushort value = TileOccupiedBit;
if (tile.x > 0 && GetValue(tile + new int2(-1, 0)) != 0) value |= (ushort)DirectionsMask4.West;
if (tile.x < Size.x - 1 && GetValue(tile + new int2(1, 0)) != 0) value |= (ushort)DirectionsMask4.East;
if (tile.y > 0 && GetValue(tile + new int2(0, -1)) != 0) value |= (ushort)DirectionsMask4.South;
if (tile.y < Size.y - 1 && GetValue(tile + new int2(0, 1)) != 0) value |= (ushort)DirectionsMask4.North;
SetValue(tile, value);
}
public void RemoveRoadTile(int2 tile)
{
SetValue(tile, 0);
}
public DirectionsMask8 GetConnectivity(int2 tile)
{
return (DirectionsMask8)(GetValue(tile) & ConnectivityBits);
}
public bool IsRoadTile(int2 tile)
{
return GetValue(tile) != 0;
}
}
}

View File

@@ -0,0 +1,17 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct RoadTileUpdatedSignal
{
public int2 Tile;
public bool RoadTileAdded;
public RoadTileUpdatedSignal(int2 tile, bool roadTileAdded)
{
Tile = tile;
RoadTileAdded = roadTileAdded;
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using Object = UnityEngine.Object;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class RoadVisualizationManager : GameSystem, IInitializable, IDisposable
{
[InjectService]
private GameConfig _config;
[InjectService]
private IScene _scene;
[InjectService]
private ISignalBus _signalBus;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private World _world;
private readonly List<GameObject> _prefabs = new();
private readonly Dictionary<int2, GameObject> _visualizations = new();
private GameObject _placementVfxPrefab;
public RoadVisualizationManager(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public async UniTask InitializeAsync()
{
var tilePrefabTasks = _config.Roads.Tiles.Select(tilePrefabRef => tilePrefabRef.LoadAssetAsync().ToUniTask()).ToArray();
var placementVfxTask = _config.Roads.PlacementVfxPrefab.LoadAssetAsync().ToUniTask();
var tilePrefabs = await UniTask.WhenAll(tilePrefabTasks);
_prefabs.AddRange(tilePrefabs);
_placementVfxPrefab = await placementVfxTask;
_signalBus.Subscribe<RoadTileUpdatedSignal>(OnRoadTileChanged);
}
public void Dispose()
{
foreach (var tilePrefabRef in _config.Roads.Tiles) tilePrefabRef.ReleaseAsset();
_config.Roads.PlacementVfxPrefab.ReleaseAsset();
_signalBus.Unsubscribe<RoadTileUpdatedSignal>(OnRoadTileChanged);
}
private void OnRoadTileChanged(RoadTileUpdatedSignal signal)
{
var tile = signal.Tile;
UpdateTile(tile);
if (tile.x > 0) UpdateTile(tile + new int2(-1, 0));
if (tile.x < _world.Size.x - 1) UpdateTile(tile + new int2(1, 0));
if (tile.y > 0) UpdateTile(tile + new int2(0, -1));
if (tile.y < _world.Size.y - 1) UpdateTile(tile + new int2(0, 1));
if (signal.RoadTileAdded) SpawnPlacementVfx(tile);
}
private void UpdateTile(int2 tile)
{
if (_visualizations.TryGetValue(tile, out var visualization))
{
Object.Destroy(visualization);
_visualizations.Remove(tile);
}
if (!_world.RoadNetwork.IsRoadTile(tile)) return;
visualization = Object.Instantiate(GetRoadTilePrefab(tile), _scene.SceneFolders.RoadTiles);
visualization.transform.position = _tileSpace.TileToWorld(tile) + new Vector3(0, 0.001f, 0);
_visualizations.Add(tile, visualization);
}
private GameObject GetRoadTilePrefab(int2 tile)
{
var connectivity = _world.RoadNetwork.GetConnectivity(tile);
return _prefabs[(int)connectivity];
}
private void SpawnPlacementVfx(int2 tile)
{
var position = _tileSpace.TileToWorld(tile);
Object.Instantiate(_placementVfxPrefab, position, Quaternion.identity);
}
}
}