riversong code showcase
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IDestroyRawResourceAnimation
|
||||
{
|
||||
UniTask PlayAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IRawResourceVisualizationCollection
|
||||
{
|
||||
bool TryGetVisualization(int id, out GameObject visualization);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct RawResourceHarvestedSignal
|
||||
{
|
||||
public int RawResourceId;
|
||||
|
||||
public RawResourceHarvestedSignal(int rawResourceId)
|
||||
{
|
||||
RawResourceId = rawResourceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IRawResourceVisualizationCollection))]
|
||||
public class RawResourceRenderingSystem : GameSystem, IInitializable, IDisposable, IRawResourceVisualizationCollection, IOnWorldGenerationCompletedCallback
|
||||
{
|
||||
private const int MaxCreatedPerUpdate = 16;
|
||||
|
||||
[InjectService]
|
||||
private IScene _scene;
|
||||
|
||||
[InjectService]
|
||||
private IGameDatabase _gameDatabase;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
private Dictionary<int, GameObject> _visualizationsById = new();
|
||||
|
||||
public RawResourceRenderingSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
|
||||
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Subscribe<RawResourcesRemovedSignal>(OnRawResourcesRemoved);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
|
||||
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Unsubscribe<RawResourcesRemovedSignal>(OnRawResourcesRemoved);
|
||||
}
|
||||
|
||||
private void OnWorldGenerationCompleted(WorldGenerationCompletedSignal signal)
|
||||
{
|
||||
signal.Callbacks.Add(this);
|
||||
}
|
||||
|
||||
public async UniTask OnWorldGenerationCompletedAsync(World world)
|
||||
{
|
||||
await CreateVisualizationsAsync();
|
||||
}
|
||||
|
||||
private async UniTask CreateVisualizationsAsync()
|
||||
{
|
||||
var visualizationCount = 0;
|
||||
|
||||
foreach (var resourceNode in _world.RawResources)
|
||||
{
|
||||
var definition = _gameDatabase.WithId<ResourceNodeDefinition>(resourceNode.DefinitionId);
|
||||
|
||||
var rotation = definition.RandomRotation ? Quaternion.Euler(0, Random.Range(0, 360), 0) : resourceNode.Orientation.ToQuaternion();
|
||||
var scale = Vector3.one * Random.Range(definition.ScaleRange.x, definition.ScaleRange.y);
|
||||
|
||||
GameObject prefab;
|
||||
if (definition.Prefab.IsValid())
|
||||
prefab = (GameObject)definition.Prefab.Asset;
|
||||
else
|
||||
prefab = await definition.Prefab.LoadAssetAsync<GameObject>();
|
||||
|
||||
var visualization = Object.Instantiate(prefab, _scene.SceneFolders.RawResources);
|
||||
visualization.transform.SetPositionAndRotation(resourceNode.Position, rotation);
|
||||
visualization.transform.localScale = Vector3.Scale(visualization.transform.localScale, scale);
|
||||
|
||||
_visualizationsById.Add(resourceNode.Id, visualization);
|
||||
|
||||
if (++visualizationCount % MaxCreatedPerUpdate == 0) await UniTask.NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
|
||||
{
|
||||
if ((signal.Filter & DeletedGameObjectsFilter.RawResources) == 0) return;
|
||||
|
||||
using var resourceNodesScope = ListPool<(int, bool)>.Get(out var resourceNodeIds);
|
||||
_world.RawResources.GetResourceNodes(signal.Rect, _config.GeneralSettings.BaseElevation, resourceNodeIds);
|
||||
|
||||
foreach (var (id, canBeDeleted) in resourceNodeIds)
|
||||
{
|
||||
if (!canBeDeleted) continue;
|
||||
|
||||
var visualization = _visualizationsById[id];
|
||||
signal.GameObjects.Add(visualization.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRawResourcesRemoved(RawResourcesRemovedSignal signal)
|
||||
{
|
||||
foreach (var resourceNode in signal.ResourceNodes)
|
||||
{
|
||||
if (!_visualizationsById.Remove(resourceNode.Id, out var visualization)) return;
|
||||
|
||||
_ = DestroyVisualizationAsync(visualization);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask DestroyVisualizationAsync(GameObject visualization)
|
||||
{
|
||||
if (visualization.TryGetComponent<IDestroyRawResourceAnimation>(out var animation)) await animation.PlayAsync();
|
||||
|
||||
Object.Destroy(visualization);
|
||||
}
|
||||
|
||||
public bool TryGetVisualization(int id, out GameObject visualization)
|
||||
{
|
||||
return _visualizationsById.TryGetValue(id, out visualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class RawResourcesRemovalSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
public RawResourcesRemovalSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
_signalBus.Subscribe<RoadTileUpdatedSignal>(OnRoadTileUpdated);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
_signalBus.Unsubscribe<RoadTileUpdatedSignal>(OnRoadTileUpdated);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
}
|
||||
|
||||
private void OnBuildingPlacementAnimationStarted(BuildingPlacementAnimationStartedSignal signal)
|
||||
{
|
||||
RemoveResourceNodesAt(signal.Rect);
|
||||
}
|
||||
|
||||
private void OnRoadTileUpdated(RoadTileUpdatedSignal signal)
|
||||
{
|
||||
RemoveResourceNodesAt(TileRect.OneTile(signal.Tile));
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
RemoveResourceNodesAt(signal.Rect);
|
||||
}
|
||||
|
||||
private void RemoveResourceNodesAt(TileRect rect)
|
||||
{
|
||||
using var _ = ListPool<ResourceNode>.Get(out var deletedResourceNodes);
|
||||
|
||||
_world.RawResources.DeleteResourceNodes(rect, _config.GeneralSettings.BaseElevation, deletedResourceNodes);
|
||||
if (deletedResourceNodes.Count == 0) return;
|
||||
|
||||
_signalBus.Raise(new RawResourcesRemovedSignal(deletedResourceNodes, RawResourcesRemovedSignal.RemovalReason.PlayerAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct RawResourcesRemovedSignal
|
||||
{
|
||||
public List<ResourceNode> ResourceNodes;
|
||||
|
||||
public RemovalReason Reason;
|
||||
|
||||
public RawResourcesRemovedSignal(List<ResourceNode> resourceNodes, RemovalReason reason)
|
||||
{
|
||||
ResourceNodes = resourceNodes;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
public enum RemovalReason
|
||||
{
|
||||
PlayerAction,
|
||||
|
||||
Harvested
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class RawResourcesState : IEnumerable<ResourceNode>
|
||||
{
|
||||
private const int SpatialLookupCellSize = 5;
|
||||
|
||||
private Dictionary<int, ResourceNode> _resourceNodesById = new();
|
||||
|
||||
private Dictionary<int, SpatialLookup<(int, bool)>> _spatialLookups = new();
|
||||
|
||||
public int Count => _resourceNodesById.Count;
|
||||
|
||||
public void AddResourceNode(in ResourceNode node)
|
||||
{
|
||||
_resourceNodesById.Add(node.Id, node);
|
||||
GetLookup(node.Elevation).Add((node.Id, node.CanBeDeleted), TileRect.OneTile(node.Tile), node.Id);
|
||||
}
|
||||
|
||||
public bool Exists(int id)
|
||||
{
|
||||
return _resourceNodesById.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool TryGetResourceNode(int id, out ResourceNode node)
|
||||
{
|
||||
return _resourceNodesById.TryGetValue(id, out node);
|
||||
}
|
||||
|
||||
public void GetResourceNodes(TileRect rect, int elevation, List<(int, bool)> resourceNodeIds)
|
||||
{
|
||||
GetLookup(elevation).Find(rect, resourceNodeIds);
|
||||
}
|
||||
|
||||
public bool DeleteResourceNode(int id, int elevation, out ResourceNode resourceNode)
|
||||
{
|
||||
var result = _resourceNodesById.Remove(id, out resourceNode);
|
||||
if (result) GetLookup(elevation).Remove(TileRect.OneTile(resourceNode.Tile), id);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void DeleteResourceNodes(TileRect rect, int elevation, List<ResourceNode> deletedResourceNodes = null)
|
||||
{
|
||||
using var removeIdsScope = ListPool<(int, bool)>.Get(out var removeIds);
|
||||
GetLookup(elevation)
|
||||
.RemoveAll(
|
||||
rect,
|
||||
value =>
|
||||
{
|
||||
var (_, canBeDeleted) = value;
|
||||
return canBeDeleted;
|
||||
},
|
||||
removeIds);
|
||||
|
||||
foreach (var (id, canBeDeleted) in removeIds)
|
||||
{
|
||||
_resourceNodesById.Remove(id, out var resourceNode);
|
||||
deletedResourceNodes?.Add(resourceNode);
|
||||
}
|
||||
}
|
||||
|
||||
private SpatialLookup<(int, bool)> GetLookup(int elevation)
|
||||
{
|
||||
if (!_spatialLookups.TryGetValue(elevation, out var lookup))
|
||||
{
|
||||
lookup = new SpatialLookup<(int, bool)>(SpatialLookupCellSize);
|
||||
_spatialLookups.Add(elevation, lookup);
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
|
||||
public Dictionary<int, ResourceNode>.ValueCollection.Enumerator GetEnumerator()
|
||||
{
|
||||
return _resourceNodesById.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<ResourceNode> IEnumerable<ResourceNode>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Source/Riversong/Game/World/RawResources/ResourceNode.cs
Normal file
21
Source/Riversong/Game/World/RawResources/ResourceNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ResourceNode
|
||||
{
|
||||
public int Id;
|
||||
|
||||
public int DefinitionId;
|
||||
|
||||
public float3 Position;
|
||||
|
||||
public int2 Tile;
|
||||
|
||||
public Directions Orientation;
|
||||
|
||||
public int Elevation;
|
||||
|
||||
public bool CanBeDeleted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ResourceNodeDefinition", menuName = "Riversong Code Showcase/Resource Node Definition")]
|
||||
public class ResourceNodeDefinition : GameDataAsset
|
||||
{
|
||||
public AssetReferenceT<GameObject> Prefab;
|
||||
|
||||
public AudioResource DeleteAudio;
|
||||
|
||||
public bool RandomRotation = true;
|
||||
|
||||
public Vector2 ScaleRange = new(1, 1);
|
||||
|
||||
[TitleGroup("Harvesting")]
|
||||
[LabelText("Agent")]
|
||||
public AgentDefinition HarvesterAgent;
|
||||
|
||||
public ProductDefinition Product;
|
||||
|
||||
[HorizontalGroup("Dropped Amount")]
|
||||
[LabelText("Dropped Min")]
|
||||
public int MinDroppedAmount = 10;
|
||||
|
||||
[HorizontalGroup("Dropped Amount")]
|
||||
[LabelText("Dropped Max")]
|
||||
public int MaxDroppedAmount = 15;
|
||||
|
||||
public AgentAnimation HarvestAnimation;
|
||||
|
||||
public float HarvestPositionOffset = 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ResourceNodeHarvestingSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackFactory _productStackFactory;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private IGameDatabase _gameDatabase;
|
||||
|
||||
public ResourceNodeHarvestingSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<RawResourceHarvestedSignal>(OnRawResourceHarvested);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<RawResourceHarvestedSignal>(OnRawResourceHarvested);
|
||||
}
|
||||
|
||||
private void OnRawResourceHarvested(RawResourceHarvestedSignal signal)
|
||||
{
|
||||
if (!_world.RawResources.DeleteResourceNode(signal.RawResourceId, _config.GeneralSettings.BaseElevation, out var resourceNode))
|
||||
{
|
||||
Debug.LogError("Could not find harvested Resource Node");
|
||||
return;
|
||||
}
|
||||
|
||||
var definition = _gameDatabase.WithId<ResourceNodeDefinition>(resourceNode.DefinitionId);
|
||||
var amount = Random.Range(definition.MinDroppedAmount, definition.MaxDroppedAmount + 1);
|
||||
_productStackFactory.CreateOrMerge(resourceNode.Tile, definition.Product, amount);
|
||||
|
||||
using var resourceNodesScope = ListPool<ResourceNode>.Get(out var resourceNodes);
|
||||
resourceNodes.Add(resourceNode);
|
||||
_signalBus.Raise(new RawResourcesRemovedSignal(resourceNodes, RawResourcesRemovedSignal.RemovalReason.Harvested));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class TreeFallAnimation : MonoBehaviour, IDestroyRawResourceAnimation
|
||||
{
|
||||
[SerializeField]
|
||||
private float _fallDelay;
|
||||
|
||||
[SerializeField]
|
||||
private float _fallDuration = 1;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationCurve _fallRotationCurve = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
|
||||
[SerializeField]
|
||||
private float _sinkDelay = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
private float _sinkDepth = 1;
|
||||
|
||||
[SerializeField]
|
||||
private float _sinkSpeed = 1;
|
||||
|
||||
public async UniTask PlayAsync()
|
||||
{
|
||||
await FallAsync();
|
||||
await SinkAsync();
|
||||
}
|
||||
|
||||
private async UniTask FallAsync()
|
||||
{
|
||||
await UniTask.WaitForSeconds(_fallDelay);
|
||||
|
||||
var axis = Quaternion.Euler(0, Random.value * 360, 0) * Vector3.forward;
|
||||
|
||||
var initialRotation = transform.rotation;
|
||||
var finalRotation = initialRotation * Quaternion.AngleAxis(90, axis);
|
||||
|
||||
var t = 0f;
|
||||
while (t < 1)
|
||||
{
|
||||
t += Time.deltaTime / _fallDuration;
|
||||
|
||||
transform.rotation = Quaternion.Lerp(initialRotation, finalRotation, _fallRotationCurve.Evaluate(t));
|
||||
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
|
||||
transform.rotation = finalRotation;
|
||||
}
|
||||
|
||||
private async UniTask SinkAsync()
|
||||
{
|
||||
await UniTask.WaitForSeconds(_sinkDelay);
|
||||
|
||||
var initialY = transform.position.y;
|
||||
var finalY = initialY - _sinkDepth;
|
||||
|
||||
while (transform.position.y > finalY)
|
||||
{
|
||||
transform.position += Vector3.down * _sinkSpeed * Time.deltaTime;
|
||||
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
|
||||
transform.position = new Vector3(transform.position.x, finalY, transform.position.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user