Hello Devforum! I have a tutorial on how to make a custom ripple mouse cursor that looks very cool!
Before we get started, create a localscript under StarterPlayerScripts
and name it whatever you’d like.
Without further ado let’s get started!
Variables and setup
Before we can get to the exciting part, we have to define some things first.
local Players = game:GetService("Players") -- The players in the game
local UserInputService = game:GetService("UserInputService") -- Let's the game handle your inputs
local TweenService = game:GetService("TweenService") -- allows for smooth animations
local RunService = game:GetService("RunService") -- Lets things run continuously.
local player = Players.LocalPlayer -- This is your player(Since it's local it's for everyone!)
local cursorColor = Color3.fromRGB(12, 255, 166) -- This will be the color of the cursor and ripple, feel free to change this to whatever!
Now you have the basics done, now we can move onto UI creation
UI creation
Now we can move onto creating the UI!
Beneath the basic variables you created, paste this.
local gui = Instance.new("ScreenGui") -- Creates the cursor GUI.
gui.Name = "SuperCoolCursor" -- Just the name of your cursorUI
gui.IgnoreGuiInset = true -- If it can override certain core elements
gui.ResetOnSpawn = false -- If the gui is reset when your character OOFS!
gui.ZIndexBehavior = Enum.ZIndexBehavior.Global -- Let's the line below able to override the other elements.
gui.DisplayOrder = 1_000_000 -- Makes the gui above every other element(unless you have something higher)
gui.Parent = player:WaitForChild("PlayerGui") -- Clones the GUI to a safe spot that makes it visible.
UserInputService.MouseIconEnabled = false -- This disables the default mouse ensuring that the mouse won't overlap your custom one.
Now we can move onto the inner circle of your mouse that will show if it is over any UI objects.
local dot = Instance.new("Frame") -- The instance of the circle
dot.Size = UDim2.new(0, 6, 0, 6) -- Sets the size of the 2 dimensional UI object.
dot.BackgroundColor3 = cursorColor -- Sets the color to the one you defined earlier!
dot.BackgroundTransparency = 0 -- Makes sure the cursor is visible.
dot.BorderSizePixel = 0 -- Sets the size of the border(I recommend 0)
dot.AnchorPoint = Vector2.new(0.5, 0.5) -- Anchors the dot to a specific location if inactive.
dot.Position = UDim2.new(0, 0, 0, 0) -- The starting location of your mouse(when you join)
dot.ZIndex = 10001 -- The ability to overlap other UI elements below this ZIndex.
dot.Parent = gui -- Places the frame under the gui we created.
Now we have to make the circle… A circle, obviously!
local dotCorner = Instance.new("UICorner") -- Gives UI roundness
dotCorner.CornerRadius = UDim.new(1, 0) -- Sets the distance from the center of a circle to its border, making it's circle shape.
dotCorner.Parent = dot -- Makes the corner under the dot we created.
Now it’s time for the circle.
local circle = Instance.new("Frame") -- The circle instance
circle.Size = UDim2.new(0, 14, 0, 14) -- Over the original dot, making it larger.
circle.BackgroundTransparency = 1 -- Sets it to transparent until over a UI element
circle.BorderSizePixel = 0 -- Border set at 0
circle.AnchorPoint = Vector2.new(0.5, 0.5) -- Same as dot.
circle.Position = UDim2.new(0, 0, 0, 0) -- Along with the dot
circle.ZIndex = 10002 -- Over all other UI
circle.Parent = gui -- Inside of the gui.
-- Circlefy the ripple
local circleCorner = Instance.new("UICorner")
circleCorner.CornerRadius = UDim.new(1, 0)
circleCorner.Parent = circle
local circleStroke = Instance.new("UIStroke") -- Gives UI thickness
circleStroke.Color = cursorColor -- Makes the circle the color of the cursor.
circleStroke.Thickness = 2 -- Makes it decently thick(you can edit this if you want it wider)
circleStroke.Parent = circle -- Under the circle
Now, if you were to try this out ingame you’d just have no mouse that’s why we need to run the mouse
Continuously running the mouse
Now we have to run the mouse,
RunService.RenderStepped:Connect(function() -- Always running
local mousePos = UserInputService:GetMouseLocation() -- The position of your mouse using the `UserInputService`
local x, y = mousePos.X, mousePos.Y -- Graphs a point on your screen.
dot.Position = UDim2.new(0, x, 0, y) -- Moves the mouse according to the point created.
circle.Position = UDim2.new(0, x, 0, y) -- Moves along with the mouse
local guisAtPos = player.PlayerGui:GetGuiObjectsAtPosition(x, y) -- Gets the gui objects at the location of the mouse.
local overGui = false -- A variable to decide if the mouse is over another gui object or not.
for _, guiElement in ipairs(guisAtPos) do -- Gets all the UI elements under the mouse
if guiElement.Visible and guiElement:IsA("GuiButton") or guiElement:IsA("TextLabel") or guiElement:IsA("ImageLabel") then
overGui = true -- Get's all UI elements and if the element is a `GuiButton` , `TextLabel` or `ImageLabel` then `overgui` is set to true.
break
end
end
dot.Visible = not overGui -- Dot visible not over UI elements.
circle.Visible = overGui -- Circle visible if over UI elements.
end)
Now we have a working mouse! go try it ingame but we still don’t have a ripple, let’s make that now!
The ripple🤫
for i = 1, 1 do -- Running continuously
local ripple = Instance.new("Frame") -- The frame of the ripple
ripple.Size = UDim2.new(0, 0, 0, 0) -- Set at 0 for invisibility.
ripple.Position = UDim2.new(0, x, 0, y) -- At the pos of the mouse cursor
ripple.AnchorPoint = Vector2.new(0.5, 0.5) -- Anchor point
ripple.BackgroundTransparency = 1 -- Double check for invisibility.
ripple.BorderSizePixel = 0
ripple.ZIndex = 9999 -- Above UI elements other than the mouse and circle.
ripple.Parent = gui -- Under the GUI
local corner = Instance.new("UICorner") -- Circle ripple
corner.CornerRadius = UDim.new(1, 0)
corner.Parent = ripple
local stroke = Instance.new("UIStroke") - Thick circle
stroke.Color = cursorColor -- Color of ripple
stroke.Thickness = 3 -- Adjustable thickness
stroke.Transparency = 0 -- Makes the stroke visible.
stroke.Parent = ripple -- Gives the ripple the stroke.
local targetSize = 30 + (i * 10) -- The size adjusted to your screen.
local expand = TweenService:Create(ripple, TweenInfo.new(0.8, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
Size = UDim2.new(0, targetSize, 0, targetSize)
}) -- Uses TweenService to create a tweeninfo with the EasingStyle(smooth animation) of Sine and EasingDirection to make it start and end.
local fade = TweenService:Create(stroke, TweenInfo.new(0.8, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
Transparency = 1
}) -- Similar to the previous one but tweens the transparency to 1.
expand:Play() -- Plays the expand tween
fade:Play() -- Plays the fade tween
task.delay(0.8, function() -- Waits 0.8 seconds
ripple:Destroy() -- Destroys the temporary ripple
end)
end
end
Finally, time to create the ripple whenever you Click or touch. You can add aditional ones for VR, PlayStation, etc.
UserInputService.InputBegan:Connect(function(input, gpe)
if gpe then return end -- Won't work with `GamePadEnabled`
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.Gamepad2 then
-- Multiple input types to trigger the ripple.
local pos = UserInputService:GetMouseLocation() -- The mouse location
createRipple(pos.X, pos.Y) -- Ripples at the position of the mouse
end
end)
If you have followed the tutorial, you now have an awesome mouse cursor you can use in your game!
Final Product
For those who couldn’t follow the tutorial, here is the finished product!
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local cursorColor = Color3.fromRGB(12, 255, 166)
local gui = Instance.new("ScreenGui")
gui.Name = "SuperAwesomeMouse"
gui.IgnoreGuiInset = true
gui.ResetOnSpawn = false
gui.ZIndexBehavior = Enum.ZIndexBehavior.Global
gui.DisplayOrder = 1_000_000
gui.Parent = player:WaitForChild("PlayerGui")
UserInputService.MouseIconEnabled = false
local dot = Instance.new("Frame")
dot.Size = UDim2.new(0, 6, 0, 6)
dot.BackgroundColor3 = cursorColor
dot.BackgroundTransparency = 0
dot.BorderSizePixel = 0
dot.AnchorPoint = Vector2.new(0.5, 0.5)
dot.Position = UDim2.new(0, 0, 0, 0)
dot.ZIndex = 10001
dot.Parent = gui
local dotCorner = Instance.new("UICorner")
dotCorner.CornerRadius = UDim.new(1, 0)
dotCorner.Parent = dot
local circle = Instance.new("Frame")
circle.Size = UDim2.new(0, 14, 0, 14)
circle.BackgroundTransparency = 1
circle.BorderSizePixel = 0
circle.AnchorPoint = Vector2.new(0.5, 0.5)
circle.Position = UDim2.new(0, 0, 0, 0)
circle.ZIndex = 10000
circle.Parent = gui
local circleCorner = Instance.new("UICorner")
circleCorner.CornerRadius = UDim.new(1, 0)
circleCorner.Parent = circle
local circleStroke = Instance.new("UIStroke")
circleStroke.Color = cursorColor
circleStroke.Thickness = 2
circleStroke.Parent = circle
RunService.RenderStepped:Connect(function()
local mousePos = UserInputService:GetMouseLocation()
local x, y = mousePos.X, mousePos.Y
dot.Position = UDim2.new(0, x, 0, y)
circle.Position = UDim2.new(0, x, 0, y)
local guisAtPos = player.PlayerGui:GetGuiObjectsAtPosition(x, y)
local overGui = false
for _, guiElement in ipairs(guisAtPos) do
if guiElement.Visible and guiElement:IsA("GuiButton") or guiElement:IsA("TextLabel") or guiElement:IsA("ImageLabel") then
overGui = true
break
end
end
dot.Visible = not overGui
circle.Visible = overGui
end)
local function createRipple(x, y)
for i = 1, 1 do
local ripple = Instance.new("Frame")
ripple.Size = UDim2.new(0, 0, 0, 0)
ripple.Position = UDim2.new(0, x, 0, y)
ripple.AnchorPoint = Vector2.new(0.5, 0.5)
ripple.BackgroundTransparency = 1
ripple.BorderSizePixel = 0
ripple.ZIndex = 9999
ripple.Parent = gui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(1, 0)
corner.Parent = ripple
local stroke = Instance.new("UIStroke")
stroke.Color = cursorColor
stroke.Thickness = 3
stroke.Transparency = 0
stroke.Parent = ripple
local targetSize = 30 + (i * 10)
local expand = TweenService:Create(ripple, TweenInfo.new(0.8, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
Size = UDim2.new(0, targetSize, 0, targetSize)
})
local fade = TweenService:Create(stroke, TweenInfo.new(0.8, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
Transparency = 1
})
expand:Play()
fade:Play()
task.delay(0.8, function()
ripple:Destroy()
end)
end
end
UserInputService.InputBegan:Connect(function(input, gpe)
if gpe then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.Gamepad2 then
local pos = UserInputService:GetMouseLocation()
createRipple(pos.X, pos.Y)
end
end)
Thanks to Home | Spark Universe - Minecraft Partner for this entire idea(their mouse cursor is super duper awesome too)
I hope this tutorial helped you create awesome with your games too! Have a great development experience