True-to-Bore Crosshair Integration for SPH Gun System
This tutorial should work for any version for Spearhead/SPH
(I am using 1.2.2)
Important Note:
This tutorial is written this way because SPH does not currently have a dedicated modding system. Any modifications or changes, including this true-to-bore crosshair, must be manually integrated into the CharacterClient
LocalScript provided by SPH. SPH’s client code is not fully modular yet, hence direct edits to the script are necessary.
Disclaimer:
I did not create the DynamicCrosshair Module or the Custom ShiftLock Module. Credit for these modules goes entirely to their original creators listed in the resource section below.
Resources
-
SPH GitHub (Installation & Setup):
SPH GitHub Repository -
RBXL File (Full Demo Game):
crossHairTestingGrounds.rbxl (8.5 MB)
(Note: This includes additional content and may be difficult to dig through and separate all the systems I have. Follow this tutorial carefully if you just want the crosshair.)
(If you want to use the demo place, publish to roblox, turn on API and Http in settings and set to R6. And you may have to re-upload animations for a gun to see the cursor work properly.)
-
SmoothShiftLock Module (RBXM):
CustomShiftLock.rbxm (14.8 KB)
(By CT1) -
DynamicCrosshair Module (RBXM & Dev Forum Post):
Dev Forum Post | DynamicCrosshair.rbxm (6.0 KB)
(Note: For this tutorial to work only use the rbxm file, it has been modified to work with SPH and the “true to bore” nature of this sytem. The post was linked for credit)
Media
Video
What You Need to Do:
1. Ensure SPH is Set Up Properly:
-
SPH Setup: Make sure SPH is set up and the
CharacterClient
script is located underSPH_Character
in StarterCharacterScripts.
2. Add Required Files:
-
Custom Shiftlock Module:
- Action: Place the custom shiftlock RBXM file into StarterPlayerScripts.
-
Dynamic Crosshair Module:
- Action: Put the DynamicCrosshair module into a folder called MiscModules in ReplicatedStorage.
3. Create Required Folders & Events:
-
Folder for Bindable Event:
- Location: In ReplicatedStorage (or your preferred location).
- Action: Create a folder (or container) for the bindable event named Test2Fire.
- Purpose: This event tracks camera offset for custom shiftlock.
4. Setup ScreenGui:
- ScreenGui Name: “CrosshairGui”
- Location: In StarterGui
- Action: Add a ScreenGui called “CrosshairGui”. This will hold the crosshair UI.
5. Insert Crosshair Code into SPH Client Script:
- Action: Place the following code snippets in the exact spots as described below.
Code Integration
ALL CODE INTEGRATIONS ARE DONE IN CHARACTERCLIENT
A. Build and Initialize the Cursor
- Location: At the top of your SPH client script (Anywhere below muzzleAttachment is declared)
-
Folder: Ensure
DynamicCrosshair
module is located inReplicatedStorage/MiscModules
. - Code:
local crosshairUI = game.Players.LocalPlayer.PlayerGui:WaitForChild("CrosshairGui") -- Ensure you have this ScreenGui
local cursor = nil
local DynamicCrosshair = require(game.ReplicatedStorage.MiscModules.DynamicCrosshair)
local function BuildCursor(muzzleAttachment)
print("BUILDING CURSOR")
cursor = DynamicCrosshair.New(crosshairUI, 20, 60, 40, 30, false)
cursor:Size(7, 2)
cursor:Display({
BackgroundTransparency = 0.4,
Image = nil,
ImageTransparency = 0,
BackgroundColor3 = Color3.new(0.729412, 0.729412, 0.729412)
})
cursor:Enable()
-- Use the muzzle for bore-based positioning
if muzzleAttachment then
cursor:UseMuzzleAttach(muzzleAttachment, 600, rayParams)
end
end
local function DestroyCursor()
if cursor then
cursor:Destroy()
cursor = nil
end
end
B. Toggle Cursor Visibility in Aiming
-
Where: Inside the
ToggleAiming
function. - Code:
if toggle then
if cursor then
cursor:ToggleVisible(false)
end
else
if cursor then
cursor:ToggleVisible(true)
end
end
C. Cleanup on Player Death
-
Where: Inside the
humanoid.Died
event handler. - Code:
humanoid.Died:Connect(function()
-- Other death-related cleanup code...
DestroyCursor() -- Clean up the cursor on death
-- Continue with remaining cleanup...
end)
D. Build Cursor When Equipping a Weapon
-
Where: Inside the
character.ChildAdded
event where a new weapon is added. -
Note: Place this code right above the
ToggleSprint
call. - Code:
character.ChildAdded:Connect(function(newChild)
if newChild:FindFirstChild("SPH_Weapon") and not dead then
-- Setup new weapon and other variables...
BuildCursor(gun.Grip:WaitForChild("Muzzle")) -- Build the cursor using the gun's muzzle attachment
ToggleSprint(userInputService:IsKeyDown(config.keySprint))
-- Continue with weapon equip code...
end
end)
E. Destroy Cursor When Unequipping a Weapon
-
Where: Inside the
character.ChildRemoved
event handler. -
Note: If
oldChild
equalsequipped
, call DestroyCursor right above settingwepStats = nil
. - Code:
character.ChildRemoved:Connect(function(oldChild)
if equipped and oldChild:FindFirstChild("SPH_Weapon") then
-- Other cleanup code...
if oldChild == equipped then
DestroyCursor() -- Destroy the cursor upon unequipping
equipped = nil
wepStats = nil
end
-- Continue with remaining cleanup...
end
end)
F. Update Cursor During Firing (Heartbeat)
-
Where: Inside the Heartbeat function, immediately after
playerFire:Fire(curModel.Grip.Muzzle.WorldCFrame)
. - Code:
if cursor then
cursor:Shove(Vector3.new(
recoilStats.vertical ,
math.random(-recoilStats.horizontal, recoilStats.horizontal) ,
recoilStats.punchMultiplier) * dt * 60)
end
G. Update Cursor Position (RenderStepped)
- Where: Inside the RenderStepped function, just above the block that checks for first-person body offset.
- Code:
if cursor then
local curModel = weaponRig.Weapon:FindFirstChildWhichIsA("Model")
if curModel then
local laserPoint = curModel.Grip.Muzzle
local rayResult = workspace:Raycast(laserPoint.WorldPosition, laserPoint.WorldCFrame.LookVector * 600, rayParams)
if rayResult then
cursor:SetWorldPosition(rayResult.Position)
else
cursor:SetWorldPosition(laserPoint.WorldPosition + laserPoint.WorldCFrame.LookVector * 600)
end
end
end
Use these resources as references or starting points for your integration. Make sure you carefully follow the tutorial above if you’re only adding the crosshair to an existing setup.