I made a rock module similar to the The Strongest Battlegrounds. Right now it’s only a crater function but I might add more to this. This module uses a raycast to spawn the crater and also uses the PartCache module for better performance.
module link: https://create.roblox.com/store/asset/113713371540830/Crater?tab=description
showcase:
here are the default settings I use:
utility.Crater.GroundCrater(
Part.Position, Vector3.new(0,-10,0), 10, 9, Vector3.new(2,3,2), true
)
-- // Services // --
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")
-- // Modules // --
local partCache = require(script.PartCache).new(Instance.new("Part"), 2000, workspace.Debris)
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Include
Params.FilterDescendantsInstances = {workspace.Map}
-- // Function // --
local module = {}
function module.GroundCrater(origin: Vector3, direction: Vector3, radius: number, amount: number, size: Vector3, flyingrocks: boolean)
local random = Random.new()
-- Starting angle for placement and angle increment per rock
local currentAngle = 30
local angleIncrement = 360 / amount
local groundRay = workspace:Raycast(origin, direction, Params)
if groundRay then
for i = 1, amount do
coroutine.wrap(function()
local baseCFrame = CFrame.new(groundRay.Position)
local offsetCFrame = baseCFrame * CFrame.fromEulerAnglesXYZ(0, math.rad(currentAngle), 0) * CFrame.new(radius / 2 + radius / 15, 10, 0)
local placementRay = workspace:Raycast(offsetCFrame.Position, Vector3.new(0, -15, 0), Params)
currentAngle += angleIncrement
if placementRay then
local finalCFrame = CFrame.new(
placementRay.Position - Vector3.new(0, size.Y * random:NextNumber(0.325, 0.45), 0),
groundRay.Position
) * CFrame.fromEulerAnglesXYZ(
random:NextNumber(-1, -0.4),
random:NextNumber(-0.1, 0.1),
random:NextNumber(-0.1, 0.2)
)
local hidingPosition = finalCFrame.Position + placementRay.Normal * -8
local part = partCache:GetPart()
part.Size = Vector3.new(
size.X * random:NextNumber(1.15, 1.5),
size.Y * random:NextNumber(0.725, 0.9),
size.Z * random:NextNumber(1.15, 1.5)
) * random:NextNumber(1, 1.5)
part.CFrame = finalCFrame
part.Position = hidingPosition
part.Material = placementRay.Instance.Material
part.Transparency = placementRay.Instance.Transparency
part.Color = placementRay.Instance.Color
part.CanCollide = true
part.CanTouch = false
part.CastShadow = false
part.Parent = workspace.FX -- replace with your own effects folder
-- Tween the part into its final position
TweenService:Create(part, TweenInfo.new(0.1), {Position = finalCFrame.Position}):Play()
-- Fade out and return the part to the cache after a delay
task.delay(5, function()
TweenService:Create(
part,
TweenInfo.new(2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut),
{Position = hidingPosition}
):Play()
task.delay(2, function()
partCache:ReturnPart(part)
end)
end)
end
end)()
end
if flyingrocks then
local sizeSwitch = 1
for i = 1, math.round(amount / 1.5) do
local rock = partCache:GetPart()
rock.Position = groundRay.Position + Vector3.new(0,2,0)
rock.CollisionGroup = "Debris"
rock.CanCollide = true
rock.Anchored = false
rock.Color = groundRay.Instance.Color
rock.Material = groundRay.Instance.Material
rock.CFrame = rock.CFrame * CFrame.new(math.random(-3, 3),1,math.random(-3, 3)) * CFrame.Angles(math.random(-360, 360), math.random(-360, 360), math.random(-360, 360))
if sizeSwitch % 2 == 0 then
rock.Size = Vector3.new(random:NextNumber(.3, .7),random:NextNumber(.3, .7),random:NextNumber(.3, .7))
else
rock.Size = Vector3.new(random:NextNumber(.75, 2.5),random:NextNumber(.75, 2.5),random:NextNumber(.75, 2.5))
end
rock.Parent = game.Workspace.FX
rock.Velocity = CFrame.new(groundRay.Position + Vector3.new(0,-3, 0) , rock.Position).lookVector * (29 * random:NextNumber(.8, 1.2)) + Vector3.new(0, math.random(35,55), 0)
task.delay(math.random(3,5), function()
local tweeninfo = TweenInfo.new(5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local properties = {
Size = Vector3.new(0,0,0)
}
local tween = TweenService:Create(rock, tweeninfo, properties)
tween:Play()
task.delay(5, function()
partCache:ReturnPart(rock)
end)
end)
sizeSwitch += 1
end
end
end
end
return module