Inoob8C

Grass Physics Module - Not Strict

Jul 28th, 2024
17
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.42 KB | None | 0 0
  1. --!native
  2. local GrassPhysics = {}
  3. local originalParents = {}
  4. GrassPhysics.__index = GrassPhysics
  5.  
  6. -- Services
  7. local Players = game:GetService("Players")
  8. local CollectionService = game:GetService("CollectionService")
  9. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  10. local TweenService = game:GetService("TweenService")
  11. local RunService = game:GetService("RunService")
  12.  
  13. -- Constants
  14. local ROOT = workspace
  15. local CAMERA = ROOT.CurrentCamera
  16.  
  17. GrassPhysics.Config = {
  18. WindShake = {
  19. Enabled = false, -- Enable/disable wind shake effect, ENABLING THIS DOES NOT ADD IT INTO YOUR CODE! You must set it up yourself, but turn this on if you have it shake the grass using wind.
  20. ScriptPath = nil, -- Path to the wind shake script, not the module, the script that requires it, example: Players.LocalPlayer.PlayerScripts.WindControllerClient
  21. },
  22. OcclusionCulling = {
  23. Enabled = false, -- Enable/disable occlusion culling, can improve performance but may cause bugs.
  24. },
  25. Sound = {
  26. Enable = true, -- Enable/disable sound effects
  27. Volume = 0.1, -- Volume of the plant sound effects (0.0 - 1.0)
  28. PitchRange = { -- Range for randomizing pitch of plant sounds
  29. Min = 0.8,
  30. Max = 1.0
  31. },
  32. EffectDuration = 0.5, -- Duration to play the sound effect before destroying
  33. Reverb = { -- Reverb settings for the sound
  34. Enable = false,
  35. DecayTime = 2.0,
  36. ReflectLevel = 0.5
  37. },
  38. Spatial = { -- Spatial audio settings
  39. Enable = true,
  40. RolloffMode = Enum.RollOffMode.Linear,
  41. MinDistance = 10,
  42. MaxDistance = 100
  43. }
  44. },
  45. Tween = {
  46. Duration = 1.6, -- Duration of animation tweens in seconds
  47. EasingStyle = Enum.EasingStyle.Elastic, -- Easing style for animation tweens
  48. EasingDirection = Enum.EasingDirection.Out, -- Easing direction for animation tweens
  49. TweenName = "pivotTween", -- Name for the tween instance
  50. },
  51. Physics = {
  52. HitboxRadius = 200, -- Radius around the hitbox within which effects will be activated
  53. Intervals = {
  54. UpdateInterval = 0.1, -- Update interval for checking activations and camera positions
  55. CameraUpdateFactor = 2, -- Factor to control how often camera updates are checked
  56. ActivationUpdateFactor = 2 -- Factor to control how often activation updates are checked
  57. },
  58. },
  59. Grass = {
  60. Tag = "Grass", -- Tag used to identify grass objects, changing this can break the plugin
  61. HitboxTag = "Hitbox", -- Tag used to identify hitbox objects, changing this can break the plugin
  62. Transparency = 1, -- Transparency level of the hitbox
  63. CollisionResponse = { -- Collision response settings for the grass
  64. Enable = true,
  65. Friction = 0.5,
  66. Bounce = 0.1
  67. },
  68. },
  69. Camera = {
  70. FieldOfViewThreshold = 2, -- Additional degrees to the camera's field of view for angle checks
  71. MovementThreshold = 1, -- Minimum movement in position or rotation to consider camera as moved
  72. },
  73. SoundAsset = {
  74. SoundEnabled = true,
  75. SoundMappings = {
  76. Grass = {
  77. SoundId = "rbxassetid://8008438167",
  78. VolumeRange = { Min = 0, Max = 0.1 }
  79. },
  80. --[[Add more sounds as needed, example
  81. Flower = {
  82. SoundId = "rbxassetid://0",
  83. VolumeRange = { Min = 0, Max = 0.1 }
  84. },]]
  85. }
  86. },
  87. Debug = {
  88. EnableLogging = false, -- Enable or disable debug logging
  89. ExtraLogging = false, -- Extra debug logging, may flood your output; must have Enable Logging set to true for this to work
  90. LogFrequency = 5, -- Number of updates between log entries
  91. }
  92. }
  93.  
  94. --[[
  95. The following code is created by varuniemcfruity for the "Interactive Grass" plugin on ROBLOX.
  96. If you need to contact me, please send a private message through the developer forum or reach out to me via my profile.
  97.  
  98. Please respect my work by not stealing or reselling this code. If you have purchased the plugin, you are welcome to use it in all of your games.
  99. However, redistribution for profit or sharing the code for free is prohibited.
  100. ]]--
  101.  
  102. -- Internal Data
  103. local grassInfo = {}
  104. local hitboxInfo = {}
  105. local activeObjects = {}
  106. local playerEvents = {}
  107. local physicsEvents = {}
  108. local hitboxEvents = {}
  109. local timeTracker = 0
  110. local updateCounter = 0
  111. local lastCamCFrame = CAMERA.CFrame
  112. local hitboxFolder = Instance.new("Folder", ROOT)
  113. hitboxFolder.Name = "HitboxContainer"
  114.  
  115. -- Constants based on Configurations
  116. local TW_INFO = TweenInfo.new(
  117. GrassPhysics.Config.Tween.Duration,
  118. GrassPhysics.Config.Tween.EasingStyle,
  119. GrassPhysics.Config.Tween.EasingDirection
  120. )
  121. local UP_VECTOR = Vector3.FromNormalId(Enum.NormalId.Back)
  122. local ROT_CFRAME = CFrame.Angles(0, math.rad(-90), math.rad(90))
  123. local SQRT_3 = math.sqrt(3)
  124. local INTERVAL = GrassPhysics.Config.Physics.Intervals.UpdateInterval
  125. local HITBOX_RADIUS = GrassPhysics.Config.Physics.HitboxRadius
  126.  
  127. -- Helper Functions
  128. local function logDebug(message)
  129. if GrassPhysics.Config.Debug.EnableLogging then
  130. print("[GrassPhysics Debug]: " .. message)
  131. end
  132. end
  133.  
  134. local function createTween(obj, properties, info)
  135. local tween = TweenService:Create(obj, info, properties)
  136. tween:Play()
  137. return tween
  138. end
  139.  
  140. local function initiateTween(name, mesh, targetCFrame)
  141. if mesh:FindFirstChild(name) then
  142. mesh[name]:Destroy()
  143. end
  144. local tween = createTween(mesh, { CFrame = targetCFrame }, TW_INFO)
  145. tween.Name = name
  146. tween.Parent = mesh
  147. tween.Completed:Connect(function()
  148. tween:Destroy()
  149. end)
  150. end
  151.  
  152. local function activateEffect(hitbox)
  153. physicsEvents[hitbox] = {
  154. Touched = hitbox.Touched:Connect(function(part)
  155. if not grassInfo[part] or not grassInfo[part].Eligible then return end
  156. if table.find(activeObjects, part) then return end
  157.  
  158. local partType = part.Parent.Name
  159. local soundConfig = GrassPhysics.Config.SoundAsset.SoundMappings[partType]
  160.  
  161. if not soundConfig then
  162. logDebug("No sound configuration found for part type: " .. partType)
  163. end
  164.  
  165. local humanoid = Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
  166. table.insert(activeObjects, part)
  167. local rotation = part.Parent:GetAttribute("Rotation")
  168. local info = grassInfo[part]
  169. local direction = ((hitbox.Position - part.Position) * -Vector3.new(1, 0, 1)).Unit * part.Size.Y / SQRT_3
  170. local targetLook = info.Top + direction
  171. local targetPos = info.Base + (targetLook - info.Base).Unit * part.Size.Y / 2
  172. local finalCFrame = CFrame.lookAt(targetPos, targetLook, UP_VECTOR) * ROT_CFRAME * CFrame.Angles(rotation.X, rotation.Y, rotation.Z)
  173. initiateTween(GrassPhysics.Config.Tween.TweenName, part.Parent, finalCFrame)
  174.  
  175. if GrassPhysics.Config.Sound.Enable and GrassPhysics.Config.SoundAsset.SoundMappings[partType] then
  176. local sound = Instance.new("Sound", hitbox)
  177. sound.SoundId = soundConfig.SoundId
  178. local pitchShift = Instance.new("PitchShiftSoundEffect", sound)
  179. pitchShift.Octave = math.random(
  180. GrassPhysics.Config.Sound.PitchRange.Min * 100,
  181. GrassPhysics.Config.Sound.PitchRange.Max * 100
  182. ) / 100
  183. sound.Volume = math.random(
  184. soundConfig.VolumeRange.Min * 100,
  185. soundConfig.VolumeRange.Max * 100
  186. ) / 100
  187. sound:Play()
  188. delay(GrassPhysics.Config.Sound.EffectDuration, function()
  189. sound:Destroy()
  190. end)
  191.  
  192. if GrassPhysics.Config.Sound.Reverb.Enable then
  193. local reverb = Instance.new("ReverbSoundEffect", sound)
  194. reverb.DecayTime = GrassPhysics.Config.Sound.Reverb.DecayTime
  195. reverb.Density = GrassPhysics.Config.Sound.Reverb.ReflectLevel
  196. end
  197.  
  198. if GrassPhysics.Config.Sound.Spatial.Enable then
  199. sound.RollOffMode = GrassPhysics.Config.Sound.Spatial.RolloffMode
  200. sound.MinDistance = GrassPhysics.Config.Sound.Spatial.MinDistance
  201. sound.MaxDistance = GrassPhysics.Config.Sound.Spatial.MaxDistance
  202. end
  203. end
  204. logDebug("Activated effect for part: " .. part.Name)
  205. if GrassPhysics.Config.WindShake.Enabled then
  206. GrassPhysics.Config.WindShake.ScriptPath.Enabled = false
  207. end
  208. end),
  209.  
  210. TouchEnded = hitbox.TouchEnded:Connect(function(part)
  211. local idx = table.find(activeObjects, part)
  212. if not grassInfo[part] or not idx then return end
  213. table.remove(activeObjects, idx)
  214. initiateTween(GrassPhysics.Config.Tween.TweenName, part.Parent, part.CFrame)
  215. logDebug("Ended touch effect for part: " .. part.Name)
  216.  
  217. local touchingParts = hitbox:GetTouchingParts()
  218. local stillTouching = false
  219. for _, touchingPart in ipairs(touchingParts) do
  220. if grassInfo[touchingPart] and table.find(activeObjects, touchingPart) then
  221. stillTouching = true
  222. break
  223. end
  224. end
  225.  
  226. if not stillTouching and GrassPhysics.Config.WindShake.Enabled then
  227. GrassPhysics.Config.WindShake.ScriptPath.Enabled = true
  228. end
  229. end)
  230. }
  231. end
  232.  
  233.  
  234. local function deactivateEffect(hitbox)
  235. if physicsEvents[hitbox] then
  236. physicsEvents[hitbox].Touched:Disconnect()
  237. physicsEvents[hitbox].TouchEnded:Disconnect()
  238. physicsEvents[hitbox] = nil
  239.  
  240. logDebug("Deactivated effect for hitbox: " .. hitbox.Name)
  241. end
  242. end
  243.  
  244. local function enableGrass(grass)
  245. grass.Hitbox.CanTouch = true
  246. end
  247.  
  248. local function disableGrass(grass)
  249. grass.Hitbox.CanTouch = false
  250. if grass:FindFirstChild(GrassPhysics.Config.Tween.TweenName) then
  251. grass[GrassPhysics.Config.Tween.TweenName]:Cancel()
  252. end
  253. grass.CFrame = grass.Hitbox.CFrame
  254. local idx = table.find(activeObjects, grass.Hitbox)
  255. if idx then
  256. table.remove(activeObjects, idx)
  257. end
  258. end
  259.  
  260. local function resetGrass(grass)
  261. if grass:FindFirstChild(GrassPhysics.Config.Tween.TweenName) then
  262. grass[GrassPhysics.Config.Tween.TweenName]:Cancel()
  263. end
  264. grass.CFrame = grass.Hitbox.CFrame
  265. local idx = table.find(activeObjects, grass.Hitbox)
  266. if idx then
  267. table.remove(activeObjects, idx)
  268. end
  269. end
  270.  
  271. local function setupGrassInstance(grass)
  272. grass:SetAttribute("Rotation", Vector3.new(math.rad(grass.Orientation.X), math.rad(grass.Orientation.Y), math.rad(grass.Orientation.Z)))
  273. local grassHitbox = grass:FindFirstChild("Hitbox") or Instance.new("Part", grass)
  274. grassInfo[grassHitbox] = {
  275. Top = grass.Position + Vector3.new(0, grass.Size.Y / 2, 0),
  276. Base = grass.Position - Vector3.new(0, grass.Size.Y / 2, 0),
  277. Eligible = true
  278. }
  279.  
  280. if not grass:FindFirstChild("Hitbox") then
  281. grassHitbox.Size = grass.Size
  282. grassHitbox.CanCollide = false
  283. grassHitbox.Anchored = true
  284. grassHitbox.Transparency = GrassPhysics.Config.Grass.Transparency
  285. grassHitbox.Name = "Hitbox"
  286. grass.CFrame = CFrame.lookAt(grass.Position, grassInfo[grassHitbox].Top, UP_VECTOR) * ROT_CFRAME * CFrame.Angles(grass:GetAttribute("Rotation").X, grass:GetAttribute("Rotation").Y, grass:GetAttribute("Rotation").Z)
  287. grassHitbox.CFrame = grass.CFrame
  288. end
  289.  
  290. logDebug("Setup grass instance: " .. grass.Name)
  291. end
  292.  
  293. local function removeGrassInstance(grass)
  294. disableGrass(grass)
  295. if grassInfo[grass.Hitbox] then
  296. grassInfo[grass.Hitbox] = nil
  297. end
  298. grass.Hitbox:Destroy()
  299.  
  300. logDebug("Removed grass instance: " .. grass.Name)
  301. end
  302.  
  303. local function setupHitboxInstance(hitbox)
  304. if not hitbox:IsDescendantOf(ROOT) then return end
  305. hitboxInfo[hitbox] = { Eligible = true }
  306. print(hitboxInfo)
  307. activateEffect(hitbox)
  308.  
  309. logDebug("Setup hitbox instance: " .. hitbox.Name)
  310. end
  311.  
  312. local function removeHitboxInstance(hitbox)
  313. deactivateEffect(hitbox)
  314. hitboxInfo[hitbox] = nil
  315. if hitboxEvents[hitbox] then
  316. hitboxEvents[hitbox]:Disconnect()
  317. hitboxEvents[hitbox] = nil
  318. end
  319.  
  320. logDebug("Removed hitbox instance: " .. hitbox.Name)
  321. end
  322.  
  323. local function cameraPositionChanged(hitboxOnly)
  324.  
  325. task.desynchronize()
  326.  
  327. local controlVec = lastCamCFrame.LookVector
  328. local threshold = CAMERA.FieldOfView + GrassPhysics.Config.Camera.FieldOfViewThreshold
  329.  
  330. for obj, data in (hitboxOnly and hitboxInfo or grassInfo) do
  331. local directionVec = obj.Position - lastCamCFrame.Position
  332. local angle = math.deg(directionVec.Unit:Angle(controlVec))
  333. local playerPosVec = Players.LocalPlayer.Character and Players.LocalPlayer.Character.PrimaryPart and (obj.Position - Players.LocalPlayer.Character.PrimaryPart.Position)
  334. local distance = playerPosVec and (Vector3.new(playerPosVec.X, 0, playerPosVec.Z)).Magnitude
  335.  
  336. if angle <= threshold and distance and distance <= HITBOX_RADIUS then
  337. if not data.Eligible then
  338. data.Eligible = true
  339. data.Updated = false
  340. end
  341. else
  342. if data.Eligible then
  343. data.Eligible = false
  344. data.Updated = false
  345. end
  346. end
  347. end
  348. lastCamCFrame = CAMERA.CFrame
  349.  
  350. if GrassPhysics.Config.Debug.ExtraLogging == true then logDebug("Camera position changed. Hitbox only: " .. tostring(hitboxOnly)) end
  351.  
  352. task.synchronize()
  353. end
  354.  
  355. local function handleCharacterAddition(character)
  356. local primaryPart = character:WaitForChild("HumanoidRootPart")
  357. local humanoid = character:WaitForChild("Humanoid")
  358.  
  359. local newHitbox = ReplicatedStorage.InteractiveGrass.Hitbox:WaitForChild("Hitbox"):Clone()
  360. newHitbox.Parent = hitboxFolder
  361.  
  362. local attachment = Instance.new("Attachment", newHitbox)
  363. local alignPosition = Instance.new("AlignPosition", attachment)
  364. alignPosition.RigidityEnabled = true
  365. alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
  366. alignPosition.Attachment0 = attachment
  367. alignPosition.Enabled = true
  368.  
  369. local alignOrientation = Instance.new("AlignOrientation", attachment)
  370. alignOrientation.RigidityEnabled = true
  371. alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
  372. alignOrientation.Attachment0 = attachment
  373. alignOrientation.Enabled = true
  374.  
  375. hitboxEvents[newHitbox] = RunService.Stepped:Connect(function()
  376. alignPosition.Position = primaryPart.Position
  377. end)
  378.  
  379. newHitbox.Position = primaryPart.Position
  380. CollectionService:AddTag(newHitbox, GrassPhysics.Config.Grass.HitboxTag)
  381.  
  382. logDebug("Character added: " .. character.Name)
  383.  
  384. humanoid.Died:Connect(function()
  385. for _, grass in ipairs(activeObjects) do
  386. resetGrass(grass.Parent)
  387. logDebug("Reset grass: " .. grass.Parent.Name)
  388. end
  389.  
  390. activeObjects = {}
  391. logDebug("Player died, deactivated all grass and hitboxes")
  392. end)
  393. end
  394.  
  395.  
  396. local function handleCharacterRemoval(character)
  397. local hitboxPart = character:FindFirstChild("Hitbox")
  398. if hitboxPart then
  399. hitboxPart:Destroy()
  400. end
  401.  
  402. logDebug("Character removed: " .. character.Name)
  403. end
  404.  
  405. local function updateCameraCFrame(dt)
  406. timeTracker += dt
  407. if timeTracker > INTERVAL * GrassPhysics.Config.Physics.Intervals.CameraUpdateFactor then
  408. timeTracker = 0
  409. if (CAMERA.CFrame.Position - lastCamCFrame.Position).Magnitude > GrassPhysics.Config.Camera.MovementThreshold or CAMERA.CFrame.Rotation ~= lastCamCFrame.Rotation then
  410. updateCounter = 0
  411. task.spawn(cameraPositionChanged)
  412. else
  413. updateCounter += 1
  414. if updateCounter > GrassPhysics.Config.Debug.LogFrequency then
  415. updateCounter = 0
  416. task.spawn(cameraPositionChanged, true)
  417. end
  418. end
  419. end
  420.  
  421. if GrassPhysics.Config.Debug.ExtraLogging == true then logDebug("Camera CFrame updated.") end
  422. end
  423.  
  424. local function updateActivations(dt)
  425. timeTracker += dt
  426. if timeTracker > INTERVAL * GrassPhysics.Config.Physics.Intervals.ActivationUpdateFactor then
  427. timeTracker = 0
  428. for obj, data in pairs(grassInfo) do
  429. if data.Eligible then
  430. if not data.Updated then
  431. data.Updated = true
  432. enableGrass(obj.Parent)
  433. end
  434. else
  435. if not data.Updated then
  436. data.Updated = true
  437. disableGrass(obj.Parent)
  438. end
  439. end
  440. end
  441.  
  442. for obj, data in pairs(hitboxInfo) do
  443. if data.Eligible then
  444. if not data.Updated then
  445. data.Updated = true
  446. activateEffect(obj)
  447. end
  448. else
  449. if not data.Updated then
  450. data.Updated = true
  451. deactivateEffect(obj)
  452. end
  453. end
  454. end
  455.  
  456. if GrassPhysics.Config.Debug.ExtraLogging == true then logDebug("Activations updated.") end
  457. end
  458. end
  459.  
  460. Players.PlayerAdded:Connect(function(player)
  461. playerEvents[player.Name] = {
  462. CharacterAdded = player.CharacterAdded:Connect(handleCharacterAddition),
  463. CharacterRemoving = player.CharacterRemoving:Connect(handleCharacterRemoval)
  464. }
  465. if player.Character then
  466. handleCharacterAddition(player.Character)
  467. end
  468. end)
  469.  
  470. Players.PlayerRemoving:Connect(function(player)
  471. local events = playerEvents[player.Name]
  472. if events then
  473. events.CharacterAdded:Disconnect()
  474. events.CharacterRemoving:Disconnect()
  475. playerEvents[player.Name] = nil
  476. end
  477. end)
  478.  
  479. CollectionService:GetInstanceAddedSignal(GrassPhysics.Config.Grass.Tag):Connect(setupGrassInstance)
  480. CollectionService:GetInstanceRemovedSignal(GrassPhysics.Config.Grass.Tag):Connect(removeGrassInstance)
  481.  
  482. CollectionService:GetInstanceAddedSignal(GrassPhysics.Config.Grass.HitboxTag):Connect(setupHitboxInstance)
  483. CollectionService:GetInstanceRemovedSignal(GrassPhysics.Config.Grass.HitboxTag):Connect(removeHitboxInstance)
  484.  
  485. RunService:BindToRenderStep("CameraUpdateStep", Enum.RenderPriority.Camera.Value, updateCameraCFrame)
  486. RunService:BindToRenderStep("ActivationUpdateStep", Enum.RenderPriority.Camera.Value + 2, updateActivations)
  487.  
  488. for _, grass in ipairs(CollectionService:GetTagged(GrassPhysics.Config.Grass.Tag)) do
  489. setupGrassInstance(grass)
  490. end
  491.  
  492. for _, hitbox in ipairs(CollectionService:GetTagged(GrassPhysics.Config.Grass.HitboxTag)) do
  493. setupHitboxInstance(hitbox)
  494. end
  495.  
  496. for _, player in ipairs(Players:GetPlayers()) do
  497. playerEvents[player.Name] = {
  498. CharacterAdded = player.CharacterAdded:Connect(handleCharacterAddition),
  499. CharacterRemoving = player.CharacterRemoving:Connect(handleCharacterRemoval)
  500. }
  501. if player.Character then
  502. handleCharacterAddition(player.Character)
  503. end
  504. end
  505.  
  506. local occlusionEnabled = GrassPhysics.Config.OcclusionCulling.Enabled
  507. script.Parent.Parent.Culling.GrassCulling.Enabled = occlusionEnabled
  508.  
  509. return GrassPhysics
Add Comment
Please, Sign In to add comment