ID:2681747
 

This is a portion of the Monthly Deep Dive article from March, posted over on my Patreon Page. If you'd like more details you can head over there for the full read and pictures, but this should give the gist enough to get you started with BYOND's new (to 514) particle system.


A quick word before I get started. This implementation is based off a system that Crazah introduced me to when particles became a thing with BYOND at the launch of the 514 beta. Without his advice and the input of lots of others this system wouldn't have turned into what it is right now. Thank you to everyone involved, especially Lummox who works tirelessly on BYOND for us to be able to make fun games for people to play! Be sure to support BYOND on Patreon too!



How Does BYOND Handle Particles?

In BYOND, there is what's called a /particles object. This object spends its days sitting around doing a whole lot of nothing. That is, until a client (or player) wanders close enough for the particle to render on their screen at which point it wakes itself up from its slumber (without coffee I might add!) and gets to work spawning little dots. Sort of. In reality what's happening is the particle generator itself just tells the player "Hey, I'm here, here's my information" and goes back to bed (relatable). The client then uses that information to begin spawning particles that look and behave a certain way based on the information the generator has provided. When the generator is no longer relevant (deleted, destroyed, no longer in view of the player) that client stops generating that particle set, and discards the information. Because this information is all processed on the client, that means that particles are dependent on the players computer for performance.

Using my little laptop with a discord stream running can significantly drop my FPS if I have a lot of particles on screen at once, but someone with a more dedicated screen can play easy peasy with 16x the number of particles and a dozen other applications running in the background. The plus side though, is this frees up the server to do a lot of other important stuff, making this a better implementation in almost every case than an /obj or /image based approach common in many games before 514.

But wait, little dots? Every particle is at its root a single pixel. Remember how we said that the client recieves a package of info from the particle generator? Inside that package are two very important bits of information: How the particles behave, and what the particles look like. So the particles can have certain icons, transformations, spins, colours, and more.


Hazordhu's Particle Setup

In Hazordhu, there's a specific setup for particles to get certain effects and afford more granular control over what's happening and how things are being displayed. I have my particle system broken up into two main sections, one of which has three levels to it. It looks something like this:

Particle Front-end (Functions I use to access and use the particle system)

Particle Back-end (How the game handles the particle system and some post processing)

The Back-end is broken up into 3 distinct levels: Particles, Emitters and Effects.

Particles are exactly what we just talked about. Each particle set is attached to an /emitter. Particle sets have most of the instructions for how particles look and all of the instructions for how they behave:
// Hazordhu's Flame particle
particles
fire
width = 100
height = 100
count = 1000
spawning = 4
lifespan = 7
fade = 10
grow = -0.01
velocity = list(0, 0)
position = generator("circle", 0, 8, NORMAL_RAND)
drift = generator("vector", list(0, -0.2), list(0, 0.2))
gravity = list(0, 0.95)
icon = 'assets/Particles/particles.dmi'
scale = generator("vector", list(0.3, 0.3), list(1,1), NORMAL_RAND)
rotation = 30
spin = generator("num", -20, 20)

color = "yellow"


Emitters are objects that contain a single particle set, along with some instructions for visual effects and displaying the particles. They can be manipulated to control some aspects of individual particle sets such as filters, layering and transform for the individual set. Here we're using it to apply two filters, an outline to blend the individual particles together, and a bloom to give the flame a nice glow:
// Hazordhu's Fire emitter
emitter
fire
alpha = 225
plane = OBJECT_PLANE
standing = TRUE
particles = new/particles/fire
filters = list( filter(type = "outline", size = 1, color = "#FF3300"),
filter(type = "bloom", threshold = rgb(255, 128, 255), size = 6, offset = 4, alpha = 255))



Effects are objects that contain multiple emitters and are used for the full effect. They can also be manipulated to control an entire set of emitters and their particles, effectively giving high-level control over an entire particle effect, such as its position, location, transform and lifespan:
// Hazordhu's Fire effect
effect
parent_type = /atom/movable
var/tmp/emitters[] // list of emitters, each with their own particle
var/tmp/lifespan // how long the effect lives for before dying out

fire
emitters = newlist(
/emitter/fire,
/emitter/smoke,
/emitter/sparks_daytime,
/emitter/sparks_nighttime,
/emitter/fire_nightglow)


The Front-end of the system just has a couple of functions that I use to create particle effects and how to position them. This is the way that I create the effects in the game as far as the user can see them. Without this step of the process, the particle effects would just sit there in the abyss and never do much of anything. This is the proc I use for spawning the particle sets:
// Global particle proc, used to create particle effects in the game
proc
Particle(effect/effect, atom/source, atom/movable/source_object, offset[] = list(0,0), duration)
set waitfor = FALSE
var/effect/new_effect = new effect

if(istype(source, /atom/movable))
source:vis_contents += new_effect

else // Only called on turfs
new_effect.set_loc(source, offset[1], offset[2])
if(source_object)
new_effect.step_x = source_object.step_x
new_effect.step_y = source_object.step_y

if(offset && islist(offset))
new_effect.step_x += offset[1]
new_effect.step_y += offset[2]

if(duration)
spawn(duration)
if(new_effect)
del new_effect

else if(new_effect.lifespan)
spawn(new_effect.lifespan)
if(new_effect)
del new_effect

return new_effect


So to break things down here a bit, let's go over what we've covered using our Fire effect that we see on campfires in the game.

First, we make the particle sets for the 5 pieces. In the code shown above, we already see the fire particle set for the flames themselves.

That particle set is attached to a fire /emitter object, which applies bloom and an outline, which makes the particles all blend together and give off a nice warm glow.

Next, we have the /effect object, which has a list of 5 different emitters:
  • The Flame
  • The Smoke
  • The Nightglow
  • The Daytime Sparks
  • The Nighttime Sparks
Each one of these emitters has its own particle set with its own rules (which we won't go over in detail here), the two special ones are the nightglow and the night sparks, and here's why.

Hazordhu's lighting system uses a single plane of darkness that's drawn overtop of the clients screen (but under the UI). Think of it like covering the lens of a camera with a dark blue see-through disk. These night-time emitters tell the client to cut out a bit of darkness from this plane, to remove the effect of the darkness from those areas, similar to poking holes in that blue disk. This is how the flame glows so nicely in the darkness, and how the sparks themselves glow in the night as well.

The nightglow effect itself is what gives fire its own nice distinct flicker effect: The light from fires is just a particle effect!


There is a lot more that you can do using particles. This is just how I've found that works for me and my workflow. Let me know what kinds of cool things you get up to using particles!

If you want to see more in-depth posts like this, head over to my Patreon Page and support me there. I make a deep dive like this every month over there, and more on the way!
Great article, F0lak!