Hi, so I’ve been seeing a couple of games using this kind of cursor. The most notable game is 3008 (by uglyburger0)
if you don’t know what kind of cursor I mean, here’s a video I took to demonstrate
I haven’t seen any tutorials or devforum posts for these kinds of things, so I figured I’d make the first one. I’d assume I can use image labels and tweens to obtain this effect alongside :getMouse() but I’m not sure
I personally use raycasting but I guess you could use mouse.Target. With raycasting you can accuratly position the cursor on the part, this is the result of that:
Haven’t tested it but something like this should work:
-- services --
local runService = game:GetService('RunService') -- you could bindtorenderstep but I'm gonna use heartbeat:Wait() so it's easier to understand
local players = game:GetService('Players')
-- constants --
local PLAYER = players.LocalPlayer
local CHARACTER = PLAYER.Character
local CURSOR_PART = workspace:WaitForChild('CursorPart') -- part that holds the billboard gui
local HEARTBEAT = runService.Heartbeat
local CURSOR_RANGE = 1000
-- loop --
while HEARTBEAT:Wait() do
local rayResult = workspace:Raycast(CHARACTER.HumanoidRootPart.Position, CHARACTER.HumanoidRootPart.CFrame.LookVector * CURSOR_RANGE)
if rayResult then
CURSOR_PART.Position = rayResult.Position
end
end
Just make a LocalScript inside of StarterCharacterScripts and put this inside of it:
-- services --
local runService = game:GetService('RunService') -- you could bindtorenderstep but I'm gonna use heartbeat:Wait() so it's easier to understand
local players = game:GetService('Players')
-- constants --
local CHARACTER = script.Parent
local HRP = CHARACTER:WaitForChild('HumanoidRootPart')
local PLAYER = players.LocalPlayer
local CAMERA = workspace.CurrentCamera
local CURSOR_PART = workspace:WaitForChild('CursorPart') -- part that holds the billboard gui
local HEARTBEAT = runService.Heartbeat
local BLACKLIST = RaycastParams.new()
BLACKLIST.FilterDescendantsInstances = {CHARACTER, CURSOR_PART}
BLACKLIST.FilterType = Enum.RaycastFilterType.Blacklist
local CURSOR_SIZE = 0.1
local CURSOR_RANGE = 1000
-- functions --
local function adjustSize()
local distanceBetween = (CAMERA.CFrame.Position - CURSOR_PART.Position).Magnitude
CURSOR_PART.BillboardGui.Size = UDim2.new(CURSOR_SIZE * distanceBetween, 0 , CURSOR_SIZE * distanceBetween, 0)
end
-- loop --
while HEARTBEAT:Wait() do
local rayResult = workspace:Raycast(CAMERA.CFrame.Position, CAMERA.CFrame.LookVector * CURSOR_RANGE, BLACKLIST)
if rayResult then
CURSOR_PART.Position = rayResult.Position
adjustSize()
end
end
Keep in mind this is only for first person games because that’s what you showed on the video. Make sure you create a CursorPart which should be anchored and cancollide set to false.
All you’d need to do is check the mouse’s target, this can be achieved via the legacy mouse object or raycasting (if you prefer).
local Game = game
local RunService = Game:GetService("RunService")
local Players = Game:GetService("Players")
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local function OnRenderStep()
local Target = Mouse.Target
if not Target then return end
print(Target.Name)
--Check the target and change the mouse's cursor icon here.
end
RunService.RenderStepped:Connect(OnRenderStep)
Try this example using RayCasting and UserInputService to get mouse position instead of Player:GetMouse() which is probably undergoing deprecation at some point. The only reason it hasn’t happened is it’s widespread use in many games. I nabbed some images from the Toolbox to show it working, they are not my images.
[EDIT] Sorry forgot to add RaycastParam to the raycaster.
Code below:
--=================================================================================
local UserInputService = game:GetService("UserInputService");
local RunService = game:GetService("RunService");
local Icons =
{
"rbxassetid://4344827440",
"rbxassetid://490658593",
"rbxassetid://398096640"
};
--=================================================================================
local RaycastParam = RaycastParams.new();
RaycastParam.FilterType = Enum.RaycastFilterType.Whitelist;
RaycastParam.FilterDescendantsInstances = {workspace.Targets};
RaycastParam.IgnoreWater = true;
local Camera = game.Workspace.CurrentCamera;
local MouseCursor = script.Parent.Cursor;
local Range = 1000;
--=================================================================================
RunService.RenderStepped:Connect(function()
local mouseLocation = UserInputService:GetMouseLocation();
local mouseRay = Camera:ViewportPointToRay(mouseLocation.X,mouseLocation.Y);
local raycastResult = workspace:Raycast(mouseRay.Origin,mouseRay.Direction*Range,RaycastParam);
if raycastResult then
local hitTarget = raycastResult.Instance;
print(hitTarget.Name);
MouseCursor.Visible = true;
UserInputService.MouseIconEnabled = false;
MouseCursor.Position = UDim2.fromOffset(mouseLocation.X,mouseLocation.Y);
if hitTarget.Parent.Name == "FirstType" then
MouseCursor.Image = Icons[1];
return;
elseif hitTarget.Parent.Name == "SecondType" then
MouseCursor.Image = Icons[2];
return;
elseif hitTarget.Parent.Name == "ThirdType" then
MouseCursor.Image = Icons[3];
return;
end
end
UserInputService.MouseIconEnabled = true;
MouseCursor.Visible = false;
end)
--=================================================================================
-- EOF...
--=================================================================================
Hello, Im just reading through this trying to learn a little about raycasts, Im just wondering what the reason for the return is at the end of the function. I’ve always been confused with the use of ‘return’ and whatnot. It would be greatly appreciated
edit: also when I tested, I found that when you hover over one of the objects my fps drops by a lot, what would you do to fix this?
Yeah actually you could lose that and put an else in, this was before I edited the post to put the RaycastParams in because at the time it wasn’t working as I expected. I’ll not edit the original because I made a single use case that can just be dropped into PlayerStarterScripts so I’ll add that here instead and make the changes in logic you suggested.
Single use case here without the need for Instances existing like in the example place.
--=================================================================================
local UserInputService = game:GetService("UserInputService");
local RunService = game:GetService("RunService");
local Players = game:GetService("Players");
local Icons =
{
"rbxassetid://4344827440",
"rbxassetid://490658593",
"rbxassetid://398096640"
};
--=================================================================================
local LocalPlayer = Players.LocalPlayer;
local GUI = Instance.new("ScreenGui");
GUI.Parent = LocalPlayer.PlayerGui;
GUI.IgnoreGuiInset = true;
GUI.Enabled = true;
--=================================================================================
local MouseCursor = Instance.new("ImageLabel");
MouseCursor.Size = UDim2.fromScale(0.05,0.05);
MouseCursor.AnchorPoint = Vector2.new(0.5,0.5);
MouseCursor.SizeConstraint = Enum.SizeConstraint.RelativeYY;
MouseCursor.BackgroundTransparency = 1;
MouseCursor.Visible = false;
MouseCursor.Parent = GUI;
MouseCursor.BorderSizePixel = 0;
--=================================================================================
local RaycastParam = RaycastParams.new();
RaycastParam.FilterType = Enum.RaycastFilterType.Whitelist;
RaycastParam.FilterDescendantsInstances = {workspace.Targets};
RaycastParam.IgnoreWater = true;
local Camera = game.Workspace.CurrentCamera;
local Range = 1000;
--=================================================================================
RunService.RenderStepped:Connect(function()
local mouseLocation = UserInputService:GetMouseLocation();
local mouseRay = Camera:ViewportPointToRay(mouseLocation.X,mouseLocation.Y);
local raycastResult = workspace:Raycast(mouseRay.Origin,mouseRay.Direction*Range,RaycastParam);
if raycastResult then
local CurrentTarget = raycastResult.Instance;
MouseCursor.Visible = true;
UserInputService.MouseIconEnabled = false;
MouseCursor.Position = UDim2.fromOffset(mouseLocation.X,mouseLocation.Y);
if CurrentTarget.Parent.Name == "FirstType" then
MouseCursor.Image = Icons[1];
elseif CurrentTarget.Parent.Name == "SecondType" then
MouseCursor.Image = Icons[2];
elseif CurrentTarget.Parent.Name == "ThirdType" then
MouseCursor.Image = Icons[3];
end
else
UserInputService.MouseIconEnabled = true;
MouseCursor.Visible = false;
end
end)
--=================================================================================
-- EOF...
--=================================================================================
ok so sorry i’m just a little confused, when I remove the returns in the first script you wrote out, it no longer changes the logo. could you explain what changed in the new script that you no longer need the returns?