Variable keeps returning nil?

Hello, I am using oop to create swords for my game, but whenever I set one of the SwordUtils.new({}) constructors/things to variables, then it always returns nil.
Here is an example of what I mean:

Here is the script:


Here is the output:

I don’t know what I am doing wrong, I tried making the Commander variable/thing to be local, I tried removing it and adding :SpawnUnits directly onto the end of SwordUtils.new({}) but nothing works, maybe this is a game engine bug? If you need my full module script, then I will gladly give it.

can i see your module script class?

function SwordUtils:SpawnUnits(UnitsWithChance)
	self.LaunchesEnemies = true
	self.LaunchEnemyInfo = UnitsWithChance

	local Percentages = {}
	local Units = {}
	local cmdPosition = -1

	for count=1,#UnitsWithChance/2 do
		cmdPosition += 2
		table.insert(Percentages,100/UnitsWithChance[cmdPosition + 1])
		table.insert(Units,UnitsWithChance[cmdPosition])
	end

	local clones = 0
	
	if spawnCheck then

		while clones < 2 do task.wait()
			for i,unit : BodyForce in pairs(Units) do
				local random = math.random(1,Percentages[i])
				if random == Percentages[i] then
					clones += 1
					local clone = unit:Clone()
					local bodyVelocity = Instance.new('BodyVelocity')

					clone.Parent = workspace.Allies 
					clone:SetPrimaryPartCFrame(self.Sword.Handle.CFrame)
					bodyVelocity.Parent = clone.HumanoidRootPart
					bodyVelocity.MaxForce = Vector3.new(10000,10000,10000)
					bodyVelocity.Velocity = Vector3.new(25,15,25) * self.Sword.Parent.HumanoidRootPart.CFrame.LookVector
					Debris:AddItem(bodyVelocity,0.5)
				end
			end
		end
	else
		spawnCheck = true
	end
end

This is how a typical OOP module script is set up. You will need to set the index of the module to itself, then create a new table with the metatable as the module INSIDE a .new() function, and finally return that new table that you created:

local SwordUtils= {}
SwordUtils.__index = SwordUtils

function SwordUtils.new(UnitsWithChance)
	local self = setmetatable({}, SwordUtils)
	
   self.Sword = UnitsWithChance.Sword --or use wahtever other way to add data to the self table you like

	return self
end

return Booth
1 Like

oh, sorry, I have a constructor script aswell, I thought you meant the SpawnUnits thing

function SwordUtils.new(SwordInfo)
	CollectionService:AddTag(SwordInfo.Sword,SwordInfo.Sword.Name)
	
	SwordInfo.Sword:Clone().Parent = game:GetService('StarterPack')
	
	CollectionService:GetInstanceAddedSignal(SwordInfo.Sword.Name):Connect(function(Sword)

You will still need to return something back after calling the .new(SwordInfo) constructor, otherwise when you run SwordUtils.new(SwordInfo) it just runs as a normal function and returns nothing. If you want it to return an object, you must create a table in it and return the table at the end by using the example I gave above. Those items are the BARE MINIMUM for using OOP in roblox

1 Like

Remove the SetPoints, that’s probably the issue.

If you want to make it error by your own, do this after }):

if Commander == nil then
 error("attempt to index nil with '"..Commander.Name.."'")
end

I can’t remove SetPoints though, it is an essential part of my script which sets RaycastHitbox points on the sword, and I don’t know why I would want to error something that already errors.

ah I think I figured out the issue, end line return newSword is inside of CollectionService:GetInstanceAddedSignal and it might be returning the metatable for collectionservice instead of returning it in the actual function, how would I solve this? add a double return? I’m not really sure, i’ve tried making a variable called returnMetatable and setting it to the metatable I have and then returning that variable, but no matter what I do, it returns nil, is there any way to return a value from one function to the previous?

function SwordUtils.new(SwordInfo)
	CollectionService:AddTag(SwordInfo.Sword,SwordInfo.Sword.Name)
	
	SwordInfo.Sword:Clone().Parent = game:GetService('StarterPack')
	
	CollectionService:GetInstanceAddedSignal(SwordInfo.Sword.Name):Connect(function(Sword)

		local animChildren = Sword.Animations:GetChildren()
		local toolActivated = false
		local newHitbox
		local newSword = setmetatable({},SwordUtils)
		local projectileInfo = SwordInfo.ProjectileInfo or {} --if first value is nil instead use an empty table so it doesn't error
		local debuffInfo = SwordInfo.DebuffInfo or {}
		newSword.Sword = Sword
		newSword.Damage = SwordInfo.Damage
		newSword.ProjectileType = projectileInfo.projectileType
		newSword.ProjectileVelocity = projectileInfo.projectileVelocity
		newSword.LaunchEnemyInfo = nil
		newSword.LaunchesEnemies = false

		if not game:GetService('StarterGui').ShopUi.Main.Swords:FindFirstChild(Sword.Name) then

			local GuiSwordTemplate = ReplicatedStorage.UI.SwordTemplate:Clone()

			GuiSwordTemplate.Name = Sword.Name
			GuiSwordTemplate.Parent = game:GetService('StarterGui').ShopUi.Main.Swords
			GuiSwordTemplate.SwordViewport.Stats.Text = 'Damage: '..tostring(SwordInfo.Damage)
				..'    Cooldown: '..tostring(SwordInfo.cooldown)
				..'    Debuff: '..tostring(debuffInfo.Debuff)
				..'    Projectile: '..tostring(projectileInfo.projectileType)
				..'    Projectile Cooldown: '..tostring(projectileInfo.projectileCooldown)
				..'    Projectile Velocity: '..tostring(projectileInfo.projectileVelocity)
			GuiSwordTemplate.SwordViewport.Price.Text = SwordInfo.price
		end

		local ActiveAbility

		if SwordInfo.Ability then
			Sword.Equipped:Connect(function()
				ActiveAbility = Sword.Parent[SwordInfo.Ability]
				ActiveAbility.Sound.SoundId = Sword.Blade[SwordInfo.Ability].SoundId
				ActiveAbility.Enabled = true
			end)

			Sword.Unequipped:Connect(function()
				ActiveAbility.Enabled = false
			end)
		end

		if projectileInfo and projectileInfo ~= {} then

			local Attacking = false
			local swordEquipped = false
			local findAmbient = Sword:FindFirstChild('Ambient',true)
			Sword.Equipped:Connect(function()
				swordEquipped = true
				if findAmbient then
					findAmbient:Resume()
				end
			end)
			Sword.Unequipped:Connect(function()
				swordEquipped = false
				if findAmbient then
					findAmbient:Pause()
				end
			end)
			ReplicatedStorage.Events.PressedE.OnServerEvent:Connect(function(plr)
				if Attacking == false and swordEquipped == true and projectileInfo ~= {} then

					local Anim = plr.Character.Humanoid:LoadAnimation(Sword.ProjectileAnim)
					Attacking = true
					Anim:Play()
					Anim:AdjustSpeed(projectileInfo.AnimSpeed)
					SwordUtils.FireProjectile(newSword)
					delay(1,function()
						Anim:Stop()
					end)
					delay(projectileInfo.projectileCooldown,function()
						Attacking = false
					end)
				end
			end)
		end
		
		Sword.Activated:Connect(function()
			if toolActivated == false then
				local randomAnim = animChildren[math.random(1,#animChildren)]
				local char = Sword.Parent
				local anim = char.Humanoid:LoadAnimation(randomAnim)
				toolActivated = true

				if newSword.LaunchesEnemies == true then
					SwordUtils.SpawnUnits(newSword,newSword.LaunchEnemyInfo)
				end

				if Sword.Handle:FindFirstChildWhichIsA('Sound') then
					Sword.Handle.Attack:Play()
					if newHitbox == nil then
						newHitbox = RaycastHitbox.new(Sword.Handle)
						newHitbox:SetPoints(Sword.Handle,SwordInfo.SetPoints)
					end
				else
					if Sword.Blade:FindFirstChild('Attack1') then
						local attackSounds = {}
						for number,sound in pairs(Sword.Blade:GetChildren()) do
							if string.match(sound.Name,'Attack') then
								table.insert(attackSounds,sound)
							end
						end
						attackSounds[math.random(1,#attackSounds)]:Play()
					else
						Sword.Blade.Attack:Play()
					end
					if newHitbox == nil then
						newHitbox = RaycastHitbox.new(Sword.Blade)
						newHitbox:SetPoints(Sword.Blade,SwordInfo.SetPoints)
					end
				end
				local Params = RaycastParams.new()
				local playerTable = {char}
				for _,player in pairs(Players:GetPlayers()) do
					table.insert(playerTable,player.Character)
				end
				Params.FilterDescendantsInstances = playerTable
				Params.FilterType = Enum.RaycastFilterType.Blacklist
				newHitbox.RaycastParams = Params
				newHitbox:HitStart()
				newHitbox.OnHit:Connect(function(hit,humanoid)
					if humanoid.Parent.Parent.Name == 'Enemies' then
						if humanoid:FindFirstChild('damageMultiplier') then
							humanoid:TakeDamage(SwordInfo.Damage * humanoid.damageMultiplier.Value)
						else
							humanoid:TakeDamage(SwordInfo.Damage)
						end
						if debuffInfo.Debuff ~= nil then
							Debuffs[debuffInfo.Debuff](humanoid,debuffInfo.duration)
						end
					end
				end)

				anim:Play()
				anim:AdjustSpeed(SwordInfo.AnimSpeed)
				anim.Stopped:Wait()
				task.wait(0.1)
				newHitbox:HitStop()
				task.wait(SwordInfo.cooldown)
				toolActivated = false
			end
		end)
		return newSword -- here is the problem I think, its probably returning newSword on the CollectionService function instead of SwordUtils.new()
	end)
end

Answered in post

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