From e9b89a1c6bd093d72ff5a76240b76bed99c70953 Mon Sep 17 00:00:00 2001 From: pathetic meowmeow Date: Thu, 3 Apr 2025 02:58:05 -0400 Subject: [PATCH] Improve sprite fading behaviour (#35863) * Click through faded sprites * Count the mouse position for which sprites to fade --- Content.Client/Clickable/ClickableSystem.cs | 13 +- Content.Client/Gameplay/GameplayStateBase.cs | 12 +- Content.Client/Sprite/SpriteFadeSystem.cs | 118 +++++++++++++----- .../Tests/ClickableTest.cs | 2 +- 4 files changed, 108 insertions(+), 37 deletions(-) diff --git a/Content.Client/Clickable/ClickableSystem.cs b/Content.Client/Clickable/ClickableSystem.cs index 15d13df625..454bff4349 100644 --- a/Content.Client/Clickable/ClickableSystem.cs +++ b/Content.Client/Clickable/ClickableSystem.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Sprite; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Utility; @@ -17,12 +18,14 @@ public sealed class ClickableSystem : EntitySystem private EntityQuery _clickableQuery; private EntityQuery _xformQuery; + private EntityQuery _fadingSpriteQuery; public override void Initialize() { base.Initialize(); _clickableQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); + _fadingSpriteQuery = GetEntityQuery(); } /// @@ -34,7 +37,7 @@ public sealed class ClickableSystem : EntitySystem /// The draw depth for the sprite that captured the click. /// /// True if the click worked, false otherwise. - public bool CheckClick(Entity entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom) + public bool CheckClick(Entity entity, Vector2 worldPos, IEye eye, bool excludeFaded, out int drawDepth, out uint renderOrder, out float bottom) { if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false)) { @@ -52,6 +55,14 @@ public sealed class ClickableSystem : EntitySystem return false; } + if (excludeFaded && _fadingSpriteQuery.Resolve(entity.Owner, ref entity.Comp4, false)) + { + drawDepth = default; + renderOrder = default; + bottom = default; + return false; + } + var sprite = entity.Comp2; var transform = entity.Comp3; diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs index 162c45d412..69e6e0b58b 100644 --- a/Content.Client/Gameplay/GameplayStateBase.cs +++ b/Content.Client/Gameplay/GameplayStateBase.cs @@ -113,18 +113,18 @@ namespace Content.Client.Gameplay return first.IsValid() ? first : null; } - public IEnumerable GetClickableEntities(EntityCoordinates coordinates) + public IEnumerable GetClickableEntities(EntityCoordinates coordinates, bool excludeFaded = true) { var transformSystem = _entitySystemManager.GetEntitySystem(); - return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates)); + return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates), excludeFaded); } - public IEnumerable GetClickableEntities(MapCoordinates coordinates) + public IEnumerable GetClickableEntities(MapCoordinates coordinates, bool excludeFaded = true) { - return GetClickableEntities(coordinates, _eyeManager.CurrentEye); + return GetClickableEntities(coordinates, _eyeManager.CurrentEye, excludeFaded); } - public IEnumerable GetClickableEntities(MapCoordinates coordinates, IEye? eye) + public IEnumerable GetClickableEntities(MapCoordinates coordinates, IEye? eye, bool excludeFaded = true) { /* * TODO: @@ -147,7 +147,7 @@ namespace Content.Client.Gameplay foreach (var entity in entities) { if (clickQuery.TryGetComponent(entity.Uid, out var component) && - clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom)) + clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, excludeFaded, out var drawDepthClicked, out var renderOrder, out var bottom)) { foundEntities.Add((entity.Uid, drawDepthClicked, renderOrder, bottom)); } diff --git a/Content.Client/Sprite/SpriteFadeSystem.cs b/Content.Client/Sprite/SpriteFadeSystem.cs index 3323dd660f..949012d04a 100644 --- a/Content.Client/Sprite/SpriteFadeSystem.cs +++ b/Content.Client/Sprite/SpriteFadeSystem.cs @@ -1,8 +1,14 @@ +using System.Numerics; using Content.Client.Gameplay; using Content.Shared.Sprite; using Robust.Client.GameObjects; +using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface; +using Robust.Shared.Map; +using Robust.Shared.Physics.Systems; using Robust.Shared.Physics; namespace Content.Client.Sprite; @@ -17,6 +23,9 @@ public sealed class SpriteFadeSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; private readonly HashSet _comps = new(); @@ -24,6 +33,11 @@ public sealed class SpriteFadeSystem : EntitySystem private EntityQuery _fadeQuery; private EntityQuery _fadingQuery; + /// + /// Radius of the mouse point for the intersection test + /// + private static Vector2 MouseRadius = new Vector2(10f * float.Epsilon, 10f * float.Epsilon); + private const float TargetAlpha = 0.4f; private const float ChangeRate = 1f; @@ -46,46 +60,82 @@ public sealed class SpriteFadeSystem : EntitySystem sprite.Color = sprite.Color.WithAlpha(component.OriginalAlpha); } - public override void FrameUpdate(float frameTime) + /// + /// Adds sprites to the fade set, and brings their alpha downwards + /// + private void FadeIn(float change) { - base.FrameUpdate(frameTime); - var player = _playerManager.LocalEntity; - var change = ChangeRate * frameTime; + // ExcludeBoundingBox is set if we don't want to fade this sprite within the collision bounding boxes for the given POI + var pointsOfInterest = new List<(MapCoordinates Point, bool ExcludeBoundingBox)>(); - if (TryComp(player, out TransformComponent? playerXform) && - _stateManager.CurrentState is GameplayState state && - _spriteQuery.TryGetComponent(player, out var playerSprite)) + if (_uiManager.CurrentlyHovered is IViewportControl vp + && _inputManager.MouseScreenPosition.IsValid) { - var mapPos = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform); - - // Also want to handle large entities even if they may not be clickable. - foreach (var ent in state.GetClickableEntities(mapPos)) - { - if (ent == player || - !_fadeQuery.HasComponent(ent) || - !_spriteQuery.TryGetComponent(ent, out var sprite) || - sprite.DrawDepth < playerSprite.DrawDepth) - { - continue; - } - - if (!_fadingQuery.TryComp(ent, out var fading)) - { - fading = AddComp(ent); - fading.OriginalAlpha = sprite.Color.A; - } + pointsOfInterest.Add((vp.PixelToMap(_inputManager.MouseScreenPosition.Position), true)); + } - _comps.Add(fading); - var newColor = Math.Max(sprite.Color.A - change, TargetAlpha); + if (TryComp(player, out TransformComponent? playerXform)) + { + pointsOfInterest.Add((_transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform), false)); + } - if (!sprite.Color.A.Equals(newColor)) + if (_stateManager.CurrentState is GameplayState state && _spriteQuery.TryGetComponent(player, out var playerSprite)) + { + foreach (var (mapPos, excludeBB) in pointsOfInterest) + { + // Also want to handle large entities even if they may not be clickable. + foreach (var ent in state.GetClickableEntities(mapPos, excludeFaded: false)) { - sprite.Color = sprite.Color.WithAlpha(newColor); + if (ent == player || + !_fadeQuery.HasComponent(ent) || + !_spriteQuery.TryGetComponent(ent, out var sprite) || + sprite.DrawDepth < playerSprite.DrawDepth) + { + continue; + } + + if (excludeBB) + { + var test = new Box2Rotated(mapPos.Position - MouseRadius, mapPos.Position + MouseRadius); + var collided = false; + foreach (var fixture in _physics.GetCollidingEntities(mapPos.MapId, test)) + { + if (fixture.Owner == ent) + { + collided = true; + break; + } + } + if (collided) + { + break; + } + } + + if (!_fadingQuery.TryComp(ent, out var fading)) + { + fading = AddComp(ent); + fading.OriginalAlpha = sprite.Color.A; + } + + _comps.Add(fading); + var newColor = Math.Max(sprite.Color.A - change, TargetAlpha); + + if (!sprite.Color.A.Equals(newColor)) + { + sprite.Color = sprite.Color.WithAlpha(newColor); + } } } } + } + /// + /// Bring sprites back up to their original alpha if they aren't in the fade set, and removes their fade component when done + /// + private void FadeOut(float change) + { var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var comp)) { @@ -106,6 +156,16 @@ public sealed class SpriteFadeSystem : EntitySystem RemCompDeferred(uid); } } + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + var change = ChangeRate * frameTime; + + FadeIn(change); + FadeOut(change); _comps.Clear(); } diff --git a/Content.IntegrationTests/Tests/ClickableTest.cs b/Content.IntegrationTests/Tests/ClickableTest.cs index 5983650908..e6d94a43f9 100644 --- a/Content.IntegrationTests/Tests/ClickableTest.cs +++ b/Content.IntegrationTests/Tests/ClickableTest.cs @@ -80,7 +80,7 @@ namespace Content.IntegrationTests.Tests var pos = clientEntManager.System().GetWorldPosition(clientEnt); - hit = clientEntManager.System().CheckClick((clientEnt, null, sprite, null), new Vector2(clickPosX, clickPosY) + pos, eye, out _, out _, out _); + hit = clientEntManager.System().CheckClick((clientEnt, null, sprite, null), new Vector2(clickPosX, clickPosY) + pos, eye, false, out _, out _, out _); }); await server.WaitPost(() => -- 2.51.2