VineModule - An approach to creating vines in Roblox Studio!

VineModule.lua, what is this?

:seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling:

This module is now in V2.

:backhand_index_pointing_right: Download Here
:backhand_index_pointing_right: Uncopylocked Place

Hi there, :waving_hand: I’m lyrnu. I’ve created a simplistic approach to creating vines. There is a vine at appears on the FORWARDS DIRECTION of a chosen part and grows randomly and procedurally, sometimes creating sub branches and little twists and turns. :seedling:

Optimizations

This resource uses the --!native and --!strict transpilers for maximum performance it also uses object-pooling for maximum performance.

:flexed_biceps:

In the Uncopylocked Place, you’ll find an intensive demonstration of how optimized this resource can be, with 5 different vines growing simultaneously with high settings! :person_lifting_weights: :green_circle:

Settings

:gear: All settings are interchangeable, and can be seen in the following list, they all affect the behaviours of the vine, and there are a lot to change!



    local config = {
		

        segmentLength = 6,
        segmentRadius = 0.6,
		growthInterval = 0.25,
		growthEnergyCost = 6,
		drainRate = 0.1,
		maxEnergy = 120,
		initialEnergy = 90,
		waterSearchRadius = 10,
		waterRegenRate = 15,
		branchChance = 0.18,
		branchScale = 0.45,
		branchLengthScale = 0.6,
		maxSegments = 120,
		wallRaycastDistance = 8,
		wallStickOffset = 0.15,
		wobbleAngle = 18,
		jitter = 0.6,
		growthTweenTime = 0.12,
		material = Enum.Material.SmoothPlastic,
		color = Color3.fromRGB(90,140,60),
		rootColor = Color3.fromRGB(60,100,45),
		rootProbability = 0.12,
		allowWaterBranch = false, -- toggle for water
		initialDirection = nil, -- can be Vector3 for custom root direction
	}

Here’s a video of it in action :raised_fist: :

Wait, there's more...

In case you had bad eyesight :pensive_face: , the VineModule :seedling: also has a changeable setting for water detection. If a part with the name “Water” is touched then the Vine will drop seeds to start a new mini plant!! By default, this setting is turned off.

I will most likely change the behaviour of this in the future, however I thought it would be a neat little thing to add. (Skip to 0:30)


(Video is a little glitchy)

Examples:

As seen in the uncopylocked place, this code provides a good example of the power of this module.

:backhand_index_pointing_down:

local VineModule = require(game.ReplicatedStorage.VineModule)
local Workspace = game:GetService("Workspace")

-- Create multiple origin parts
local originParts = {}
local positions = {
	Vector3.new(-5, 10, 0),
	Vector3.new(0, 10, 0),
	Vector3.new(5, 10, 0),
	Vector3.new(10, 10, 0),
	Vector3.new(15, 10, 0)
}

for i, position in ipairs(positions) do
	local originPart = Instance.new("Part")
	originPart.Name = "VineOrigin" .. i
	originPart.Size = Vector3.new(2, 2, 2)
	originPart.Anchored = true
	originPart.Position = position
	originPart.Color = Color3.fromRGB(120, 60, 30)
	originPart.CanCollide = true
	originPart.Parent = Workspace

	-- Add visual indicator
	local mesh = Instance.new("SpecialMesh")
	mesh.MeshType = Enum.MeshType.Sphere
	mesh.Scale = Vector3.new(1.5, 1.5, 1.5)
	mesh.Parent = originPart

	-- Add ClickDetector
	local clickDetector = Instance.new("ClickDetector")
	clickDetector.Parent = originPart

	table.insert(originParts, {part = originPart, vine = nil, detector = clickDetector})
end

-- Vine configuration
local vineConfig = {
	segmentLength = 6,
	segmentRadius = 0.5,
	growthInterval = 0.2,
	branchChance = 0.45,
	maxSegments = 160,
	rootProbability = 0.3,
	color = Color3.fromRGB(80, 160, 80),
	rootColor = Color3.fromRGB(50, 120, 40)
}

-- Function to grow a vine on a specific origin
local function growVine(originData)
	-- Destroy existing vine if present
	if originData.vine and originData.vine.Destroy then
		pcall(function()
			originData.vine:Destroy()
		end)
		originData.vine = nil
	end

	-- Spawn new vine
	originData.vine = VineModule:SpawnVine(originData.part, vineConfig)
end

-- Connect click events to each part
for _, originData in ipairs(originParts) do
	originData.detector.MouseClick:Connect(function(player)
		growVine(originData)
	end)
end

-- Optional: grow vines immediately on creation
for _, originData in ipairs(originParts) do
	growVine(originData)
end


Closing off

This is my first ever open-source. Let me know what I should add/improve on. I will be optimizing this even further if I get interest in this resource. Thank you guys!

:heart:

Download link and Uncopylocked place here:

Download Here
Uncopylocked Place

:seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling: :seedling:

14 Likes

It’s pretty interesting.

EDIT: the things I said below must be rewritten as they were posted in early versions.

And code has a lot of fixable things.

First:


Why would you encapsulate this function when it’s used only once afterwards? Pretty strange solution that only wastes CPU time.
Second:

This is somewhat same as Raycast advice, but there’s even more to tell. Each time you’re creating a new TweenInfo, which can lead to memory intensivity. I’ve more likely fixed it with utilizing TweenInfo for vine and sending it as a function parameter.
Third:

First of all, since you don’t use --!native, the most common thing for optimization here is to use vector library instead of Vector3. Also, there are already prepared constants in Vector3 (Vector3.yAxis and etc.) which replace all Vector3.create(1,0,0) and others alike Vector3 constructions for better runtime.
Forth:
image
Calculating magnitude is kind of slow since it has square root operation. Instead, you should compare the dot product of the sub vector (vector.dot(subtractedvector,subtractedvector)) with squared radius. Obviously, you should raise radius to a power before the for loop.
Fivth:
image
Creating a new connection each time gives bigger memory consumption and (I’m not quite sure!!!) increases CPU time consume with each growing vine.
Sixth:

The same vector advice and the .Magnitude check one.
Seventh:

You’re creating a new dictionary key entry which costs 16 bytes for each one. Meaning, each time you’re creating a 48 bytes of the same information with each vine. Also, :GetEnergy and :IsActive methods are useless since they do the same thing a developer can do by himself without losing code readability.

Also about these functions - there’s no need for pcall() and creating new function just to call one single method. Also, you’ve made a memory leak because you don’t clean the self.branches table.

Also, I suggest you to add explicit type annotation for those programmers that use --!strict and for further use of --!native for more complex vines.

I might’ve missed some issues since I read the code shallowly.

EDIT: this post should be rewritten.

4 Likes

Thank you so much for the in depth feedback. This afternoon I’ll fix everything in this reply.

On a side note, I’m very impressed on how you spot these sorts of issues. As a person who is making a high-end openworld RPG I’d really like to know how I can optimise my code well. Where did you learn to code like this?

1 Like

Hello! It’s pleasant to hear that it was impressive. But the things I’ve shown are pretty much popular (atleast I’m seeing them frequently).

I’m glad you’re interested in educating, so I’ll answer as helpfully as I can. Personally I was self learning.

I’ve learned syntax and few peculiarities and it was enough to code (test projects). I was also reading the devforum and documentation a lot. If I couldn’t understand how to make something then I’ve searched it in the Internet or asked more experienced people in roblox developer groups for advices. I have used AI as a learning tool (mostly math formulas and examples of code). Then I started looking in other people’s code and make it more efficient, the great way to do that was Youtube and toolbox models.

“The knowledge of optimization” is rooted in math and algorithmics with some understanding of languages. It does sound pretty boring, but these optimization worth it.

If I finalize my result, it’ll be something like: I’ve became as good as I’m right now by learning math, socializing with more experienced people, taking other people code to improve it (aka intellectual work). LuaU is not hard, so learning it in-depth is not needed. The main problems come in practical part. In my opinion main sources of these problems are ignorance of math and inattention (and almost every programmer suffer from them, including me).

However in games like RPG top priority must be on Client->Server and Server->Client responsiveness (because LuaU is natively fast), so you’d likely to learn about memory control, compressation, serialization and deserialization.buffers and bit32 library is the main things for this (or already posted solutions like Packet by suphi).

2 Likes

This is awesome. Thanks so much for the tips I’m definetly going to give them a try. Lua isn’t my first langauge and honestly I’m pretty loose with it as I work with langauges such as C/C++ that requier me to be much more strict. Do you know any other langauges?

Also, new version has been released!

I’m currently learning C++. And knowing C languages is actually beneficial for Lua and LuaU programming as LuaU was written in C++ (Lua was written in C)

Nice

1 Like

what is water supposed to do?

Also I do not know, but is there any way to real time do unions on parts? then the connections would look smooth…

By default, water is turned off. You can turn it on through the default settings in the module or the ServerScript. Read the documentation to see what it does :heart:

Also I do not know, but is there any way to real time do unions on parts? then the connections would look smooth…<

Never exactly thought of that as personally I had liked it choppy. If you want it smooth you’ll be trading aesthetics with performance.

This isn’t scripting support.

Plus this is just very nit picky and is not really needed. It very much seems you are just purposely finding things.


To stay on topic: I love this! If I ever make vines in my game I will remember to use this! Thank you for the module.

This isn’t scripting support.
Plus this is just very nit picky and is not really needed. It very much seems you are just purposely finding things.<

What @kolakolaki6663 said might not be relevent for this channel it surely was helpful for me as a developer, as I always love feedback on my code.

To stay on topic: I love this! If I ever make vines in my game I will remember to use this! Thank you for the module.<

Thank you so much!! I hope I made something useful and I look so forward to that implmenetation. Tell me when you do :heart:

2 Likes


“Let me know what I should add/improve on”.

You make me sound like I’m hating, but do I? Is it so bad to point out things that could be optimized? What’s so wrong in giving a constructive feedback on the code of a resource?

There is - GeometryService provides such things. Although, it might be slow with real time for complex part shapes.

ngl this just comes across as nitpicking, this is a useless criticism of code, because the CPU time wasted is negligible, in fact this is barely measurable. This is a good example of premature optimization. The real issue is clearly the ::GetDescendants() call inside.

Edit: I see the part now where you wrote you “read the code shallowly”. Do you not see something wrong with critiquing code when you’ve barely read through it properly? Just curious :sweat_smile:.

1 Like

Hello.

Your words are half correct. CPU time wasted by function call most of the time is negligible, however it is measurable. Comparing function call to a multiplication of 2 vector3, the function call is around 2.5 times slower than the multiplication of vectors (In RLuaU, of course.). It is pretty expensive as this difference will increase as amount of parameters increase.


image
(P.S. with --!native flag, the function call becomes same in speed)

If we’ll talk more deeply, the whole RaycastParams creating and editing was the issue (which is still half present in newer version), not just GetDescendants call (the GetDescendants call was derived from the fact that Lyrnu created new RaycastParams each call). And even bigger issue is creating a new Vine for each newly created branch and root.

I don’t see anything wrong in providing tips that provide improve in optimization or functionality. That’s why I analyzed code shallowly, without thinking more deeply at the solutions the developer made.

1 Like

Please let me know what game you use this module in! I just like seeing my stuff in other peoples games lol!

1 Like