Create a leaf with it's natural motion acting upon gravity?

Hello Devs,

I am trying to create a leaf where you drop it and it falls acting upon gravity. It has to be a real leaf or should I say I am trying to create something very similar to a simulator. I was thinking about a configurable joint across the leaf. But I am not sure though. As for as the physics is concerned, I was going to add a rigidbody and bring down the mass, angular drag to minimum depending upon the play testing. Plus I was also about to bring the overall Gravity to -2 from -9.81.

For the future I was planning upon adding
1.Fire
2.Wind
3.Water
to give more realism. If a part of the leaf gets a hit by a branch then it should detach itself from it’s parent.

Hassle :
My question is, as being a lone dev I would like to know how should I approach this problem. I do not have a good leaf model either. I would also prefer to buy some assets on the asset store (last option), if I have to.

Kindly help me as of how to approach.

regards,

Karsnen.

“or should I say I am trying to create something very similar to a simulator”

I love that sentence!

  1. do not, ever, change gravity. Never.

  2. make absolutely sure your leaf has real-world sizes and real-world masses. you’ll need an accurate scale to go out and weigh some leaves

  3. it is all about air flow. you are going to have to model, in some way, airflow. likely you can simply add an upwards force - probably randomized every about 1.7 seconds I’d say - on a script on the object

  4. you mention hinges, are you implying you want the leaf to bend in the middle, ie the leaf has two parts? i would not do this, just leave it as one objet

  5. it is a pain in the arse to make rocking movements, like a boat on water. or like, err, a leaf in air. one very simple approach is make a little thingy that applies force at each of the two ends (ie, as in point 3) and also do “something” in the middle such as: add an invisible rod a couple inches long that hangs down, and stick a small weight on the end. It will sort of balance it and rock it.

Now you’re simulating - you’re a real game programmer! Enjoy!

Don’t forget to WORK TIDILY. It is a hallmark of good simulation programming that you always for example carefully calculate your own mass (very likely some bastard has changed the mass of some component while tweaking it), for example attach this to everything antigravityFixer.js

#pragma strict
function Awake()
	{
	GetComponent(ConstantForce).force =
	     Physics.gravity * giveTotalMass() * -1.0;
	}
function giveTotalMass():float
	{
	// all rigidbodies here and below, sum the mass!!!
	var tm:float = 0.0;
	for (var rb : Rigidbody in
	         transform.parent.GetComponentsInChildren(Rigidbody))
		tm += rb.mass;
	return tm;
	}

and then you might see code like this kicking around…

function Awake()
	{
	if ( GetComponent(antigravityFixer) )
		massOfObject = GetComponent(antigravityFixer).giveTotalMass();
	getJoystickForceHere.localPosition = Vector3.zero; // just in case
	}
function FixedUpdate()
	{
	pushMeBaby = getJoystickForceHere.localPosition * 1.5;
	rigidbody.AddForce( pushMeBaby * massOfObject );
	blah blah...
	}

Hope it helps in some way!

Really the best solution here is, someone probably has a really good “leaf rig” already that they don’t mind giving away.

Personally if I was doing this… You see in point 5 it describes “two” places with random air-blasts upwards. I’d make a little sled that randomizes the position and number of those “air blast areas” and then have a little trivial evolutionary algorithm that runs for a day looking for some simple metrics, like, “does not flip over often” or “doesn’t just boringly never move” or “slowly heads downwards overall”.

That being said, you can very likely get a great result just by guessing two good spots for “little air blasts”

Again - there’s surely someone who already has a decent leaf-rig they don’t mind giving away, making all this chat useless.

Also … it’s worth noting that an unbelievably simple way to do this is:

#MAKE AN ANIMATION…

just make a long animation of a leaf with a nice natural jiggle on it (get the famous scene with the ticket from the movie polar express for reference). Just randomly play sections of the animations as it falls!

Total work time, 10 minutes.

Worth remembering there is very often “an incredibly simpler solution that is better anyway” :slight_smile:

I realize this post is quite old, but I recently needed to create my own script for a non-particle falling sheet of paper and @fattie’s answer helped me a lot. In addition to the ‘puff’ idea, I also added a slide force based on the slope of the paper that worked out quite well for my purposes.

Here’s my script in case it helps anyone else.

Note: This script assumes the paper is thinnest on the Y axis (as if the default orientation is laying flat on a table).

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

[RequireComponent(typeof(BoxCollider), typeof(Rigidbody), typeof(ConstantForce))]
public class FeatherFall : MonoBehaviour {
	[SerializeField, Range(0.01f, 1f)] private float m_FloatForce = 0.8f;
	[SerializeField, Range(0.01f, 1f)] private float m_SlidePower = 0.2f;
	[SerializeField, Range(0.01f, 1f)] private float m_PuffPower = 0.05f;
	[SerializeField, Range(0.01f, 1f)] private float m_PuffDelayMin = 0.2f;
	[SerializeField, Range(0.01f, 1f)] private float m_PuffDelayMax = 0.3f;

	private Rigidbody m_Rigidbody;
	private BoxCollider m_Collider;
	private Vector3 m_AntigravityForce;
	private float m_LastTime;
	private float m_Delay;
	private Vector3[] m_EdgePoints;
	private Vector3 m_SlideVector;
	private Vector3 m_LastPuffPosition;
	private Vector3 m_LastPuffPower;

	private void Start() {
		m_Rigidbody = GetComponent<Rigidbody>();
		m_Collider = GetComponent<BoxCollider>();
		m_AntigravityForce = GetAntigravityForce();
		GetComponent<ConstantForce>().force = m_AntigravityForce*m_FloatForce;

		Vector3 center = m_Collider.center;
		Vector3 size = m_Collider.size/2;

		m_EdgePoints = new[] {
			new Vector3(      0, 0, -size.z) + center,  //bottom
			new Vector3(      0, 0,  size.z) + center,  //top
			new Vector3(-size.x, 0,       0) + center,  //left
			new Vector3( size.x, 0,       0) + center,  //right
			new Vector3(-size.x, 0, -size.z) + center,  //bottom left
			new Vector3( size.x, 0, -size.z) + center,  //bottom right
			new Vector3(-size.x, 0,  size.z) + center,  //top left
			new Vector3( size.x, 0,  size.z) + center   //top right
		};
	}

	private void Update() {
		UpdateSlide();
		UpdatePuffs();
	}

	private void UpdateSlide() {
		Vector3 normal = transform.up;
		m_SlideVector.x = normal.x*normal.y;
		m_SlideVector.z = normal.z*normal.y;
		m_SlideVector.y = -(normal.x*normal.x) - normal.z*normal.z;
		m_Rigidbody.AddForce(m_SlideVector.normalized*m_SlidePower);
	}

	private void UpdatePuffs() {
		if (m_LastTime + m_Delay < Time.time) {
			Puff();
		}
	}

	private void Puff() {
		float downwardVelocity = -m_Rigidbody.velocity.y;
		if (downwardVelocity > 0.001f) {
			m_LastTime = Time.time;
			m_Delay = Random.Range(m_PuffDelayMin, m_PuffDelayMax);
			Vector3 puffPosition = GetPuffPosition();
			m_LastPuffPower = m_AntigravityForce*m_PuffPower*downwardVelocity;
			m_LastPuffPosition = transform.InverseTransformPoint(puffPosition);
			m_Rigidbody.AddForceAtPosition(m_LastPuffPower, puffPosition, ForceMode.Impulse);
		}
	}

	private Vector3 GetPuffPosition() {
		Vector3 worldOffset = m_Collider.bounds.center;
		List<Vector3> worldEdges = m_EdgePoints.Select<Vector3, Vector3>(transform.TransformPoint).ToList();
		List<Vector3> validEdges = worldEdges.Where(v => v.y <= worldOffset.y).ToList();

		if (validEdges.Count == 0) {
			validEdges = worldEdges;
		}

		int edgeIndex = Random.Range(0, validEdges.Count - 1);
		return validEdges[edgeIndex];
	}

	private Vector3 GetAntigravityForce() {
		float totalMass = transform.GetComponentsInChildren<Rigidbody>().Sum(rb => rb.mass);
		return Physics.gravity*totalMass*-1f;
	}

	private void OnDrawGizmos() {
		if (m_EdgePoints == null) {
			return;
		}

		// Slide Vector
		Debug.DrawRay(transform.position, m_SlideVector, Color.blue);

		// Last Puff Vector
		Vector3 puffPosition = transform.TransformPoint(m_LastPuffPosition);
		Color puffColor = Color.white;
		float timeSinceLast = Time.time - m_LastTime;
		puffColor.a = 1 - timeSinceLast/m_Delay;
		Debug.DrawRay(puffPosition, m_LastPuffPower*10, puffColor);

		foreach (Vector3 edgePoint in m_EdgePoints) {
			Vector3 edgePosition = transform.TransformPoint(edgePoint);
			Gizmos.color = Color.white;
			Gizmos.DrawSphere(edgePosition, 0.01f);
		}
	}
}