riversong code showcase
This commit is contained in:
61
Source/Riversong/Game/World/Buildings/Building.cs
Normal file
61
Source/Riversong/Game/World/Buildings/Building.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class Building : Entity, IBuildingShape, IProductStorageEntity, IAgentSourceEntity
|
||||
{
|
||||
private ProductStorage _storage;
|
||||
|
||||
private ProductStoragePolicyState _productStoragePolicy;
|
||||
|
||||
private BuildingProductionState _productionState;
|
||||
|
||||
private AgentSourceState _agentsState;
|
||||
|
||||
private PopulationNeedsState _needsState;
|
||||
|
||||
private BuildingSleepState _sleepState;
|
||||
|
||||
public BuildingDefinition Definition { get; set; }
|
||||
|
||||
public TileRect Rect { get; set; }
|
||||
|
||||
public Directions Orientation { get; set; }
|
||||
|
||||
public int WeekCreated { get; set; }
|
||||
|
||||
public int TierIndex { get; set; }
|
||||
|
||||
public int PopulationCapacity { get; set; }
|
||||
|
||||
public float SpawnCooldown => Definition.SpawnCooldown;
|
||||
|
||||
public ref ProductStorage GetStorageRW()
|
||||
{
|
||||
return ref _storage;
|
||||
}
|
||||
|
||||
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
|
||||
{
|
||||
return ref _productStoragePolicy;
|
||||
}
|
||||
|
||||
public ref BuildingProductionState GetProductionStateRW()
|
||||
{
|
||||
return ref _productionState;
|
||||
}
|
||||
|
||||
public ref AgentSourceState GetAgentSourceStateRW()
|
||||
{
|
||||
return ref _agentsState;
|
||||
}
|
||||
|
||||
public ref PopulationNeedsState GetNeedsStateRW()
|
||||
{
|
||||
return ref _needsState;
|
||||
}
|
||||
|
||||
public ref BuildingSleepState GetSleepStateRW()
|
||||
{
|
||||
return ref _sleepState;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Source/Riversong/Game/World/Buildings/BuildingAoESystem.cs
Normal file
55
Source/Riversong/Game/World/Buildings/BuildingAoESystem.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[UpdateAfter(typeof(EditingStateGameSystem))]
|
||||
public class BuildingAoESystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private EditingState _editingState;
|
||||
|
||||
[InjectService]
|
||||
private UIState _uiState;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingSpatialQuery _buildingSpatialQuery;
|
||||
|
||||
[InjectService]
|
||||
private IAoERenderingService _aoeRenderingService;
|
||||
|
||||
public BuildingAoESystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var tool = _editingState.ActiveTool;
|
||||
if (tool is BuildTool buildTool && buildTool.Preview.IsVisible) AddAoE(buildTool.Building, buildTool.BuildingRect);
|
||||
|
||||
var selectedBuilding = _uiState.SelectedBuilding;
|
||||
if (selectedBuilding != null) AddAoE(selectedBuilding.Definition, selectedBuilding.Rect);
|
||||
}
|
||||
|
||||
private void AddAoE(BuildingDefinition definition, TileRect rect)
|
||||
{
|
||||
var range = definition.Range;
|
||||
if (range > 0) _aoeRenderingService.Add(definition.AoELayer, rect.Inflate(range));
|
||||
|
||||
if (definition.IsHouse)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForHouse(rect, providers);
|
||||
|
||||
foreach (var provider in providers) _aoeRenderingService.Add(provider.Definition.AoELayer, provider.Rect.Inflate(provider.Definition.Range));
|
||||
}
|
||||
|
||||
if (definition.IsStorage)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForStorage(rect, providers);
|
||||
|
||||
foreach (var provider in providers) _aoeRenderingService.Add(provider.Definition.AoELayer, provider.Rect.Inflate(provider.Definition.Range));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingCreatedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public BuildingCreatedSignal(Building building)
|
||||
{
|
||||
Building = building;
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Source/Riversong/Game/World/Buildings/BuildingDefinition.cs
Normal file
115
Source/Riversong/Game/World/Buildings/BuildingDefinition.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "BuildingDefinition", menuName = "Riversong Code Showcase/Building Definition")]
|
||||
public class BuildingDefinition : GameDataAsset, IComparable<BuildingDefinition>
|
||||
{
|
||||
public int UIOrder;
|
||||
|
||||
[PreviewField]
|
||||
public Sprite Icon;
|
||||
|
||||
public string BuildingName = "Building";
|
||||
|
||||
[TextArea(5, 10)]
|
||||
public string BuildingDescription = "Building";
|
||||
|
||||
public AssetReferenceGameObject Visualization;
|
||||
|
||||
public List<ProductAmountAuthoring> BuildingMaterials;
|
||||
|
||||
public int Width = 1;
|
||||
|
||||
public int Height = 1;
|
||||
|
||||
public int Range;
|
||||
|
||||
[LabelText("AoE Layer")]
|
||||
public int AoELayer;
|
||||
|
||||
public bool CanBeDeleted = true;
|
||||
|
||||
[TitleGroup("Workers")]
|
||||
public int WorkerCount;
|
||||
|
||||
public float SpawnCooldown;
|
||||
|
||||
[TitleGroup("Storage")]
|
||||
public int StorageCapacity = 1000;
|
||||
|
||||
[LabelText("Show Storage Tooltip?")]
|
||||
public bool ShowStorageTooltip;
|
||||
|
||||
[TitleGroup("Harvester")]
|
||||
[LabelText("Resource")]
|
||||
public ResourceNodeDefinition HarvestedResource;
|
||||
|
||||
[TitleGroup("Hunter")]
|
||||
public CritterDefinition TargetCritter;
|
||||
|
||||
[TitleGroup("Farm")]
|
||||
public bool IsFarm;
|
||||
|
||||
[LabelText("Product")]
|
||||
public ProductDefinition FarmProduct;
|
||||
|
||||
[HorizontalGroup("Farm/Dropped Amount")]
|
||||
[LabelText("Amt. Min")]
|
||||
public int MinDroppedAmount = 1;
|
||||
|
||||
[HorizontalGroup("Farm/Dropped Amount")]
|
||||
[LabelText("Amt. Max")]
|
||||
public int MaxDroppedAmount = 1;
|
||||
|
||||
[TitleGroup("Producer")]
|
||||
public RecipeDefinition Recipe;
|
||||
|
||||
[LabelText("Delivers Output?")]
|
||||
public bool DeliversOutput = true;
|
||||
|
||||
[TitleGroup("Provider")]
|
||||
[LabelText("Products")]
|
||||
public List<ProductAmountAuthoring> ProvidedProducts;
|
||||
|
||||
[LabelText("Fetches Products?")]
|
||||
public bool FetchesProducts = true;
|
||||
|
||||
[TitleGroup("House")]
|
||||
[LabelText("Is House?")]
|
||||
public bool IsHouse;
|
||||
|
||||
[ShowIf("IsHouse")]
|
||||
public List<HouseTierAuthoring> HouseTiers;
|
||||
|
||||
[TitleGroup("Storage")]
|
||||
[LabelText("Is Storage?")]
|
||||
public bool IsStorage;
|
||||
|
||||
[TitleGroup("Placement Rules")]
|
||||
[LabelText("Near Water?")]
|
||||
public bool NearWater;
|
||||
|
||||
[LabelText("Requires Fertile Tile?")]
|
||||
public bool RequiresFertileTile;
|
||||
|
||||
public int CompareTo(BuildingDefinition other)
|
||||
{
|
||||
return UIOrder.CompareTo(other.UIOrder);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HouseTierAuthoring
|
||||
{
|
||||
public int Capacity = 1;
|
||||
|
||||
public List<ProductAmountAuthoring> UpgradeMaterials;
|
||||
|
||||
public List<PopulationNeedAuthoring> Needs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingDeletedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public DeleteBuildingOptions Options;
|
||||
|
||||
public BuildingDeletedSignal(Building building, DeleteBuildingOptions options)
|
||||
{
|
||||
Building = building;
|
||||
Options = options;
|
||||
}
|
||||
}
|
||||
}
|
||||
323
Source/Riversong/Game/World/Buildings/BuildingManagerSystem.cs
Normal file
323
Source/Riversong/Game/World/Buildings/BuildingManagerSystem.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IBuildingFactory))]
|
||||
[Service(typeof(IDeleteBuildingService))]
|
||||
[Service(typeof(IBuildingSpatialQuery))]
|
||||
public class BuildingManagerSystem : GameSystem, IInitializable, IDisposable, IBuildingFactory, IDeleteBuildingService, IBuildingSpatialQuery
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private IProductStorageManager _productStorageManager;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackFactory _productStackFactory;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IAgentFactory _agentFactory;
|
||||
|
||||
[InjectService]
|
||||
private IFailedPathCache _failedPathCache;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
private Func<Building, int, bool> _fetchValidator;
|
||||
|
||||
private Func<Building, int, bool> _deliveryValidator;
|
||||
|
||||
private Func<Building, int, bool> _storageRequestValidator;
|
||||
|
||||
private Func<Building, int, int> _sourceRangeFunc;
|
||||
|
||||
private Func<Building, int, int> _candidateRangeFunc;
|
||||
|
||||
private Func<Building, int, int> _infiniteRangeFunc;
|
||||
|
||||
public BuildingManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
_fetchValidator = FetchValidator;
|
||||
_deliveryValidator = DeliveryValidator;
|
||||
_storageRequestValidator = StorageRequestValidator;
|
||||
_sourceRangeFunc = SourceRange;
|
||||
_candidateRangeFunc = CandidateRange;
|
||||
_infiniteRangeFunc = InfiniteRange;
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
}
|
||||
|
||||
private void OnConstructionCompleted(ConstructionCompletedSignal signal)
|
||||
{
|
||||
var constructionSite = signal.ConstructionSite;
|
||||
Create(constructionSite.Building, constructionSite.Rect, constructionSite.Orientation);
|
||||
}
|
||||
|
||||
public Building Create(BuildingDefinition definition, TileRect rect, Directions orientation)
|
||||
{
|
||||
var building = _entityCollection.Create<Building>();
|
||||
|
||||
building.Definition = definition;
|
||||
building.Rect = rect;
|
||||
building.Orientation = orientation;
|
||||
building.WeekCreated = _world.TimeState.TotalWeeks;
|
||||
InitializeStorage(building);
|
||||
InitializeProductionState(building);
|
||||
InitializeAgentSource(building);
|
||||
InitializeHouse(building);
|
||||
InitializeSleepState(building);
|
||||
|
||||
_entityCollection.Add(building);
|
||||
|
||||
_signalBus.Raise(new BuildingCreatedSignal(building));
|
||||
|
||||
return building;
|
||||
}
|
||||
|
||||
private void InitializeStorage(Building building)
|
||||
{
|
||||
var capacity = building.Definition.StorageCapacity;
|
||||
if (capacity <= 0) return;
|
||||
|
||||
_productStorageManager.InitializeProductStorage(building, capacity);
|
||||
}
|
||||
|
||||
private void InitializeProductionState(Building building)
|
||||
{
|
||||
var recipeDefinition = building.Definition.Recipe;
|
||||
if (!recipeDefinition) return;
|
||||
|
||||
ref var productionState = ref building.GetProductionStateRW();
|
||||
|
||||
productionState.State = ProducerState.NotWorking;
|
||||
|
||||
productionState.Recipe.Inputs.Length = recipeDefinition.Inputs.Count;
|
||||
for (var i = 0; i < recipeDefinition.Inputs.Count; i++)
|
||||
{
|
||||
var input = recipeDefinition.Inputs[i];
|
||||
var productHandle = _productCatalog.GetHandle(input.Product);
|
||||
productionState.Recipe.Inputs[i] = (productHandle, input.Amount);
|
||||
}
|
||||
|
||||
productionState.Recipe.OutputProductHandle = _productCatalog.GetHandle(recipeDefinition.Output.Product);
|
||||
productionState.Recipe.OutputAmount = recipeDefinition.Output.Amount;
|
||||
|
||||
productionState.Recipe.ProductionTime = recipeDefinition.ProductionTime;
|
||||
}
|
||||
|
||||
private void InitializeAgentSource(Building building)
|
||||
{
|
||||
_agentFactory.InitializeAgentSource(building);
|
||||
}
|
||||
|
||||
private void InitializeHouse(Building building)
|
||||
{
|
||||
if (!building.Definition.IsHouse) return;
|
||||
|
||||
var tiers = building.Definition.HouseTiers;
|
||||
building.PopulationCapacity = tiers.Count > 0 ? tiers[0].Capacity : 0;
|
||||
|
||||
InitializeHouseNeeds(building);
|
||||
}
|
||||
|
||||
private void InitializeHouseNeeds(Building building)
|
||||
{
|
||||
ref var needsState = ref building.GetNeedsStateRW();
|
||||
|
||||
var tiers = building.Definition.HouseTiers;
|
||||
for (var i = 0; i < tiers.Count; i++)
|
||||
foreach (var need in tiers[i].Needs)
|
||||
needsState.Needs.Add(
|
||||
new PopulationNeed
|
||||
{
|
||||
TierIndex = (byte)i,
|
||||
Type = need.Type,
|
||||
ProductHandle = _productCatalog.GetHandle(need.Product),
|
||||
HappinessScore = (ushort)need.HappinessScore,
|
||||
ConsumptionRate = (byte)need.ConsumptionRate,
|
||||
FetchThreshold = (byte)need.FetchThreshold,
|
||||
YieldOnFetch = (ushort)need.YieldOnFetch
|
||||
});
|
||||
|
||||
needsState.UpgradeState = tiers.Count > 1 ? TierUpgradeState.NotReady : TierUpgradeState.MaxedOut;
|
||||
|
||||
needsState.Happiness = _config.Population.InitialHouseHappiness;
|
||||
}
|
||||
|
||||
private void InitializeSleepState(Building building)
|
||||
{
|
||||
ref var sleepState = ref building.GetSleepStateRW();
|
||||
sleepState.HasHomelessWorkers = false;
|
||||
sleepState.EfficiencyModifier = _config.Economy.RestedWorkersEfficiencyModifier;
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
using var toDeleteScope = HashSetPool<int>.Get(out var toDelete);
|
||||
|
||||
foreach (var tile in TileRange.From(signal.Rect))
|
||||
foreach (Building building in _entityCollection.GetInternalEntityList(typeof(Building)))
|
||||
{
|
||||
if (!building.Definition.CanBeDeleted || !building.Rect.Contains(tile)) continue;
|
||||
|
||||
toDelete.Add(building.Id);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var id in toDelete)
|
||||
{
|
||||
var building = _entityCollection.Get<Building>(id);
|
||||
Delete(building, DeleteBuildingOptions.WithFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(Building building, DeleteBuildingOptions options)
|
||||
{
|
||||
_entityCollection.Remove(building.Id);
|
||||
|
||||
_productStackFactory.CreateProductStacksFrom(building);
|
||||
|
||||
_signalBus.Raise(new BuildingDeletedSignal(building, options));
|
||||
}
|
||||
|
||||
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _infiniteRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, sourceRange, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _sourceRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForDelivery(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _deliveryValidator, _infiniteRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForRequest(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(
|
||||
sourceId,
|
||||
sourceRect,
|
||||
sourceRange,
|
||||
productHandle,
|
||||
_entityCache.GetStorageRequestBuildings(),
|
||||
_storageRequestValidator,
|
||||
_infiniteRangeFunc,
|
||||
out building);
|
||||
}
|
||||
|
||||
public bool FindProviderForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetProviders(), _fetchValidator, _candidateRangeFunc, out building);
|
||||
}
|
||||
|
||||
public void FindProvidersForHouse(TileRect sourceRect, List<Building> providers)
|
||||
{
|
||||
foreach (var provider in _entityCache.GetProviders())
|
||||
if (TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
|
||||
providers.Add(provider);
|
||||
}
|
||||
|
||||
public void FindProvidersForStorage(TileRect sourceRect, List<Building> providers)
|
||||
{
|
||||
foreach (var provider in _entityCache.GetProviders())
|
||||
if (provider.Definition.FetchesProducts && TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
|
||||
providers.Add(provider);
|
||||
}
|
||||
|
||||
private bool FindReachableBuilding(int sourceId,
|
||||
TileRect sourceRect,
|
||||
int sourceRange,
|
||||
int productHandle,
|
||||
List<Building> candidates,
|
||||
Func<Building, int, bool> validator,
|
||||
Func<Building, int, int> rangeFunc,
|
||||
out Building building)
|
||||
{
|
||||
building = null;
|
||||
var best = int.MaxValue;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (_failedPathCache.IsKnownFailedPath(sourceId, candidate.Id)) continue;
|
||||
|
||||
var stepCount = TileMath.StepCount(sourceRect, candidate.Rect);
|
||||
if (stepCount >= best || stepCount > rangeFunc.Invoke(candidate, sourceRange)) continue;
|
||||
|
||||
if (!validator.Invoke(candidate, productHandle)) continue;
|
||||
|
||||
best = stepCount;
|
||||
building = candidate;
|
||||
}
|
||||
|
||||
return best < int.MaxValue;
|
||||
}
|
||||
|
||||
private bool FetchValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
return storage.AvailableNow(productHandle) > 0;
|
||||
}
|
||||
|
||||
private bool DeliveryValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
|
||||
return storage.FreeSpace() > 0 && storagePolicy.IsTakingAllowed(productHandle);
|
||||
}
|
||||
|
||||
private bool StorageRequestValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
|
||||
return storage.CountIncludingReservations(productHandle) > storagePolicy.GetRequestedAmount(productHandle) && storagePolicy.CanFulfillRequests(productHandle);
|
||||
}
|
||||
|
||||
private int SourceRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return sourceRange;
|
||||
}
|
||||
|
||||
private int CandidateRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return candidate.Definition.Range;
|
||||
}
|
||||
|
||||
private int InfiniteRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Source/Riversong/Game/World/Buildings/BuildingSelectionSystem.cs
Normal file
110
Source/Riversong/Game/World/Buildings/BuildingSelectionSystem.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[UpdateAfter(typeof(EditingStateGameSystem))]
|
||||
public class BuildingSelectionSystem : GameSystem, IInitializable, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _gameConfig;
|
||||
|
||||
[InjectService]
|
||||
private IPointerService _pointerService;
|
||||
|
||||
[InjectService]
|
||||
private UIState _uiState;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private ICancelAction _cancelAction;
|
||||
|
||||
[InjectService]
|
||||
private EditingState _editingState;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingVisualizationCollection _buildingVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private MaterialReplacementCache _materialReplacementCache;
|
||||
|
||||
public BuildingSelectionSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_cancelAction.AddHandler(
|
||||
(int)CancelActions.CancelSelection,
|
||||
_ =>
|
||||
{
|
||||
if (_uiState.SelectedBuilding == null) return false;
|
||||
SetSelectedBuilding(null);
|
||||
return true;
|
||||
});
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_uiState.SelectedBuilding != null && !_entityCollection.Exists(_uiState.SelectedBuilding.Id))
|
||||
{
|
||||
SetSelectedBuilding(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_editingState.ActiveTool != null)
|
||||
{
|
||||
SetSelectedBuilding(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var buildingUnderPointer = GetBuildingUnderPointer();
|
||||
if (_pointerService.TryConsumeLeftClick()) SetSelectedBuilding(buildingUnderPointer);
|
||||
|
||||
HighlightBuilding(buildingUnderPointer);
|
||||
HighlightBuilding(_uiState.SelectedBuilding);
|
||||
}
|
||||
|
||||
private Building GetBuildingUnderPointer()
|
||||
{
|
||||
if (_pointerService.IsPointerOverUI) return null;
|
||||
if (!_pointerService.TryGetPositionOnTerrain(out var position)) return null;
|
||||
|
||||
var tile = _tileSpace.WorldToTile(position);
|
||||
ref var entityIdMapValue = ref _world.EntityIdMap.GetValueRW(tile);
|
||||
if (entityIdMapValue.BuildingId == Entity.InvalidId) return null;
|
||||
|
||||
return _entityCollection.TryGet<Building>(entityIdMapValue.BuildingId, out var building) ? building : null;
|
||||
}
|
||||
|
||||
private void HighlightBuilding(Building building)
|
||||
{
|
||||
if (building == null) return;
|
||||
if (!_buildingVisualizationCollection.TryGetVisualization(building.Id, out var visualization)) return;
|
||||
|
||||
_materialReplacementCache.ReplaceMaterials(visualization.gameObject, _gameConfig.UI.HighlightedGameObjectsMaterial);
|
||||
}
|
||||
|
||||
private void SetSelectedBuilding(Building selectedBuilding)
|
||||
{
|
||||
if (_uiState.SelectedBuilding == selectedBuilding) return;
|
||||
|
||||
var oldSelection = _uiState.SelectedBuilding;
|
||||
_uiState.SelectedBuilding = selectedBuilding;
|
||||
|
||||
_signalBus.Raise(new SelectedBuildingChangedSignal(oldSelection, _uiState.SelectedBuilding));
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Source/Riversong/Game/World/Buildings/BuildingSleepState.cs
Normal file
13
Source/Riversong/Game/World/Buildings/BuildingSleepState.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingSleepState
|
||||
{
|
||||
public int RestedWorkerCount;
|
||||
|
||||
public int AllocatedWorkerCount;
|
||||
|
||||
public bool HasHomelessWorkers;
|
||||
|
||||
public float EfficiencyModifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ConstructionCompletedSignal
|
||||
{
|
||||
public ConstructionSite ConstructionSite;
|
||||
|
||||
public ConstructionCompletedSignal(ConstructionSite constructionSite)
|
||||
{
|
||||
ConstructionSite = constructionSite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ConstructionSite : Entity, IBuildingShape, IProductStorageEntity, IAgentSourceEntity
|
||||
{
|
||||
private ProductStorage _storage;
|
||||
|
||||
private ProductStoragePolicyState _productStoragePolicy;
|
||||
|
||||
private AgentSourceState _agentSourceState;
|
||||
|
||||
public BuildingDefinition Building { get; set; }
|
||||
|
||||
public TileRect Rect { get; set; }
|
||||
|
||||
public Directions Orientation { get; set; }
|
||||
|
||||
public float SpawnCooldown => 0;
|
||||
|
||||
public ref ProductStorage GetStorageRW()
|
||||
{
|
||||
return ref _storage;
|
||||
}
|
||||
|
||||
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
|
||||
{
|
||||
return ref _productStoragePolicy;
|
||||
}
|
||||
|
||||
public ref AgentSourceState GetAgentSourceStateRW()
|
||||
{
|
||||
return ref _agentSourceState;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ConstructionSiteDeletedSignal
|
||||
{
|
||||
public ConstructionSite ConstructionSite;
|
||||
|
||||
public ConstructionSiteDeletedSignal(ConstructionSite constructionSite)
|
||||
{
|
||||
ConstructionSite = constructionSite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ConstructionSiteManager : GameSystem, IInitializable, IDisposable, IUpdatable
|
||||
{
|
||||
private const int SpatialLookupCellSize = 5;
|
||||
|
||||
private const int StorageCapacity = 100;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private IScene _scene;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IProductStorageManager _productStorageManager;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackFactory _productStackFactory;
|
||||
|
||||
[InjectService]
|
||||
private IAgentFactory _agentFactory;
|
||||
|
||||
private Dictionary<int, GameObject> _visualizations = new();
|
||||
|
||||
private SpatialLookup<(ConstructionSite, GameObject)> _spatialLookup = new(SpatialLookupCellSize);
|
||||
|
||||
private MaterialPropertyBlock _materialPropertyBlock;
|
||||
|
||||
public ConstructionSiteManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingPlacementAnimationCompletedSignal>(OnBuildingPlaced);
|
||||
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingPlacementAnimationCompletedSignal>(OnBuildingPlaced);
|
||||
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
}
|
||||
|
||||
private void OnBuildingPlaced(BuildingPlacementAnimationCompletedSignal signal)
|
||||
{
|
||||
var constructionSite = _entityCollection.Create<ConstructionSite>();
|
||||
|
||||
constructionSite.Building = signal.Building;
|
||||
constructionSite.Rect = signal.Rect;
|
||||
constructionSite.Orientation = signal.Orientation;
|
||||
_productStorageManager.InitializeProductStorage(constructionSite, StorageCapacity);
|
||||
_agentFactory.InitializeAgentSource(constructionSite);
|
||||
|
||||
_entityCollection.Add(constructionSite);
|
||||
|
||||
_ = CreateVisualizationAsync(constructionSite);
|
||||
}
|
||||
|
||||
private async UniTask CreateVisualizationAsync(ConstructionSite constructionSite)
|
||||
{
|
||||
var position = _tileSpace.GetRectWorldCenter(constructionSite.Rect);
|
||||
var rotation = constructionSite.Orientation.ToQuaternion();
|
||||
var folder = _scene.SceneFolders.Buildings;
|
||||
|
||||
var visualization = await constructionSite.Building.Visualization.InstantiateAsync(position, rotation, folder);
|
||||
ReplaceMaterials(visualization);
|
||||
|
||||
_visualizations.Add(constructionSite.Id, visualization);
|
||||
_spatialLookup.Add((constructionSite, visualization), constructionSite.Rect, constructionSite.Id);
|
||||
}
|
||||
|
||||
private void ReplaceMaterials(GameObject gameObject)
|
||||
{
|
||||
var material = _config.Buildings.ConstructionSiteMaterial;
|
||||
|
||||
using var renderersScope = ListPool<Renderer>.Get(out var renderers);
|
||||
gameObject.GetComponentsInChildren(renderers);
|
||||
|
||||
_materialPropertyBlock ??= new MaterialPropertyBlock();
|
||||
|
||||
foreach (var renderer in renderers) MaterialReplacementCache.ReplaceRendererMaterials(renderer, material, _materialPropertyBlock);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
using var toCompleteScope = ListPool<ConstructionSite>.Get(out var toComplete);
|
||||
|
||||
foreach (ConstructionSite constructionSite in _entityCollection.GetInternalEntityList(typeof(ConstructionSite)))
|
||||
if (_visualizations.ContainsKey(constructionSite.Id) && AllBuildingMaterialsFetched(constructionSite))
|
||||
toComplete.Add(constructionSite);
|
||||
|
||||
foreach (var constructionSite in toComplete)
|
||||
{
|
||||
_entityCollection.Remove(constructionSite.Id);
|
||||
|
||||
_visualizations.Remove(constructionSite.Id, out var visualization);
|
||||
_spatialLookup.Remove(constructionSite.Rect, constructionSite.Id);
|
||||
|
||||
constructionSite.Building.Visualization.ReleaseInstance(visualization);
|
||||
|
||||
_signalBus.Raise(new ConstructionCompletedSignal(constructionSite));
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllBuildingMaterialsFetched(ConstructionSite constructionSite)
|
||||
{
|
||||
ref var storage = ref constructionSite.GetStorageRW();
|
||||
|
||||
foreach (var (product, amount) in constructionSite.Building.BuildingMaterials)
|
||||
{
|
||||
var handle = _productCatalog.GetHandle(product);
|
||||
if (storage.AvailableNow(handle) < amount) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
|
||||
{
|
||||
if ((signal.Filter & DeletedGameObjectsFilter.Buildings) == 0) return;
|
||||
|
||||
using var lookupResultScope = ListPool<(ConstructionSite, GameObject)>.Get(out var lookupResult);
|
||||
_spatialLookup.Find(signal.Rect, lookupResult);
|
||||
|
||||
foreach (var (_, visualization) in lookupResult) signal.GameObjects.Add(visualization);
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
using var lookupResultScope = ListPool<(ConstructionSite, GameObject)>.Get(out var lookupResult);
|
||||
_spatialLookup.Find(signal.Rect, lookupResult);
|
||||
|
||||
foreach (var (constructionSite, visualization) in lookupResult)
|
||||
{
|
||||
_productStackFactory.CreateProductStacksFrom(constructionSite);
|
||||
|
||||
_entityCollection.Remove(constructionSite.Id);
|
||||
_visualizations.Remove(constructionSite.Id);
|
||||
_spatialLookup.Remove(constructionSite.Rect, constructionSite.Id);
|
||||
|
||||
constructionSite.Building.Visualization.ReleaseInstance(visualization);
|
||||
|
||||
_signalBus.Raise(new ConstructionSiteDeletedSignal(constructionSite));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum DeleteBuildingOptions
|
||||
{
|
||||
WithFeedback,
|
||||
|
||||
Silent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingFactory
|
||||
{
|
||||
Building Create(BuildingDefinition definition, TileRect rect, Directions orientation);
|
||||
}
|
||||
}
|
||||
7
Source/Riversong/Game/World/Buildings/IBuildingShape.cs
Normal file
7
Source/Riversong/Game/World/Buildings/IBuildingShape.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingShape
|
||||
{
|
||||
TileRect Rect { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingSpatialQuery
|
||||
{
|
||||
bool FindStorageForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForFetch(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForDelivery(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForRequest(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building);
|
||||
|
||||
bool FindProviderForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
void FindProvidersForHouse(TileRect sourceRect, List<Building> providers);
|
||||
|
||||
void FindProvidersForStorage(TileRect sourceRect, List<Building> providers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IDeleteBuildingService
|
||||
{
|
||||
void Delete(Building building, DeleteBuildingOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct SelectedBuildingChangedSignal
|
||||
{
|
||||
public Building OldSelection;
|
||||
|
||||
public Building NewSelection;
|
||||
|
||||
public SelectedBuildingChangedSignal(Building oldSelection, Building newSelection)
|
||||
{
|
||||
OldSelection = oldSelection;
|
||||
NewSelection = newSelection;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Source/Riversong/Game/World/Buildings/TentRemovalSystem.cs
Normal file
44
Source/Riversong/Game/World/Buildings/TentRemovalSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class TentRemovalSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IDeleteBuildingService _deleteBuildingService;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
public TentRemovalSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<DayStartedSignal>(OnDayStarted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<DayStartedSignal>(OnDayStarted);
|
||||
}
|
||||
|
||||
private void OnDayStarted(DayStartedSignal signal)
|
||||
{
|
||||
var tents = _entityCache.GetTentBuildings();
|
||||
|
||||
for (var i = tents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var tent = tents[i];
|
||||
_deleteBuildingService.Delete(tent, DeleteBuildingOptions.Silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingDeleteAnimation : MonoBehaviour
|
||||
{
|
||||
private static readonly int IntensityPropertyID = Shader.PropertyToID("Intensity");
|
||||
|
||||
private static readonly int SizePropertyID = Shader.PropertyToID("Size");
|
||||
|
||||
[SerializeField]
|
||||
[ReadOnly]
|
||||
private float _sinkingHeight;
|
||||
|
||||
[SerializeField]
|
||||
private float _sinkingSpeed = 1;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationCurve _sinkingSpeedCurve = AnimationCurve.Constant(0, 1, 1);
|
||||
|
||||
[SerializeField]
|
||||
private float _shakeFrequency = 10;
|
||||
|
||||
[SerializeField]
|
||||
private float _shakeAmplitude = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
private VisualEffect _vfxPrefab;
|
||||
|
||||
[SerializeField]
|
||||
private int _vfxCount = 1;
|
||||
|
||||
[SerializeField]
|
||||
private float _vfxInterval = 1;
|
||||
|
||||
[SerializeField]
|
||||
private float _vfxIntensityDecay = 0.5f;
|
||||
|
||||
private void ComputeSinkingHeight()
|
||||
{
|
||||
var bounds = default(Bounds);
|
||||
|
||||
var renderers = GetComponentsInChildren<Renderer>();
|
||||
foreach (var renderer in renderers) bounds.Encapsulate(renderer.bounds);
|
||||
|
||||
_sinkingHeight = bounds.size.y;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_sinkingHeight == 0) ComputeSinkingHeight();
|
||||
}
|
||||
|
||||
public async UniTask PlayAsync(Building building)
|
||||
{
|
||||
var initialPosition = transform.position;
|
||||
var animatedPosition = transform.position;
|
||||
var angularFrequency = 2 * Mathf.PI * _shakeFrequency;
|
||||
var shakeWeights = new Vector3(Random.Range(-1, 1), 0, Random.Range(-1, 1));
|
||||
|
||||
for (var i = 0; i < _vfxCount; i++)
|
||||
{
|
||||
var delay = i * _vfxInterval;
|
||||
var intensity = Mathf.Pow(_vfxIntensityDecay, i);
|
||||
_ = PlayVfxAsync(building, initialPosition, delay, intensity);
|
||||
}
|
||||
|
||||
var t = 0f;
|
||||
while (animatedPosition.y > initialPosition.y - _sinkingHeight)
|
||||
{
|
||||
var dt = Time.deltaTime;
|
||||
|
||||
t += dt;
|
||||
|
||||
var sinkingSpeed = _sinkingSpeed * _sinkingSpeedCurve.Evaluate(t);
|
||||
animatedPosition += Vector3.down * (sinkingSpeed * dt);
|
||||
|
||||
var shake = Mathf.Sin(t * angularFrequency) * _shakeAmplitude * _sinkingSpeed;
|
||||
transform.position = animatedPosition + shake * shakeWeights;
|
||||
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask PlayVfxAsync(Building building, Vector3 position, float delay, float intensity)
|
||||
{
|
||||
if (delay > 0) await UniTask.WaitForSeconds(delay);
|
||||
|
||||
var vfx = Instantiate(_vfxPrefab, position, Quaternion.identity);
|
||||
vfx.SetFloat(IntensityPropertyID, intensity);
|
||||
vfx.SetVector2(SizePropertyID, new Vector2(building.Rect.Width, building.Rect.Height));
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ComputeSinkingHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingDeleteAnimationCompletedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public BuildingDeleteAnimationCompletedSignal(Building building)
|
||||
{
|
||||
Building = building;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class BuildingPlacementAnimation : MonoBehaviour, IAnimationClipSource
|
||||
{
|
||||
[SerializeField]
|
||||
private AnimationClip _animationClip;
|
||||
|
||||
[SerializeField]
|
||||
private float _playbackSpeed = 1;
|
||||
|
||||
[SerializeField]
|
||||
private Vector3 _positionLerpWeights = new(1.5f, 1, 1.5f);
|
||||
|
||||
[SerializeField]
|
||||
private float _rotationLerpWeight = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
private VisualEffect _impactVfxPrefab;
|
||||
|
||||
[SerializeField]
|
||||
private float _impactIntensityDecay = 0.75f;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float _animationProgress;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float _bounce;
|
||||
|
||||
private PlayableGraph _playableGraph;
|
||||
|
||||
private AnimationClipPlayable _playableClip;
|
||||
|
||||
private BuildingDefinition _building;
|
||||
|
||||
private Vector3 _startPosition;
|
||||
|
||||
private Vector3 _finalPosition;
|
||||
|
||||
private Quaternion _finalRotation;
|
||||
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_playableGraph = PlayableGraph.Create();
|
||||
_playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
|
||||
|
||||
_playableClip = AnimationClipPlayable.Create(_playableGraph, _animationClip);
|
||||
_playableClip.SetSpeed(_playbackSpeed);
|
||||
|
||||
var animator = GetComponent<Animator>();
|
||||
var output = AnimationPlayableOutput.Create(_playableGraph, nameof(BuildingPlacementAnimation), animator);
|
||||
output.SetSourcePlayable(_playableClip);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_playableGraph.Destroy();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
|
||||
_playableGraph.Evaluate(Time.unscaledDeltaTime);
|
||||
|
||||
var position = transform.position;
|
||||
position.x = Mathf.Lerp(_startPosition.x, _finalPosition.x, _positionLerpWeights.x * _animationProgress);
|
||||
position.y = Mathf.Lerp(_startPosition.y, _finalPosition.y, _positionLerpWeights.y * _bounce);
|
||||
position.z = Mathf.Lerp(_startPosition.z, _finalPosition.z, _positionLerpWeights.z * _animationProgress);
|
||||
transform.position = position;
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, _finalRotation, _rotationLerpWeight * _animationProgress);
|
||||
}
|
||||
|
||||
public async UniTask PlayAsync(BuildingDefinition building, Vector3 startPosition, Vector3 finalPosition, Quaternion finalRotation)
|
||||
{
|
||||
IsPlaying = true;
|
||||
|
||||
_building = building;
|
||||
_startPosition = startPosition;
|
||||
_finalPosition = finalPosition;
|
||||
_finalRotation = finalRotation;
|
||||
|
||||
_playableClip.SetTime(0);
|
||||
_playableClip.SetTime(0);
|
||||
_playableGraph.Play();
|
||||
|
||||
_animationProgress = 0;
|
||||
while (_animationProgress < 1) await UniTask.NextFrame();
|
||||
|
||||
_playableGraph.Stop();
|
||||
|
||||
transform.SetPositionAndRotation(finalPosition, finalRotation);
|
||||
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
public void GetAnimationClips(List<AnimationClip> results)
|
||||
{
|
||||
if (_animationClip) results.Add(_animationClip);
|
||||
}
|
||||
|
||||
private void OnImpact(int impact)
|
||||
{
|
||||
var impactVfx = Instantiate(_impactVfxPrefab, transform.position, transform.rotation);
|
||||
|
||||
var intensity = Mathf.Pow(_impactIntensityDecay, impact);
|
||||
var size = new Vector2(_building.Width, _building.Height);
|
||||
DustVfxProperties.Setup(impactVfx, intensity, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingPlacementAnimationCompletedSignal
|
||||
{
|
||||
public BuildingDefinition Building;
|
||||
|
||||
public TileRect Rect;
|
||||
|
||||
public Directions Orientation;
|
||||
|
||||
public BuildingPlacementAnimationCompletedSignal(BuildingDefinition building, TileRect rect, Directions orientation)
|
||||
{
|
||||
Building = building;
|
||||
Rect = rect;
|
||||
Orientation = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingPlacementAnimationStartedSignal
|
||||
{
|
||||
public BuildingDefinition Building;
|
||||
|
||||
public TileRect Rect;
|
||||
|
||||
public Directions Orientation;
|
||||
|
||||
public BuildingPlacementAnimationStartedSignal(BuildingDefinition building, TileRect rect, Directions orientation)
|
||||
{
|
||||
Building = building;
|
||||
Rect = rect;
|
||||
Orientation = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingStorageVisualization : MonoBehaviour
|
||||
{
|
||||
[PropertySpace(SpaceAfter = 20)]
|
||||
[SerializeField]
|
||||
private List<Transform> _slots;
|
||||
|
||||
[VerticalGroup("Arrange Slots/Controls")]
|
||||
[LabelText("Min")]
|
||||
[SerializeField]
|
||||
private Vector2 _boundsMin;
|
||||
|
||||
[VerticalGroup("Arrange Slots/Controls")]
|
||||
[LabelText("Max")]
|
||||
[SerializeField]
|
||||
private Vector2 _boundsMax;
|
||||
|
||||
[VerticalGroup("Arrange Slots/Controls")]
|
||||
[SerializeField]
|
||||
private int _columns = 4;
|
||||
|
||||
public int SlotCount => _slots.Count;
|
||||
|
||||
public Transform GetSlot(int slot)
|
||||
{
|
||||
return _slots[slot];
|
||||
}
|
||||
|
||||
public void SetProductVisualization(int slotIndex, GameObject productVisualization)
|
||||
{
|
||||
productVisualization.transform.SetParent(GetSlot(slotIndex));
|
||||
productVisualization.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
}
|
||||
|
||||
public bool TryGetProductVisualization(int slotIndex, out GameObject productVisualization)
|
||||
{
|
||||
var slot = GetSlot(slotIndex);
|
||||
productVisualization = slot.childCount > 0 ? slot.GetChild(0).gameObject : null;
|
||||
return productVisualization;
|
||||
}
|
||||
|
||||
[HorizontalGroup("Arrange Slots", 200)]
|
||||
[Button(ButtonSizes.Large)]
|
||||
[GUIColor("cyan")]
|
||||
private void ArrangeSlots()
|
||||
{
|
||||
var rows = Mathf.CeilToInt((float)_slots.Count / _columns);
|
||||
|
||||
var w = _boundsMax.x - _boundsMin.x;
|
||||
var h = _boundsMax.y - _boundsMin.y;
|
||||
|
||||
var spacing = new Vector2(w, h) / new Vector2(_columns, rows);
|
||||
|
||||
for (var i = 0; i < _slots.Count; i++)
|
||||
{
|
||||
var slot = _slots[i];
|
||||
|
||||
var row = i / _columns;
|
||||
var column = i % _columns;
|
||||
|
||||
var p = slot.position;
|
||||
|
||||
p.x = _boundsMin.x + (0.5f + column) * spacing.x;
|
||||
p.z = _boundsMin.y + (0.5f + row) * spacing.y;
|
||||
|
||||
slot.position = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingStorageVisualizationSystem : GameSystem, IInitializable, IDisposable, IUpdatable
|
||||
{
|
||||
private const int StackProductCount = 10;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IPoolingService _poolingService;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingVisualizationCollection _buildingVisualizations;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
public BuildingStorageVisualizationSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingDeletedSignal>(OnBuildingDeleted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingDeletedSignal>(OnBuildingDeleted);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var storehouse in _entityCache.GetStorageBuildings()) UpdateStorehouse(storehouse);
|
||||
}
|
||||
|
||||
private void UpdateStorehouse(Building storehouse)
|
||||
{
|
||||
if (!_buildingVisualizations.TryGetVisualization(storehouse.Id, out var buildingVisualization)) return;
|
||||
|
||||
var storageVisualization = buildingVisualization.GetComponent<BuildingStorageVisualization>();
|
||||
if (storageVisualization.SlotCount <= 0) return;
|
||||
|
||||
ReleaseProductVisualizations(storageVisualization);
|
||||
|
||||
ref var storage = ref storehouse.GetStorageRW();
|
||||
var slotIndex = 0;
|
||||
foreach (var (productHandle, amount) in storage)
|
||||
{
|
||||
var product = _productCatalog.GetProduct(productHandle);
|
||||
var poolKey = ((GameObject)product.ProductStackVisualization.Asset).GetInstanceID();
|
||||
|
||||
var amountLeft = amount;
|
||||
while (amountLeft > 0)
|
||||
{
|
||||
var productVisualization = _poolingService.Get<GameObject>(poolKey);
|
||||
storageVisualization.SetProductVisualization(slotIndex, productVisualization);
|
||||
|
||||
if (++slotIndex >= storageVisualization.SlotCount) return;
|
||||
|
||||
amountLeft -= StackProductCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBuildingDeleted(BuildingDeletedSignal signal)
|
||||
{
|
||||
if (!signal.Building.Definition.IsStorage) return;
|
||||
|
||||
_buildingVisualizations.TryGetVisualization(signal.Building.Id, out var buildingVisualization);
|
||||
var storageVisualization = buildingVisualization.GetComponent<BuildingStorageVisualization>();
|
||||
|
||||
ReleaseProductVisualizations(storageVisualization);
|
||||
}
|
||||
|
||||
private void ReleaseProductVisualizations(BuildingStorageVisualization storageVisualization)
|
||||
{
|
||||
for (var i = 0; i < storageVisualization.SlotCount; i++)
|
||||
{
|
||||
if (!storageVisualization.TryGetProductVisualization(i, out var productVisualization)) return;
|
||||
|
||||
_poolingService.Release(productVisualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingUpgradeAnimation : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private VisualEffect _vfxPrefab;
|
||||
|
||||
[SerializeField]
|
||||
private float _stretchDuration = 0.25f;
|
||||
|
||||
[SerializeField]
|
||||
private float _stretchFactor = 1.2f;
|
||||
|
||||
[SerializeField]
|
||||
private float _settleDuration = 0.75f;
|
||||
|
||||
[SerializeField]
|
||||
private float _settleElasticity = 4.5f;
|
||||
|
||||
public async UniTask PlayAsync(Building building)
|
||||
{
|
||||
var vfx = Instantiate(_vfxPrefab, transform.position, transform.rotation);
|
||||
DustVfxProperties.Setup(vfx, 1, new Vector2(building.Rect.Width, building.Rect.Height));
|
||||
|
||||
var fromScale = transform.localScale;
|
||||
var toScale = new Vector3(fromScale.x, fromScale.y * _stretchFactor, fromScale.z);
|
||||
|
||||
var elapsed = 0f;
|
||||
while (elapsed < _stretchDuration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
var t = EaseOutBack(Mathf.Clamp01(elapsed / _stretchDuration));
|
||||
transform.localScale = Vector3.LerpUnclamped(fromScale, toScale, t);
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
|
||||
elapsed = 0;
|
||||
while (elapsed < _settleDuration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
var t = EaseOutElastic(Mathf.Clamp01(elapsed / _settleDuration), _settleElasticity);
|
||||
transform.localScale = Vector3.LerpUnclamped(toScale, fromScale, t);
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
|
||||
transform.localScale = fromScale;
|
||||
}
|
||||
|
||||
private static float EaseOutBack(float t)
|
||||
{
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1;
|
||||
return 1 + c3 * Mathf.Pow(t - 1, 3) + c1 * Mathf.Pow(t - 1, 2);
|
||||
}
|
||||
|
||||
private static float EaseOutElastic(float t, float elasticity)
|
||||
{
|
||||
var c4 = 2 * Mathf.PI / elasticity;
|
||||
return t switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => 1,
|
||||
_ => Mathf.Pow(2, -10 * t) * Mathf.Sin((10 * t - 0.75f) * c4) + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingVisualization : MonoBehaviour
|
||||
{
|
||||
public List<GameObject> Tiers;
|
||||
|
||||
public void SetTier(int tier)
|
||||
{
|
||||
for (var i = 0; i < Tiers.Count; i++) Tiers[i].SetActive(i == tier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingVisualizationCreatedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public BuildingVisualization Visualization;
|
||||
|
||||
public BuildingVisualizationCreatedSignal(Building building, BuildingVisualization visualization)
|
||||
{
|
||||
Building = building;
|
||||
Visualization = visualization;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IBuildingVisualizationCollection))]
|
||||
public class BuildingVisualizationManager : GameSystem, IInitializable, IDisposable, IBuildingVisualizationCollection
|
||||
{
|
||||
private const int SpatialLookupCellSize = 5;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IScene _scene;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
private HashSet<int> _pendingVisualizations = new();
|
||||
|
||||
private Dictionary<int, BuildingVisualization> _visualizations = new();
|
||||
|
||||
private SpatialLookup<GameObject> _allVisualizationsSpatialLookup = new(SpatialLookupCellSize);
|
||||
|
||||
private SpatialLookup<GameObject> _canBeDeletedSpatialLookup = new(SpatialLookupCellSize);
|
||||
|
||||
public BuildingVisualizationManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingCreatedSignal>(OnBuildingCreated);
|
||||
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Subscribe<BuildingDeletedSignal>(OnBuildingDeleted);
|
||||
_signalBus.Subscribe<BuildingUpgradedSignal>(OnBuildingUpgraded);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingCreatedSignal>(OnBuildingCreated);
|
||||
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Unsubscribe<BuildingDeletedSignal>(OnBuildingDeleted);
|
||||
_signalBus.Unsubscribe<BuildingUpgradedSignal>(OnBuildingUpgraded);
|
||||
}
|
||||
|
||||
private void OnBuildingCreated(BuildingCreatedSignal signal)
|
||||
{
|
||||
var building = signal.Building;
|
||||
|
||||
_pendingVisualizations.Add(building.Id);
|
||||
|
||||
_ = CreateVisualizationAsync(building);
|
||||
}
|
||||
|
||||
private async UniTask CreateVisualizationAsync(Building building)
|
||||
{
|
||||
var folder = _scene.SceneFolders.Buildings;
|
||||
var visualizationGameObject = await building.Definition.Visualization.InstantiateAsync(folder);
|
||||
|
||||
if (!_pendingVisualizations.Remove(building.Id))
|
||||
{
|
||||
building.Definition.Visualization.ReleaseInstance(visualizationGameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
var visualization = visualizationGameObject.GetComponent<BuildingVisualization>();
|
||||
if (!visualization)
|
||||
{
|
||||
Debug.LogError($"Building visualization '{visualizationGameObject.name}' has no {nameof(BuildingVisualization)} component");
|
||||
return;
|
||||
}
|
||||
|
||||
visualization.SetTier(0);
|
||||
|
||||
var position = _tileSpace.GetRectWorldCenter(building.Rect);
|
||||
var rotation = building.Orientation.ToQuaternion();
|
||||
visualization.transform.SetPositionAndRotation(position, rotation);
|
||||
|
||||
_visualizations.Add(building.Id, visualization);
|
||||
_signalBus.Raise(new BuildingVisualizationCreatedSignal(building, visualization));
|
||||
|
||||
_allVisualizationsSpatialLookup.Add(visualization.gameObject, building.Rect, building.Id);
|
||||
if (building.Definition.CanBeDeleted) _canBeDeletedSpatialLookup.Add(visualization.gameObject, building.Rect, building.Id);
|
||||
}
|
||||
|
||||
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
|
||||
{
|
||||
if ((signal.Filter & DeletedGameObjectsFilter.Buildings) == 0) return;
|
||||
|
||||
_canBeDeletedSpatialLookup.Find(signal.Rect, signal.GameObjects);
|
||||
}
|
||||
|
||||
private void OnBuildingDeleted(BuildingDeletedSignal signal)
|
||||
{
|
||||
var building = signal.Building;
|
||||
|
||||
if (_pendingVisualizations.Remove(building.Id))
|
||||
{
|
||||
_signalBus.Raise(new BuildingDeleteAnimationCompletedSignal(building));
|
||||
return;
|
||||
}
|
||||
|
||||
_visualizations.Remove(building.Id, out var visualization);
|
||||
_allVisualizationsSpatialLookup.Remove(building.Rect, building.Id);
|
||||
_canBeDeletedSpatialLookup.Remove(building.Rect, building.Id);
|
||||
|
||||
if (signal.Options == DeleteBuildingOptions.Silent)
|
||||
{
|
||||
FinalizeBuildingDeletion(building, visualization);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = PlayDeleteAnimationAsync(building, visualization);
|
||||
}
|
||||
|
||||
private async UniTask PlayDeleteAnimationAsync(Building building, BuildingVisualization visualization)
|
||||
{
|
||||
var deleteAnimation = visualization.GetComponent<BuildingDeleteAnimation>();
|
||||
await deleteAnimation.PlayAsync(building);
|
||||
|
||||
FinalizeBuildingDeletion(building, visualization);
|
||||
}
|
||||
|
||||
private void FinalizeBuildingDeletion(Building building, BuildingVisualization visualization)
|
||||
{
|
||||
_signalBus.Raise(new BuildingDeleteAnimationCompletedSignal(building));
|
||||
|
||||
building.Definition.Visualization.ReleaseInstance(visualization.gameObject);
|
||||
}
|
||||
|
||||
private void OnBuildingUpgraded(BuildingUpgradedSignal signal)
|
||||
{
|
||||
var building = signal.Building;
|
||||
var visualization = _visualizations[building.Id];
|
||||
|
||||
visualization.SetTier(building.TierIndex);
|
||||
|
||||
if (visualization.TryGetComponent<BuildingUpgradeAnimation>(out var animation)) _ = animation.PlayAsync(building);
|
||||
}
|
||||
|
||||
public bool IsVisualizationPending(int id)
|
||||
{
|
||||
return _pendingVisualizations.Contains(id);
|
||||
}
|
||||
|
||||
public bool TryGetVisualization(int id, out BuildingVisualization visualization)
|
||||
{
|
||||
return _visualizations.TryGetValue(id, out visualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingVisualizationCollection
|
||||
{
|
||||
bool IsVisualizationPending(int id);
|
||||
|
||||
bool TryGetVisualization(int id, out BuildingVisualization visualization);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class ProducerAnimation : MonoBehaviour
|
||||
{
|
||||
public abstract void UpdateAnimation(in BuildingProductionState productionState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EconomySystemGroup))]
|
||||
[UpdateAfter(typeof(ProductionTickGameSystem))]
|
||||
public class ProducerAnimationSystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingVisualizationCollection _buildingVisualizations;
|
||||
|
||||
public ProducerAnimationSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var producer in _entityCache.GetProducers()) UpdateProducer(producer);
|
||||
}
|
||||
|
||||
private void UpdateProducer(Building producer)
|
||||
{
|
||||
if (!_buildingVisualizations.TryGetVisualization(producer.Id, out var buildingVisualization)) return;
|
||||
if (!buildingVisualization.TryGetComponent<ProducerAnimation>(out var producerAnimation)) return;
|
||||
|
||||
ref var productionState = ref producer.GetProductionStateRW();
|
||||
producerAnimation.UpdateAnimation(productionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class WindmillProducerAnimation : ProducerAnimation
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform _rotationTarget;
|
||||
|
||||
[SerializeField]
|
||||
private float _rotationSpeed = 180;
|
||||
|
||||
[SerializeField]
|
||||
private float _rampUpTime = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
private float _slowDownTime = 0.5f;
|
||||
|
||||
private float _currentRotationSpeed;
|
||||
|
||||
public override void UpdateAnimation(in BuildingProductionState productionState)
|
||||
{
|
||||
if (!_rotationTarget) return;
|
||||
|
||||
var isWorking = productionState.State == ProducerState.Working;
|
||||
var targetRotationSpeed = isWorking ? _rotationSpeed : 0;
|
||||
var rampTime = isWorking ? _rampUpTime : _slowDownTime;
|
||||
var maxDelta = rampTime > 0 ? _rotationSpeed / rampTime * Time.deltaTime : _rotationSpeed;
|
||||
_currentRotationSpeed = Mathf.MoveTowards(_currentRotationSpeed, targetRotationSpeed, maxDelta);
|
||||
|
||||
var rotationDelta = _currentRotationSpeed * Time.deltaTime;
|
||||
_rotationTarget.localRotation *= Quaternion.Euler(0, 0, rotationDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user