riversong code showcase
This commit is contained in:
@@ -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