using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; namespace DanieleMarotta.RiversongCodeShowcase { public class BuildingBadgeUIController : UIControllerSystem, IInitializable, IDisposable, IUpdatable { [InjectService] private ISignalBus _signalBus; [InjectService] private IWorldUIService _worldUIService; [InjectService] private ICameraProperties _cameraProperties; [InjectService] private GameConfig _gameConfig; [InjectService] private World _world; private VisualElement _container; private readonly Dictionary _entries = new(); public BuildingBadgeUIController(IServiceLocator serviceLocator) : base(serviceLocator) { } public override async UniTask InitializeAsync() { await base.InitializeAsync(); _container = UIRoot.RootVisualElement.Q(className: "world-ui-layer"); _signalBus.Subscribe(OnBuildingVisualizationCreated); _signalBus.Subscribe(OnBuildingDeleted); } public void Dispose() { _signalBus.Unsubscribe(OnBuildingVisualizationCreated); _signalBus.Unsubscribe(OnBuildingDeleted); } private void OnBuildingVisualizationCreated(BuildingVisualizationCreatedSignal signal) { var building = signal.Building; if (building.Definition.WorkerCount <= 0) return; CreateBadgeAsync(building, signal.Visualization).Forget(); } private async UniTask CreateBadgeAsync(Building building, BuildingVisualization visualization) { var element = UIService.TemplateLibrary.WorldUI.Badge.CloneTree(); _container.Add(element); var view = (BuildingBadgeUIView)await UIService.CreateView(typeof(BuildingBadgeUIView), element); view.RootElement.SetPickingModeRecursive(PickingMode.Ignore); view.Show(false); _worldUIService.Register(view.RootElement, visualization.transform); _entries.Add(building.Id, new BadgeEntry(building, view)); } private void OnBuildingDeleted(BuildingDeletedSignal signal) { if (!_entries.Remove(signal.Building.Id, out var entry)) return; _worldUIService.Unregister(entry.View.RootElement); entry.View.RootElement.RemoveFromHierarchy(); } public void Update() { var zoomRange = _gameConfig.Camera.ZoomRange; var config = UIService.TemplateLibrary.WorldUI; var normalizedZoom = Mathf.InverseLerp(zoomRange.x, config.BadgeZoomCullThreshold, _cameraProperties.Zoom); var offset = Mathf.Lerp(config.BadgeYOffset.x, config.BadgeYOffset.y, normalizedZoom); var scale = Mathf.Lerp(config.BadgeScale.x, config.BadgeScale.y, normalizedZoom); var culledByZoom = _cameraProperties.Zoom > config.BadgeZoomCullThreshold; UpdateBadges(culledByZoom); foreach (var entry in _entries.Values) { entry.View.RootElement.SetScale(scale); _worldUIService.SetWorldOffset(entry.View.RootElement, offset * Vector3.up); } } private void UpdateBadges(bool culledByZoom) { foreach (var entry in _entries.Values) { ref var sleepState = ref entry.Building.GetSleepStateRW(); var show = !culledByZoom && _world.TimeState.DayNightCycleStep == DayNightCycleStep.Day && sleepState.HasHomelessWorkers; entry.View.Show(show); } } private readonly struct BadgeEntry { public readonly Building Building; public readonly BuildingBadgeUIView View; public BadgeEntry(Building building, BuildingBadgeUIView view) { Building = building; View = view; } } } }