Testing Client-Sided Procedural Physics Object Generation

Everything created was constructed and run using OOP methods.

Figuring out how to randomly place objects inside of weirdly-angled regions sure was fun. That was a filthy lie. I had the math right for keeping parts within a region, but was stuck for hours on the angle thing until I found out about ToWorldSpace

local spX = zone.Size.X/2
local spY = zone.Size.Y/2
local spZ = zone.Size.Z/2

part.CFrame = zone.CFrame:ToWorldSpace() * CFrame.new(vector3.new(math.random(-spX,spX)+(part.Size.X/2),math.random(-spY,spY)+(part.Size.Y/2),math.random(-spZ,spZ)+(part.Size.Z/2)))

And here’s how the mines work and make each other explode:

  • When mines are instantiated, they raycast down relative to their UpVector, tween to whatever is directly beneath them and weld themselves in place.
  • Explosions are created through the part creation module.
-- inside the generation loop
local fWeld = physObj.new('Weld', part) {
	Name = 'FloorWeld';
	Part0 = part;
	C0 = CFrame.new(v3(0,part.Size.Y/2,0));
	Part1 = cast.Instance;
	C1 = CFrame.new(part.Position-cast.Instance.Position);
}
-- Custom Instance module for flexible returns and possible associations
--[[
Example:

local part, associate = physObj.new(){}

part.Position = Vector3.new() -- moves the part

associate:HitTheGriddy() -- Hits the griddy

]]

part.Touched:Connect(function(p)
	if not game:GetService('Players'):GetPlayerFromCharacter(p.Parent) then return end

	part.CanTouch = false
	local cl = 15

	while true do
		cl -= 1
		if cl == 0 then break end
		blinkerPart.Color = rgb(255,125,50)
		beep:Play()
		task.wait(.1)
		blinkerPart.Color = rgb(0,0,0)
		task.wait(.005*cl)
		beep:Stop()
	end

	game:GetService('Debris'):AddItem(minemodel,3)
	blinkerPart.Transparency = 1
	physObj.Explode(part)
end)
----------

-- separate from object generation loop
workspace.DescendantAdded:Connect(function(desc)
	if not desc:IsA('Explosion') then return end
	desc.Hit:Connect(function(part)
		if not part:HasTag('Explosive') or not part.CanTouch then return end
		part.CanTouch = false
		task.wait(.2)
		physObj.Explode(part)
		if not part.Parent:IsA('Model') then
            game:GetService('Debris'):AddItem(part,3)
            part.Transparency = 1
        return end
        game:GetService('Debris'):AddItem(part.Parent,3)
		for _,p in ipairs(part.Parent:GetChildren()) do
			if not p:IsA('BasePart')  then continue end
			p.Transparency = 1
		end
	end)
end)

Edits:

  • Title change
  • A few notes
  • Added objects blown up by other explosives to debris service
4 Likes

Looks interesting, what do you plan to use it for?

Also, instead of connecting to workspace.DescendantAdded, I suggest tagging your explosions upon creating them using CollectionService. Then you can listen for when the tag is added to an explosion to do whatever with it.

1 Like

I’m thinking some sort of co-op puzzle shooter with mechanics like Half-Life/Left 4 Dead.

I’ve already got a working grab/toss mechanic set off to the side. It switches between different constraint types depending on the type of part being grabbed, so players can do some interesting things with it like manually turn cranks/gears, flip levers, use pullstring lightswitches, etc.

NPCs also in the works. Have FOV and some rudimentary pathfinding, but have some serious memory issues and become confused and sort of stuck thinking when they revert back to neutral or lose sight of the player more than once. Kinda saving that for later lmao

A lot of stuff to deal with that would really turn the game into a slog if too much is being rendered on the server, so I’m looking into ways of just firing physics info back and forth.

I’m handing a lot of trust over to the clients and dumping the majority of the workload on them. Currently, hardly anything but the map and player characters themselves are rendered server-side in my main place. Most things only appear for clients in-game and nearby.

I was mulling over ways to optimize the explosions, and that just didn’t occur to me, so thanks lmao