Trying to create a beam wepaon

I’m trying to create a beam set up in the Roblox Weapon Kit..

WeaponsSystem.Libraries.WeaponTypes

BeamWeapon
-- WeaponsSystem/WeaponTypes/BeamWeapon

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local ContextActionService = game:GetService("ContextActionService")
local CollectionService = game:GetService("CollectionService")
local ContentProvider = game:GetService("ContentProvider")

local IsServer = RunService:IsServer()

local WeaponsSystemFolder = script.Parent.Parent
local Libraries = WeaponsSystemFolder:WaitForChild("Libraries")
local BaseWeapon = require(Libraries:WaitForChild("BaseWeapon"))
local Parabola = require(Libraries:WaitForChild("Parabola"))
local Roblox = require(Libraries:WaitForChild("Roblox"))

local Effects = WeaponsSystemFolder:WaitForChild("Assets"):WaitForChild("Effects")
local ShotsFolder = Effects:WaitForChild("Shots")

local localPlayer = not IsServer and Players.LocalPlayer

local BeamWeapon = {}
BeamWeapon.__index = BeamWeapon
setmetatable(BeamWeapon, BaseWeapon)

BeamWeapon.CanAimDownSights = true
BeamWeapon.CanBeFired = true
BeamWeapon.CanBeReloaded = false
BeamWeapon.CanHit = false

function BeamWeapon.new(weaponsSystem, instance)
	local self = BaseWeapon.new(weaponsSystem, instance)
	setmetatable(self, BeamWeapon)
	self.usesCharging = false
	self.charge = 0
	self.triggerDisconnected = false
	self.nextFireTime = 0
	self.recoilIntensity = 0
	self.aimPoint = Vector3.new()
	self:addOptionalDescendant("tipAttach", "TipAttachment")
	self:addOptionalDescendant("muzzleFlash0", "MuzzleFlash0")
	self:addOptionalDescendant("muzzleFlash1", "MuzzleFlash1")
	self:addOptionalDescendant("muzzleFlashBeam", "MuzzleFlash")
	self:doInitialSetup()
	return self
end

function BeamWeapon:onEquippedChanged()
	BaseWeapon.onEquippedChanged(self)

	if not IsServer then
		if self.equipped then
			ContextActionService:BindAction("ReloadWeapon", function(...) self:onReloadAction(...) end, false, Enum.KeyCode.R, Enum.KeyCode.ButtonX)
		else
			ContextActionService:UnbindAction("ReloadWeapon")
		end
		self.triggerDisconnected = false
	end
end

function BeamWeapon:onReloadAction(actionName, inputState, inputObj)
end

function BeamWeapon:getRandomSeedForId(id)
	return id
end

function BeamWeapon:simulateFire(firingPlayer, fireInfo)
--	print("simulateFire called")
	BaseWeapon.simulateFire(self, fireInfo)

	if self.lastFireSound then
		self.lastFireSound:Stop()
	end
	self.lastFireSound = self:tryPlaySound("Fired", self:getConfigValue("FiredPlaybackSpeedRange", 0.1))

	self:simulateProjectile(firingPlayer, fireInfo, 1, Random.new(self:getRandomSeedForId(fireInfo.id)))
end

function BeamWeapon:simulateProjectile(firingPlayer, fireInfo, projectileIdx, randomGenerator)
	if self.tipAttach ~= nil then
		local tipCFrame = self.tipAttach.WorldCFrame
		local tipPos = tipCFrame.Position
		local tipDir = tipCFrame.LookVector
		local amountToCheatBack = math.abs((self.instance:FindFirstChild("Handle").Position - tipPos):Dot(tipDir)) + 1
		local gunRay = Ray.new(tipPos - tipDir.Unit * amountToCheatBack, tipDir.Unit * amountToCheatBack)
		local hitPart, hitPoint = Roblox.penetrateCast(gunRay, self:getIgnoreList(true))
		if hitPart and math.abs((tipPos - hitPoint).Magnitude) > 0 then
			fireInfo.origin = hitPoint - tipDir.Unit * 0.1
			fireInfo.dir = tipDir.Unit
		end
	end

	local origin, dir = fireInfo.origin, fireInfo.dir
	local bulletSpeed = self:getConfigValue("BulletSpeed", 5000)
	local maxDistance = self:getConfigValue("MaxDistance", 2000)
	local beamThickness0 = self:getConfigValue("BeamWidth0", 0.15)
	local beamThickness1 = self:getConfigValue("BeamWidth1", 0.15)
	local beamFadeTime = self:getConfigValue("BeamFadeTime", 0.5)

	local bulletEffect = self.bulletEffectTemplate:Clone()
	bulletEffect.CFrame = CFrame.new(origin, origin + dir)
	bulletEffect.Parent = workspace.CurrentCamera
	CollectionService:AddTag(bulletEffect, "WeaponsSystemIgnore")
	
	local beam0 = bulletEffect:FindFirstChild("Beam0")
	if beam0 then beam0.Enabled = true end

	local parabola = Parabola.new()
	parabola:setPhysicsLaunch(origin, dir * bulletSpeed, nil, 0)
	parabola:setNumSamples(1)

	local stepConn = nil
	local pTravelDistance = 0
	local startTime = tick()
	local didHit = false
	local stoppedMotion = false
	local stoppedMotionAt = 0
	local fadingOut = false

	local steppedCallback = function(dt)
		local now = tick()
		local timeSinceStart = now - startTime
		local travelDist = bulletSpeed * dt

		local projFront = pTravelDistance
		local maxDist = maxDistance

		if not didHit then
			local castProjBack, castProjFront = projFront, projFront + travelDist
			parabola:setDomain(castProjBack, castProjFront)
			local hitPart, hitPoint, hitNormal, hitMaterial, hitT = parabola:findPart(self:getIgnoreList(true))

			if hitPart then
				didHit = true
				projFront = castProjBack + hitT * (castProjFront - castProjBack)
				parabola:setDomain(0, projFront)
				maxDist = projFront
			end
		end

		if projFront >= maxDist then
			if not stoppedMotion then
				stoppedMotion = true
				stoppedMotionAt = now
			end
		end

		parabola:setDomain(0, math.min(projFront, maxDist))

		if projFront < maxDist then
			pTravelDistance = math.max(0, timeSinceStart * bulletSpeed)
		end
		
		if self.tipAttach and bulletEffect then
			bulletEffect.CFrame = self.tipAttach.WorldCFrame
		end
		
		
		if stoppedMotion then
			if not self.activated and not fadingOut then
				fadingOut = true
				stoppedMotionAt = now
			end

			local thickness0 = beamThickness0
			local thickness1 = beamThickness1

			if fadingOut then
				local fadeAlpha = math.clamp((now - stoppedMotionAt) / beamFadeTime, 0, 1)
				thickness0 = thickness0 * (1 - fadeAlpha)
				thickness1 = thickness1 * (1 - fadeAlpha)

				if fadeAlpha >= 1 then
					if bulletEffect then
						bulletEffect:Destroy()
						bulletEffect = nil
					end
					stepConn:Disconnect()
					return
				end
			end

			if beam0 then
				beam0.Width0 = thickness0
				beam0.Width1 = thickness1
				parabola:renderToBeam(beam0)
			end
		else
			if beam0 then
				beam0.Width0 = beamThickness0
				beam0.Width1 = beamThickness1
				parabola:renderToBeam(beam0)
			end
		end
	end

	stepConn = RunService.Heartbeat:Connect(steppedCallback)
end

function BeamWeapon:getIgnoreList(includeLocalPlayer)
	local ignoreList = {
		self.instanceIsTool and self.instance.Parent or self.instance,
		workspace.CurrentCamera
	}
	if not RunService:IsServer() then
		if includeLocalPlayer and Players.LocalPlayer and Players.LocalPlayer.Character then
			table.insert(ignoreList, Players.LocalPlayer.Character)
		end
	end
	return ignoreList
end

function BeamWeapon:onConfigValueChanged(valueName, newValue, oldValue)
	BaseWeapon.onConfigValueChanged(self, valueName, newValue, oldValue)
	if valueName == "ShotEffect" then
		self.bulletEffectTemplate = ShotsFolder:FindFirstChild(self:getConfigValue("ShotEffect", "Bullet"))
		if self.bulletEffectTemplate then
			local config = self.bulletEffectTemplate:FindFirstChildOfClass("Configuration")
			if config then
				self:importConfiguration(config)
			end
			local beam0 = self.bulletEffectTemplate:FindFirstChild("Beam0")
			if beam0 then
				coroutine.wrap(function()
					ContentProvider:PreloadAsync({ beam0 })
				end)()
			end
		end
	end
end

function BeamWeapon:onActivatedChanged()
	BaseWeapon.onActivatedChanged(self)

	if not IsServer then
		if self.activated and self.player == localPlayer and self:canFire() and tick() > self.nextFireTime then
			self:doLocalFire()
		end
		if not self.activated and self.triggerDisconnected then
			self.triggerDisconnected = false
		end
	end
end

function BeamWeapon:onFired(firingPlayer, fireInfo, fromNetwork)
	if not IsServer and firingPlayer == Players.LocalPlayer and fromNetwork then
		return
	end
	self.nextFireTime = tick() + self:getConfigValue("ShotCooldown", 0.1)
	BaseWeapon.onFired(self, firingPlayer, fireInfo, fromNetwork)
end

function BeamWeapon:getAmmoInWeapon()
	return 1
end

function BeamWeapon:useAmmo(amount)
	return amount
end
function BeamWeapon:canFire()
--	print("canFire called", self.player == Players.LocalPlayer, self.activated, not self.triggerDisconnected, not self.reloading)
	return self.player == Players.LocalPlayer and self.activated and not self.triggerDisconnected and not self.reloading
end

function BeamWeapon:doLocalFire()
--	print("doLocalFire called", self.tipAttach, self.aimPoint)
	if self.tipAttach then
		local tipCFrame = self.tipAttach.WorldCFrame
		local tipPos = tipCFrame.Position
		local aimDir = (self.aimPoint - tipPos).Unit
--		print("firing", tipPos, aimDir)
		self:fire(tipPos, aimDir, self.charge)
	end
end

function BeamWeapon:onRenderStepped(dt)
	BaseWeapon.onRenderStepped(self, dt)
	if not self.tipAttach then return end
	if not self.equipped then return end

	local tipCFrame = self.tipAttach.WorldCFrame

	if self.player == Players.LocalPlayer then
		local aimTrack = self:getAnimTrack(self:getConfigValue("AimTrack", "RifleAim"))
		local aimZoomTrack = self:getAnimTrack(self:getConfigValue("AimZoomTrack", "RifleAimDownSights"))
		if aimTrack then
			local aimDir = tipCFrame.LookVector
			local gunLookRay = Ray.new(tipCFrame.p, aimDir * 500)
			local _, gunHitPoint = Roblox.penetrateCast(gunLookRay, self:getIgnoreList(true))

			if self.weaponsSystem.aimRayCallback then
				local _, hitPoint = Roblox.penetrateCast(self.weaponsSystem.aimRayCallback(), self:getIgnoreList(true))
				self.aimPoint = hitPoint
			else
				self.aimPoint = gunHitPoint
			end

			if not aimTrack.IsPlaying and not self.reloading then
				aimTrack:Play(0.15)
			end

			if aimZoomTrack and not self.reloading then
				if not aimZoomTrack.IsPlaying then
					aimZoomTrack:Play(0.15)
				end
				aimZoomTrack:AdjustSpeed(0.001)
				if self.weaponsSystem.camera:isZoomed() then
					if aimTrack.WeightTarget ~= 0 then
						aimZoomTrack:AdjustWeight(1)
						aimTrack:AdjustWeight(0)
					end
				elseif aimTrack.WeightTarget ~= 1 then
					aimZoomTrack:AdjustWeight(0)
					aimTrack:AdjustWeight(1)
				end
			end

			local MIN_ANGLE = -80
			local MAX_ANGLE = 80
			local aimYAngle = math.deg(self.recoilIntensity)
			if self.weaponsSystem.camera.enabled then
				aimYAngle = math.deg(self.weaponsSystem.camera:getRelativePitch() + self.weaponsSystem.camera.currentRecoil.Y + self.recoilIntensity)
			end
			local aimTimePos = 2 * ((aimYAngle - MIN_ANGLE) / (MAX_ANGLE - MIN_ANGLE))
			aimTrack:AdjustSpeed(0.001)
			aimTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)

			if aimZoomTrack then
				aimZoomTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)
			end
		end
	end
end

function BeamWeapon:onStepped(dt)
	if not self.tipAttach then return end
	if not self.equipped then return end
	BaseWeapon.onStepped(self, dt)
end

return BeamWeapon

This comes out like this.. Beam stops when I left off fire..

I want to make this into a gravity beam or a scanner beam, but it has to stay with the gun, and the beam has to move where I point the gun. Any thoughts here? Just getting to this point was a bear.

It’s a good thing I’m good at debugging. A skill that never loses its value. No one even tried to take this one on.. :rofl:

Weapon Kit with Beam Weapons, woot.

1 Like

Even going to share it.. If you know the Kit you’ll know how to use this too.
It goes in the same place as BowWeapon and BulletWeapon. Needs a bit more tweaking..
I’d like to have a config setting for the BEAM_TEXTURE used.

BeamWeapon
-- WeaponsSystem/WeaponTypes/BeamWeapon

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local CollectionService = game:GetService("CollectionService")

local IsServer = RunService:IsServer()

local WeaponsSystemFolder = script.Parent.Parent
local Libraries = WeaponsSystemFolder:WaitForChild("Libraries")
local BaseWeapon = require(Libraries:WaitForChild("BaseWeapon"))
local Roblox = require(Libraries:WaitForChild("Roblox"))

local camera = workspace.CurrentCamera
local localPlayer = not IsServer and Players.LocalPlayer

local BEAM_TEXTURE = "rbxassetid://446111271"
local BEAM_WIDTH = 4
local BEAM_COLOR = Color3.fromRGB(0, 85, 255)
local BEAM_FADE_TIME = 0.3
local MAX_DISTANCE = 500

local BeamWeapon = {}
BeamWeapon.__index = BeamWeapon
setmetatable(BeamWeapon, BaseWeapon)

BeamWeapon.CanAimDownSights = true
BeamWeapon.CanBeFired = true
BeamWeapon.CanBeReloaded = false
BeamWeapon.CanHit = false

function BeamWeapon.new(weaponsSystem, instance)
	local self = BaseWeapon.new(weaponsSystem, instance)
	setmetatable(self, BeamWeapon)

	self.usesCharging = false
	self.charge = 0
	self.triggerDisconnected = false
	self.nextFireTime = 0
	self.recoilIntensity = 0
	self.aimPoint = Vector3.new()
	self.beamSound = nil
	self.beamSoundVolume = nil
	self.activeBeam = nil

	self:addOptionalDescendant("tipAttach", "TipAttachment")

	self:doInitialSetup()

	return self
end

function BeamWeapon:onEquippedChanged()
	BaseWeapon.onEquippedChanged(self)

	if not IsServer then
		if self.equipped then
			ContextActionService:BindAction("ReloadWeapon", function() end, false, Enum.KeyCode.R, Enum.KeyCode.ButtonX)
		else
			ContextActionService:UnbindAction("ReloadWeapon")
		end
		self.triggerDisconnected = false
	end
end

function BeamWeapon:reload()
end

function BeamWeapon:getAmmoInWeapon()
	return 1
end

function BeamWeapon:useAmmo(amount)
	return amount
end

function BeamWeapon:createBeam(firingPlayer, tipAttach)
	local folder = Instance.new("Folder")
	folder.Name = "BeamVisual"
	folder.Parent = camera

	local a0 = Instance.new("Attachment")
	a0.Name = "BeamStart"
	a0.Parent = folder

	local a1 = Instance.new("Attachment")
	a1.Name = "BeamEnd"
	a1.Parent = folder

	local beam = Instance.new("Beam")
	beam.Attachment0 = a0
	beam.Attachment1 = a1
	beam.Texture = BEAM_TEXTURE
	beam.TextureSpeed = 1
	beam.TextureLength = 1
	beam.TextureMode = Enum.TextureMode.Stretch
	beam.Segments = 10
	beam.Width0 = BEAM_WIDTH
	beam.Width1 = BEAM_WIDTH * 0.8
	beam.LightEmission = 1
	beam.FaceCamera = true
	beam.Color = ColorSequence.new(BEAM_COLOR)
	beam.Enabled = true
	beam.Parent = folder

	local isLocal = firingPlayer == localPlayer

	local stepConn = RunService.Heartbeat:Connect(function()
		if not tipAttach or not tipAttach.Parent then
			beam.Enabled = false
			return
		end

		local tipPos = tipAttach.WorldPosition
		a0.WorldPosition = tipPos

		local rayDir
		if isLocal then
			rayDir = (self.aimPoint - tipPos).Unit
		else
			rayDir = tipAttach.WorldCFrame.LookVector
		end

		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude
		raycastParams.FilterDescendantsInstances = {firingPlayer.Character, camera}

		local result = workspace:Raycast(tipPos, rayDir * MAX_DISTANCE, raycastParams)
		local hitPoint = result and result.Position or (tipPos + rayDir * MAX_DISTANCE)

		if result and result.Instance then
			print(result.Instance:GetFullName())
		end

		a1.WorldPosition = hitPoint
		beam.Width0 = BEAM_WIDTH
		beam.Width1 = BEAM_WIDTH * 0.8
	end)

	return {folder = folder, beam = beam, conn = stepConn}
end

function BeamWeapon:destroyBeam(beamData)
	if not beamData then return end
	beamData.conn:Disconnect()
	local beam = beamData.beam
	local folder = beamData.folder
	local startTime = tick()
	local fadeConn
	fadeConn = RunService.Heartbeat:Connect(function()
		local alpha = math.clamp((tick() - startTime) / BEAM_FADE_TIME, 0, 1)
		beam.Width0 = BEAM_WIDTH * (1 - alpha)
		beam.Width1 = BEAM_WIDTH * 0.8 * (1 - alpha)
		if alpha >= 1 then
			fadeConn:Disconnect()
			folder:Destroy()
		end
	end)
end

function BeamWeapon:simulateFire(firingPlayer, fireInfo)
	local tool = self.instance
	local tipAttach = tool:FindFirstChild("TipAttachment", true)
	if not tipAttach then return end

	if self.activeBeam then
		self:destroyBeam(self.activeBeam)
		self.activeBeam = nil
	end

	local beamData = self:createBeam(firingPlayer, tipAttach)

	local weaponActivated = self.weaponsSystem.getRemoteEvent("WeaponActivated")
	local conn
	conn = weaponActivated.OnClientEvent:Connect(function(player, instance, activated)
		if instance ~= tool then return end
		if not activated then
			conn:Disconnect()
			self:destroyBeam(beamData)
			if self.activeBeam == beamData then
				self.activeBeam = nil
			end
		end
	end)

	self.activeBeam = beamData
end

function BeamWeapon:onActivatedChanged()
	BaseWeapon.onActivatedChanged(self)

	if not IsServer then
		if self.activated and self.player == localPlayer then
			local sound = self:getSound("Fired")
			if sound and not sound.IsPlaying then
				if self.beamSoundVolume then
					sound.Volume = self.beamSoundVolume
				else
					self.beamSoundVolume = sound.Volume
				end
				sound:Play()
				self.beamSound = sound
			end
			if self:canFire() and tick() > self.nextFireTime then
				self:doLocalFire()
			end
		end
		if not self.activated then
			if self.beamSound then
				local sound = self.beamSound
				self.beamSound = nil
				TweenService:Create(sound, TweenInfo.new(0.3), {Volume = 0}):Play()
				task.delay(0.3, function()
					sound:Stop()
				end)
			end
			if self.muzzleFlashBeam then
				self.muzzleFlashBeam.Enabled = false
			end
			if self.triggerDisconnected then
				self.triggerDisconnected = false
			end
		end
	end
end

function BeamWeapon:canFire()
	return self.player == Players.LocalPlayer and self.activated and not self.triggerDisconnected and not self.reloading
end

function BeamWeapon:doLocalFire()
	if self.tipAttach then
		local tipCFrame = self.tipAttach.WorldCFrame
		local tipPos = tipCFrame.Position
		local aimDir = (self.aimPoint - tipPos).Unit
		self:fire(tipPos, aimDir, self.charge)
	end
end

function BeamWeapon:fire(origin, dir, charge)
	if not self:isOwnerAlive() then return end
	local fireInfo = {}
	fireInfo.origin = origin
	fireInfo.dir = dir
	fireInfo.charge = 1
	fireInfo.id = self.nextShotId
	self.nextShotId = self.nextShotId + 1
	if not IsServer then
		self:onFired(self.player, fireInfo, false)
		self.weaponsSystem.getRemoteEvent("WeaponFired"):FireServer(self.instance, fireInfo)
	end
end

function BeamWeapon:onFired(firingPlayer, fireInfo, fromNetwork)
	if not IsServer and firingPlayer == Players.LocalPlayer and fromNetwork then return end
	self.nextFireTime = tick() + self:getConfigValue("ShotCooldown", 0.1)
	self.triggerDisconnected = true
	if not IsServer then
		self:simulateFire(firingPlayer, fireInfo)
	end
end

function BeamWeapon:onRenderStepped(dt)
	BaseWeapon.onRenderStepped(self, dt)
	if not self.tipAttach then return end
	if not self.equipped then return end

	if self.player == Players.LocalPlayer then
		local aimTrack = self:getAnimTrack(self:getConfigValue("AimTrack", "RifleAim"))
		local aimZoomTrack = self:getAnimTrack(self:getConfigValue("AimZoomTrack", "RifleAimDownSights"))
		if aimTrack then
			local tipCFrame = self.tipAttach.WorldCFrame
			local aimDir = tipCFrame.LookVector
			local gunLookRay = Ray.new(tipCFrame.p, aimDir * 500)
			local _, gunHitPoint = Roblox.penetrateCast(gunLookRay, {self.instance, camera})

			if self.weaponsSystem.aimRayCallback then
				local _, hitPoint = Roblox.penetrateCast(self.weaponsSystem.aimRayCallback(), {self.instance, camera})
				self.aimPoint = hitPoint
			else
				self.aimPoint = gunHitPoint
			end

			if not aimTrack.IsPlaying and not self.reloading then
				aimTrack:Play(0.15)
			end

			if aimZoomTrack and not self.reloading then
				if not aimZoomTrack.IsPlaying then
					aimZoomTrack:Play(0.15)
				end
				aimZoomTrack:AdjustSpeed(0.001)
				if self.weaponsSystem.camera:isZoomed() then
					if aimTrack.WeightTarget ~= 0 then
						aimZoomTrack:AdjustWeight(1)
						aimTrack:AdjustWeight(0)
					end
				elseif aimTrack.WeightTarget ~= 1 then
					aimZoomTrack:AdjustWeight(0)
					aimTrack:AdjustWeight(1)
				end
			end

			local MIN_ANGLE = -80
			local MAX_ANGLE = 80
			local aimYAngle = math.deg(self.recoilIntensity)
			if self.weaponsSystem.camera.enabled then
				aimYAngle = math.deg(self.weaponsSystem.camera:getRelativePitch() + self.weaponsSystem.camera.currentRecoil.Y + self.recoilIntensity)
			end
			local aimTimePos = 2 * ((aimYAngle - MIN_ANGLE) / (MAX_ANGLE - MIN_ANGLE))
			aimTrack:AdjustSpeed(0.001)
			aimTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)
			if aimZoomTrack then
				aimZoomTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)
			end
		end
	end
end

function BeamWeapon:onStepped(dt)
	if not self.tipAttach then return end
	if not self.equipped then return end
	BaseWeapon.onStepped(self, dt)
end

return BeamWeapon

Not fully picked over.. (yet)

Also, this doesn’t do any damage. Ripped all that out starting with the BulletWeapon mod.
This will end up being a gravity beam and a scanner beam. It will print what it is hitting however,
I need that for the next step..

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.