Hello everyone! This is my first community tutorial so any corrections you might have would be appreciated!
Today I’ll be sharing my approach to making scope shadow/eye relief effect for a gun sight in Roblox using a neat trick I hope you all can appreciate as much as I do. For this tutorial, It’s best if you have some knowledge about UV unwrapping and texturing and basic skills in Blender. I used this technique in a game I worked on, Operation Scorpion, for the magnified optics. This can probably be expanded to have multiple layers of the effect including one for a reticle that’ll move around, but I’m lazy and won’t be doing all that.
The final (and very rough) result we’ll be getting in this tutorial:
Step One
First up, we’re gonna make a circle with however many verts you think is best. Here, I chose 16. This is literally the only mesh based thing we’re going to be doing, everything else is either texture or code.
Step 2
Next, go ahead and unwrap your circle and scale down the unwrap by around 50% it’s original size. This will let us move around a texture on the surface without it going too far over and around the other side of our circle when we move it around.
Step 3
Next up, you’re going to need to create a completely black image with a transparent hole cut out in the middle of it. This ideally should line up with the unwrap you did. For better results, blur the edges. This can take some time to get right, if you don’t want to take that time, go ahead and take the one I’ve made for this tutorial. This was made easily in photopea by creating a new project, making a layer that’s completely black, drawing an elipse with half the size of the image and positioned in the center, rasterize the circle, select the circle’s pixels, go back to the black layer and delete the pixels from the circle on the black layer, then blur the black layer and hide the circle. It’s not too tough if you know some basic photoshop utilities and there may be other simpler ways I don’t know about.
Open up the texture in blender behind the unwrap and be sure it nicely goes around the unwrapped circle
Perfect, almost there!
Step 4
Now we need to export the mesh to a file and bring it into studio.Now that we have our fbx file exported, head into studio and create a new MeshPart. Inside the mesh part there’s a property called MeshId with a folder icon to the right of it. Click the folder icon and find your fbx file and open it. It’ll ask a few questions about if you want to resize it and keep it’s position, do whatever you really want because odds are our scope meshes aren’t gonna be the same size and it won’t matter either way.
Step 5
Excellent! Our mesh is in studio, however, texture isn’t there. What I’ll do is upload it through roblox as a decal then use the URL for a texture object under the MeshPart. After you do this, make the mesh transparent by setting it’s Transparency property to 1.
Step 6
Looks pretty funny right now because our mesh isn’t to scale so I’ll go ahead and fix that right now using a scope mesh I found on the toolbox. Make sure the circle mesh’s size is the same width as height.
Now that my scope fits inside the optic squarely, a really important step is to set the texture’s “OffsetStudsU” and “OffsetStudsV” as half the side length of the circle mesh and the “StudsPerTileU” and “StudsPerTileV” as the side length of the circle mesh. In my case, the circle mesh’s size is: 0.5, 0.001, 0.5. Here’s how my texture properties should look for this sizing:
And here’s how it looks on the scope:
Awesome! We’re really, really close now! Hopefully you’ll see by playing with the OffsetStuds properties, you can shift the circle around and it obscures different parts of the mesh.
Step 7
Finally, I wrote a quick script that’ll attach the sight to my camera. This definitely won’t fit your circumstance because I just threw it together but it’s pretty simple to understand and adapt your own approach.Here, I put a localscript inside StarterPlayerScripts. I put a weld constraint between the circle mesh and the optic mesh before grouping them together and making the optic mesh the PrimaryPart. Finally, I put the model as a child of the localscript. Some time making this code later and we’re done!
local run = game:GetService("RunService")
local uis = game:GetService("UserInputService")
local function lerp(a, b, t)
return a + (b - a) * t
end
local camera = workspace.CurrentCamera
local acog = script.ACOG:Clone()
acog.Parent = workspace
local shadow = acog.shadow.Texture
local tx, ty = shadow.OffsetStudsU, shadow.OffsetStudsV
local x, y = tx, ty
-- Change the x and y values when the mouse is moved
uis.InputChanged:Connect(function(i)
if i.UserInputType == Enum.UserInputType.MouseMovement then
x += i.Delta.X * 0.001
y += i.Delta.Y * 0.001
end
end)
-- Set an offset to position the optic in front of the camera
local offset = CFrame.new(0, -0.05, -2) * CFrame.Angles(0, math.pi, 0)
run.RenderStepped:Connect(function(dt)
acog:SetPrimaryPartCFrame(camera.CFrame:ToWorldSpace(offset))
-- Lerp the values back to the original position
x = lerp(x, tx, dt * 10)
y = lerp(y, ty, dt * 10)
-- Set the offset to the lerped values
shadow.OffsetStudsU = -y
shadow.OffsetStudsV = x
end)
Here’s the place file for what I’ve made here.
scopeshadow.rbxl (67.5 KB)
Hope you find this helpful, I know I would’ve when i was trying to think of a way. Any questions you guys have, go ahead and ask them. I’m having some trouble uploading images and videos to the draft here so maybe I’ll go back and add some pictures later if the issue resolves itself.
Thanks for reading!