riversong code showcase
This commit is contained in:
9
Source/Riversong/Game/World/Roads/IRoadFactory.cs
Normal file
9
Source/Riversong/Game/World/Roads/IRoadFactory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IRoadFactory
|
||||
{
|
||||
void CreateRoad(int2 startTile, int2 endTile);
|
||||
}
|
||||
}
|
||||
118
Source/Riversong/Game/World/Roads/RoadManagerSystem.cs
Normal file
118
Source/Riversong/Game/World/Roads/RoadManagerSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Source/Riversong/Game/World/Roads/RoadNetwork.cs
Normal file
60
Source/Riversong/Game/World/Roads/RoadNetwork.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Source/Riversong/Game/World/Roads/RoadTileUpdatedSignal.cs
Normal file
17
Source/Riversong/Game/World/Roads/RoadTileUpdatedSignal.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Source/Riversong/Game/World/Roads/RoadVisualizationManager.cs
Normal file
100
Source/Riversong/Game/World/Roads/RoadVisualizationManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user