Help making parts "stick" to other parts

Hello! I am creating a TF2-esque Stickybomb Launcher but have run into a slight problem while doing so: when shot at a wall from an angle, the sticky bombs bounce and freeze mid air, instead of sticking to the wall when shot at straight-on. The network ownership for the bomb is the player.

bomb.Touched:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") or hit.Transparency > 0 or hit.Parent.ClassName == "Tool" or hit.Parent.ClassName == "Accessory" then return end
		bomb.Anchored = true
		bomb.CanCollide = false
		bomb.CanTouch = false

What it looks like when shot straight on (works fine)

What it looks like when shot at an angle (not so fine, sticky bounces off of the side of the doorway and freezes mid-air)

I have tried getting the bomb’s position exactly when the touch connects and then setting the bomb to that position shortly afterward, but it didn’t seem to help much if at all. As you can see in the code there is no delay between when the touch connects and when the bomb is anchored. If any Roblox physics wizard is out there, I would appreciate the help.

1 Like

Welding, you can weld un anchored objects to each other like flex seal or gorilla glue.

3 Likes

Using welds doesn’t seem to work for me, either. Just kinda makes it worse

Those welds are all at the exact same distance from the wall. Thats formulaic! If you aren’t doing that via script, it’s part of the mesh. Something is “displaced” when you set the position that sticking happens.

I suspect the origin of one of those 2 parts (most likely the bomb) is not the center of the part, or you scripted an offset based on an erroneous size.

1 Like

The bombs use a special mesh (using it for vertex color) with an offset of -.07, inside of a part that’s .6x.6. There’s nothing about the model that should be causing issues, unless somehow special meshes have some sort of weird behavior I’m not aware of.


Here is pretty much the whole code for firing the bomb if it helps! It’s very possible that two things I did don’t go together well and is messing something up.

function fire(direction)
	local player = players:GetPlayerFromCharacter(char)

	local bomb = Instance.new("Part")
	local spawnPos = handle.FirePoint.WorldPosition
	spawnPos  = spawnPos + (direction * 5)
	bomb.Position = spawnPos
	bomb.Size = Vector3.new(.6,.6,.6)
	bomb.Velocity = direction * 100
	bomb.CustomPhysicalProperties = PhysicalProperties.new(100,0,0,0,100)
	bomb.Name = "stickybomb"
	local meshclone = serverstorage.models.mdl_stickybomb:Clone()
	meshclone.Parent = bomb
	meshclone.VertexColor = teamcolor
	bomb:SetAttribute("canDet", false)
	tool.Handle.snd_explode:Clone().Parent = bomb
	bomb.snd_explode.PlayOnRemove = true

	creatorTag = Instance.new("ObjectValue")
	creatorTag.Value = player
	creatorTag.Name = "creator"
	creatorTag.Parent = bomb
	weld = Instance.new("WeldConstraint")
	weld.Parent = bomb
	weld.Part1 = bomb

	bomb.Parent = projectileHolder
	bomb:SetNetworkOwner(player)

	spawn(function() --stickybomb cooldown
		local mesh = bomb.mdl_stickybomb
		wait(bombCooldown)
		bomb:SetAttribute("canDet",true)
		mesh.VertexColor = mesh.VertexColor * 1.5
		wait(.07)
		mesh.VertexColor = mesh.VertexColor / 1.5
		wait(.07)
		mesh.VertexColor = mesh.VertexColor * 1.5
	end)
	bomb.Touched:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") or hit.Transparency > 0 or hit.Parent.ClassName == "Tool" or hit.Parent.ClassName == "Accessory" then return end
		weld.Part0 = hit
		--bomb.Anchored = true
		bomb.CanCollide = false
		bomb.CanTouch = false
		--print(hit.Name)
	end)
end

The weld happens the first time something touches that bomb. But the bomb does not stop moving while the collision object continues moving. Maybe the collision detection is being passed from server side? Could create a small lag while client and server communicate.

It appears to be hitting something at that spot (is the wall really L shaped? What is it’s collision fidelity? Perhaps it is “Box” and you really are hitting the wall mid-air? If it is, try setting it to “PreciseConvexDecomposition” and see if that helps.

Roblox released a new constraint recently, the RigidConstraint

Simply put, you have to place an attachment inside of the sticky grenade and then place an attachment at the position of collision.
And then set the att0 and att1 of the RigidConstraint to those attachments. Also, make sure the sticky grenade remains unanchored when you are attaching it to the wall.

But how do we get the position of the collision? That’s a bit tougher, and studio doesn’t exactly have a built in function for it either.
However, I’ve figured out that raycasting towards to hitPart works effectively. Since we are only casting one ray we don’t have to worry about performance either!

Judging by your current work, I assume you’re quite skilled with Lua as is so I’m only going to gleam over the details.

local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Whitelist
rayParams.IgnoreWater = true

local function CreateSticky(pos)
	local sticky = Instance.new("Part")
	sticky.TopSurface = Enum.SurfaceType.Smooth
	sticky.BottomSurface = Enum.SurfaceType.Smooth
	sticky.Size = Vector3.new(1,1,1)
	sticky.Position = pos
	sticky.Parent = workspace
	sticky.Color = Color3.new(0,0,0)

	local att0 = Instance.new("Attachment")
	att0.Parent = sticky
	att0.Name = "att0"
	
	local att1 = Instance.new("Attachment")
	att1.Parent = sticky
	att1.Name = "att1"

	local rigid = Instance.new("RigidConstraint")
	rigid.Attachment0 = att0
	rigid.Attachment1 = att1
	rigid.Parent = sticky
	return sticky
end

local function CalculateCollision(sticky, hitPart)
	local stickyPos = sticky.Position
	local targetPos = hitPart.Position

	local distance = (stickyPos - targetPos).magnitude
	
	rayParams.FilterDescendantsInstances = {hitPart}
	local rayResult = workspace:Raycast(stickyPos, (targetPos - stickyPos).Unit*distance, rayParams)
	if rayResult then
		return rayResult.Position, rayResult.Normal
	end
end

local sticky = CreateSticky(Vector3.new(21, 9, -45))

sticky.Touched:Connect(function(hitPart)
	local hitPos, hitNormal = CalculateCollision(sticky, hitPart)
	local att1 = sticky:FindFirstChild("att1")
	
	if att1 then
		att1.Parent = hitPart
		att1.WorldPosition = hitPos + (sticky.Size/2*hitNormal)
	
		print(hitPart, hitPos)
	end
end)

This script I’ve written here performs perfectly and even calculates for angled surfaces (calculating for mesh-based collisions is difficult so concave objects present bugs).

When the sticky grenade hits a part it sends a ray towards the hit part. Then that ray returns the nearest surface point (a.k.a. the most likely point of collision). After that it assigns att1 to the object we collided with and changes the worldposition of said attachment to the raycast result.

And since the grenade is unanchored, we can reposition the hit object and the grenade will follow suit.
image

Meaning you can have sticky grenades attach to moving cars and rotating objects!

Hope this helped!

6 Likes

Sorry for the late response! I had no idea this existed, thank you! I’ll test it out later and if things work out I’ll mark it as solution

Hi, unfortunately it appears that the stickybomb appears close to the center of the hitpart, instead of applying to the position calculated. Here’s the code I compiled using your example, I probably messed something up.

function fire(direction)
	local player = players:GetPlayerFromCharacter(char)

	local bomb = Instance.new("Part")
	local spawnPos = handle.FirePoint.WorldPosition
	spawnPos  = spawnPos + (direction * 5)
	bomb.Position = spawnPos
	bomb.Size = Vector3.new(.6,.6,.6)
	bomb.Velocity = direction * 100
	bomb.CustomPhysicalProperties = PhysicalProperties.new(100,0,0,0,100)
	bomb.Name = "stickybomb"
	local meshclone = serverstorage.models.mdl_stickybomb:Clone()
	meshclone.Parent = bomb
	meshclone.VertexColor = teamcolor
	bomb:SetAttribute("canDet", false)
	tool.Handle.snd_explode:Clone().Parent = bomb
	bomb.snd_explode.PlayOnRemove = true

	creatorTag = Instance.new("ObjectValue")
	creatorTag.Value = player
	creatorTag.Name = "creator"
	creatorTag.Parent = bomb

	local att0 = Instance.new("Attachment")
	att0.Parent = bomb
	att0.Name = "att0"

	local att1 = Instance.new("Attachment")
	att1.Parent = bomb
	att1.Name = "att1"

	local rigid = Instance.new("RigidConstraint")
	rigid.Attachment0 = att0
	rigid.Attachment1 = att1
	rigid.Parent = bomb

	bomb.Parent = projectileHolder
	bomb:SetNetworkOwner(player)

	spawn(function() --stickybomb cooldown
		local mesh = bomb.mdl_stickybomb
		wait(bombCooldown)
		bomb:SetAttribute("canDet",true)
		mesh.VertexColor = mesh.VertexColor * 1.5
		wait(.07)
		mesh.VertexColor = mesh.VertexColor / 1.5
		wait(.07)
		mesh.VertexColor = mesh.VertexColor * 1.5
	end)

	local function CalculateCollision(sticky, hitPart)
		local stickyPos = sticky.Position
		local targetPos = hitPart.Position

		local distance = (stickyPos - targetPos).magnitude

		rayParams.FilterDescendantsInstances = {hitPart}
		local rayResult = workspace:Raycast(stickyPos, (targetPos - stickyPos).Unit*distance, rayParams)
		if rayResult then
			return rayResult.Position, rayResult.Normal
		end
	end

	bomb.Touched:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") or hit.Transparency > 0 or hit.Parent.ClassName == "Tool" or hit.Parent.ClassName == "Accessory" then return end
		local hitPos, hitNormal = CalculateCollision(bomb, hit)
		local att1 = bomb:FindFirstChild("att1")

		if att1 then
			att1.Parent = hit
			att1.WorldPosition = hitPos + (bomb.Size/2*hitNormal)

			print(hit, hitPos)
			bomb.CanCollide = false
			bomb.CanTouch = false
		end
	end)
end

The reason it recalculates it into another position is the att1.WorldPosition = hitPos + (bomb.Size/2*hitNormal) line. Try removing the + (bomb.Size/2*hitNormal), this will place it into the exact ray position but will make the sticky clip half-way into the part it has hit.

Originally the vector math I did was meant to offset the object from the surface, but I can see now how taking the total size and comparing it to the normal could cause placement issues.

You could also try att1.WorldCFrame = CFrame.new(hitPos, hitPos + normal), following that you could try adding an offset to the origin parameter of CFrame.new(), I’m not sure on the exact vector math but try CFrame.new(hitPos - (normal+.5), hitPos + normal)(This simply takes the opposite of the surface normal and adds .5, you can try different numbers to see which fits best)

1 Like

Do you think you could be running into a collisionfidelity issue? Maybe you need to set the wall with the hole in it to preciseconvex, or whatever its called.

1 Like

There are no meshes or unions that have collision enabled at play here.

I am assuming “normal” is meant to be hitNormal? Neither solution seemed to work or really change anything interestingly. For convenience I could send you the place file if you wanted to investigate, but I understand if you’re not that committed lol

If you reply with the rblx file I’ll gladly take a look at it and see if I can find a solution for you! :slight_smile:

1 Like

The script is called “server” located in the tool in StarterPack. Thank you so much for the help!
stickylauncher.rbxl (67.1 KB)

Just realized I sent the place file without fixing an error in the code on line 120, but that’s the line you’re probably editing anyway. Sorry about that.

Alright, I’ve been testing out a few solutions and found out the root cause of the issue. The .Touched event.

As stated in the post above, non-cancollide parts add a delay of ~100ms, which is long enough to offset the position of the bomb by about a half stud.

I tried looking into region-checking and dynamic raycasting techniques, however, both failed to remove the delay factor.

I think it would be best to have each individual client handle the physics during air travel.
When one player fires their gun it will send a RemoteEvent to the server which then runs a FireAllClients.

Then each client will create their own copy of the sticky which will move through the air out of the requested players sticky launcher.

Once the client that sent the fire command receives the .Touched event you can then replicate the same result across all clients by replacing the client bombs with a singular server bomb at the correct location.

This should also reduce physics lag and reduce input delay for shooting the launcher.

Player 1 Shoots (L) → Server Receives (S) → Server Fires All Clients (S) → Each Client Replicates Action (L) → Player 1’s Bomb Lands (L) → Server Receives (S) → Server Fires All Clients + Creates Stationary Server Bomb (S) → (L) Client Deletes Local Bomb

My approach is based upon RemoteEvents, however you could probably make it work more efficiently using RemoteFunctions.

Unfortunately, Roblox Studio simply doesn’t have the capabilities yet to perform fast real-time collision filtering. Your best bet would be to create the cheaty infastructure listed above, or just accept that the bombs will bounce of a bit.

If you need help setting up the system let me know! :slight_smile:

2 Likes

Forgive me for my lack of understanding, I am not too experienced with scripting in general and am still learning a lot about how things work.

  1. Is firing a remoteevent every time a weapon is activated not a laggier solution? For some reason I always figured remoteevents would have slightly more delay than just firing it from the server directly, in-script.
  2. How exactly does this circumvent the original issue? How does creating local copies of the bomb fix it? Also it says that the lag comes from non-cancollide parts when the sticky bomb is cancollide when fired, and only becomes non-cancollide after the touch (and even removing that there’s still a delay).

This old 2011 TF2 clone seems to have perfectly fine stickies that seem to have the same, or similar problem mine have but then slide to their original position. At first I thought maybe it was welds but earlier in the thread it was made clear that welds were not working.


I’ll consider your solution, but it’d probably take the overcoming of a learning curve for me + I don’t know how this method would actually fix my problem.

Again, I really appreciate your help!

From the looks of the video, the scripter is using AlignPosition constraints in parallel with attachments. That’s why the grenades sort of gravitate slowly back to the origin of collision.

The only problem is getting the origin of collision, and I have no clue how @RegularTetragon set up their code.
They might have used raycasts during flight time, simply send constant raycasts towards the point of velocity (a.k.a. the direction in which it’s flying) and once the distance between the bomb and the end of the raycast meet below 1 stud set an attachment.

Upon closer inspection of the grenades in game, I can verify that the bombs flying through the air are local. This is evident by the brief second the bomb disappears and reappears on screen upon sticking. Meaning the creator is using remote calls to replicate the bombs.

TL;DR, the creator is using AlignPosition constraints to keep the bombs in place. They also create the bombs on each client first and then replicate it to the server upon collision. My best guess is they’re using raycasts in the direction of velocity to determine the distance of the wall, but I could be wrong about that.

1 Like

I see. I noticed the flickering upon rewatching the video but forgot to mention it. I will look into this further. Thank you!

1 Like

The stickies in my game work by giving the character who created them a folder inside their character called projectiles so I can run local scripts in the projectile. When created I give the character explicit physics control with set network owner. Then using Touched in a local script, I freeze the sticky in place with a body position and body gyro (anchoring it/welds gave really janky undesirable behavior). Locally, Touched happens immediately, and is far more reliable than on the server. I don’t need to use RemoteEvents because the player has network ownership anyway. If you’re worried about extra precision, you can raycast each frame to make sure the object won’t travel too fast through something.

This ofc was after a lot of trial and error haha

5 Likes

I never even thought of putting the folder inside of the player so it just replicates itself. Thanks for the response!!