How to Create a Black Hole
This is an Advanced, Lua Script related tutorial. |
Introduction
This tutorial is designed for advanced scripters and scripters who can be described as medium-level. It will cover several concepts, such as area-of-effect, and will provide the finished code for a black hole at the end.
If you are unable to understand this tutorial, it is suggested that you learn from the Novice tutorials first. If you are merely here to get a black hole, you can find in Roblox Studio in Insert / Free Models / typing "Black Hole" / Clicking "Search" / double-clicking the black hole you want.
Starting off
Before creating a black hole, it is prudent to clear an area and anchor anything in the area which you would like to keep. Beware, as black holes actually increase in power as they gain mass, so the area should probably be at least 128 units in radius. It is also a good idea to figure out EXACTLY what you want the black hole to do. In this case, I have laid out the desired effects and the concepts that I use.
Desired Effects
The black hole will do the following:
- Suck up ANYTHING that can move within a reasonable radius.
- Destroy objects that get close enough.
Concepts
- Anchored The black hole, to be efficient, should not attempt to suck up any brick that cannot move anyway. This also prevents the floor from being destroyed.
- Area of Effect This black hole will suck ANYTHING that comes within a reasonable distance into itself.
- Arrays Arrays are sets of variables.
- Children In order to find every brick, functions will need to find them first.
- Classes The black hole must only work on certain types of objects, namely "Parts".
- Iteration Functions will use loops to cycle through arrays.
- Mass Similar to weight, but without taking gravity into account.
- Recursion The function for finding objects will call itself.
Variable Initialization
The black hole will need to keep track of a number of things. It will need an array to keep track of objects and a floating-point variable to keep track of its mass (power).
local hole = script.Parent -- Optional. Added for optimization local childList = {} local massConstant = 5.8 -- Generally a good value (again, optional) local mass = 32000 * massConstant
Area of Effect
Area of effect is one of the most important concepts when creating a black hole, or any script intended to affect a wide range of bricks. The basic idea is to find every brick in the map, add them to an array, and use the array later. A recursive function will work well, allowing both simplicity and elegance.
Create a function, in this case called checkObject, which accepts an object's handle as an input. I named this variable obj. This function will first get the array of children using
local list = obj:GetChildren()
If the child's className variable indicates that it is a Part (brick), then it is added to the array of bricks using
table.insert(childList, 1, list[n])
If it is a Model, Workspace, Tool, or Hat, then it is checked for bricks by calling
local list = obj:GetChildren()
(This sets list to an array containing every child) and using a for loop to call
checkObject(list[n])
on every child, and it is made to check for children that are added later by using
list[n].ChildAdded:connect(checkObject)
This works because the ChildAdded event calls function with an argument of the child that was added. This ends the checkObject function. To get all bricks included, the script calls
checkObject(workspace)
Put together, the above code makes this:
-- Note: This should only be run once for each object function checkObject(obj) if (obj ~= hole) and (obj.className == "Part") then if (obj.Anchored == false) then table.insert(childList, 1, obj) end elseif (obj.className == "Model") or (obj.className == "Hat") or (obj.className == "Tool") or (obj == workspace) then local child = obj:GetChildren() for x = 1, #child do checkObject(child[x]) end obj.ChildAdded:connect(checkObject) end end
Finished Code
When complete, the code will look something like this:
local hole = script.Parent local childList = {} local massConstant = 5.8 -- Generally a good value local mass = 32000 * massConstant -- This is basically a function that finds all unanchored parts and adds them to childList. -- Note: This should only be run once for each object function checkObject(obj) if (obj ~= hole) and (obj.className == "Part") then if (obj.Anchored == false) then table.insert(childList, 1, obj) end elseif (obj.className == "Model") or (obj.className == "Hat") or (obj.className == "Tool") or (obj == workspace) then local child = obj:GetChildren() for x = 1, #child do checkObject(child[x]) end obj.ChildAdded:connect(checkObject) end end checkObject(workspace) print("Black Hole script loaded.") local n = 0 while true do if n < #childList then n = n + 1 if n % 800 == 0 then wait() end else n = 1 wait() end local child = childList[n] if (child ~= hole) and (child.className == "Part") and (child.Anchored == false) then local relPos = hole.Position - child.Position local motivator = child:FindFirstChild("BlackHole Influence") if relPos.magnitude * 240 * massConstant < mass then child:BreakJoints() if (relPos.magnitude * 320 * massConstant < mass) and (child.Size.z + hole.Size.x > relPos.magnitude * 2 - 4) then mass = mass + child:GetMass() child:Remove() table.remove(childList, n) n = n - 1 -- This is the reason I need a counter of my own design else child.CanCollide = false -- I Can assume that things won't escape the black hole. if motivator == nil then motivator = Instance.new("BodyPosition") motivator.Parent = child motivator.Name = "BlackHole Influence" end motivator.position = hole.Position motivator.maxForce = Vector3.new(1, 1, 1) * mass * child:GetMass() / (relPos.magnitude * massConstant) end elseif motivator ~= nil then motivator:Remove() end end end