Per-map parallax support (#9786)
* Per-map parallax support * Comments for future sloth * c * bet * Fix exception * VV support * Fix parallax * mem * weightless sounds * Gravity stuff * placeholder coz im too lazy to stash don't @ me son * decent clouds * sky * Fast parallax * Imagine spelling * Loicense * perish * Fix weightless status Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Parallax.Data
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.IoC;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Parallax.Data;
|
||||
|
||||
@@ -62,5 +62,12 @@ public sealed class ParallaxLayerConfig
|
||||
/// </summary>
|
||||
[DataField("slowness")]
|
||||
public float Slowness { get; set; } = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Should the parallax scroll at a specific rate per second.
|
||||
/// </summary>
|
||||
[DataField("scrolling")] public Vector2 Scrolling = Vector2.Zero;
|
||||
|
||||
[DataField("shader")] public string? Shader = "unshaded";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Content.Client.Parallax;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Parallax.Managers;
|
||||
|
||||
public interface IParallaxManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The current parallax.
|
||||
/// Changing this causes a new parallax to be loaded (eventually).
|
||||
/// Do not alter until prototype manager is available.
|
||||
/// Useful "csi" input for testing new parallaxes:
|
||||
/// using Content.Client.Parallax.Managers; IoCManager.Resolve<IParallaxManager>().ParallaxName = "test";
|
||||
/// </summary>
|
||||
string ParallaxName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All WorldHomePosition values are offset by this.
|
||||
/// </summary>
|
||||
Vector2 ParallaxAnchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The layers of the currently loaded parallax.
|
||||
/// This will change on a whim without notification.
|
||||
/// </summary>
|
||||
ParallaxLayerPrepared[] ParallaxLayers { get; }
|
||||
bool IsLoaded(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the manager.
|
||||
/// The layers of the selected parallax.
|
||||
/// </summary>
|
||||
ParallaxLayerPrepared[] GetParallaxLayers(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Loads in the default parallax to use.
|
||||
/// Do not call until prototype manager is available.
|
||||
/// </summary>
|
||||
void LoadParallax();
|
||||
void LoadDefaultParallax();
|
||||
|
||||
Task LoadParallaxByName(string name);
|
||||
|
||||
void UnloadParallax(string name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,111 +1,103 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Parallax.Data;
|
||||
using Content.Shared;
|
||||
using Content.Shared.CCVar;
|
||||
using Nett;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Content.Client.Parallax.Managers;
|
||||
|
||||
internal sealed class ParallaxManager : IParallaxManager
|
||||
public sealed class ParallaxManager : IParallaxManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private string _parallaxName = "";
|
||||
public string ParallaxName
|
||||
{
|
||||
get => _parallaxName;
|
||||
set
|
||||
{
|
||||
LoadParallaxByName(value);
|
||||
}
|
||||
}
|
||||
private ISawmill _sawmill = Logger.GetSawmill("parallax");
|
||||
|
||||
public Vector2 ParallaxAnchor { get; set; }
|
||||
|
||||
private CancellationTokenSource? _presentParallaxLoadCancel;
|
||||
private readonly ConcurrentDictionary<string, ParallaxLayerPrepared[]> _parallaxesLQ = new();
|
||||
private readonly ConcurrentDictionary<string, ParallaxLayerPrepared[]> _parallaxesHQ = new();
|
||||
|
||||
private ParallaxLayerPrepared[] _parallaxLayersHQ = {};
|
||||
private ParallaxLayerPrepared[] _parallaxLayersLQ = {};
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _loadingParallaxes = new();
|
||||
|
||||
public ParallaxLayerPrepared[] ParallaxLayers => _configurationManager.GetCVar(CCVars.ParallaxLowQuality) ? _parallaxLayersLQ : _parallaxLayersHQ;
|
||||
public bool IsLoaded(string name) => _parallaxesLQ.ContainsKey(name);
|
||||
|
||||
public async void LoadParallax()
|
||||
public ParallaxLayerPrepared[] GetParallaxLayers(string name)
|
||||
{
|
||||
await LoadParallaxByName("default");
|
||||
if (_configurationManager.GetCVar(CCVars.ParallaxLowQuality))
|
||||
{
|
||||
return !_parallaxesLQ.TryGetValue(name, out var lq) ? Array.Empty<ParallaxLayerPrepared>() : lq;
|
||||
}
|
||||
|
||||
return !_parallaxesLQ.TryGetValue(name, out var hq) ? Array.Empty<ParallaxLayerPrepared>() : hq;
|
||||
}
|
||||
|
||||
private async Task LoadParallaxByName(string name)
|
||||
public void UnloadParallax(string name)
|
||||
{
|
||||
// Update _parallaxName
|
||||
if (_parallaxName == name)
|
||||
if (_loadingParallaxes.TryGetValue(name, out var loading))
|
||||
{
|
||||
loading.Cancel();
|
||||
_loadingParallaxes.Remove(name, out _);
|
||||
return;
|
||||
}
|
||||
_parallaxName = name;
|
||||
|
||||
if (!_parallaxesLQ.ContainsKey(name)) return;
|
||||
_parallaxesLQ.Remove(name, out _);
|
||||
_parallaxesHQ.Remove(name, out _);
|
||||
}
|
||||
|
||||
public async void LoadDefaultParallax()
|
||||
{
|
||||
await LoadParallaxByName("Default");
|
||||
}
|
||||
|
||||
public async Task LoadParallaxByName(string name)
|
||||
{
|
||||
if (_parallaxesLQ.ContainsKey(name) || _loadingParallaxes.ContainsKey(name)) return;
|
||||
|
||||
// Cancel any existing load and setup the new cancellation token
|
||||
_presentParallaxLoadCancel?.Cancel();
|
||||
_presentParallaxLoadCancel = new CancellationTokenSource();
|
||||
var cancel = _presentParallaxLoadCancel.Token;
|
||||
|
||||
// Empty parallax name = no layers (this is so that the initial "" parallax name is consistent)
|
||||
if (_parallaxName == "")
|
||||
{
|
||||
_parallaxLayersHQ = _parallaxLayersLQ = new ParallaxLayerPrepared[] {};
|
||||
return;
|
||||
}
|
||||
var token = new CancellationTokenSource();
|
||||
_loadingParallaxes[name] = token;
|
||||
var cancel = token.Token;
|
||||
|
||||
// Begin (for real)
|
||||
Logger.InfoS("parallax", $"Loading parallax {name}");
|
||||
_sawmill.Info($"Loading parallax {name}");
|
||||
|
||||
try
|
||||
{
|
||||
var parallaxPrototype = _prototypeManager.Index<ParallaxPrototype>(name);
|
||||
|
||||
ParallaxLayerPrepared[] hq;
|
||||
ParallaxLayerPrepared[] lq;
|
||||
ParallaxLayerPrepared[][] layers;
|
||||
|
||||
if (parallaxPrototype.LayersLQUseHQ)
|
||||
{
|
||||
lq = hq = await LoadParallaxLayers(parallaxPrototype.Layers, cancel);
|
||||
layers = new ParallaxLayerPrepared[2][];
|
||||
layers[0] = layers[1] = await LoadParallaxLayers(parallaxPrototype.Layers, cancel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var results = await Task.WhenAll(
|
||||
layers = await Task.WhenAll(
|
||||
LoadParallaxLayers(parallaxPrototype.Layers, cancel),
|
||||
LoadParallaxLayers(parallaxPrototype.LayersLQ, cancel)
|
||||
);
|
||||
hq = results[0];
|
||||
lq = results[1];
|
||||
}
|
||||
|
||||
// Still keeping this check just in case.
|
||||
if (_parallaxName == name)
|
||||
{
|
||||
_parallaxLayersHQ = hq;
|
||||
_parallaxLayersLQ = lq;
|
||||
Logger.InfoS("parallax", $"Loaded parallax {name}");
|
||||
}
|
||||
_loadingParallaxes.Remove(name, out _);
|
||||
|
||||
if (token.Token.IsCancellationRequested) return;
|
||||
|
||||
_parallaxesLQ[name] = layers[0];
|
||||
_parallaxesHQ[name] = layers[1];
|
||||
|
||||
_sawmill.Info($"Loaded parallax {name}");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorS("parallax", $"Failed to loaded parallax {name}: {ex}");
|
||||
_sawmill.Error($"Failed to loaded parallax {name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
@@ -28,7 +25,7 @@ public sealed class ParallaxControl : Control
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
foreach (var layer in _parallaxManager.ParallaxLayers)
|
||||
foreach (var layer in _parallaxManager.GetParallaxLayers("Default"))
|
||||
{
|
||||
var tex = layer.Texture;
|
||||
var texSize = tex.Size * layer.Config.Scale.Floored();
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
using System;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class ParallaxOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IParallaxManager _manager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public ParallaxOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
|
||||
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configurationManager.GetCVar(CCVars.ParallaxEnabled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
||||
var screenHandle = args.WorldHandle;
|
||||
screenHandle.UseShader(_shader);
|
||||
|
||||
foreach (var layer in _parallaxManager.ParallaxLayers)
|
||||
var layers = _parallax.GetParallaxLayers(args.MapId);
|
||||
var realTime = (float) _timing.RealTime.TotalSeconds;
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
ShaderInstance? shader;
|
||||
|
||||
if (!string.IsNullOrEmpty(layer.Config.Shader))
|
||||
shader = _prototypeManager.Index<ShaderPrototype>(layer.Config.Shader).Instance();
|
||||
else
|
||||
shader = null;
|
||||
|
||||
screenHandle.UseShader(shader);
|
||||
var tex = layer.Texture;
|
||||
|
||||
// Size of the texture in world units.
|
||||
@@ -52,10 +60,11 @@ public sealed class ParallaxOverlay : Overlay
|
||||
// The effects of this are such that a slowness of 1 anchors the layer to the centre of the screen, while a slowness of 0 anchors the layer to the world.
|
||||
// (For values 0.0 to 1.0 this is in effect a lerp, but it's deliberately unclamped.)
|
||||
// The ParallaxAnchor adapts the parallax for station positioning and possibly map-specific tweaks.
|
||||
var home = layer.Config.WorldHomePosition + _parallaxManager.ParallaxAnchor;
|
||||
var home = layer.Config.WorldHomePosition + _manager.ParallaxAnchor;
|
||||
var scrolled = layer.Config.Scrolling * realTime;
|
||||
|
||||
// Origin - start with the parallax shift itself.
|
||||
var originBL = (args.Viewport.Eye.Position.Position - home) * layer.Config.Slowness;
|
||||
var originBL = (position - home) * layer.Config.Slowness + scrolled;
|
||||
|
||||
// Place at the home.
|
||||
originBL += home;
|
||||
@@ -90,6 +99,8 @@ public sealed class ParallaxOverlay : Overlay
|
||||
screenHandle.DrawTextureRect(tex, Box2.FromDimensions(originBL, size));
|
||||
}
|
||||
}
|
||||
|
||||
screenHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
71
Content.Client/Parallax/ParallaxSystem.cs
Normal file
71
Content.Client/Parallax/ParallaxSystem.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.Parallax;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class ParallaxSystem : SharedParallaxSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IParallaxManager _parallax = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private const string Fallback = "Default";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlay.AddOverlay(new ParallaxOverlay());
|
||||
SubscribeLocalEvent<ParallaxComponent, ComponentHandleState>(OnParallaxHandleState);
|
||||
_protoManager.PrototypesReloaded += OnReload;
|
||||
}
|
||||
|
||||
private void OnReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
_parallax.UnloadParallax(Fallback);
|
||||
_parallax.LoadDefaultParallax();
|
||||
|
||||
foreach (var comp in EntityQuery<ParallaxComponent>(true))
|
||||
{
|
||||
_parallax.UnloadParallax(comp.Parallax);
|
||||
_parallax.LoadParallaxByName(comp.Parallax);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlay.RemoveOverlay<ParallaxOverlay>();
|
||||
_protoManager.PrototypesReloaded -= OnReload;
|
||||
}
|
||||
|
||||
private void OnParallaxHandleState(EntityUid uid, ParallaxComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ParallaxComponentState state) return;
|
||||
component.Parallax = state.Parallax;
|
||||
|
||||
if (!_parallax.IsLoaded(component.Parallax))
|
||||
{
|
||||
_parallax.LoadParallaxByName(component.Parallax);
|
||||
}
|
||||
}
|
||||
|
||||
public ParallaxLayerPrepared[] GetParallaxLayers(MapId mapId)
|
||||
{
|
||||
return _parallax.GetParallaxLayers(GetParallax(_map.GetMapEntityId(mapId)));
|
||||
}
|
||||
|
||||
public string GetParallax(MapId mapId)
|
||||
{
|
||||
return GetParallax(_map.GetMapEntityId(mapId));
|
||||
}
|
||||
|
||||
public string GetParallax(EntityUid mapUid)
|
||||
{
|
||||
return TryComp<ParallaxComponent>(mapUid, out var parallax) ? parallax.Parallax : Fallback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user