Download the complete file here plasma.rbxl or follow the steps below.
First, we need to create a physical part in the workspace that will display our plasma effect.
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
-- Configuration
local GRID_SIZE = 32 -- Resolution of plasma
local PLANE_SIZE = 50 -- Size in studs
-- Create the base part
local basePart = Instance.new("Part")
basePart.Name = "PlasmaPlane"
basePart.Size = Vector3.new(PLANE_SIZE, 0.5, PLANE_SIZE)
basePart.Position = Vector3.new(0, 5, 0)
basePart.Anchored = true
basePart.Material = Enum.Material.Neon
basePart.Parent = Workspace
A SurfaceGui allows us to display 2D interface elements on a 3D surface. This is perfect for our pixel grid.
-- Create SurfaceGui for the plasma effect
local surfaceGui = Instance.new("SurfaceGui")
surfaceGui.Face = Enum.NormalId.Top
surfaceGui.Brightness = 2
surfaceGui.LightInfluence = 0 -- Ignore lighting
surfaceGui.Parent = basePart
local frame = Instance.new("Frame")
frame.Size = UDim2.new(1, 0, 1, 0)
frame.BackgroundTransparency = 1
frame.Parent = surfaceGui
LightInfluence = 0 means our plasma won't be affected by in-game lighting, ensuring consistent brightness.
Now we'll create a grid of small Frame objects that act as individual pixels we can color.
-- Create grid of pixels
local pixels = {}
local pixelSize = 1 / GRID_SIZE
for y = 0, GRID_SIZE - 1 do
pixels[y] = {}
for x = 0, GRID_SIZE - 1 do
local pixel = Instance.new("Frame")
pixel.BorderSizePixel = 0
pixel.Size = UDim2.new(pixelSize, 0, pixelSize, 0)
pixel.Position = UDim2.new(x * pixelSize, 0, y * pixelSize, 0)
pixel.Parent = frame
pixels[y][x] = pixel
end
end
pixels[y][x]) so we can easily access any pixel by its coordinates. Each pixel is positioned as a fraction of the total size.
This is where the magic happens! We use overlapping sine waves to create organic, flowing patterns.
-- Plasma generation function
local function plasma(x, y, time)
local value = math.sin(x * 0.1 + time)
value = value + math.sin(y * 0.1 + time * 1.3)
value = value + math.sin((x + y) * 0.1 + time * 0.7)
value = value + math.sin(math.sqrt(x * x + y * y) * 0.1 + time * 1.1)
return value / 4
end
Now we need to turn our plasma values (-1 to 1) into beautiful colors.
-- Convert plasma value to color
local function valueToColor(value)
local normalized = (value + 1) / 2 -- Convert [-1,1] to [0,1]
-- Create smooth color transitions
local r = math.sin(normalized * math.pi * 2) * 0.5 + 0.5
local g = math.sin(normalized * math.pi * 2 + 2) * 0.5 + 0.5
local b = math.sin(normalized * math.pi * 2 + 4) * 0.5 + 0.5
return Color3.new(r, g, b)
end
* 0.5 + 0.5 ensures values stay between 0 and 1.
Finally, we animate everything using RunService.Heartbeat, which runs every frame.
local time = 0
local UPDATE_RATE = 0.03 -- Update every 0.03 seconds
local lastUpdate = 0
RunService.Heartbeat:Connect(function(dt)
time = time + dt
lastUpdate = lastUpdate + dt
if lastUpdate >= UPDATE_RATE then
lastUpdate = 0
for y = 0, GRID_SIZE - 1 do
for x = 0, GRID_SIZE - 1 do
-- Center coordinates
local cx = x - GRID_SIZE / 2
local cy = y - GRID_SIZE / 2
-- Calculate plasma value
local value = plasma(cx, cy, time)
-- Set pixel color
pixels[y][x].BackgroundColor3 = valueToColor(value)
end
end
end
end)
UPDATE_RATE throttles updates to maintain good performance. Lower values = smoother but more CPU-intensive.
Modify the time multipliers in the plasma function:
-- Slower animation
value = value + math.sin(y * 0.1 + time * 0.5)
-- Faster animation
value = value + math.sin(y * 0.1 + time * 3.0)
-- Fire colors (red/orange/yellow)
local r = normalized
local g = normalized * 0.5
local b = 0
-- Ocean colors (blue/cyan/green)
local r = 0
local g = normalized * 0.7 + 0.3
local b = normalized
-- Change 0.1 to smaller values for bigger waves
local value = math.sin(x * 0.05 + time) -- Bigger waves!