Advertisement
Guest User

Unity SpeechBubble

a guest
Aug 14th, 2020
133
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.60 KB | None | 0 0
  1. [RequireComponent(
  2.         typeof(Collider2D),
  3.         typeof(Animator),
  4.         typeof(Canvas))]
  5.     public class SpeechBubble : MonoBehaviour, IPointerClickHandler
  6.     {
  7.         static List<SpeechBubble> activeSpeechBubbles;
  8.         const int collisionSolverMaxSteps = 100;
  9.         const int rotationSteps = 18;
  10.         const float alpha = 0.99f;
  11.         const float minDist = 0.01f;
  12.         const float maxForce = 30.0f;
  13.         const float G = 10.0f;
  14.  
  15.         float timeoutTime;
  16.         float currentTimeoutTime;
  17.         bool canTimeOut
  18.         {
  19.             get { return timeoutTime > 0.0f; }
  20.         }
  21.         Action onHide;
  22.         Action onClick;
  23.         Collider2D col2D;
  24.         Animator animator;
  25.         Vector3 destination;
  26.         TextMeshProUGUI textfield
  27.         {
  28.             get
  29.             {
  30.                 if (_textfield == null)
  31.                     _textfield = GetComponentInChildren<TextMeshProUGUI>();
  32.  
  33.                 return _textfield;
  34.             }
  35.         }
  36.         TextMeshProUGUI _textfield;
  37.  
  38.         string text
  39.         {
  40.             get
  41.             {
  42.                 return _text;
  43.             }
  44.             set
  45.             {
  46.                 _text = value;
  47.                 textfield.text = value;
  48.             }
  49.         }
  50.         string _text;
  51.  
  52.         public Transform target;
  53.  
  54.         float lastAlpha;
  55.         [SerializeField] float collisionSolverStepSize = 1.0f;
  56.         [SerializeField] protected Vector2 defaultOffsetToTargetObject = new Vector2(-5.0f, 2.5f);
  57.  
  58.         private void OnEnable()
  59.         {
  60.             if (activeSpeechBubbles == null)
  61.                 activeSpeechBubbles = new List<SpeechBubble>();
  62.             activeSpeechBubbles.Add(this);
  63.  
  64.             col2D = GetComponent<Collider2D>();
  65.             animator = GetComponent<Animator>();
  66.  
  67.             lastAlpha = 1.0f;
  68.             destination = transform.position;
  69.             SolveSpeechBubbleOverlaps(this);
  70.             animator.Play("Enter");
  71.         }
  72.  
  73.         private void OnDisable()
  74.         {
  75.             activeSpeechBubbles?.Remove(this);
  76.             onHide?.Invoke();
  77.         }
  78.  
  79.         private void Update()
  80.         {
  81.             transform.position = Vector3.Lerp(transform.position, destination, 0.05f);
  82.             transform.position = Vector3.MoveTowards(transform.position, destination, Time.deltaTime);
  83.         }
  84.  
  85.         public void OnPointerClick(PointerEventData eventData)
  86.         {
  87.             onClick?.Invoke();
  88.         }
  89.  
  90.         //paul pc: ~25 speech bubbles drop below 60 fps (for one frame when creating the speech bubble)
  91.         private static void SolveSpeechBubbleOverlaps(SpeechBubble ogBubble)
  92.         {
  93.             UIController.instance.StartCoroutine(SolveStuff());
  94.         }
  95.         static IEnumerator SolveStuff()
  96.         {
  97.             yield return null; //must wait for one frame so the scene graph updates with the instantiated collider!
  98.             Dictionary<SpeechBubble, Vector3> ogPositions = new Dictionary<SpeechBubble, Vector3>();
  99.             Physics2D.autoSyncTransforms = false;
  100.  
  101.             foreach (SpeechBubble speechBubble in activeSpeechBubbles)
  102.             {
  103.                 ogPositions.Add(speechBubble, speechBubble.transform.position);
  104.             }
  105.             for (int i = 0; i < collisionSolverMaxSteps; i++)
  106.             {
  107.                 foreach (SpeechBubble speechBubble in activeSpeechBubbles)
  108.                 {
  109.                     Vector3 force = ComputeForce(speechBubble);
  110.                     speechBubble.transform.Translate(force * speechBubble.lastAlpha);
  111.                     speechBubble.lastAlpha *= alpha;
  112.  
  113.                 }
  114.                 Physics2D.SyncTransforms();
  115.             }
  116.             Physics2D.autoSyncTransforms = true;
  117.  
  118.             foreach (SpeechBubble speechBubble in ogPositions.Keys)
  119.             {
  120.                 speechBubble.destination = speechBubble.transform.position;
  121.                 speechBubble.transform.position = ogPositions[speechBubble];
  122.             }
  123.         }
  124.         private static Vector3 ComputeForce(SpeechBubble speechBubble)
  125.         {
  126.  
  127.             Vector3 aggForce = Vector3.zero;
  128.             Bounds bounds = speechBubble.col2D.bounds;
  129.             int mask = ~LayerMask.GetMask("");
  130.  
  131.             ContactFilter2D cFilter = new ContactFilter2D();
  132.             cFilter.layerMask = mask;
  133.  
  134.             //Collider2D[] overlaps = Physics2D.OverlapBoxAll(bounds.center, bounds.size, 0f, mask);
  135.             List<Collider2D> overlaps = new List<Collider2D>();
  136.             Physics2D.OverlapCollider(speechBubble.col2D, cFilter, overlaps);
  137.             //each collider contributes to the force vector
  138.             foreach (Collider2D col in overlaps)
  139.             {
  140.                 if (col == null)
  141.                     continue;
  142.                 if (col == speechBubble.col2D)
  143.                     continue;
  144.                 Vector3 dir = speechBubble.transform.position - col.transform.position;
  145.                 float dist = dir.magnitude;
  146.                 dist = Mathf.Max(dist, minDist); //need epsilon to avoid dividing by 0
  147.                 if (dir == Vector3.zero)
  148.                     dir = UnityEngine.Random.insideUnitCircle;
  149.  
  150.                 float den = (col.bounds.extents.magnitude * bounds.extents.magnitude);
  151.                 float force = G * den / (dist * dist);
  152.  
  153.                 aggForce += dir.normalized * force;
  154.             }
  155.  
  156.             //the target where the speech bubble wants to be also contributes to the force vector
  157.             if (speechBubble.target != null)
  158.             {
  159.                 Vector3 dirToTargetPoint = speechBubble.target.transform.position - speechBubble.transform.position;
  160.                 float forceToTargetPoint = dirToTargetPoint.magnitude;
  161.                 aggForce += dirToTargetPoint.normalized * forceToTargetPoint;
  162.             }
  163.             aggForce /= overlaps.Count + 1;
  164.  
  165.             Vector3 output = aggForce;
  166.             Vector3 boundsOG = bounds.center;
  167.             for (int i = 0; i < rotationSteps; i++)
  168.             {
  169.                 output = Vector3.zero;
  170.  
  171.                 aggForce = Quaternion.AngleAxis(360.0f / rotationSteps * i, Vector3.back) * aggForce;
  172.                 RaycastHit2D[] hit = Physics2D.RaycastAll(
  173.                     speechBubble.transform.position,
  174.                     aggForce.normalized,
  175.                     aggForce.magnitude);
  176.                 if (hit.Length <= 0 ||
  177.                     hit.All(h => h.fraction == 0))
  178.                 {
  179.                     output = aggForce;
  180.                     break;
  181.                 }
  182.             }
  183.             return Vector3.ClampMagnitude(output, maxForce);
  184.         }
  185.  
  186.         public static SpeechBubble Show(
  187.             string text,
  188.             GameObject target,
  189.             Action onHide,
  190.             Action onClick = null,
  191.             float timeoutTime = 0.0f)
  192.         {
  193.             SpeechBubble inst = Instantiate(UIController.instance.speechBubble);
  194.             inst.col2D = inst.GetComponent<Collider2D>();
  195.             Physics2D.SyncTransforms();
  196.             inst.timeoutTime = 0.0f;
  197.             inst.currentTimeoutTime = 0.0f;
  198.             inst.onHide = onHide;
  199.             inst.onClick = onClick;
  200.             inst.text = text;
  201.             inst.GetComponent<Canvas>().worldCamera = UIController.instance.worldUICamera;
  202.            
  203.             if (target != null)
  204.             {
  205.                 inst.target = target.transform;
  206.                 inst.transform.position =
  207.                     target.transform.position +
  208.                     (Vector3)inst.defaultOffsetToTargetObject;
  209.             }
  210.  
  211.             return inst;
  212.         }        
  213.     }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement