Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1a318a6
that's the easy bit done
amylizzle Feb 3, 2025
238712e
guess we need a component now?
amylizzle Feb 3, 2025
8977b23
jesus christ this gives me a headache
amylizzle Feb 3, 2025
2263646
headachee
amylizzle Feb 3, 2025
9eea9ee
oh duh
amylizzle Feb 4, 2025
eabf039
okay, particles are being rendered in opendream
amylizzle Feb 5, 2025
494acd1
bedtime
amylizzle Feb 5, 2025
daa9c3e
so much hacky bullshit
amylizzle Feb 6, 2025
5bf0610
yaml
amylizzle Feb 7, 2025
be1397a
oh no vector2 isn't serialisable
amylizzle Feb 7, 2025
fb9d89d
we're getting there!
amylizzle Feb 7, 2025
48ba12d
textured
amylizzle Feb 7, 2025
1c669aa
YAML component
amylizzle Feb 10, 2025
f442bba
rename some thing
amylizzle Feb 10, 2025
40a1284
fuggin ecs
amylizzle Feb 10, 2025
d22a8e5
I should stop
amylizzle Feb 11, 2025
dd8eb19
DynamicParticle
amylizzle Feb 15, 2025
e949d4f
helper
amylizzle Feb 15, 2025
5b1c6a7
updates
amylizzle Feb 17, 2025
6f6495f
sandbox
amylizzle Feb 17, 2025
89f307e
registerignore
amylizzle Feb 18, 2025
311bdd1
split components bad
amylizzle Feb 20, 2025
4083432
god this is such a headache
amylizzle Feb 20, 2025
cf5a4d0
fix particle arrays
amylizzle Feb 20, 2025
00ebe31
cleaner transform
amylizzle Feb 20, 2025
a3a90aa
need some stuff for acceleration on OD
amylizzle Feb 20, 2025
9f8e254
threads go brr
amylizzle Feb 20, 2025
0c0d878
wrong type
amylizzle Feb 20, 2025
cf12c7b
BYOND generator PDFs
amylizzle Feb 26, 2025
a43442b
mathhhssss
amylizzle Feb 26, 2025
5659a64
fix transform
amylizzle Mar 3, 2025
0ace22c
nullable texture and z sorting
amylizzle Mar 3, 2025
a328dc9
Merge remote-tracking branch 'upstream' into particlez
amylizzle Mar 7, 2025
4760a2a
Merge remote-tracking branch 'upstream' into particlez
amylizzle Mar 7, 2025
bcfef18
Merge remote-tracking branch 'upstream/master' into particlez
amylizzle May 10, 2025
e7368c7
can't parallelise this
amylizzle May 10, 2025
3cd58cc
Merge remote-tracking branch 'upstream/master' into particlez
amylizzle May 20, 2025
41a7db1
official permission to do it the easy way
amylizzle May 20, 2025
853a996
beepboop
amylizzle May 20, 2025
48303aa
I hate computers
amylizzle Jun 1, 2025
5a2fd91
Merge remote-tracking branch 'upstream/master' into particlez
amylizzle Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Resources/EnginePrototypes/ParticleEffects/example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
- type: particles
id: example
#The width and height of the drawing surface for the particles - this is the size of the texture that the particles are drawn on.
width: 64
height: 64
#Maximum number of particles in this system
count: 1000
#Maximum number of particles spawned per second
spawning: 100
#Texture that the particles have. This is a list of paths to textures, and the particle system will randomly choose one of them for each particle. If blank, the particle will be a 1px white dot.
texture: []
#Maximum lifespan of the partcles in seconds
lifespan:
#these values are set by a generator specified here. Constant is a constant value, uniform is a random value between two values, and normal is a random value from a normal distribution.
type: constant
value: 50
fadein:
type: uniform
low: 0
high: 10
fadeout:
type: constant
value: 5
#Color of the particles. This can either be a list of a colours, or a gradient that the particles will interpolate between over their lifespan.
color: ["#FF0000", "#00FF00", "#0000FF"]
#Starting position of the particles. X,Y,Z. The Z co-ordinate determines layering order.
spawn_position:
type: uniform
low: [-64, 0, 0]
high: [64, 0, 0]
#Starting velocity of the particles
spawn_velocity:
type: uniform
low: [0, 5, 0]
high: [0, 10, 0]
#Acceleration applied to the particles per second
acceleration:
type: constant
value: [0, 0, 0]
#Scaling applied to the particles in (x,y)
scale:
type: constant
value: [2,2]
#Rotation applied to the particles in degrees
rotation:
type: constant
value: 0
#Increase in scale per second
growth:
type: constant
value: [0,0]
#Change in rotation per second
spin:
type: constant
value: 0


1 change: 1 addition & 0 deletions Robust.Client/ClientIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public static void RegisterIoC(GameController.DisplayMode mode, IDependencyColle
deps.Register<IPlacementManager, PlacementManager>();
deps.Register<IOverlayManager, OverlayManager>();
deps.Register<IOverlayManagerInternal, OverlayManager>();
deps.Register<ParticlesManager>();
deps.Register<IViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
Expand Down
8 changes: 8 additions & 0 deletions Robust.Client/GameController/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ internal sealed partial class GameController : IGameControllerInternal
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly ParticlesManager _particleManager = default!;

[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
Expand Down Expand Up @@ -212,6 +214,7 @@ internal bool StartupContinue(DisplayMode displayMode)
_replayPlayback.Initialize();
_replayRecording.Initialize();
_userInterfaceManager.PostInitialize();
_particleManager.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);

if (_commandLineArgs?.Username != null)
Expand Down Expand Up @@ -614,6 +617,11 @@ private void Update(FrameEventArgs frameEventArgs)
_placementManager.FrameUpdate(frameEventArgs);
}

using (_prof.Group("Particles"))
{
_particleManager.FrameUpdate(frameEventArgs);
}

using (_prof.Group("Entity"))
{
_entityManager.FrameUpdate(frameEventArgs.DeltaSeconds);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Vector3 = Robust.Shared.Maths.Vector3;

namespace Robust.Client.GameObjects;

[UsedImplicitly]
public sealed class ClientDynamicParticlesSystem : SharedDynamicParticlesSystem
{
[Dependency] private readonly ParticlesManager _particlesManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private Random random = new();

public override void Initialize() {
base.Initialize();
SubscribeLocalEvent<DynamicParticlesComponent, AfterAutoHandleStateEvent>(OnDynamicParticlesComponentChange);
SubscribeLocalEvent<DynamicParticlesComponent, ComponentAdd>(HandleComponentAdd);
SubscribeLocalEvent<DynamicParticlesComponent, ComponentRemove>(HandleComponentRemove);
}

private void OnDynamicParticlesComponentChange(EntityUid uid, DynamicParticlesComponent component, ref AfterAutoHandleStateEvent args)
{
if(_particlesManager.TryGetParticleSystem(uid, out var system))
system.UpdateSystem(GetParticleSystemArgs(component));
}

private void HandleComponentAdd(EntityUid uid, DynamicParticlesComponent component, ref ComponentAdd args)
{
_particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component));
}

private void HandleComponentRemove(EntityUid uid, DynamicParticlesComponent component, ref ComponentRemove args)
{
_particlesManager.DestroyParticleSystem(uid);
}

private ParticleSystemArgs GetParticleSystemArgs(DynamicParticlesComponent component){
Func<Texture> textureFunc;
if(component.TextureList is null || component.TextureList.Length == 0)
textureFunc = () => Texture.White;
else
textureFunc = () => _resourceCache.GetResource<TextureResource>(new Random().Pick(component.TextureList)); //TODO

var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning);

GeneratorFloat lifespan = new();
result.Lifespan = GetGeneratorFloat(component.LifespanLow, component.LifespanHigh, component.LifespanType);
result.Fadein = GetGeneratorFloat(component.FadeInLow, component.FadeInHigh, component.FadeInType);
result.Fadeout = GetGeneratorFloat(component.FadeOutLow, component.FadeOutHigh, component.FadeOutType);
if(component.ColorList.Length > 0)
result.Color = (float lifetime) => {
var colorIndex = (int)(lifetime * component.ColorList.Length);
colorIndex = Math.Clamp(colorIndex, 0, component.ColorList.Length - 1);
return component.ColorList[colorIndex];
};
else
result.Color = (float lifetime) => System.Drawing.Color.White;
result.Acceleration = (float _, Vector3 _ ) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)();
result.SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType);
result.SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType);
result.Transform = (float lifetime) => {
var scale = GetGeneratorVector2(component.ScaleLow, component.ScaleHigh, component.ScaleType)();
var rotation = GetGeneratorFloat(component.RotationLow, component.RotationHigh, component.RotationType)();
var growth = GetGeneratorVector2(component.GrowthLow, component.GrowthHigh, component.GrowthType)();
var spin = GetGeneratorFloat(component.SpinLow, component.SpinHigh, component.SpinType)();
return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) *
Matrix3x2.CreateRotation(rotation + spin);
};

return result;
}

private Func<float> GetGeneratorFloat(float low, float high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
case ParticlePropertyType.RandomUniform:
return () => random.NextFloat(low, high);
case ParticlePropertyType.RandomNormal:
return () => (float) Math.Clamp(random.NextGaussian((low+high)/2, (high-low)/6), low, high);
case ParticlePropertyType.RandomLinear:
return () => MathF.Sqrt(random.NextFloat(0, 1)) * (high - low) + low;
case ParticlePropertyType.RandomSquare:
return () => MathF.Cbrt(random.NextFloat(0, 1)) * (high - low) + low;
default:
throw new NotImplementedException();
}
}

private Func<Vector2> GetGeneratorVector2(Vector2 low, Vector2 high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
default:
return () => new Vector2(GetGeneratorFloat(low.X, high.X, type)(), GetGeneratorFloat(low.Y, high.Y, type)());
}
}

private Func<Vector3> GetGeneratorVector3(Vector3 low, Vector3 high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
default:
return () => new Vector3(GetGeneratorFloat(low.X, high.X, type)(), GetGeneratorFloat(low.Y, high.Y, type)(), GetGeneratorFloat(low.Z, high.Z, type)());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;

namespace Robust.Client.GameObjects;

[UsedImplicitly]
public sealed class ClientYamlParticlesSystem : SharedYamlParticlesSystem
{
[Dependency] private readonly ParticlesManager _particlesManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

public override void Initialize() {
base.Initialize();
SubscribeLocalEvent<YamlParticlesComponent, AfterAutoHandleStateEvent>(OnYamlParticlesComponentChange);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
SubscribeLocalEvent<YamlParticlesComponent, ComponentAdd>(HandleComponentAdd);
SubscribeLocalEvent<YamlParticlesComponent, ComponentRemove>(HandleComponentRemove);
}

private void OnYamlParticlesComponentChange(EntityUid uid, YamlParticlesComponent component, ref AfterAutoHandleStateEvent args)
{
if(_prototypeManager.TryIndex<ParticlesPrototype>(component.ParticleType, out var prototype)){
ParticleSystemArgs particleSystemArgs = prototype.GetParticleSystemArgs(_resourceCache);
if(_particlesManager.TryGetParticleSystem(uid, out var system))
system.UpdateSystem(particleSystemArgs);
else
_particlesManager.CreateParticleSystem(uid, particleSystemArgs);
}
else
{
throw new InvalidPrototypeNameException($"{component.ParticleType} is not a valid particles prototype");
}
}

private void HandleComponentAdd(EntityUid uid, YamlParticlesComponent component, ref ComponentAdd args)
{
if(string.IsNullOrEmpty(component.ParticleType))
return;
//do a lookup for YAML defined particles
if(_prototypeManager.TryIndex<ParticlesPrototype>(component.ParticleType, out var prototype)){
ParticleSystemArgs particleSystemArgs = prototype.GetParticleSystemArgs(_resourceCache);
_particlesManager.CreateParticleSystem(uid, particleSystemArgs);
}
else
{
throw new InvalidPrototypeNameException($"{component.ParticleType} is not a valid particles prototype");
}
}

private void HandleComponentRemove(EntityUid uid, YamlParticlesComponent component, ref ComponentRemove args)
{
_particlesManager.DestroyParticleSystem(uid);
}

private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
{
if (!args.TryGetModified<ParticlesPrototype>(out var modified))
return;
//TODO reload registered particles
}
}
1 change: 1 addition & 0 deletions Robust.Client/Graphics/Clyde/Clyde.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEv
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly ClientEntityManager _entityManager = default!;
[Dependency] private readonly ParticlesManager _particlesManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IReloadManager _reloads = default!;

Expand Down
26 changes: 26 additions & 0 deletions Robust.Client/Graphics/Overlays/ParticlesOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Numerics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;

namespace Robust.Client.Graphics;

public sealed class ParticlesOverlay : Overlay
{
private ParticlesManager _particlesManager = default!;
private IEntityManager _entManager = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
protected internal override void Draw(in OverlayDrawArgs args)
{
_particlesManager ??= IoCManager.Resolve<ParticlesManager>();
_entManager ??= IoCManager.Resolve<IEntityManager>();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var xformSystem = _entManager.System<SharedTransformSystem>();

foreach (var entity in _particlesManager.GetEntitiesWithParticles)
if (_particlesManager.TryGetParticleSystem(entity, out var system) && xformQuery.TryGetComponent(entity, out var xform))
{
system.Draw(args.WorldHandle, xformSystem.GetWorldPositionRotationInvMatrix(xform).InvWorldMatrix);
}
}
}
Loading
Loading