Convert particle animation from CSS or HTML or JS to lua

  1. I would like to convert a script I found on the internet to lua. It comes in CSS, JS, and HTML
  2. The problem is the script is not in lua its in CSS, JS, and HTML which I can’t find a way to convert.
  3. I tried to convert it myself but I did succeed. I attempted to ask people who are familiar if they could help but they couldn’t.

Particle Animation: https://codepen.io/jkiss/pen/OVEeqK
Any guidance would be appreciated, I would love to have a particle animation for my game!

1 Like

You’ll have to script custom invisible parts flying around and draw beams between them

1 Like

How would I do this using a frame?

You can use a viewport frame

1 Like

Most of the code will map 1:1 to lua. Just replace the canvas with a ScreenGui, fix the getAttribute stuff, etc.

Replace the goMovieFunction with something like

local function goMovie()
    initBalls(30);
    game:GetService("RunService").Stepped:Connect(function()
        render()
    end)
end
goMovie()

Then your biggest issue is anything with ctx (the renderBalls and renderLines functions).

You’ll need some way to replace “painting circles on a canvas” with “placing ImageLabels at the right positions”. Same for lines.

That’s two problems:

  1. Keeping track of all the GUIs – that’s easy enough for the balls since the number of balls stays constant, just make an array of ImageLabels that lines up with balls, but the lines is harder, because it only conditionally renders. You could use some kind of object pool or something.

  2. Positioning the lines – this isn’t too bad, I wrote about it here: Gui drawing - PointA - PointB? - #4 by nicemike40

2 Likes

Why thank you! Which langage should I convert from? The link I provided has HTML, CSS, And JS. What would be the easiest and could you please give me an example of what the code should look like? Would mean a lot as I’m not the most advanced coder.

All the functionality is in the JS in this case. That’s JavaScript, which is a pretty easy to understand language that’s kinda similar to lua.

Are you asking how to do this in general? Or just for this specific thing?

Because this specific one isn’t too hard. But converting a website into a roblox GUI in general is much harder.

I would like to know for this script specifically. I don’t have any idea on what to do as I don’t really use JavaScript

It’s not too bad :slight_smile:

JavaScript is probably the easiest language to google about. And it’ll be useful to know anyways (because websites run on it!).

But really most of it is very close to Lua.

For example

// Update balls
function updateBalls(){
    var new_balls = [];
    Array.prototype.forEach.call(balls, function(b){
        b.x += b.vx;
        b.y += b.vy;
        
        if(b.x > -(50) && b.x < (can_w+50) && b.y > -(50) && b.y < (can_h+50)){
           new_balls.push(b);
        }
        
        // alpha change
        b.phase += alpha_f;
        b.alpha = Math.abs(Math.cos(b.phase));
        // console.log(b.alpha);
    });
    
    balls = new_balls.slice(0);
}

Would become

-- Update balls
local function updateBalls()
    local new_balls = {};
    for _, b in pairs(balls) do
        b.x += b.vx;
        b.y += b.vy;
        
        if b.x > -(50) && b.x < (can_w+50) && b.y > -(50) && b.y < (can_h+50) then
           table.insert(new_balls, b)
        end
        
        -- alpha change
        b.phase += alpha_f;
        b.alpha = math.abs(math.cos(b.phase));
        -- print(b.alpha);
    end
    
    balls = new_balls;
end

There’s a few tricks there, like knowing that new_balls.slice(0) really means “copy this array”, but the first result on google for “javascript slice(0)” gives me:

slice ( 0 ) makes a copy of the original array by taking a slice from the element at index 0 to the last element. slice ( 0 ) creates a new array identical to the original array. Many a times you want to preserve your original array and create a new one.

1 Like

Thank you very much, that helps quite a lot! One question I have is, would you be so kind as to change the script for me so that it works in Roblox? I would be very grateful if you could fix it as converting JavaScript for me is like trying to cast Robux out of a stone lol. Thank you very much as well! I appreciate it a lot!

Also one more thing don’t we remove the ;'s for each line if we convert from JS to Lua? Am I wrong on that

Lua doesn’t need semicolons at the end of lines, but it just ignores them if they’re there.

Also part of me wants to say that it would be a good learning experience and you should try it yourself (and I still recommend you give javascript a shot some time if you’re interested in learning more languages)… but I’m already procrastinating so sure I’ll give it a shot.

Ah that makes sense. Thank you so much by the way, I’ve always wanted to give JavaScript a shot but I just always mid-way give up and work on Lua. I appreciate you helping me alot as well!

Ended up being slightly harder than expected :slight_smile:

You’ll need this “ObjectPool” module I found (as a ModuleScript that’s a child of this script): https://github.com/Roblox/Core-Scripts/blob/master/CoreScriptsRoot/Modules/Common/ObjectPool.lua

And then just stick this inside whatever container like

image

-- converted to lua from https://codepen.io/jkiss/pen/OVEeqK by nicemike40

local canvas = script.Parent
local can_w = canvas.AbsoluteSize.X
local can_h = canvas.AbsoluteSize.Y

-- ADDED
local ObjectPool = require(script.ObjectPool)
local pool = ObjectPool.new(300) -- max 300 balls or 300 lines
local CIRCLE_IMG = "http://www.roblox.com/asset/?id=172650188"

local ball = {
	x = 0,
	y = 0,
	vx = 0,
	vy = 0,
	r = 0,
	alpha = 1,
	phase = 0
}
local ball_color = Color3.new(207/255, 255/255, 4/255)
local R = 6
local guis = {} -- ADDED for guis
local min_v = 6
local max_v = 60
local alpha_f = 1.8
local alpha_phase = 0

-- Line
local link_line_width = 1
local dis_limit = 260
local add_mouse_point = true
local mouse_in = false
local mouse_ball = {
	x = 0,
	y = 0,
	vx = 0,
	vy = 0,
	r = 0,
	type = 'mouse'
}

local balls = {mouse_ball} -- CHANGED: always have mouse ball


-- ADDED to support GUIs
local function GetLine(x1, y1, x2, y2)
	local line = pool:GetInstance("Frame")
	line.BorderSizePixel = 0
	line.AnchorPoint = Vector2.new(0.5, 0.5)
	line.Visible = true
	line.Parent = canvas
	table.insert(guis, line)
	return line
end
local function GetBallGui()
	local gui = pool:GetInstance("ImageLabel")
	gui.Image = CIRCLE_IMG
	gui.Size = UDim2.fromOffset(R, R)
	gui.BackgroundTransparency = 1
	gui.ImageColor3 = ball_color
	gui.Parent = canvas
	gui.Visible = true
	gui.AnchorPoint = Vector2.new(0.5, 0.5)
	table.insert(guis, gui)
	return gui
end

-- Random speed
local function randomNumFrom(min, max)
	return math.random()*(max - min) + min;
end
local function getRandomSpeed(pos)
	local min = -1
	local max = 1
	-- CHANGED: switch statement -> elseif
	if pos == 'top' then
		return randomNumFrom(-max_v, max_v), randomNumFrom(min_v, max_v)
	elseif pos == 'right' then
		return randomNumFrom(-max_v, -min_v), randomNumFrom(-max_v, max_v)
	elseif pos == 'bottom' then
		return randomNumFrom(-max_v, max_v), randomNumFrom(-max_v, -min_v)
	elseif pos ==  'left' then
		return randomNumFrom(min_v, max_v), randomNumFrom(-max_v, max_v)
	end
end
local function randomArrayItem(arr)
	return arr[math.random(1, #arr)];
end
-- Random Ball
local DIRS = {'top', 'right', 'bottom', 'left'}
local function randomSidePos(length)
	return math.random(0, length);
end
local function getRandomBall()
	local pos = randomArrayItem(DIRS)
	local vx, vy = getRandomSpeed(pos)
	local ball = {
		x = 0,
		y = 0,
		vx = vx,
		vy = vy,
		r = R,
		alpha = 1,
		phase = randomNumFrom(0, 10)
	}

	if pos == 'top' then
		ball.x = randomSidePos(can_w)
		ball.y = -R
	elseif pos == 'right' then
		ball.x = can_w + R
		ball.y = randomSidePos(can_h)
	elseif pos == 'bottom' then
		ball.x = randomSidePos(can_w)
		ball.y = can_h + R
	elseif pos == 'left' then
		ball.x = -R
		ball.y = randomSidePos(can_h)
	end

	return ball
end

-- Draw Ball
local function renderBalls()
	for i = 1, #balls do
		local b = balls[i]
		if b.type == nil then
			local gui = GetBallGui()
			gui.ImageTransparency = b.alpha
			gui.Position = UDim2.fromOffset(b.x, b.y)
		end
	end
end

-- Update balls
local function updateBalls(dt)
	local new_balls = {};
	for i = 1, #balls do
		local b = balls[i]
		if b.type then
			table.insert(new_balls, b)
		elseif b.x > -(50) and b.x < (can_w+50) and b.y > -(50) and b.y < (can_h+50) then
			table.insert(new_balls, b)

			-- alpha change
			b.phase += alpha_f * dt; -- CHANGED use the delta time to make motion consistent regardless of framerate
			b.alpha = math.abs(math.cos(b.phase));
			b.x += b.vx * dt;
			b.y += b.vy * dt;
		end
	end

	balls = new_balls;
end

-- calculate distance between two points
local function getDisOf(b1, b2)
	local delta_x = math.abs(b1.x - b2.x)
	local delta_y = math.abs(b1.y - b2.y)

	return math.sqrt(delta_x*delta_x + delta_y*delta_y);
end

-- Draw lines
local function renderLines()
	local fraction;
	for i = 1, #balls do
		for j = 1, #balls do
			local dist = getDisOf(balls[i], balls[j])
			fraction = dist / dis_limit;

			if(fraction < 1) then
				local x1, y1, x2, y2 = balls[i].x, balls[i].y, balls[j].x, balls[j].y
				local l = GetLine()
				l.BackgroundTransparency = fraction
				l.Size = UDim2.fromOffset(dist, link_line_width)
				l.Position = UDim2.fromOffset((x1+x2)/2, (y1+y2)/2)
				l.Rotation = math.deg(math.atan2(y2-y1, x2-x1))
			end
		end
	end
end

-- add balls if there a little balls
local function addBallIfy()
	if(#balls < 20) then
		table.insert(balls, getRandomBall());
	end
end

-- Render
local function render(dt)    
	-- ADDED clean up all GUIs before rendering this frame
	for i = 1, #guis do
		local g = guis[i]
		g.Visible = false
		pool:ReturnInstance(g)
	end
	guis = {}

	updateBalls(dt);
	renderBalls();
	renderLines();
	addBallIfy();
end

-- Init Balls
local function initBalls(num)
	for i = 1, num do
		local vx, vy = getRandomSpeed('top')
		table.insert(balls, {
			x = randomSidePos(can_w),
			y = randomSidePos(can_h),
			vx = vx,
			vy = vy,
			r = R,
			alpha = 1,
			phase = randomNumFrom(0, 10)
		});
	end
end

-- REMOVED initCanvas and moved goMovie to end

-- Mouse effect CHANGED: always have mouse ball so delete mouse enter/leave stuff

game:GetService("UserInputService").InputChanged:Connect(function(input, gameProcessed)
	if input.UserInputType == Enum.UserInputType.MouseMovement then
		mouse_ball.x = input.Position.X - canvas.AbsolutePosition.X
		mouse_ball.y = input.Position.Y - canvas.AbsolutePosition.Y
	end
end)

initBalls(30)
game:GetService("RunService").Stepped:Connect(function(_t, dt)
	render(dt)
end)

That was fun—left some comments around when I made big changes. It could still use some optimization and cleaning up (the original code does some weird stuff and had a few bugs). Have fun!

4 Likes

Thank you so much! Sorry for the late response I actually went to sleep. I appreciate it alot! Have a great rest of you’re week!

1 Like

It works so well! I don’t know how else to thank you. I really really really appreciate it!
Thank you so so much!

1 Like