BEST Tree Leaf+Trunk Animator + 40 FREE Trees + Optimized + Simple + Wind Direction

I have created this system to handle animations of trees. It’s nothing new in terms of functionality, but it’s very optimized and simple to understand. These are all the trees. All of them are animated and located in the workspace of this example.

 {
   [1] = Maple tree,
   [2] = Willow tree,
   [3] = Golden Willow tree,
   [4] = Birch tree,
   [5] = Oak tree,
   [6] = Pink Willow tree,
   [7] = Banana tree,
   [8] = Sakura tree,
   [9] = Pine tree,
   [10] = Oak tree,
   [11] = Pine tree,
   [12] = Pine tree,
   [13] = Pine tree,
   [14] = Pine tree,
   [15] = Birch tree,
   [16] = Maple tree,
   [17] = Oak tree,
   [18] = Maidenhair tree,
   [19] = Ash tree,
   [20] = Douglass Fir tree,
   [21] = Broadleaf tree,
   [22] = Redwood tree,
   [23] = Maple tree,
   [24] = Oak tree,
   [25] = Wise old tree,
   [26] = Beechwood tree,
   [27] = tree,
   [28] = tree,
   [29] = tree,
   [30] = Dogwood tree,
   [31] = Thick Fir tree,
   [32] = Fir Tree,
   [33] = Fir Tree,
   [34] = Magic Tree,
   [35] = Fir tree,
   [36] = Oak tree,
   [37] = Birch tree,
   [38] = Pine tree,
   [39] = tall Birch tree,
   [40] = short Birch tree
}

All of them have optimized collisions and rendering. I use this library for my own project and I will be testing a client sided version of this code soon. The good news is this code is pretty short so it should be easy to understand! You can copy and paste the server code to the client and it should work the same but provide better responsiveness.

You can copy the public place here to check it out!
ServerSided Tree Animations Example

Please grab a copy of the Client Sided Animation script for the Best Performance!

Optimized Client Tree Animations Script - Supports Streaming Enabled and Camera Frustrum Culling + Tweening
[PlaceIn_ReplicatedFirst - Creator Store (https://create.roblox.com/store/asset/94248172114274/PlaceInReplicatedFirst)

I have done testing and this method scales much better than running one script per tree.
One original script runs .1% for one tree for example while this is .5% at 40 trees .
image

This also animates the trunk!
It does so beautifully and optimized by using calculations from the leaves to align the trunk!

--Trunk Animation Module

local treeanim={}
local wind=workspace.GlobalWind
function treeanim.Initialize(Leaves)
	local pos=	Leaves.CFrame
	local T = -99999
	local tall = Leaves.Size.Y / 2
	local rand = (math.random(0,20))/10
	local px5=pos.Position.x/5
	local pz6=pos.Position.z/6
	local function Animate(x,z)
		local x = x and x*.75 or (math.sin(T+(px5))*math.sin(T/9))/4.5
		local z = z and z*.75 or (math.sin(T+(pz6))*math.sin(T/12))/6
		Leaves.CFrame =pos:ToWorldSpace(CFrame.new(x,0,z) * CFrame.Angles((z)/tall, 0,(x)/-tall))* CFrame.Angles(math.rad(wind.Z/tall), 0,math.rad(wind.X/tall))		
		T = T + 0.12
		return Leaves
	end
	return Animate
end
return treeanim

The example code perfectly handles Trees being rendered in an out

I tested different methods of timers and I found that while true do is best for the timing frequency of this system. I really scratch my head considering how to further optimize the code.
Originally I tested versus the original source code and did what I thought would be most efficient but turned out to not be. Then I started over and found some optimizations and that is about all there is to it.

–Features List

  1. Animate all the Trees with one Loop.
  2. Animate The Trunk and Leaves.
  3. 40 Formatted and Optimized Tree Models ready for Game.
  4. The code is short and optimized with a small footprint, compared to other methods.
  5. Supports Wind Direction
11 Likes

If you enjoy this leave a like and check out some of my other open resources I created for the community! (Hint they all work together)
Check out my other popular developer assets I created for the community

2 Likes

The game you linked is not uncopylocked.

2 Likes

Sorry about that! I fixed it. it is good to go. :slight_smile:

1 Like

This looks great and much better than what I was using before, the WindShake module.

What I was using before in my game:
https://i.gyazo.com/704160f96ec145fe1f0051bc279a3aee.gif

This module:
https://i.gyazo.com/2597be9677d462b1381ad273d1a78587.gif

I will look forward to if you choose to create an official release of this on the client as although optimized I am still worried about this being on the server.

1 Like

Thanks for the support mako! When implementing this to my game, I made this modified version that handles the animations locally on the client, uses the tween service for smooth animations and uses occlusion culling!
[PlaceIn_ReplicatedFirst - Creator Store (https://create.roblox.com/store/asset/94248172114274/PlaceInReplicatedFirst)

This is the complete local sided source code

local camera=workspace.CurrentCamera
workspace:WaitForChild("Trees")
local function IsInView(object,cameraViewportSize)
	local objectPosition = camera:WorldToViewportPoint(object.Position)
	-- Check if the object is within the camera's viewport
	if objectPosition.X <= cameraViewportSize.X and
		--objectPosition.Y <= cameraViewportSize.Y and
		objectPosition.Z > 0 then
		return true
	elseif objectPosition.X <= cameraViewportSize.X and
		objectPosition.Y <= cameraViewportSize.Y and
		objectPosition.Z > 0 then -- Z > 0 means the object is in front of the camera
		return true
	else
		return false
	end
end



print("Hello world!")
local format=require(script.AnimateLeaves)
local format2=require(script.AnimateTrunk)

local candidates={}
local count=0
local function registertree(obj)
	local leaf= obj:FindFirstChild("Leaf") 
	if leaf then			
		count+=1	
		candidates[count.."t"]={func=format.Initialize(leaf),obj=leaf}
		local index=Instance.new("StringValue")	
		index.Name="AnimationIndex"
		index.Value=#candidates
		index.Parent=leaf	
	end
	if not leaf then 
		local trunk=obj:FindFirstChild("Trunk")	
		if trunk then
			count+=1	
			candidates[count.."t"]={func=format2.Initialize(trunk),obj=trunk}
			local index=Instance.new("StringValue")	
			index.Name="AnimationIndex"
			index.Value=#candidates
			index.Parent=trunk		
		end
	end		
	
end

local function ejecttree(obj)
	local leaf= obj:FindFirstChild("Leaf") 
	if leaf then
		local index=leaf:FindFirstChild("AnimationIndex")
		if index then			
			candidates[index.Value]=nil
		end
	end
end

for i,v in workspace.Trees:GetChildren() do 
	registertree(v)
end
workspace.Trees.ChildAdded:Connect(registertree)
workspace.Trees.ChildRemoved:Connect(ejecttree)
--while true do 
--game:GetService("RunService").:Connect(function()
local timer=.0666

while true do 
	local lastpos=camera.CFrame
	task.wait(timer)
	local cameraViewportSize=camera.ViewportSize
	for i,v in candidates do 	
		if IsInView(v.obj,cameraViewportSize) then
		v.func()
		end
	end
end
3 Likes

Some of the main differences is I am using ToWorldSpace to respect the original orientation of your tree and leaves. I tested this with 40 different tree models and it works beautifully! You can include a trunk or only a Trunk to sway the trunk of the tree by using the leaf calculations multiplied by .5 to increase efficiency when using both at the same time.
As of my knowledge this is the most efficient way to do this type of tree animations. I may test further by implementing tween service instead of directly changing the CFrame.

1 Like

Use these Trees with my Open Sourced Large Language Model agent System! Put Aurora in a Forest of trees and let the AI see the trees!

whats wrong with windshake, the gifs look the same with windshake just having very high settings

It’s overly complicated and not as optimized as this code is. I’ve never used windshake module.
But I optimized this code from it’s very basic elements and tried many different settings to achieve this level of performance. It’s important that you use the most efficient one so you can use it without worrying about that.

Also, whoever wrote windshake module didn’t respect the Original CFrame of the leaves like the original code didn’t. I noticed this issue becuase I tested 40 trees (it would cause trees leaves to reset to 0,0,0 making them all face the same direction given the noise parameters). I solved it. Also you can sway your tree trunks with this code and it is aligned with the leaves so they are double jointed and sway together without making the geometry out of its intentional alignment.

Additionally, the newer version uses tweening, and frustrum culling for local sided animation solution of higher fidelity.

The one demonstrated in the clip was on the server.

never had any of these issues youve mentioned, along with the performance has been fine

the module stock is a bit overkill to show what it does, tone down the settings and itll be visually fine

From what I understand this one basically works the same way, both are based off the same initial algorithm, and perhaps they caught the same issue the orignal algorithm had.
But can you sway the tree limbs?
In consequence of being based off the same open source algorithm both resources work the same way which is by the object called “Leaf”. Additionally with this one you can use only a Trunk, or a Trunk and Leaves. It’s also only 120 lines long and has modules that make it designed to be expanded upon if someone wished to do so.

For example a easy new addition would be objects tagged as “:Plants” and run them with the Trunk or Leaf or a third module Algorithm would make for swaying plants.

It’s good to see you’re using the trunk setting as well! There is a nice subtle sway in the trunk.

1 Like

This module looks a lot more realistic compared to WindShake in my opinion, without any extra setup required, all I did was change the module. The framerate you see in the WindShake gif is not a fault of my computer or gyazo, it legitimately looks like that in game (clunky, unrealistic imo).

There’s nothing wrong with WindShake as I was using it before I came across this module.

1 Like

To add, this is a gif of me using WindShake in another project, and after spending some time lowering the wind speed almost as much as possible and tweaking the module script (namely the refresh rate), this is the best I could achieve with WindShake.
https://gyazo.com/204732e96f898780603a095436bf305e
To me this still has very noticeable unrealistic swaying, and even some noticeable ‘clunkiness’.
*Keep in mind this is all on the almost lowest WindSpeed with the refresh rate at a higher than default amount.

Maybe I am using the module wrong or missing something, but until that is revealed to me I’ll continue to use this module.

1 Like

I use it too! I would suggest the the local script over the server sided script provided in the example. New update coming soon to this module! I will be adding plant support similar to those interactive grass plugins that allow you to interact with moving plants

1 Like

This i nice but i wished it could get GlobalWind every update or so instead of once

1 Like

Thanks for pointing that out! I didn’t notice. To fix it you can ctrl+F → Type wind → Replace all iterations of wind with workspace.GlobalWind. I’ll be posting an update soon!

Cool project but I think there’s some work to be done before you can call this very optimized or more optimized than windshake

A few general suggestions:

  • You should throttle the updates in a way where it only does as much work as it can in a frame before it crosses some threshold(like 0.5 ms or so). Your implementation won’t scale. If you have a crazy amount of trees in an area, it’ll process all of them per update and your frames will tank

  • This is something you should be multi threading, you could be assigning x amount of trees to process across 8 actors or so, where each actor does the math or whatever and returns results to the main thread with a buffer to then CFrame

  • You should be using BulkMoveTo to CFrame stuff, as it’ll speed up everything by like 20(?)%

  • You should be culling stuff based off your camera frustum like what windshake does, instead of updating things that don’t need to update. It’s also a good idea to adjust update rate based on some heuristic like how far the tree is from your camera

2 Likes

The local version handles all of that!

local camera=workspace.CurrentCamera
workspace:WaitForChild("Trees")
local function IsInView(object,cameraViewportSize)
	local objectPosition = camera:WorldToViewportPoint(object.Position)
	-- Check if the object is within the camera's viewport
	if objectPosition.X <= cameraViewportSize.X and
		--objectPosition.Y <= cameraViewportSize.Y and
		objectPosition.Z > 0 then
		return true
	elseif objectPosition.X <= cameraViewportSize.X and
		objectPosition.Y <= cameraViewportSize.Y and
		objectPosition.Z > 0 then -- Z > 0 means the object is in front of the camera
		return true
	else
		return false
	end
end


local format=require(script.AnimateLeaves)
local format2=require(script.AnimateTrunk)

local candidates={}
local count=0
local function registertree(obj)
	local leaf= obj:FindFirstChild("Leaf") 
	if leaf then			
		count+=1	
		candidates[count]={func=format.Initialize(leaf),obj=leaf}
		local index=Instance.new("StringValue")	
		index.Name="AnimationIndex"
		index.Value=#candidates
		index.Parent=leaf	
	end
	if not leaf then 
		local trunk=obj:FindFirstChild("Trunk")	
		if trunk then
			count+=1	
			candidates[count]={func=format2.Initialize(trunk),obj=trunk}
			local index=Instance.new("StringValue")	
			index.Name="AnimationIndex"
			index.Value=#candidates
			index.Parent=trunk		
		end
	end		
	
end

local function ejecttree(obj)
	local leaf= obj:FindFirstChild("Leaf") 
	if leaf then
		local index=leaf:FindFirstChild("AnimationIndex")
		if index then			
			candidates[index.Value]=nil
		end
	end
end

for i,v in workspace.Trees:GetChildren() do 
	registertree(v)
end
workspace.Trees.ChildAdded:Connect(registertree)
workspace.Trees.ChildRemoved:Connect(ejecttree)
--while true do 
--game:GetService("RunService").:Connect(function()
local timer=.0666
local playergui=game.Players.LocalPlayer.PlayerGui
while true do 
	local lastpos=camera.CFrame
	task.wait(timer)
	if playergui:FindFirstChild("Bars") and playergui.Bars:WaitForChild("Running") and playergui.Bars.Running.Value==false then
	local cameraViewportSize=camera.ViewportSize+Vector2.new(100,100)
	for i,v in candidates do 	
		if IsInView(v.obj,cameraViewportSize) then
		local leaf=v.func()
		if leaf.Parent==nil then
			table.remove(candidates,i)
		end
		end
	end
	end
end


I use this in a procedurally generated map of hundreds of trees streaming in and out, It works because the state of a locally manipulated object reverts back to its server state when streamed out.
It’s very scalable, although the example place does not include the local script yet. I included both links to give developers more options.

I optimized every variable from several iterations, while monitoring the differences in performance, to surprising effect. Such as, localizing every reused variable in the algorithm did not give it better performance. I performed similar tests when scaling it up as well.
I’m very keen on optimization because I wanted to use this without worrying about performance, I would suggest the local script called PlaceInReplicatedFirst. Let me know what you think of that! It’s designed to work with streaming enabled, but works without it.
Additionally, the trunk can be used with almost no difference in performance because it uses the calculations from the leaf (if any). This results in the unique effect of tree trunks swaying with less intensity but in the same direction as the leaves.

When I get the time I’ll be looking at it again, I plan to add plants in the future to the same effect.
The code is working super well for my application and scales with streaming enabled.

I just haven’t had any issue with performance. Most issues you would have with performance would be attributed to the polygon count which is why I included a mixture of imposter trees high poly and low poly trees.

To include a magnitude check for every tree would be not very efficient, in conclusion this is open sourced and the implementation is bare bones, I would improve it with using CollectionService and tagging tree models instead of putting them in a directory.

local CollectionService=game:GetService:("CollectionService")
for i,v in workspace.Trees:GetChildren() do
CollectionService:AddTag("Tree",treemodel)
end

Finally, I decided to use TweenService to animate the trees smoothly without executing a cframe change every frame. I’ll be looking into the bulkmoveto soon!

1 Like