A moving flashlight!

A moving flashlight!

(Please note that this only works on R6 avatars, and they must be in first person.)


Hello, my fellow scripters!

Today, we are going to be talking about a flashlight, but not just any regular one. A moving one!

I’m not talking about a PointLight glued to your camera, I mean a flashlight tool that points to your mouse direction… mostly.

I’ve seen many articles about this issue, so I decided to add a working solution that lots of people want, so I feel important. >:)

What most of those people request is a flashlight that points to where your camera is looking at. So that’s what we’re going to do, but the correct way.


Let’s start by adding a flashlight with a simple light script already implemented inside. The sound is the on/off sound that plays every time you toggle your flashlight, and the mesh which makes the tool’s Handle look like… a flashlight.

Screenshot 2023-08-11 at 16.00.48

With this 4-line Server script, making the on/off switch functional:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

Now, how do we make this flashlight go up and down? Well, we’re not using sockets, or any other kind of complicated math. This will be possible by getting the Motor6Ds.

In every single player’s Torso, there are multiple Motor6Ds inside, and we’re going to be modifying one: the Right Shoulder, aka your right arm’s shoulder (The arm which holds that tool). We can very simply change the .C0’s orientation to make it look where your camera looks. So what do we change it to?

Screenshot 2023-08-11 at 16.07.37

We need to add a local script and an event to communicate with the server. The client will be responsible for checking the player’s CurrentCamera, then sends that information to the server, so that script can then set the arm’s orientation.

So, create a LocalScript named “Client”, and a RemoteEvent called “Event”, looking like this:

Screenshot 2023-08-11 at 16.08.46

Now, we need to make the client-side check for the player’s camera, so the server-side can appropriately set the arm’s orientation:


— CLIENT SCRIPT —


Step 1:

local camera = workspace.CurrentCamera

This gets the player’s local camera, basically what the player sees.


Step 2:

local camera = workspace.CurrentCamera

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	
end)

This function runs every time the camera’s position or orientation changes. This saves lots of performance because if the player isn’t moving their camera, then it’ll be updating the arm’s orientation for no reason, in the server-side script. Having a for loop is not recommended unless there really is no other option.


Step 3:

local camera = workspace.CurrentCamera

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	script.Parent.Event:FireServer()
end)

Now, the event inside our Tool gets fired every time that function gets fired. We need to pass on information to the server-side however.


Step 4:

local camera = workspace.CurrentCamera

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	script.Parent.Event:FireServer(camera.CFrame)
end)

So now we’re getting the camera’s CFrame (position and orientation). There’s only one value in this that’s very important, so let’s search deeper into this property.


Step 5:

local camera = workspace.CurrentCamera

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	script.Parent.Event:FireServer(camera.CFrame.LookVector)
end)

Now we’re looking at the orientation of the player’s camera. This will be useful since the server needs to know where we’re looking at, so it can then set the arm’s orientation.

(I probably said “arm’s orientation”, like, 6 times now.)


Step 6:

local camera = workspace.CurrentCamera

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	script.Parent.Event:FireServer(camera.CFrame.LookVector.Y)
end)

Lastly, for this client script, we get the Y axis of the orientation. Since the player will be in first person, the X and Z angles won’t really matter, since the flashlight will naturally rotate with you, but it doesn’t go with you when you look up and down. So that’s what we’re currently doing, if you already didn’t know.

How this works is that Y axis’s value goes between 1 and -1. If you directly at the middle, the value will be 0, if you look up, it’ll be 1, if you look down, it’ll be -1, anywhere between will make the value go between. I hope you can understand that.

Now let’s go to the server script. This one will be longer, and much more difficult, but I’ll be with you every step of the way. Don’t worry!


— SERVER SCRIPT —


Step 1:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
end)

Just after those 4 lines, we will make a function that runs every time that RemoteEvent is fired from the client script, giving us the player that fired it, and the “axis” variable, which is our Y axis orientation. So now we’re going to move the player’s arm.


Step 2 (Optional):

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
end)

This is just an anti-cheat feature. You don’t have to add this, but this can be useful if your game is prone to exploiters.


Step 3:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character then
		
	end
	
end)

The script checks if the character exists before actually editing the arm’s orientation, to prevent any errors. Of course, you can use a pcall(), but i’m not like other people. Or maybe most people use my method. Wait, do they? I’m gunna need to find out. ahem Anyway, moving on.


Step 4:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		
	end
	
end)

The script is now also checking if the Tool is equipped or not. You’re not going to be moving your arm without even holding your flashlight, you’ll just look like you’re waving your hand to someone.


Step 5:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		player.Character.Torso["Right Shoulder"]
	end
	
end)

Now, we’re going to be looking for that right shoulder. This is basically a connection between your torso and right shoulder, you can change the position and orientation, (which is what we wanna do), and some other things which are irrelevant.


Step 6:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		player.Character.Torso["Right Shoulder"].C0
	end
	
end)

Now we’re going to access the actual arm’s position and orientation, this .C0 is a CFrame, once again, a position and an orientation. This is getting tiring; saying things over and over.


Step 7:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),math.rad(axis*60))
	end
	
end)

This post is taking me 1 hour already, so I’ll make this a long jump. What we’re doing now is creating a new CFrame, setting it’s position to what it already is, not modifying it, then the orientation… oh boy, what did I do?

The original orientation for the shoulder is (0,90,0), and if we change the Z axis to 60, it’ll move up, -60 and it’ll go down. So we’re using the axis and multiplying it by 60, so where we look will then make the arm go either up or down.


Step 8:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end
	
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),math.rad(axis*60))
	else
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),0)
	end
	
end)

Just one more thing, if you unequip the flashlight your arm will stay where it was, either up or down. So we need to set it back to what it originally was.


— CONCLUSION —

So now we’ve finished. I have just told you step-by-step (mostly) to how to make a working moving flashlight. I tested this myself, and the flashlight does get a bit hidden if you look directly up or down, but at least the arm moves wherever your camera looks!?

I’ll be honest, I don’t think my tutorial was 100% clear or even correct, because I did research this, and I had to do about 40 attempts to get this right. The reason I did this is because there was nothing to help me, and I brute-forced my peanut brain to get this correct, and I eventually did, and I didn’t want anyone else to experience this mental breakdown, so I made this post.

And no, I’m not going to add the aftermath of the server and client scripts. You’re reading through this.

This took me 2 hours to type all out, so I hope this article is useful to someone. Please. Please make use of this. ;-;

OK, bye!


-DiamondDrencher1

33 Likes

Thank you so much! I have been stuck on this same problem for 2 days now and this worked perfectly for my fps game!

2 Likes

Glad to see someone who actually thinks my tutorial is useful. Thank you.

1 Like

THANK YOU BRO! You don’t know how many tutorials I went through, just to try and make a stupid brick arm follow my camera. Your tutorial taught me what I needed, and now everything works. Very appreciative of your tutorial because I am still learning how to script and am teaching myself. :grinning: :grinning:

2 Likes

Good resource, one thing though.

If this does happen, you should add a return, because it currently errors since it can’t find the character anymore on the next lines.

Code:

script.Parent.Activated:Connect(function()
	script.Parent.Handle.Light.Enabled = not script.Parent.Handle.Light.Enabled
	script.Parent.Handle.Sound:Play()
end)

script.Parent.Event.OnServerEvent:Connect(function(player,axis)
	if axis > 1 or axis < -1 then player:Kick("Maliciously using RemoteEvents.") return end

	if player.Character and player.Character:FindFirstChild(script.Parent.Name) then
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),math.rad(axis*60))
	else
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),0)
	end
end)

The C0 is always going to be between 1 and -1, and if the character doesn’t exist, then the server script already checks that, with an if statement saying if player.Character.

i guess what you mean is put the anticheat thing inside the if parameter that checks if the character exists. You could do that.

I don’t think you’re understanding what I’m saying.

I added a return in your anticheat check, because without that return, on this line:

Your script errors, because player.Character.Torso no longer exists since you kicked them. You can test it yourself without the return.

Error:

I tried it before on my own game and nothing errored.

I guess you can add a return.

Try adding if player.CharacterAppearanceLoaded() or something.

The player has already been kicked, which is why the error appears. CharacterAppearanceLoaded would just cause another error. Returning after the anticheat check prevents it.

I did say (optional) in the section where you add the anticheat, in my tutorial.

Just remove it. Sure, an exploiter can make their C0 orientation crazy. It doesn’t benefit them.

No there’s no need to remove it if you can fix it by adding a return. I’m not saying there’s an unsolvable problem, I’m telling you the solution.

Do as you please though, just telling you how to fix it.

1 Like

Can’t you kick the player, and then use return?

That’s how you’re supposed to do it

1 Like

Yes, that’s what my fix does.

I updated my post, kicking the player then using return on the script, so there’s no errors.

Apologies for asking, but how would you do this with r15 avatars?

Since there’s more Motor6Ds in R15, you’d have to probably alter more than just the Right Shoulder for R6.

This was a super useful post! Thanks for making this! I’ve also made some edits that people can use if they want this to only be in effect while the player is in first person. This post is where I got the first person detection from: How do I detect if a player is in first person? - Help and Feedback / Scripting Support - Developer Forum | Roblox

The code below will reset the flashlight back to the default position whenever the player is in third person.

Server:

script.Parent.Event.OnServerEvent:Connect(function(player,axis,firstperson)
	if player.Character and player.Character:FindFirstChild(script.Parent.Name) and firstperson then
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),math.rad(axis*60))
	else
		player.Character.Torso["Right Shoulder"].C0 = CFrame.new(player.Character.Torso["Right Shoulder"].C0.Position) * CFrame.Angles(0,math.rad(90),0)
	end
end)

Client:

local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local head = char:WaitForChild("Head")

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	local fp = nil
	if head.LocalTransparencyModifier == 1 then
		fp = true
	else
		fp = false
	end
	
	script.Parent.Event:FireServer(camera.CFrame.LookVector.Y, fp)
end)

While working on the code, I also accidentally made a version where it doesn’t reset so you can basically set your flashlight’s Y position by going in first person and it’ll lock when going in third person. The code for that is below.

Client:

local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local head = char:WaitForChild("Head")

camera:GetPropertyChangedSignal("CFrame"):Connect(function()
	if head.LocalTransparencyModifier == 1 then
		script.Parent.Event:FireServer(camera.CFrame.LookVector.Y)
	end
end)

(The server script remains unchanged for this version.)

Your notification just made me revive my devforum account and I’m back.

Also, thanks (:

1 Like