Last active
April 13, 2022 21:17
-
-
Save LordNed/c16715c1b7d9a496aa19 to your computer and use it in GitHub Desktop.
A 2D grass that sways and springs back when you walk through it or land on it. Place onto a 2D quad.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
[RequireComponent(typeof(BoxCollider2D))] | |
public class ReactiveGrassSway : MonoBehaviour | |
{ | |
[SerializeField] private float m_bendForceOnExit = -0.1f; | |
[SerializeField] private bool m_windIsEnabled; | |
[SerializeField] private float m_baseWindForce = 0f; | |
[SerializeField] private float m_windPeriod = 0f; | |
[SerializeField] private float m_windOffset; | |
[SerializeField] private float m_windForceMultiplier = 0f; | |
[SerializeField] private float m_bendFactor =0.5f; | |
private bool m_isBending; | |
private bool m_isRebounding; | |
private float m_exitOffset; | |
private float m_enterOffset; | |
private float m_colliderHalfWidth; | |
[SerializeField] private Spring m_spring= new Spring(); | |
private Mesh m_meshCache; | |
private Transform m_transformCache; | |
private Collider2D m_colliderCache; | |
private void Awake() | |
{ | |
m_colliderCache = collider2D; | |
m_colliderCache.isTrigger = true; | |
m_colliderHalfWidth = collider2D.bounds.size.x / 2f; | |
m_transformCache = transform; | |
m_meshCache = GetComponent<MeshFilter>().mesh; | |
} | |
private void OnDestroy() | |
{ | |
Destroy(m_meshCache); | |
} | |
private void OnTriggerEnter2D(Collider2D col) | |
{ | |
CharacterMovement charMovement = col.GetComponent<CharacterMovement>(); | |
if(charMovement != null) | |
{ | |
m_enterOffset = col.transform.position.x - m_transformCache.position.x; | |
if (charMovement.Velocity.y < -3f) | |
{ | |
// Apply a force in the proper direction based on which half of the grass we landed on. | |
if (col.transform.position.x < m_transformCache.position.x) | |
m_spring.ApplyAdditiveForce(-m_bendForceOnExit); | |
else | |
m_spring.ApplyAdditiveForce(m_bendForceOnExit); | |
m_isRebounding = true; | |
} | |
} | |
} | |
private void OnTriggerStay2D(Collider2D col) | |
{ | |
if(col.GetComponent<Character>() != null) | |
{ | |
float offset = col.transform.position.x - m_transformCache.position.x; | |
if(m_isBending || Mathf.Sign(m_enterOffset) != Mathf.Sign(offset)) | |
{ | |
m_isRebounding =false; | |
m_isBending = true; | |
// Figure out how far we have moved into the trigger and then map the offset to a [-1, 1] range. | |
float radius = m_colliderHalfWidth + m_colliderCache.bounds.size.x * 0.5f; | |
m_exitOffset = Map(offset, -radius, radius, -1f, 1f); | |
// Apply a slow drag back to center | |
m_exitOffset *= 0.9f; | |
SetHorizontalOffset(m_exitOffset); | |
} | |
} | |
} | |
private void OnTriggerExit2D(Collider2D col) | |
{ | |
if (col.GetComponent<Character>() != null) | |
{ | |
if (m_isBending) | |
{ | |
// Apply a force in the opposite direction of the way that we're currently bending | |
m_spring.ApplyForceStartingAtPosition(m_bendForceOnExit * Mathf.Sign(m_exitOffset), m_exitOffset); | |
} | |
m_isBending = false; | |
m_isRebounding = true; | |
} | |
} | |
private void FixedUpdate() | |
{ | |
if (m_windIsEnabled && !m_isBending) | |
{ | |
var windForce = m_baseWindForce + Mathf.Pow(Mathf.Sin(Time.time * m_windPeriod + m_windOffset) * 0.7f + 0.05f, 4) * 0.05f * m_windForceMultiplier; | |
m_spring.ApplyAdditiveForce(windForce); | |
// Only simulate if we're not rebounding, as that overwritten below. | |
if (!m_isRebounding) | |
{ | |
SetHorizontalOffset(m_spring.Simulate()); | |
} | |
} | |
if (m_isRebounding) | |
{ | |
SetHorizontalOffset(m_spring.Simulate()); | |
// Apply spring forces until its acceleration dies off. | |
if (Mathf.Abs(m_spring.Velocity) < 0.000005f) | |
{ | |
// Reset the grass to zero and stop rebounding. | |
SetHorizontalOffset(0f); | |
m_isRebounding = false; | |
} | |
} | |
} | |
private void SetHorizontalOffset(float offset) | |
{ | |
//MaterialPropertyBlock dest = new MaterialPropertyBlock(); | |
//GetComponent<SpriteRenderer>().GetPropertyBlock(dest); | |
//dest.SetFloat("_FlashAmount", offset); | |
//GetComponent<SpriteRenderer>().SetPropertyBlock(dest); | |
var verts = m_meshCache.vertices; | |
verts[1].x = 0.5f + offset * m_bendFactor / m_transformCache.localScale.x; | |
verts[3].x = -0.5f + offset * m_bendFactor / m_transformCache.localScale.x; | |
m_meshCache.vertices = verts; | |
} | |
public static float Map(float value, float leftMin, float leftMax, float rightMin, float rightMax) | |
{ | |
return rightMin + (value - leftMin) * (rightMax - rightMin) / (leftMax - leftMin); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[System.Serializable] | |
public class Spring | |
{ | |
public float Dampening = 0.1f; | |
public float Tension = 0.05f; | |
public float BaseHeight; //get; private set;? | |
public float Velocity; // get; private set;? | |
public float CurrentHeight; // get; private set;? | |
public void ApplyAdditiveForce(float force) | |
{ | |
Velocity += force; | |
} | |
public void ApplyForceStartingAtPosition(float force, float position) | |
{ | |
CurrentHeight = position; | |
Velocity = force; | |
} | |
public float Simulate() | |
{ | |
float heightOffset = BaseHeight - CurrentHeight; | |
Velocity += Tension * heightOffset - Velocity * Dampening; | |
CurrentHeight += Velocity; | |
return CurrentHeight; | |
} | |
} |
" collider2D" used to be a property on MonoBehaviors that called the
correct GetComponent under the hood. It wasn't very performance friendly
(ie: you could use it a lot and not realize it was doing a GetComponent
lookup) so that's why this cached it. I think it pre-dates some of the
GetComponent type functions. So yes, I suspect it originally wanted a 2D
Box collider. Unfortunately I don't remember how this works - the gist
doesn't seem to have a date on it but the code is definitely 4+ years old!
Looking at the actual code, line #138, #139 look like they were directly
modifying the vertices of the quad, assuming that the top two verts were
[1] and [3] (so only the top of the grass sways). You'll also need to look
at line #67, which was checking if the influence was caused by a GameObject
with a Character component on it, can't remember if that's custom or not.
It looks like it only used the code to filter for characters (so props and
other things in the world didn't cause it), so you may want to revisit that
idea.
Understood, this information does help. Appreciate the response, @LordNed. Cheers.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hopefully you see this response to this old post. I see that this follows a very similar setup to the interactive foliage script from Prime 31. ( http://prime31.github.io/grass2d/). I'm trying to implement this to my own 2d grass sprites, but am having trouble getting it to work. May I ask what you mean by "Place onto a 2D quad."? I was just trying to use it on a sprite game object with a 2D box collider. Is this not correct?
Also, for code line 32 "m_colliderCache = collider2D;", what is this doing? Shouldn't it be m_colliderCache = GetComponent< Collider2D >();? using just collider2D returns an error for me. Thank you for your time.