How to get stuff to magnetise to a player?

Im trying to make a game where your character is a magnet and stuff attaches to yourself. I’m struggling with getting parts to actually attach to your character. Ive made a script utilising AlignPosition but when trying to use it with a big part getting attached to my character, my character would go to the part/stop my character instead of the part coming to me. How can i fix this?

I know AlignPosition has a property called “ReactionForceEnabled” which basically lets only one part apply force but wouldnt work as something inside the character needs to be anchored. Only problem is that it would stop the character from moving.

local player = game.Players.LocalPlayer

local char = player.Character or player.Character:Wait()
local touch = workspace.Touch
local placeholder = workspace.Placeholder

local parts = {
	"Pipe",
	"Cog",
	"Wood planks",
	"Paper",
	"Apple",
	"Litter",
}

-- Touch Event
touch.Touched:Connect(function(inst)
	if inst.Parent:FindFirstChild("Humanoid") then
		if inst.Parent == char then
			for i,part in pairs(parts) do
				local randomNumber = math.random(1,#part)
				if i <= randomNumber then

					-- Main
					local attachment = Instance.new("Attachment");attachment.Parent = char.HumanoidRootPart

					placeholder.Anchored = false
					placeholder.AlignPosition.Attachment0 = placeholder.Attachment
					placeholder.AlignPosition.Attachment1 = char.HumanoidRootPart.Attachment
					placeholder.AlignPosition.Enabled = true

					placeholder.Touched:Connect(function(ins)
						if ins.Parent.Name == char.Name then
							local wc = Instance.new("WeldConstraint")
							wc.Part0 = char.HumanoidRootPart
							wc.Part1 = placeholder
							wc.Parent = char.HumanoidRootPart
							wc.Enabled = true
							placeholder.AlignPosition.Enabled = false
						end
					end)
				end
			end
		end
	end
end)

Video:

3 Likes

Have you tried swapping the attachments?

Also I would recommend using something like Region3 instead of Touch if you want it to be more similar to a magnet.

If i swap the attachments, my character will go to the part instead of the part coming to the character.

You’ll have to swap everything that goes along with that. But it seems that the weld is what the issue is, and it also seems that the part is anchored in that video?

This is because the part has a larger mass?

regardless of the mass, the character will always go to the part due to the reaction force being enabled. However when the mass of the part is really low, it looks like the player isn’t moving towards the part at all, but it is slightly. The higher the parts mass, the more the player moves towards the part.

Why are you like using a weld constraint???

I feel like an eaiser method would be using bodyposition.

Alright, ill try it

(3 0 chars)

The BodyPosition | Roblox Creator Documentation class has been superseded by the AlignPosition | Roblox Creator Documentation class. AlignPosition is preferable because generally it will be more performant and allow greater control.

Also you should probably use Collision Groups in order to avoid magnetized parts from pushing the player around while still colliding with each other, if that’s what you want. But things can get a little crazy when it comes to parts and collision.

1 Like

In the AlignPosition documentation it says, " By default, this constraint only applies forces on Attachment0, although it can be configured to apply forces on both attachments."

AlignPosition | Roblox Creator Documentation should be set to false. The player does not need to be anchored.

Also, as @MightyDantheman mentioned, you should not have the parts collide with the player because it will push them around. You could use either collision groups or a NoCollisionConstraint | Roblox Creator Documentation.

1 Like

Only problem with this is that for it to work, the character needs to be anchored. If i anchor the character they wont move, which is an issue for the game im making.

I’ve tested two parts together using an AlignPostion without anchoring either and it worked, which means it’s probably a design flaw in your code.

From looking at the video, it looks like the part is anchored and the the character is being welded to it prematurely.

Are you able to send over a code or a rbx file of it working?

Yes. I’m actually working on creating my own little magnetization system now, just because it seemed like fun. I’ll give you the file when it’s completed (probably about 15-30 minutes).

1 Like

Alright, as I said, it only took 15-30 minutes. However, I sort of got carried away and expanded on the original idea. That took a few hours longer. Anyways, heres a model I made:

If you want, here’s the code it uses:

Code
-----------------------
-- Services -----------

local CollectionService: CollectionService = game:GetService("CollectionService")
local PhysicsService: PhysicsService = game:GetService("PhysicsService")
local Players: Players = game:GetService("Players")
local Debris: Debris = game:GetService("Debris")


-----------------------
-- Variables ----------

-- Types
type ferro = {target: Attachment, origin: Attachment, magnet: AlignPosition, bond: WeldConstraint, bonded: boolean}
type targetPoints = {magnet: BasePart, magnetPos: Vector3, ferroPos: Vector3}

-- Objects
local magnetFolder: Folder = script.Parent
local magnetCreator: BasePart = magnetFolder:WaitForChild("MagnetCreator")::BasePart
local magnetTemplate: BasePart = magnetCreator:Clone() do
	magnetTemplate.Name = "Magnet"
	CollectionService:AddTag(magnetTemplate, "ferromagnetic")
end

local emptyVector = Vector3.new()

-- Track
local ferroCache: {[BasePart]: ferro} = {}

-- Settings
local isRealistic: boolean = false -- whether to try and make it be somewhat more realistic
local isChainable: boolean = true -- whether to link ferromagnets together

local nearMass: number = 15 -- the maximum difference in mass between two parts to classify as 'equal'
local radius: number = 30 -- the distance the magnetic parts need to be from the character

local showWelds: boolean = true

local material: Enum.Material = Enum.Material.SmoothPlastic -- the material of the magnetic parts
local color: Color3 = Color3.fromRGB(163, 162, 165) -- the color of the magnetic parts

-- Params
local rayParams: RaycastParams = RaycastParams.new() do
	rayParams.FilterType = Enum.RaycastFilterType.Whitelist
end
local overlapParams: OverlapParams = OverlapParams.new() do
	overlapParams.CollisionGroup = if isChainable then "ferromagnetic" else "Default"
end

-- Collision groups
local magneticGroup: number = PhysicsService:CreateCollisionGroup("magnetic")
local ferromagneticGroup: number = PhysicsService:CreateCollisionGroup("ferromagnetic")

PhysicsService:CollisionGroupSetCollidable("magnetic", "ferromagnetic", false)
PhysicsService:CollisionGroupSetCollidable("ferromagnetic", "ferromagnetic", isChainable)
magnetTemplate.CollisionGroupId = ferromagneticGroup


-----------------------
-- Methods ------------

local abs: (number) -> number = math.abs

-- Returns if a point is in a parts bounds
local function isPointInPartBounds(part: BasePart, point: Vector3): boolean
	local size: Vector3 = part.Size * 0.5
	local relativePos: Vector3 = part.CFrame:PointToObjectSpace(point)

	return (math.clamp(relativePos.X,-size.X,size.X) == relativePos.X and math.clamp(relativePos.Y,-size.Y,size.Y) == relativePos.Y and math.clamp(relativePos.Z,-size.Z,size.Z) == relativePos.Z)
end

-- Returns if the magnet and ferromagnet are touch and an array containing the magnet and attachment positions
local function getTargetPoints(ferroPart: BasePart, target: Vector3): (boolean,targetPoints)
	local isTouching: boolean, points: targetPoints = false, {}::targetPoints
	local origin: Vector3 = ferroPart.Position

	rayParams.FilterDescendantsInstances = CollectionService:GetTagged("bonded")
	local targetResult: RaycastResult? = workspace:Raycast(origin, (target-origin), rayParams)
	if targetResult then
		points.magnet = targetResult.Instance
		points.magnetPos = targetResult.Position

		target = points.magnetPos
	end
	
	rayParams.FilterDescendantsInstances = {ferroPart}
	local originResult: RaycastResult? = workspace:Raycast(target, (origin-target), rayParams)
	points.ferroPos = if originResult then originResult.Position else origin
	
	isTouching = isPointInPartBounds(points.magnet, origin) or isPointInPartBounds(ferroPart, target)

	return isTouching,points
end

-- Initializes and returns a magnet force
local function createMagneticForce(origin: BasePart, attachment0: Attachment, attachment1: Attachment): AlignPosition
	local originMass: number, targetMass: number = origin.AssemblyMass, attachment1.Parent.AssemblyMass

	local alignPosition: AlignPosition = Instance.new("AlignPosition") do
		alignPosition.Name = "MagneticForce"
		alignPosition.ReactionForceEnabled = isRealistic and nearMass >= abs(originMass-targetMass) -- apply force to both?

		if origin.Anchored or (isRealistic and originMass > targetMass) then attachment0,attachment1 = attachment1,attachment0 end
		alignPosition.Attachment0 = attachment0
		alignPosition.Attachment1 = attachment1

		alignPosition.Parent = origin
	end
	return alignPosition
end

-- Initializes and returns a magnet attachment at the relative position
local function createAttachment(part: BasePart, position: Vector3?): Attachment
	local attachment: Attachment = Instance.new("Attachment") do
		attachment.Name = "MagnetAttachment"
		attachment.Position = if position then part.CFrame:PointToObjectSpace(position) else emptyVector
		attachment.Parent = part
	end
	return attachment
end

-- Bonds a ferromagnetic and magnetic part together
local function createBond(ferroPart: BasePart, magnetPart: BasePart): WeldConstraint
	local weld: WeldConstraint = Instance.new("WeldConstraint") do
		weld.Name = "MagneticBond"
		weld.Enabled = false
		weld.Part0 = magnetPart
		weld.Part1 = ferroPart
		weld.Parent = ferroPart
	end
	return weld
end

local function highlight(part: BasePart): ()
	local highlight: SelectionBox = Instance.new("SelectionBox") do
		highlight.Adornee = part
		highlight.Color3 = Color3.fromRGB(100,100,255)
		highlight.SurfaceColor3 = Color3.fromRGB(100,100,200)
		highlight.Parent = part
	end
end

-- Magnetizes a part
local function attractFerromagnet(magneticOrigin: Vector3, ferroPart: BasePart): ()
	local ferro: ferro = ferroCache[ferroPart] or {}::ferro
	local isTouching: boolean, points: targetPoints = getTargetPoints(ferroPart, magneticOrigin)
	
	ferro.target = ferro.target or createAttachment(points.magnet, points.magnetPos)
	ferro.origin = ferro.origin or createAttachment(ferroPart, points.ferroPos)
	ferro.magnet = ferro.magnet or createMagneticForce(ferroPart, ferro.origin, ferro.target)
	ferro.bond = ferro.bond or createBond(ferroPart, points.magnet)
	ferro.bonded = isTouching
	
	ferro.target.Position = points.magnet.CFrame:PointToObjectSpace(points.magnetPos)
	ferro.origin.Position = ferroPart.CFrame:PointToObjectSpace(points.ferroPos)
	
	if isTouching then
		ferro.bond.Enabled = isTouching
		ferro.magnet.Enabled = not isTouching
		if isChainable then CollectionService:AddTag(ferroPart, "bonded") end
		if showWelds then highlight(ferroPart) end
	end

	ferroCache[ferroPart] = ferro
end

-- Create magnetic parts
local function CreateFerromagneticPart(): ()
	local magnetPart: BasePart = magnetTemplate:Clone() do
		magnetPart.Size = Vector3.new(math.random(5),math.random(5),math.random(5))
		magnetPart.Material = material
		magnetPart.Color = color
		magnetPart.Anchored = false
		magnetPart.Parent = magnetFolder
	end

	Debris:AddItem(magnetPart, 20)
end

-- Returns true if a PVInstance exist in 3D space and false if not
local function HasMass(pv: PVInstance): boolean
	local exist: boolean = pv:IsA("BasePart") or (pv:FindFirstChildOfClass("BasePart") ~= nil)

	if not exist then
		for _, descendant: Instance in pairs(pv:GetDescendants()) do
			exist = descendant:IsA("BasePart")
			if exist then break end
		end
	end
	return exist
end

-- Sets the character collision group
local function MagnetizeCharacter(character: Model): ()
	task.defer(function()
		for _, child: Instance in pairs(character:GetChildren()) do
			if child:IsA("BasePart") then
				child.CollisionGroupId = magneticGroup
				if child ~= character.PrimaryPart then CollectionService:AddTag(child, "bonded") end
			end
		end
		CollectionService:AddTag(character, "magnetic")
	end)
end


-----------------------
-- Events -------------

-- Collision groups
for _, player: Player in pairs(Players:GetPlayers()) do -- set already loaded characters collision
	MagnetizeCharacter(player.Character or player.CharacterAdded:Wait())
	player.CharacterAdded:Connect(MagnetizeCharacter)
end
Players.PlayerAdded:Connect(function(player: Player) -- set loading characters collision
	player.CharacterAdded:Connect(MagnetizeCharacter)
end)

-- Magnet creation
Instance.new("ClickDetector", magnetCreator).MouseClick:Connect(CreateFerromagneticPart)

-- Detect parts
game:GetService("RunService").Heartbeat:Connect(function(): ()
	for _, magnet: PVInstance in pairs(CollectionService:GetTagged("magnetic")::{PVInstance}) do
		if (magnet.Parent ~= nil) and HasMass(magnet) then -- check if it exist
			local magneticOrigin: Vector3 = magnet:GetPivot().Position
			
			for _, part: BasePart in pairs(workspace:GetPartBoundsInRadius(magneticOrigin, radius, overlapParams)) do
				local ferro: ferro = ferroCache[part]
				if (part.CollisionGroupId ~= ferromagneticGroup) or (ferro and ferro.bonded) or CollectionService:HasTag(part, "bonded") then continue end

				task.spawn(attractFerromagnet, magneticOrigin, part)
			end
		else -- remove if it doesn't
			CollectionService:RemoveTag(magnet, "magnetic")
		end
	end
end)

CollectionService:GetInstanceRemovedSignal("ferromagnetic"):Connect(function(ferroPart: BasePart) ferroCache[ferroPart] = nil end)


-----------------------
-- Execution ----------

for i=1, 30 do
	CreateFerromagneticPart()
	task.wait(2)
end

There are several settings that you can set, such as whether it will highlight welded parts (debugging purposes), whether parts can form a magnetic “chain”, and an option for semi-realistic magnetism (in relation to part mass). Also know that parts that are anchored will switch so that the player is pulled to them instead.

3 Likes