riversong code showcase
This commit is contained in:
126
Source/Riversong/Game/EditTools/BuildTool/BuildTool.cs
Normal file
126
Source/Riversong/Game/EditTools/BuildTool/BuildTool.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildTool : EditTool
|
||||
{
|
||||
private IPointerService _pointerService;
|
||||
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
private IEditToolValidatorService _validator;
|
||||
|
||||
private IBuildToolPreviewManager _previewManager;
|
||||
|
||||
private Directions _buildingOrientation;
|
||||
|
||||
private EditToolValidationResult _validationResult;
|
||||
|
||||
public BuildTool(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public BuildingDefinition Building { get; set; }
|
||||
|
||||
public BuildToolPreview Preview { get; private set; }
|
||||
|
||||
public TileRect BuildingRect { get; private set; }
|
||||
|
||||
public override UniTask InitializeAsync()
|
||||
{
|
||||
var config = ServiceLocator.GetService<GameConfig>();
|
||||
|
||||
_pointerService = ServiceLocator.GetService<IPointerService>();
|
||||
_tileSpace = ServiceLocator.GetService<ITileSpace>();
|
||||
_validator = ServiceLocator.GetService<IEditToolValidatorService>();
|
||||
_previewManager = ServiceLocator.GetService<IBuildToolPreviewManager>();
|
||||
Preview = new BuildToolPreview(config.UI.BuildTool, _tileSpace);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public override void OnEnabled()
|
||||
{
|
||||
base.OnEnabled();
|
||||
|
||||
if (!Building)
|
||||
{
|
||||
Debug.LogError("Building not set when activating Build Tool");
|
||||
return;
|
||||
}
|
||||
|
||||
Preview.PrepareForBuilding(Building);
|
||||
|
||||
_buildingOrientation = Directions.North;
|
||||
}
|
||||
|
||||
public override void OnDisabled()
|
||||
{
|
||||
base.OnDisabled();
|
||||
|
||||
Preview.Release();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var pointerOnTerrain = _pointerService.TryGetPositionOnTerrain(out var position);
|
||||
|
||||
var keyboard = Keyboard.current;
|
||||
if (keyboard.tKey.wasPressedThisFrame) _buildingOrientation = (Directions)(((int)_buildingOrientation + 6) % 8);
|
||||
|
||||
var buildingCenter = _tileSpace.WorldToTile(position);
|
||||
BuildingRect = TileMath.GetBuildingRect(buildingCenter, _buildingOrientation, Building.Width, Building.Height);
|
||||
|
||||
_validationResult = ValidatePlacement(pointerOnTerrain);
|
||||
var isValid = _validationResult == EditToolValidationResult.Success;
|
||||
|
||||
AffectedTiles.Clear();
|
||||
if (pointerOnTerrain)
|
||||
{
|
||||
var highlightType = isValid ? TileHighlightType.ValidTile : TileHighlightType.InvalidTile;
|
||||
foreach (var tile in TileRange.From(BuildingRect)) AffectedTiles.Add((tile, highlightType));
|
||||
}
|
||||
|
||||
Preview.Update(isValid, position, _buildingOrientation);
|
||||
|
||||
if (isValid && _pointerService.TryConsumeLeftClick()) Build();
|
||||
}
|
||||
|
||||
private EditToolValidationResult ValidatePlacement(bool pointerOnTerrain)
|
||||
{
|
||||
if (!pointerOnTerrain) return EditToolValidationResult.BlockedTile;
|
||||
|
||||
var commonValidation = _validator.DoCommonValidation(BuildingRect, BlockReason.CannotBuild);
|
||||
if (commonValidation != EditToolValidationResult.Success) return commonValidation;
|
||||
|
||||
return _validator.ValidateBuildingPlacementRules(BuildingRect, Building);
|
||||
}
|
||||
|
||||
public EditToolValidationResult GetLastValidationResult()
|
||||
{
|
||||
return _validationResult;
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
_previewManager.PlayPlacementAnimationAndBuild(Preview, Building, BuildingRect, _buildingOrientation);
|
||||
}
|
||||
|
||||
public override void GetDeleteGameObjectsPreviewInfo(out DeletedGameObjectsFilter filter, out TileRect rect)
|
||||
{
|
||||
if (_validationResult != EditToolValidationResult.Success)
|
||||
{
|
||||
filter = DeletedGameObjectsFilter.None;
|
||||
rect = TileRect.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
filter = DeletedGameObjectsFilter.RawResources | DeletedGameObjectsFilter.ProductStacks;
|
||||
rect = BuildingRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Source/Riversong/Game/EditTools/BuildTool/BuildToolPreview.cs
Normal file
126
Source/Riversong/Game/EditTools/BuildTool/BuildToolPreview.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildToolPreview
|
||||
{
|
||||
private readonly GameConfig.UIConfig.BuildToolConfig _config;
|
||||
|
||||
private readonly ITileSpace _tileSpace;
|
||||
|
||||
private BuildingDefinition _building;
|
||||
|
||||
private Vector3 _tilt;
|
||||
|
||||
private Vector3 _tiltVelocity;
|
||||
|
||||
private float _yaw;
|
||||
|
||||
private float _yawVelocity;
|
||||
|
||||
public BuildToolPreview(GameConfig.UIConfig.BuildToolConfig config, ITileSpace tileSpace)
|
||||
{
|
||||
_config = config;
|
||||
_tileSpace = tileSpace;
|
||||
}
|
||||
|
||||
public GameObject PreviewObject { get; private set; }
|
||||
|
||||
public bool IsVisible => PreviewObject && PreviewObject.activeSelf;
|
||||
|
||||
public float3 Position => PreviewObject ? PreviewObject.transform.position : Vector3.zero;
|
||||
|
||||
public void PrepareForBuilding(BuildingDefinition building)
|
||||
{
|
||||
_building = building;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
ClearPreviewObject();
|
||||
}
|
||||
|
||||
public void Update(bool isValid, Vector3 pointer, Directions orientation)
|
||||
{
|
||||
if (!PreviewObject)
|
||||
{
|
||||
if (_building.Visualization.IsDone)
|
||||
CreatePreviewObject();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
var snap = !PreviewObject.activeSelf && isValid;
|
||||
if (snap)
|
||||
{
|
||||
_tilt = Vector3.zero;
|
||||
_tiltVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
PreviewObject.SetActive(isValid);
|
||||
if (!isValid) return;
|
||||
|
||||
var rect = TileMath.GetBuildingRect(_tileSpace.WorldToTile(pointer), orientation, _building.Width, _building.Height);
|
||||
var position = _tileSpace.GetRectWorldCenter(rect);
|
||||
position.y += _config.Height;
|
||||
|
||||
var dt = Time.unscaledDeltaTime;
|
||||
|
||||
position = snap ? position : Vector3.Lerp(PreviewObject.transform.position, position, _config.LerpFactor * dt);
|
||||
|
||||
var yaw = orientation.ToQuaternion().eulerAngles.y;
|
||||
_yaw = snap ? yaw : Mathf.SmoothDampAngle(_yaw, yaw, ref _yawVelocity, 0.1f, Mathf.Infinity, dt);
|
||||
var rotation = Quaternion.Euler(0, _yaw, 0);
|
||||
if (!snap) rotation = SimulateSpring(position, dt) * rotation;
|
||||
|
||||
PreviewObject.transform.SetPositionAndRotation(position, rotation);
|
||||
}
|
||||
|
||||
private Quaternion SimulateSpring(Vector3 targetPosition, float dt)
|
||||
{
|
||||
var positionDelta = targetPosition - PreviewObject.transform.position;
|
||||
|
||||
_tilt -= positionDelta * _config.ImpulseScale;
|
||||
_tilt = _tilt.normalized * Mathf.Min(_tilt.magnitude, _config.MaxTilt);
|
||||
|
||||
_tiltVelocity -= _tilt * (_config.Elasticity * dt);
|
||||
_tiltVelocity *= Mathf.Pow(1 - _config.Damping, dt);
|
||||
|
||||
_tilt += _tiltVelocity * dt;
|
||||
|
||||
if (_tilt.sqrMagnitude > 0.001f)
|
||||
{
|
||||
var axis = Vector3.Cross(Vector3.down, _tilt.normalized);
|
||||
var angle = _tilt.magnitude / _config.MaxTilt * _config.MaxTiltAngle;
|
||||
return Quaternion.AngleAxis(angle, axis);
|
||||
}
|
||||
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
private void CreatePreviewObject()
|
||||
{
|
||||
PreviewObject = Object.Instantiate((GameObject)_building.Visualization.Asset);
|
||||
PreviewObject.SetLayerRecursively<Renderer>(GameObjectLayers.IgnoreAoE);
|
||||
|
||||
using var _ = ListPool<HideOnBuildingPreview>.Get(out var hide);
|
||||
PreviewObject.GetComponentsInChildren(hide);
|
||||
foreach (var obj in hide) obj.gameObject.SetActive(false);
|
||||
|
||||
// Start the preview in the disabled state, causes the position to be snapped when enabled
|
||||
PreviewObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void ClearPreviewObject()
|
||||
{
|
||||
if (PreviewObject)
|
||||
{
|
||||
Object.Destroy(PreviewObject);
|
||||
PreviewObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IBuildToolPreviewManager))]
|
||||
public class BuildToolPreviewManager : GameSystem, IBuildToolPreviewManager
|
||||
{
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
public BuildToolPreviewManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void PlayPlacementAnimationAndBuild(BuildToolPreview preview, BuildingDefinition definition, TileRect rect, Directions orientation)
|
||||
{
|
||||
_ = PlayPlacementAnimationAndBuildAsync(preview, definition, rect, orientation);
|
||||
}
|
||||
|
||||
private async UniTask PlayPlacementAnimationAndBuildAsync(BuildToolPreview preview, BuildingDefinition definition, TileRect rect, Directions orientation)
|
||||
{
|
||||
_signalBus.Raise(new BuildingPlacementAnimationStartedSignal(definition, rect, orientation));
|
||||
|
||||
var animatedObject = Object.Instantiate(preview.PreviewObject);
|
||||
|
||||
preview.ClearPreviewObject();
|
||||
|
||||
var placementAnimation = animatedObject.GetComponent<BuildingPlacementAnimation>();
|
||||
if (placementAnimation)
|
||||
{
|
||||
var finalPosition = _tileSpace.GetRectWorldCenter(rect);
|
||||
var finalRotation = orientation.ToQuaternion();
|
||||
|
||||
await placementAnimation.PlayAsync(definition, animatedObject.transform.position, finalPosition, finalRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Prefab '{definition.name}' has no {nameof(BuildingPlacementAnimation)} component");
|
||||
}
|
||||
|
||||
_signalBus.Raise(new BuildingPlacementAnimationCompletedSignal(definition, rect, orientation));
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
Object.Destroy(animatedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildToolValidator
|
||||
{
|
||||
private readonly World _world;
|
||||
|
||||
private readonly int _baseElevation;
|
||||
|
||||
public BuildToolValidator(World world, int baseElevation)
|
||||
{
|
||||
_world = world;
|
||||
_baseElevation = baseElevation;
|
||||
}
|
||||
|
||||
public bool ValidatePlacement(List<int2> affectedTiles)
|
||||
{
|
||||
foreach (var tile in affectedTiles)
|
||||
{
|
||||
if (_world.Heightmap.GetValue(tile) != _baseElevation) return false;
|
||||
|
||||
if (_world.BlockMap.IsBlocked(tile)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[UpdateAfter(typeof(EditingStateGameSystem))]
|
||||
public class GameObjectsHighlightingSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _gameConfig;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private EditingState _editingState;
|
||||
|
||||
[InjectService]
|
||||
private UIState _uiState;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingSpatialQuery _buildingSpatialQuery;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackVisualizationCollection _productStackVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private IRawResourceVisualizationCollection _rawResourceVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private IAgentVisualizationCollection _agentVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingVisualizationCollection _buildingVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
public GameObjectsHighlightingSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<CollectHighlightedGameObjectsSignal>(OnCollectHighlightedGameObjects);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<CollectHighlightedGameObjectsSignal>(OnCollectHighlightedGameObjects);
|
||||
}
|
||||
|
||||
private bool TryGetSourceBuilding(out BuildingDefinition building, out TileRect sourceRect)
|
||||
{
|
||||
var tool = _editingState.ActiveTool;
|
||||
if (tool is BuildTool buildTool && buildTool.Preview.IsVisible)
|
||||
{
|
||||
building = buildTool.Building;
|
||||
sourceRect = buildTool.BuildingRect;
|
||||
return true;
|
||||
}
|
||||
|
||||
var selectedBuilding = _uiState.SelectedBuilding;
|
||||
if (selectedBuilding != null)
|
||||
{
|
||||
building = selectedBuilding.Definition;
|
||||
sourceRect = selectedBuilding.Rect;
|
||||
return true;
|
||||
}
|
||||
|
||||
building = null;
|
||||
sourceRect = TileRect.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnCollectHighlightedGameObjects(CollectHighlightedGameObjectsSignal signal)
|
||||
{
|
||||
if (!TryGetSourceBuilding(out var building, out var sourceRect)) return;
|
||||
|
||||
CollectBuildings(signal.GameObjects, building, sourceRect);
|
||||
CollectProductStacks(signal.GameObjects, building, sourceRect);
|
||||
CollectResources(signal.GameObjects, building, sourceRect);
|
||||
CollectCritters(signal.GameObjects, building, sourceRect);
|
||||
}
|
||||
|
||||
private void CollectBuildings(List<GameObject> gameObjects, BuildingDefinition sourceBuilding, TileRect sourceRect)
|
||||
{
|
||||
if (sourceBuilding.ProvidedProducts.Count > 0)
|
||||
{
|
||||
foreach (var house in _entityCache.GetHouses())
|
||||
{
|
||||
if (TileMath.StepCount(sourceRect, house.Rect) > sourceBuilding.Range) continue;
|
||||
AddHighlightedBuilding(gameObjects, house);
|
||||
}
|
||||
|
||||
if (sourceBuilding.FetchesProducts)
|
||||
foreach (var storage in _entityCache.GetStorageBuildings())
|
||||
{
|
||||
if (TileMath.StepCount(sourceRect, storage.Rect) > sourceBuilding.Range) continue;
|
||||
AddHighlightedBuilding(gameObjects, storage);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceBuilding.IsHouse)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForHouse(sourceRect, providers);
|
||||
|
||||
foreach (var provider in providers) AddHighlightedBuilding(gameObjects, provider);
|
||||
}
|
||||
|
||||
if (sourceBuilding.IsStorage)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForStorage(sourceRect, providers);
|
||||
|
||||
foreach (var provider in providers) AddHighlightedBuilding(gameObjects, provider);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddHighlightedBuilding(List<GameObject> gameObjects, Building building)
|
||||
{
|
||||
if (_buildingVisualizationCollection.TryGetVisualization(building.Id, out var visualization)) gameObjects.Add(visualization.gameObject);
|
||||
}
|
||||
|
||||
private void CollectProductStacks(List<GameObject> gameObjects, BuildingDefinition sourceBuilding, TileRect sourceRect)
|
||||
{
|
||||
if (!sourceBuilding.IsStorage) return;
|
||||
|
||||
var rangeRect = sourceRect.Inflate(sourceBuilding.Range);
|
||||
using var productStacksScope = ListPool<ProductStack>.Get(out var productStacks);
|
||||
_world.ProductStacks.Find(rangeRect, productStacks);
|
||||
|
||||
foreach (var productStack in productStacks)
|
||||
{
|
||||
if (!_productStackVisualizationCollection.TryGetVisualization(productStack.Id, out var visualization)) continue;
|
||||
|
||||
gameObjects.Add(visualization);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectResources(List<GameObject> gameObjects, BuildingDefinition sourceBuilding, TileRect sourceRect)
|
||||
{
|
||||
if (!sourceBuilding.HarvestedResource) return;
|
||||
|
||||
var rangeRect = sourceRect.Inflate(sourceBuilding.Range);
|
||||
using var resourceIdsScope = ListPool<(int, bool)>.Get(out var resourceIds);
|
||||
_world.RawResources.GetResourceNodes(rangeRect, _gameConfig.GeneralSettings.BaseElevation, resourceIds);
|
||||
|
||||
foreach (var (id, _) in resourceIds)
|
||||
{
|
||||
_world.RawResources.TryGetResourceNode(id, out var resourceNode);
|
||||
if (resourceNode.DefinitionId != sourceBuilding.HarvestedResource.RuntimeId) continue;
|
||||
|
||||
if (!_rawResourceVisualizationCollection.TryGetVisualization(resourceNode.Id, out var visualization)) continue;
|
||||
|
||||
gameObjects.Add(visualization);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectCritters(List<GameObject> gameObjects, BuildingDefinition sourceBuilding, TileRect sourceRect)
|
||||
{
|
||||
if (!sourceBuilding.TargetCritter) return;
|
||||
|
||||
var rangeRect = sourceRect.Inflate(sourceBuilding.Range);
|
||||
foreach (var critter in _entityCache.GetCritterAgents())
|
||||
{
|
||||
ref var critterState = ref critter.GetCritterStateRW();
|
||||
if (critterState.CritterDefinitionId != sourceBuilding.TargetCritter.RuntimeId) continue;
|
||||
|
||||
var critterTile = _tileSpace.WorldToTile(critter.Position);
|
||||
if (!rangeRect.Contains(critterTile)) continue;
|
||||
|
||||
if (!_agentVisualizationCollection.TryGetVisualization(critter.Id, out var visualization)) continue;
|
||||
|
||||
gameObjects.Add(visualization.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class HideOnBuildingPreview : MonoBehaviour
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildToolPreviewManager
|
||||
{
|
||||
void PlayPlacementAnimationAndBuild(BuildToolPreview preview, BuildingDefinition definition, TileRect rect, Directions orientation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct CollectDeletedGameObjectsSignal
|
||||
{
|
||||
public DeletedGameObjectsFilter Filter;
|
||||
|
||||
public TileRect Rect;
|
||||
|
||||
public List<GameObject> GameObjects;
|
||||
|
||||
public CollectDeletedGameObjectsSignal(DeletedGameObjectsFilter filter, TileRect rect, List<GameObject> gameObjects)
|
||||
{
|
||||
Filter = filter;
|
||||
Rect = rect;
|
||||
GameObjects = gameObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct CollectHighlightedGameObjectsSignal
|
||||
{
|
||||
public List<GameObject> GameObjects;
|
||||
|
||||
public CollectHighlightedGameObjectsSignal(List<GameObject> gameObjects)
|
||||
{
|
||||
GameObjects = gameObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Source/Riversong/Game/EditTools/DeleteTool/DeleteTool.cs
Normal file
57
Source/Riversong/Game/EditTools/DeleteTool/DeleteTool.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class DeleteTool : DragTool
|
||||
{
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
public DeleteTool(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DeletedGameObjectsFilter DeletedGameObjectsFilter => DeletedGameObjectsFilter.All;
|
||||
|
||||
public override async UniTask InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
|
||||
_signalBus = ServiceLocator.GetService<ISignalBus>();
|
||||
}
|
||||
|
||||
protected override bool Validate(ref int2 startTile, ref int2 endTile)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateAffectedTiles(bool isValid, int2 startTile, int2 endTile)
|
||||
{
|
||||
foreach (var tile in TileRange.From(startTile, endTile)) AffectedTiles.Add((tile, TileHighlightType.DeletePreview));
|
||||
}
|
||||
|
||||
protected override void DoTool(int2 startTile, int2 endTile)
|
||||
{
|
||||
_ = DoToolAsync(new TileRect(startTile, endTile));
|
||||
}
|
||||
|
||||
private async UniTask DoToolAsync(TileRect rect)
|
||||
{
|
||||
const int count = 10;
|
||||
|
||||
var countX = (int)math.ceil((float)rect.Width / count);
|
||||
var countY = (int)math.ceil((float)rect.Height / count);
|
||||
|
||||
for (var x = 0; x < countX; x++)
|
||||
for (var y = 0; y < countY; y++)
|
||||
{
|
||||
var r = new TileRect(rect.Min + new int2(x, y) * count, count, count);
|
||||
r.Max = math.min(r.Max, rect.Max);
|
||||
|
||||
_signalBus.Raise(new DoDeleteToolSignal(r));
|
||||
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct DoDeleteToolSignal
|
||||
{
|
||||
public TileRect Rect;
|
||||
|
||||
public DoDeleteToolSignal(TileRect rect)
|
||||
{
|
||||
Rect = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Source/Riversong/Game/EditTools/DeletedGameObjectsFilter.cs
Normal file
18
Source/Riversong/Game/EditTools/DeletedGameObjectsFilter.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Flags]
|
||||
public enum DeletedGameObjectsFilter
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Buildings = 1,
|
||||
|
||||
RawResources = 1 << 1,
|
||||
|
||||
ProductStacks = 1 << 2,
|
||||
|
||||
All = ~None
|
||||
}
|
||||
}
|
||||
96
Source/Riversong/Game/EditTools/DragTool.cs
Normal file
96
Source/Riversong/Game/EditTools/DragTool.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class DragTool : EditTool
|
||||
{
|
||||
private IPointerService _pointerService;
|
||||
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
private bool _isDragging;
|
||||
|
||||
private int2 _startTile;
|
||||
|
||||
private int2 _endTile;
|
||||
|
||||
private bool _isValid;
|
||||
|
||||
protected DragTool(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual DeletedGameObjectsFilter DeletedGameObjectsFilter => DeletedGameObjectsFilter.None;
|
||||
|
||||
public override async UniTask InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
|
||||
_pointerService = ServiceLocator.GetService<IPointerService>();
|
||||
_tileSpace = ServiceLocator.GetService<ITileSpace>();
|
||||
}
|
||||
|
||||
public override void OnDisabled()
|
||||
{
|
||||
base.OnDisabled();
|
||||
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
AffectedTiles.Clear();
|
||||
|
||||
var lmb = Mouse.current.leftButton;
|
||||
|
||||
var isPointerOnTerrain = _pointerService.TryGetPositionOnTerrain(out var position);
|
||||
if (!isPointerOnTerrain)
|
||||
{
|
||||
_isDragging &= lmb.isPressed;
|
||||
_isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isDragging)
|
||||
{
|
||||
_startTile = _tileSpace.WorldToTile(position);
|
||||
_endTile = _startTile;
|
||||
}
|
||||
_isDragging |= _pointerService.TryConsumeLeftClick();
|
||||
if (_isDragging && !_pointerService.IsPointerOverUI) _endTile = _tileSpace.WorldToTile(position);
|
||||
|
||||
_isValid = Validate(ref _startTile, ref _endTile);
|
||||
UpdateAffectedTiles(_isValid, _startTile, _endTile);
|
||||
|
||||
if (lmb.wasReleasedThisFrame)
|
||||
{
|
||||
if (_isValid && !_pointerService.IsPointerOverUI) DoTool(_startTile, _endTile);
|
||||
|
||||
_isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool Validate(ref int2 startTile, ref int2 endTile);
|
||||
|
||||
protected abstract void UpdateAffectedTiles(bool isValid, int2 startTile, int2 endTile);
|
||||
|
||||
protected abstract void DoTool(int2 startTile, int2 endTile);
|
||||
|
||||
public override void GetDeleteGameObjectsPreviewInfo(out DeletedGameObjectsFilter filter, out TileRect rect)
|
||||
{
|
||||
if (!_isValid)
|
||||
{
|
||||
filter = DeletedGameObjectsFilter.None;
|
||||
rect = TileRect.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
filter = DeletedGameObjectsFilter;
|
||||
rect = new TileRect(_startTile, _endTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Source/Riversong/Game/EditTools/EditTool.cs
Normal file
46
Source/Riversong/Game/EditTools/EditTool.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class EditTool : IDisposable
|
||||
{
|
||||
protected EditTool(IServiceLocator serviceLocator)
|
||||
{
|
||||
ServiceLocator = serviceLocator;
|
||||
}
|
||||
|
||||
protected IServiceLocator ServiceLocator { get; }
|
||||
|
||||
public List<(int2, TileHighlightType)> AffectedTiles { get; } = new();
|
||||
|
||||
public virtual UniTask InitializeAsync()
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnEnabled()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnDisabled()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void GetDeleteGameObjectsPreviewInfo(out DeletedGameObjectsFilter filter, out TileRect rect)
|
||||
{
|
||||
filter = DeletedGameObjectsFilter.None;
|
||||
rect = TileRect.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Source/Riversong/Game/EditTools/EditingState.cs
Normal file
30
Source/Riversong/Game/EditTools/EditingState.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class EditingState : IDisposable
|
||||
{
|
||||
public BuildTool BuildTool { get; set; }
|
||||
|
||||
public DeleteTool DeleteTool { get; set; }
|
||||
|
||||
public RoadTool RoadTool { get; set; }
|
||||
|
||||
public EditTool ActiveTool { get; set; }
|
||||
|
||||
public async UniTask InitializeAsync()
|
||||
{
|
||||
await BuildTool.InitializeAsync();
|
||||
await DeleteTool.InitializeAsync();
|
||||
await RoadTool.InitializeAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BuildTool.Dispose();
|
||||
DeleteTool.Dispose();
|
||||
RoadTool.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Source/Riversong/Game/EditTools/EditingStateGameSystem.cs
Normal file
151
Source/Riversong/Game/EditTools/EditingStateGameSystem.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using IServiceProvider = DanieleMarotta.RiversongCodeShowcase.IServiceProvider;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[RequiresWorldReadyForUpdate]
|
||||
public class EditingStateGameSystem : GameSystem, IServiceProvider, IInitializable, IDisposable, IUpdatable, IEditingService
|
||||
{
|
||||
[InjectService]
|
||||
private ICancelAction _cancelAction;
|
||||
|
||||
[InjectService]
|
||||
private WorldRenderingState _renderingState;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private MaterialReplacementCache _materialReplacementCache;
|
||||
|
||||
public EditingStateGameSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public EditingState EditingState { get; private set; }
|
||||
|
||||
public event Action<EditTool> ActiveToolChanged;
|
||||
|
||||
public void RegisterServices(IServiceLocator serviceLocator)
|
||||
{
|
||||
serviceLocator.RegisterService<IEditingService>(this);
|
||||
|
||||
EditingState = new EditingState();
|
||||
serviceLocator.RegisterService(EditingState);
|
||||
}
|
||||
|
||||
public async UniTask InitializeAsync()
|
||||
{
|
||||
EditingState.BuildTool = new BuildTool(ServiceLocator);
|
||||
EditingState.DeleteTool = new DeleteTool(ServiceLocator);
|
||||
EditingState.RoadTool = new RoadTool(ServiceLocator);
|
||||
await EditingState.InitializeAsync();
|
||||
|
||||
_cancelAction.AddHandler(
|
||||
(int)CancelActions.CancelEditTool,
|
||||
_ =>
|
||||
{
|
||||
if (EditingState.ActiveTool == null) return false;
|
||||
|
||||
DeactivateTool();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditingState?.Dispose();
|
||||
EditingState = null;
|
||||
}
|
||||
|
||||
public void ActivateTool(EditTool tool)
|
||||
{
|
||||
EditingState.ActiveTool?.OnDisabled();
|
||||
EditingState.ActiveTool = tool;
|
||||
EditingState.ActiveTool?.OnEnabled();
|
||||
|
||||
ActiveToolChanged?.Invoke(EditingState.ActiveTool);
|
||||
}
|
||||
|
||||
public void DeactivateTool()
|
||||
{
|
||||
ActivateTool(null);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
UpdateActiveTool();
|
||||
UpdateHighlightedGameObjects();
|
||||
}
|
||||
|
||||
private void UpdateActiveTool()
|
||||
{
|
||||
if (EditingState.ActiveTool == null) return;
|
||||
|
||||
EditingState.ActiveTool.Update();
|
||||
|
||||
UpdateAffectedTiles();
|
||||
UpdateDeletedGameObjects();
|
||||
}
|
||||
|
||||
private void UpdateAffectedTiles()
|
||||
{
|
||||
foreach (var (tile, type) in EditingState.ActiveTool.AffectedTiles)
|
||||
{
|
||||
if (_world.BlockMap.IsBlocked(tile, BlockReason.InvalidElevation)) continue;
|
||||
|
||||
Color32 color;
|
||||
var config = _config.UI.TileHighlight;
|
||||
switch (type)
|
||||
{
|
||||
case TileHighlightType.ValidTile:
|
||||
color = config.ValidColor;
|
||||
break;
|
||||
|
||||
case TileHighlightType.InvalidTile:
|
||||
color = config.InvalidColor;
|
||||
break;
|
||||
|
||||
case TileHighlightType.DeletePreview:
|
||||
color = config.DeletePreviewColor;
|
||||
break;
|
||||
|
||||
default:
|
||||
color = Color.magenta;
|
||||
break;
|
||||
}
|
||||
|
||||
_renderingState.TileHighlight.AddTile(tile, color);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDeletedGameObjects()
|
||||
{
|
||||
EditingState.ActiveTool.GetDeleteGameObjectsPreviewInfo(out var filter, out var rect);
|
||||
if (filter == DeletedGameObjectsFilter.None) return;
|
||||
|
||||
using var gameObjectsScope = ListPool<GameObject>.Get(out var gameObjects);
|
||||
_signalBus.Raise(new CollectDeletedGameObjectsSignal(filter, rect, gameObjects));
|
||||
|
||||
_materialReplacementCache.ReplaceMaterials(gameObjects, _config.UI.DeletedGameObjectsMaterial);
|
||||
}
|
||||
|
||||
private void UpdateHighlightedGameObjects()
|
||||
{
|
||||
using var gameObjectsScope = ListPool<GameObject>.Get(out var gameObjects);
|
||||
_signalBus.Raise(new CollectHighlightedGameObjectsSignal(gameObjects));
|
||||
|
||||
_materialReplacementCache.ReplaceMaterials(gameObjects, _config.UI.HighlightedGameObjectsMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/EditTools/IEditingService.cs
Normal file
15
Source/Riversong/Game/EditTools/IEditingService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IEditingService
|
||||
{
|
||||
EditingState EditingState { get; }
|
||||
|
||||
event Action<EditTool> ActiveToolChanged;
|
||||
|
||||
void ActivateTool(EditTool tool);
|
||||
|
||||
void DeactivateTool();
|
||||
}
|
||||
}
|
||||
50
Source/Riversong/Game/EditTools/RoadTool.cs
Normal file
50
Source/Riversong/Game/EditTools/RoadTool.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class RoadTool : DragTool
|
||||
{
|
||||
private IEditToolValidatorService _validator;
|
||||
|
||||
private IRoadFactory _roadFactory;
|
||||
|
||||
public RoadTool(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DeletedGameObjectsFilter DeletedGameObjectsFilter => DeletedGameObjectsFilter.RawResources;
|
||||
|
||||
public override async UniTask InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
|
||||
_validator = ServiceLocator.GetService<IEditToolValidatorService>();
|
||||
_roadFactory = ServiceLocator.GetService<IRoadFactory>();
|
||||
}
|
||||
|
||||
protected override bool Validate(ref int2 startTile, ref int2 endTile)
|
||||
{
|
||||
var dx = endTile.x - startTile.x;
|
||||
var dy = endTile.y - startTile.y;
|
||||
|
||||
if (math.abs(dx) >= math.abs(dy))
|
||||
endTile = new int2(endTile.x, startTile.y);
|
||||
else
|
||||
endTile = new int2(startTile.x, endTile.y);
|
||||
|
||||
return _validator.DoCommonValidation(new TileRect(startTile, endTile), BlockReason.CannotBuildRoad) == EditToolValidationResult.Success;
|
||||
}
|
||||
|
||||
protected override void UpdateAffectedTiles(bool isValid, int2 startTile, int2 endTile)
|
||||
{
|
||||
var type = isValid ? TileHighlightType.ValidTile : TileHighlightType.InvalidTile;
|
||||
foreach (var tile in TileRange.From(startTile, endTile)) AffectedTiles.Add((tile, type));
|
||||
}
|
||||
|
||||
protected override void DoTool(int2 startTile, int2 endTile)
|
||||
{
|
||||
_roadFactory.CreateRoad(startTile, endTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum EditToolValidationResult
|
||||
{
|
||||
Success,
|
||||
|
||||
BlockedTile,
|
||||
|
||||
CanOnlyBePlacedNearWater,
|
||||
|
||||
CanOnlyBePlacedOnFertileGround
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IEditToolValidatorService))]
|
||||
public class EditToolValidatorSystem : GameSystem, IEditToolValidatorService
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
public EditToolValidatorSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public EditToolValidationResult DoCommonValidation(TileRect rect, BlockReason blockReason)
|
||||
{
|
||||
foreach (var tile in TileRange.From(rect))
|
||||
if (!DoCommonTileValidation(tile, blockReason))
|
||||
return EditToolValidationResult.BlockedTile;
|
||||
|
||||
return EditToolValidationResult.Success;
|
||||
}
|
||||
|
||||
private bool DoCommonTileValidation(int2 tile, BlockReason blockReason)
|
||||
{
|
||||
return _world.Contains(tile) && !_world.BlockMap.IsBlocked(tile, blockReason);
|
||||
}
|
||||
|
||||
public EditToolValidationResult ValidateBuildingPlacementRules(TileRect rect, BuildingDefinition buildingDefinition)
|
||||
{
|
||||
if (buildingDefinition.NearWater)
|
||||
{
|
||||
var waterMap = _world.WaterMap;
|
||||
foreach (var tile in TileRange.From(rect))
|
||||
if (!waterMap.IsNearWater(tile))
|
||||
return EditToolValidationResult.CanOnlyBePlacedNearWater;
|
||||
}
|
||||
|
||||
if (buildingDefinition.RequiresFertileTile)
|
||||
{
|
||||
foreach (var tile in TileRange.From(rect))
|
||||
{
|
||||
var fertility = _world.Fertility.GetValue(tile);
|
||||
if (fertility.MaxFertility > 0) return EditToolValidationResult.Success;
|
||||
}
|
||||
|
||||
return EditToolValidationResult.CanOnlyBePlacedOnFertileGround;
|
||||
}
|
||||
|
||||
return EditToolValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IEditToolValidatorService
|
||||
{
|
||||
EditToolValidationResult DoCommonValidation(TileRect rect, BlockReason blockReason);
|
||||
|
||||
public EditToolValidationResult ValidateBuildingPlacementRules(TileRect rect, BuildingDefinition buildingDefinition);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user