Advertisement
Skizerzz

Unity Faux Physics Antenna

May 10th, 2024 (edited)
627
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 11.84 KB | Source Code | 0 0
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3.  
  4. public class FauxPhysicsAntenna : MonoBehaviour {
  5.     [Tooltip("The prefab to be used for each antenna segment. Make sure the Transform of this prefab is at the base of the segment, as " +
  6.         "they will spawn assuming the Transform of the object is the bottom of the segment. Childing your Mesh to an empty Transform is an easy " +
  7.         "way to accomplish this.")]
  8.     [SerializeField] private GameObject _antennaSegmentPrefab;
  9.     [Tooltip("OPTIONAL: In case you want something at the top of the Antenna.")]
  10.     [SerializeField] private GameObject _antennaTopperPrefab;
  11.     [Tooltip("The Transform that exists in this GameObject that you want the base of the Antenna to be at.")]
  12.     [SerializeField] private Transform _antennaBase;
  13.     [Tooltip("The number of AntennaSegmentPrefab's to spawn in order to create the Antenna. You can modify this range without causing any problems. " +
  14.         "However, whatever this number is dictates how many iterations occur per FixedUpdate to update the Antenna Transforms.")]
  15.     [Range(1, 20)]
  16.     [SerializeField] private int _segmentCount;
  17.     [Tooltip("The maximum amount of force applied when using ApplyForce each FixedUpdate to push the Antenna around. " +
  18.         "At an ApplyForce of 1, you will be applying this MaxForce amount.")]
  19.     [Range(0f, 200f)]
  20.     [SerializeField] private float _maxForce;
  21.     [Tooltip("The maximum amount of force applied when using ApplyImpulse to push the Antenna instantaneously. " +
  22.         "At an ApplyImpulse of 1, you will be applying this MaxImpulseForce amount.")]
  23.     [Range(0f, 200f)]
  24.     [SerializeField] private float _maxImpulseForce;
  25.     [Tooltip("The maximum number of degrees the Antenna should bend. Experiment with this to achieve desired look. Results are visible during runtime.")]
  26.     [Range(1, 90)]
  27.     [SerializeField] private float _maxDegrees;
  28.     [Tooltip("The stiffness of the Antenna. Experiment with this to achieve desired look. Results are visible during runtime.")]
  29.     [Range(0f, 800f)]
  30.     [SerializeField] private float _stiffness;
  31.     [Tooltip("The dampening of the Antenna. Lower dampening means the Antenna will take longer to return to stationary after movement. " +
  32.         "Experiment with this to achieve desired look. Results are visible during runtime.")]
  33.     [Range(0f, 50f)]
  34.     [SerializeField] private float _dampening;
  35.     [Tooltip("This is just a unique string to assign each individual AntennaSegmentPrefab so the internal Object Pool can retrieve the correct " +
  36.         "pooled GameObject. Leaving this blank means the Object Pool will not be used. It's recommended you put some string value here, as " +
  37.         "the Object Pool will significantly reduce Instantiation calls. (Simply put the same string " +
  38.         "here on all your objects that use the same AntennaSegmentPrefab, it's just a Dictionary internally, don't overthink it.)")]
  39.     [SerializeField] private string _segmentIdentifier;
  40.  
  41.     private Transform[] antennaTransforms;
  42.     private Transform topperTransform;
  43.     private Vector2 wobblePoint = new Vector2();
  44.     private Vector2 wobbleVelocity = new Vector2();
  45.     private float segmentLength = 0f;
  46.     private const string TOPPER_IDENTIFIER_SUFFIX = "Topper";
  47.     private string topperIdentifier;
  48.  
  49.     private static Dictionary<string, List<GameObject>> antennaPool = new Dictionary<string, List<GameObject>>();
  50.  
  51.     void Awake() {
  52.         if (_antennaBase == null || _antennaSegmentPrefab == null) return;
  53.  
  54.         if (string.IsNullOrEmpty(_segmentIdentifier) || string.IsNullOrWhiteSpace(_segmentIdentifier)) _segmentIdentifier = null;
  55.  
  56.         Renderer segmentRenderer = _antennaSegmentPrefab.GetComponentInChildren<Renderer>();
  57.         Transform segmentTransform = segmentRenderer.GetComponent<Transform>();
  58.         if(segmentRenderer == null || segmentTransform == null) {
  59.             Debug.Log("No Renderer and/or Transform in Antenna Segment prefab");
  60.             return;
  61.         }
  62.         Bounds segmentBounds = segmentRenderer.localBounds;
  63.         segmentLength = segmentBounds.size.y * segmentTransform.localScale.y;
  64.         antennaTransforms = new Transform[_segmentCount];
  65.  
  66.         for (int i = 0; i < antennaTransforms.Length; i++) {
  67.             GameObject segmentGO = RetrievePooledGameObject(_segmentIdentifier);
  68.             if (segmentGO == null) segmentGO = Instantiate(_antennaSegmentPrefab);
  69.             Vector3 segmentPosition = _antennaBase.position;
  70.             segmentPosition.y += i * segmentLength;
  71.             segmentGO.transform.position = segmentPosition;
  72.             antennaTransforms[i] = segmentGO.transform;
  73.             segmentGO.transform.SetParent(_antennaBase);
  74.         }
  75.  
  76.         if (_antennaTopperPrefab == null || _segmentIdentifier == null) return;
  77.  
  78.         topperIdentifier = _segmentIdentifier + TOPPER_IDENTIFIER_SUFFIX;
  79.         GameObject topperGO = RetrievePooledGameObject(topperIdentifier);
  80.         if (topperGO == null) topperGO = Instantiate(_antennaTopperPrefab);
  81.         topperTransform = topperGO.transform;
  82.         topperTransform.SetParent(_antennaBase);
  83.     }
  84.  
  85.  
  86.     void FixedUpdate() {
  87.         ApplyGravityAndDampening();
  88.         UpdateWobblePoint();
  89.         UpdateTransforms();
  90.     }
  91.  
  92.     public void OnDestroy() {
  93.         if (_segmentIdentifier == null) return;
  94.         for(int i = 0; i < antennaTransforms.Length; i++) {
  95.             Transform current = antennaTransforms[i];
  96.             current.SetParent(null);
  97.         }
  98.         PoolGameObjects(_segmentIdentifier, antennaTransforms);
  99.         if (topperTransform == null) return;
  100.         PoolGameObject(topperIdentifier, topperTransform);
  101.     }
  102.  
  103.     /// <summary>
  104.     /// This is one of two functions that will make the Antenna actually move.
  105.     /// Applies an instantaneous force to the Antenna. <paramref name="globalDirection"/> is the direction of the force and <paramref name="force"/>
  106.     /// is the amount of force (between 0f and 1f).
  107.     /// </summary>
  108.     /// <param name="globalDirection"></param>
  109.     /// <param name="force"></param>
  110.     public void ApplyImpulse(Vector3 globalDirection, float force) {
  111.         Vector2 localizedDirection = GlobalToLocalVector(globalDirection, _antennaBase.forward);
  112.         wobbleVelocity = ApplyImpulseToVelocity(wobbleVelocity, localizedDirection, force);
  113.     }
  114.  
  115.     /// <summary>
  116.     /// This is one of two functions that will make the Antenna actually move.
  117.     /// Applies a force over time to the Antenna. <paramref name="globalDirection"/> is the direction of the force and <paramref name="force"/>
  118.     /// is the amount of force (between 0f and 1f).
  119.     /// </summary>
  120.     /// <param name="globalDirection"></param>
  121.     /// <param name="force"></param>
  122.     public void ApplyForce(Vector3 globalDirection, float force) {
  123.         Vector2 localizedDirection = GlobalToLocalVector(globalDirection, _antennaBase.forward);
  124.         wobbleVelocity = ApplyForceToVelocity(wobbleVelocity, localizedDirection, force);
  125.     }
  126.  
  127.     private Vector2 ApplyImpulseToVelocity(Vector2 wobbleVelocity, Vector2 direction, float force) {
  128.         force = ClampForceAmount(force);
  129.         wobbleVelocity += direction.normalized * _maxImpulseForce * force;
  130.         return wobbleVelocity;
  131.     }
  132.  
  133.     private Vector2 ApplyForceToVelocity(Vector2 wobbleVelocity, Vector2 direction, float force) {
  134.         force = ClampForceAmount(force);
  135.         wobbleVelocity += direction.normalized * _maxForce * force * Time.fixedDeltaTime;
  136.         return wobbleVelocity;
  137.     }
  138.  
  139.     private Vector2 GlobalToLocalVector(Vector3 globalDirection, Vector3 globalFacingDirection) {
  140.         Vector2 globalFacingDirectionV2 = new Vector2(globalFacingDirection.x, globalFacingDirection.z);
  141.         float angleOffset = Vector2.SignedAngle(globalFacingDirectionV2, Vector2.up);
  142.         globalDirection.y = 0f;
  143.         globalDirection = Quaternion.AngleAxis(-angleOffset, Vector3.up) * globalDirection;
  144.         return new Vector2(globalDirection.x, globalDirection.z);
  145.     }
  146.  
  147.     private void ApplyGravityAndDampening() {
  148.         Vector2 xGravityDirection = new Vector2(-wobblePoint.x, 0f);
  149.         wobbleVelocity = ApplyForceAdvanced(wobbleVelocity, xGravityDirection, Mathf.Abs(wobblePoint.x), _stiffness);
  150.  
  151.         Vector2 yGravityDirection = new Vector2(0f, -wobblePoint.y);
  152.         wobbleVelocity = ApplyForceAdvanced(wobbleVelocity, yGravityDirection, Mathf.Abs(wobblePoint.y), _stiffness);
  153.  
  154.         Vector2 dampeningDirection = wobbleVelocity * -1;
  155.         wobbleVelocity = ApplyForceAdvanced(wobbleVelocity, dampeningDirection, 1f, wobbleVelocity.magnitude * _dampening);
  156.     }
  157.  
  158.     private void UpdateWobblePoint() {
  159.         wobblePoint += wobbleVelocity * Time.fixedDeltaTime;
  160.         wobblePoint = ClampWobblePoint(wobblePoint);
  161.     }
  162.  
  163.     private void UpdateTransforms() {
  164.         float degreeTotal = _maxDegrees * wobblePoint.magnitude;
  165.         float degreesPerSegment = degreeTotal / antennaTransforms.Length;
  166.         float currentSegmentDegrees = degreesPerSegment;
  167.         // rotate wobblePoint 90 degrees CCW
  168.         Vector3 rotationVector = new Vector3(-wobblePoint.y, 0f, wobblePoint.x);
  169.         Vector3 transformOffset = new Vector3();
  170.         for(int i = 0; i < antennaTransforms.Length; i++) {
  171.             Transform antennaTransform = antennaTransforms[i];
  172.             antennaTransform.localRotation = Quaternion.AngleAxis(currentSegmentDegrees, rotationVector);
  173.             antennaTransform.position = _antennaBase.position + transformOffset;
  174.             transformOffset += antennaTransform.up * segmentLength;
  175.             currentSegmentDegrees += degreesPerSegment;
  176.         }
  177.  
  178.         if (topperTransform == null) return;
  179.         topperTransform.localRotation = Quaternion.AngleAxis(currentSegmentDegrees, rotationVector);
  180.         topperTransform.position = _antennaBase.position + transformOffset;
  181.     }
  182.  
  183.     private Vector2 ApplyForceAdvanced(Vector2 wobbleVelocity, Vector2 direction, float force, float maxForce) {
  184.         wobbleVelocity += direction.normalized * maxForce * force * Time.fixedDeltaTime;
  185.         return wobbleVelocity;
  186.     }
  187.  
  188.     private Vector2 ClampWobblePoint(Vector2 wobblePoint) {
  189.         wobblePoint = Vector2.ClampMagnitude(wobblePoint, 1f);
  190.         return wobblePoint;
  191.     }
  192.  
  193.     private float ClampForceAmount(float forceAmount) {
  194.         return Mathf.Clamp(forceAmount, 0.0f, 1.0f);
  195.     }
  196.  
  197.     // POOL FUNCTIONS ========================================
  198.     private void PoolGameObject(string identifier, Transform goTransform) {
  199.         List<GameObject> pool;
  200.         if(!antennaPool.TryGetValue(identifier, out pool)) {
  201.             pool = new List<GameObject>();
  202.             antennaPool.Add(identifier, pool);
  203.         }
  204.  
  205.         GameObject gObject = goTransform.gameObject;
  206.         gObject.SetActive(false);
  207.         pool.Add(gObject);
  208.     }
  209.  
  210.     private void PoolGameObjects(string identifier, Transform[] transforms) {
  211.         List<GameObject> pool;
  212.         if (!antennaPool.TryGetValue(identifier, out pool)) {
  213.             pool = new List<GameObject>();
  214.             antennaPool.Add(identifier, pool);
  215.         }
  216.  
  217.         for (int i = 0; i < transforms.Length; i++) {
  218.             GameObject gObject = transforms[i].gameObject;
  219.             gObject.SetActive(false);
  220.             pool.Add(gObject);
  221.         }
  222.     }
  223.  
  224.     private GameObject RetrievePooledGameObject(string identifier) {
  225.         if (identifier == null) return null;
  226.         List<GameObject> pool;
  227.         if (!antennaPool.TryGetValue(identifier, out pool)) return null;
  228.         if (pool.Count < 1) return null;
  229.  
  230.         GameObject GO = null;
  231.         while(GO == null && pool.Count > 0) {
  232.             GO = pool[pool.Count - 1];
  233.             pool.RemoveAt(pool.Count - 1);
  234.         }
  235.  
  236.         if (GO == null) return null;
  237.  
  238.         GO.SetActive(true);
  239.         return GO;
  240.     }
  241.     // =======================================================
  242. }
Tags: C# Unity antenna
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement