Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Moving objects away from a position in 8 directions.

Discussion in 'Scripting' started by peepo12343, Jan 29, 2022.

  1. peepo12343

    peepo12343

    Joined:
    Jul 11, 2021
    Posts:
    12
    Hello, I have a circle and random shapes are generated in it. Im trying to make these shapes move outward in 8 directions until they don't overlap but still snap to the tilemap grid. What is the easiest way to do this?
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    The easiest way to do this is an iterative process.

    First you need to have a way to check for overlaps. I'm going to assume you already have one. If you have only a several shapes, you can use the easiest, most dumb algorithm and check each shape with every other.

    Then you need a function to snap a value to some grid steps. Imagine if ruler had grid marks on it and if a point snapped to the nearest one. If you can do this with a 1D value, then you can do this for any number of dimensions, 2D or 3D.

    In your case, you mention tilemap, so it's probably 2D. A grid can be defined by scale and optionally by its offset from the origin. Scale: (1, 1), the values should snap on whole units, (2, 2) double units, while (0, 0) is an invalid grid, you can also do anything else i.e. (1.5, 3).

    If you want consistently to snap position so that it lies between two whole units, for example 1.5, 2.5, 3.5, you want to introduce an offset (0.5, 0.5) for example.

    Code (csharp):
    1. float snap(float v, float scale, float offset)
    2.   => Mathf.Round((v - offset) / scale) * scale + offset;
    3.  
    4. Vector2 snapPos(Vector2 pos, Vector2 scale, Vector2 offset)
    5.   => new Vector2(snap(pos.x, scale.x, offset.x), snap(pos.y, scale.y, offset.y));
    Finally you can do the whole thing (this is what you asked for written as code)
    Code (csharp):
    1. var nudgeCenter = Vector2.zero;
    2. var nudgeAmount = 0.5f;
    3. var gridScale = Vector2.one;
    4. var gridOffset = Vector2.zero;
    5.  
    6. while(hasOverlapsWhileSnapped(myShapes, gridScale, gridOffset)) {
    7.   nudgeShapesInAllDirections(myShapes, nudgeCenter, nudgeAmount);
    8. }
    9. snapShapes(myShapes, gridScale, gridOffset);
    To prevent an issue where you snap the shapes back after every nudge, which might result in shapes not moving, and thus getting stuck, we won't snap them really until the very end, but we'll treat them snapped when checking for overlaps.

    I don't know what your objects are, but lets pretend they're gameobjects. Now you can write snapShape and getSnappedShapePos.
    Code (csharp):
    1. Vector2 getSnappedShapePos(GameObject shape, Vector2 gridScale, Vector2 gridOffset)
    2.   => snapPos(shape.transform.position, gridScale, gridOffset); // position will implicitly drop Z component
    3.  
    4. void snapShape(GameObject shape, Vector2 gridScale, Vector2 gridOffset) {
    5.   shape.transform.position = getSnappedShapePos(shape, gridScale, gridOffset);
    6. }
    7.  
    8. void snapShapes(GameObject[] shapes, Vector2 gridScale, Vector2 gridOffset) {
    9.   for(int i = 0; i < shapes.Length; i++) snapShape(shapes[i], gridScale, gridOffset);
    10. }
    Code (csharp):
    1. bool hasOverlap(GameObject shape1, GameObject shape2) {
    2.   // this one should be tackled appropriately
    3.   // if you don't know how, we can talk about it afterwards
    4. }
    5.  
    6. // the point of this is to replace the positions with the snapped ones,
    7. // test for overlap, then return everything where it was originally.
    8. // there are numerous ways to achieve this more elegantly
    9. // but here I'm fixating on the idea that you have to rely on the method above
    10. // which works only with the actual shapes in place
    11. bool hasOverlapsWhileSnapped(GameObject shape1, GameObject shape2, Vector2 gridScale, Vector2 gridOffset) {
    12.   var oldPos1 = shape1.transform.position;
    13.   var oldPos2 = shape2.transform.position;
    14.   snapShape(shape1, gridScale, gridOffset);
    15.   snapShape(shape2, gridScale, gridOffset);
    16.   var result = hasOverlap(shape1, shape2);
    17.   shape1.transform.position = oldPos1;
    18.   shape2.transform.position = oldPos2;
    19.   return result;
    20. }
    21.  
    22. // the simplest algorithm there is, works fine for simple set ups and not too many shapes
    23. // we check every shape with every other shape, but we make sure not to test the same pairs twice
    24. // it ends up having N(N+1)/2 iterations, which is of O(N^2) time complexity
    25. // again, for simple set ups, who cares right?
    26. // but if you want to do this for dozens, hundreds, or thousands of objects, then your original
    27. // question is a serious problem even for programming veterans and needs to be approached
    28. // differently
    29. bool hasOverlapsWhileSnapped(GameObject[] shapes, Vector2 gridScale, Vector2 gridOffset) {
    30.   if(shapes.Length < 2) return false; // no overlaps by definition
    31.   for(int a = 0; a < shapes.Length - 1; a++) {
    32.     for(int b = a + 1; b < shapes.Length; b++) {
    33.       if(hasOverlapWhileSnapped(shapes[a], shapes[b], gridScale, gridOffset)) return true;
    34.     }
    35.   }
    36.   return false;
    37. }
    To properly nudge shapes in all directions around a common center, you want to cast rays from this point. These rays should pass through the shapes' coordinates, then you turn them into vectors with a set magnitude. Finally, you move individual shapes by that much.

    Code (csharp):
    1. void nudgeShapesInAllDirections(GameObject[] shapes, Vector2 nudgeCenter, float nudgeAmount) {
    2.   for(int i = 0; i < shapes.Length; i++) {
    3.     // first, we get the original positional vector, going from nudgeCenter
    4.     var r = (Vector2)shapes[i].transform.position - nudgeCenter;
    5.  
    6.     // then, we multiply our set magnitude with a unit vector of this ray
    7.     r = nudgeAmount * r.normalized;
    8.  
    9.     // we can now apply this offset, shapes will be pushed away from the center
    10.     // by set amount, no need for angles or trigonometry
    11.     shapes[i].transform.position += r;
    12.   }
    13. }
    Please note that I'm writing these methods with a small caps, because that's how I personally write
    private
    methods, because I like them easily distinguishable. Recommended C# convention is to always capitalize the first letter FYI.

    Edit:
    Instead of passing GameObjects or Sprites or whatever, you can also pass Transform around, which is much more appropriate.

    You can also make gridScale and gridOffset be class members, so that you don't have to aggregate them into every function, this will make your code more readable.

    The exact way you can do your overlap test really depends on how accurate you want it to be. If you're testing only for 2D bounds, this can be written differently, but here I'm expecting that you're maybe relying on Unity events to trigger on a collision or something. Still, you have to adapt this code to make use of that.

    Oh, and the way I'm supplying an array of GameObjects is really just an example. Feel free to use any other collection as you see fit. But for these examples it has to be something that's indexed.
     
    Last edited: Jan 29, 2022
    peepo12343 likes this.
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    If you're heavily relying on Unity collision tests to tell you if there is an overlap or not, then your job is to store this state somewhere, and iteratively nudge the shapes away until this state goes "green". It is a very similar solution in essence, but logistically very different because you don't have direct control over the test itself.
     
    Last edited: Jan 29, 2022
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Btw if you really want to work with the 8 directions as mentioned, you have to encode your directions somehow.
    For this you can use
    enum
    .
    Code (csharp):
    1. public enum MyDirection {
    2.   E,
    3.   NE,
    4.   N,
    5.   NW,
    6.   W,
    7.   SW,
    8.   S,
    9.   SE
    10. }
    These enum constants will go from 0 to 7, and are intentionally aligned to mathematical convention for angles going from 0 to 360 (or 0 to 2Pi). So you can easily convert back and forth.

    Now you can make some extensions to work with this
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public static MyDirectionExtensions {
    4.  
    5.   // this will get you a proper vector from a named direction
    6.   public static Vector2 ToVector2(this MyDirection dir) {
    7.     switch((int)dir) {
    8.       case 0: return Vector2.right;
    9.       case 1: return Vector2.right + Vector2.up;
    10.       case 2: return Vector2.up;
    11.       case 3: return Vector2.left + Vector2.up;
    12.       case 4: return Vector2.left;
    13.       case 5: return Vector2.left + Vector2.down;
    14.       case 6: return Vector2.down;
    15.       case 7: return Vector2.right + Vector2.down;
    16.       default: throw new System.ArgumentException();
    17.     }
    18.   }
    19.  
    20.   // this will get you a direction from the given angle in degrees
    21.   public static MyDirection FromAngleDegrees(float angleDegrees) {
    22.     // first we unwind the angle back to 0..360 interval
    23.     angleDegrees %= 360f;
    24.     if(angleDegrees < 0f) angleDegrees += 360f;
    25.  
    26.     // this should be enough
    27.     var index = Mathf.RoundToInt(angleDegrees / 45f) % 8;
    28.     return (MyDirection)index;
    29.   }
    30.  
    31.   // same thing in radians
    32.   public static MyDirection FromAngle(float angle) {
    33.     const float SECTOR = Mathf.Pi / 4f;
    34.     const float TAU = 8f * SECTOR;
    35.     if((angle %= TAU) < 0f) angle += TAU;
    36.     return (MyDirection)(Mathf.RoundToInt(angle / SECTOR) % 8);
    37.   }
    38.  
    39.   // alternatively, FromAngleDegrees is now just
    40.   // FromAngle(angleDegrees * Mathf.Deg2Rad);
    41.  
    42.   // now when you want to get a direction from a 2D vector,
    43.   // all you need to do is to find the angle of that vector
    44.   public static MyDirection ToDirection(this Vector2 vec)
    45.     => FromAngle(Mathf.Atan2(vec.y, vec.x));
    46.  
    47. }

    Finally, you can tweak the nudge function to worry about these fixed directions instead
    Code (csharp):
    1. void nudgeShapesInAllDirections(GameObject[] shapes, Vector2 nudgeCenter, float nudgeAmount) {
    2.   for(int i = 0; i < shapes.Length; i++) {
    3.     // first, we get the original positional vector, going from nudgeCenter
    4.     var r = (Vector2)shapes[i].transform.position - nudgeCenter;
    5.  
    6.     // then we find which direction this is
    7.     var dir = r.ToDirection();
    8.  
    9.     // then, we multiply our set magnitude with a normalized vector representation of this direction
    10.     r = nudgeAmount * dir.ToVector2().normalized;
    11.  
    12.     // we can now apply this offset, shapes will be pushed away from the center by set amount
    13.     shapes[i].transform.position += r;
    14.   }
    15. }
     
    Last edited: Jan 29, 2022
    cerestorm and peepo12343 like this.
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Don't be afraid to ask if something's not working as intended. I do these things from my head (and I can see I've maybe omitted some keywords here and there; for example,
    MyDirectionExtensions
    should be declared with
    public static class
    etc.).

    Edit:
    Also, to simplify somewhat, all of this assumes you are working on XY plane. If you're working on XZ, some changes have to be made, because there are several implicit conversions between Vector2 and Vector3 and vice versa.
     
    Last edited: Jan 30, 2022
  6. peepo12343

    peepo12343

    Joined:
    Jul 11, 2021
    Posts:
    12
    Im having an issue with intersecting objects.
    Code (CSharp):
    1.     private bool Intersect(Data object1, Data objectIntersecting) {
    2.         Bounds bounds = objectIntersecting.GetComponent<Collider2D>().bounds;
    3.         bool overlapping = object1.GetComponent<Collider2D>().bounds.Intersects(bounds);
    4.         return overlapping;
    5.     }
    moving objects in directions:
    Code (CSharp):
    1. private void moveShapesInMultipleDirections() {
    2.         foreach(Data object in List) {
    3.             foreach(Data _room in List) {
    4.                 if(Intersect(object, _object)) {
    5.                     int amount = 1;
    6.                     Vector2 direction = (Vector2)_object.transform.position - circleCenter;
    7.                     direction = amount * direction.normalized;
    8.                     Vector3 Position = direction;
    9.                     _object.transform.position += Position;
    10.                 }
    11.             }
    12.         }
    13.     }
    if i make
    Intersect(object, _object)
    to
    !Intersect(object, _object)
    in the if statement then the position gets increased by a lot more. Also my intersect function doesn't work and everything just moves instead of objects that are overlapping.
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Okay.

    Well, for starters, what are your
    Data
    things? Are you sure
    Intersect
    method does what it's supposed to? Also what is the inner
    foreach
    in the 2nd box, you never seem to use
    _room
    . You shouldn't probably use
    object
    keyword because it's reserved by C# for a data type alias of the same name. I don't know where
    _object
    comes from and what its type is.

    When you're multiplying
    amount * direction.normalized
    that's a bit weird, because Unity doesn't have this (int, Vector2) * operator. I'm guessing C# is upcasting
    int
    to
    float
    , but it's a bit weird.
    amount
    should really be
    float
    or you should at least do
    (float)amount * direction.normalized
    (it is always always better to be safe than sorry, these are hard mistakes to notice and fix, don't just assume auto-conversions).

    I also can't tell what things are collected in
    List
    , except they're
    Data
    , I can't tell in which order or how they relate to each other. I see that both
    object
    and
    _room
    live there, in that same collection, which is odd.
    List
    is also a word that's specifically used for a certain data structure, and I would recommend you to rename a lot of things, be much more specific with the names, so that when you or someone else reads it, it gives away information, instead of withholding it.

    You know, in these 3 lines alone,
    Code (csharp):
    1. foreach(Data object in List) {
    2.   foreach(Data _room in List) {
    3.     if(Intersect(object, _object)) {
    you violate basically every C# convention there is, and I have a hard time figuring what it's supposed to do. Yeah, of course, I can tell from your previous context and my own code, but still at this rate, maybe you have unintentionally mixed up some things somewhere else?
     
    Last edited: Jan 30, 2022
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    All in all, your question makes it so that you're absolutely sure that you're having an issue with intersecting. However, I'm not sure that's the case. The real issue is all over the place in tiny amounts. With programming, you need only a couple of these to align just right to throw your expectations wildly off the mark. Never assume, but test your intention.

    If you want to make sure that
    Intersect
    works as intended, do just that and output the result. Make a controlled environment, test just two objects/sprites whatever, isolate this scenario, and make sure it works as intended. Only then you can escalate into something greater than that. That's the only thing I can recommend at this point, anyway.
     
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Finally, observe that I've split the concerns very intentionally. I don't do intersect test inside of nudge method. This is really important. I think this is the key fumble in your code.

    The actual algorithm was this, and I made sure to take your needs into consideration.
    Code (csharp):
    1. while(hasOverlapsWhileSnapped(myShapes, gridScale, gridOffset)) {
    2.   nudgeShapesInAllDirections(myShapes, nudgeCenter, nudgeAmount);
    3. }
    4.  
    5. snapShapes(myShapes, gridScale, gridOffset);
    You're not supposed to change something as fundamental as this, because it definitely won't work. Now I'm not saying this is 100% the issue you're having, but I can see it on a horizon.
     
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    In plain words, the logic goes like this, "do nudging of every shape, in small steps, as long as any two shapes are overlapping".

    Now if you want instead to move shapes separately until they cease overlapping, we can make that too, but it's a slightly different algorithm, which might end up giving you ugly or slow solutions (at least compared to the one where they explode in all directions).

    This should look like this (pseudo-code)
    Code (csharp):
    1. foreach shape in list {
    2.   while(snapped shape overlaps) { nudge shape }
    3.   snap shape
    4. }
    You can immediately see that you haven't followed the iterative logic that requires a
    while
    loop.

    Btw, be careful with this or you will freeze Unity.
    When coding
    while
    loops, at least until you're satisfied with the result, please make a safety counter or a logical mistake will get you stuck inside a loop (happens to all of us all the time).
    Code (csharp):
    1. int safety = 0;
    2. while(some condition) {
    3.   // regular code goes here
    4.   if(++safety > 500) break;
    5. }
     
    Last edited: Jan 30, 2022
  11. peepo12343

    peepo12343

    Joined:
    Jul 11, 2021
    Posts:
    12
    Im pretty sure it is my intersect function as i recoded my moveShapesInMultipleDirections() and was able to crash unity.
    Code (CSharp):
    1.     private void moveShapesInMultipleDirections() {
    2.         while(hasOverlaps(ShapeList)) {
    3.             for(int i = 0; i < ShapeList.Count; i++) {
    4.                 int IntersectingShapeNumber = i == ShapeList.Count ? 0 : i;
    5.                 if(rIntersect(ShapeList[i], ShapeList[IntersectingShapeNumber])) {
    6.                     float amount = 1;
    7.                     Vector2 direction = (Vector2)ShapeList[i].transform.position - circleCenter;
    8.                     direction = amount * direction.normalized;
    9.                     Vector3 Position = direction;
    10.                     ShapeList[i].transform.position += Position;
    11.                 }
    12.             }
    13.         }
    14.     }
    I recoded my overlaps function to see if that would change it but it didn't.
    Code (CSharp):
    1. private bool hasOverlaps(List<Data> shapes) {
    2.         bool isOverlapping = false;
    3.         foreach(Data shape in shapes) {
    4.             foreach(Data _shape in shapes) {
    5.                 Bounds bounds = _shape.GetComponent<Collider2D>().bounds;
    6.                 if(shape.GetComponent<Collider2D>().bounds.Intersects(bounds)) {
    7.                     isOverlapping = true;
    8.                 }
    9.             }
    10.         }
    11.         return isOverlapping;
    12.     }
    I know i shouldn't be using two foreach loops but im looping through the first time to get one shape and then checking for the rest to see if they intersect that shape. Is there a better way and why isn't this function working?
     
    Last edited: Jan 31, 2022
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Can you share the full code so I have more to work with?
     
  13. peepo12343

    peepo12343

    Joined:
    Jul 11, 2021
    Posts:
    12
    Generator script:

    Code (CSharp):
    1. [SerializeField] private int numberOfShapes = 50;
    2. [SerializeField] private int startCircleRadius = 10;
    3. [SerializeField] private int ShapeSizeMean = 5;
    4. [SerializeField] private int ShapeSizeStandardDeviation = 2;
    5. [SerializeField] private GameObject shapeObject;
    6. [SerializeField] private int ShapeMinSize = 1;
    7. [SerializeField] private int minShapeSizeRatio;
    8. private List<Data> shapeList;
    9. Vector2 circleCenter = Vector2.zero;
    10.  
    11. private void Start() {
    12.     shapeList = new List<Data>();
    13. }
    14.  
    15. private void GenerateNewShapes() {
    16.     int newWidth;
    17.     int newHeight;
    18.     for(int i = 0; i < numberOfShapes; i++) {
    19.         newWidth = Mathf.RoundToInt(ShapeSizeMean + NextGaussianDouble() * ShapeSizeStandardDeviation);
    20.         newHeight = Mathf.RoundToInt(ShapeSizeMean + NextGaussianDouble() * ShapeSizeStandardDeviation);
    21.  
    22.         if(newHeight == 0) { //fixes dividing by zero issue;
    23.             newHeight += 1;
    24.         }
    25.  
    26.         newWidth = newWidth < ShapeMinSize ? ShapeMinSize : newWidth;
    27.         newHeight = newWidth < ShapeMinSize ? ShapeMinSize : newHeight;
    28.         if(newWidth / newHeight > minShapeSizeRatio) {
    29.             newHeight = Mathf.RoundToInt(newWidth / minShapeSizeRatio);
    30.         }
    31.         if(newHeight / newWidth > minShapeSizeRatio) {
    32.             newWidth = Mathf.RoundToInt(newHeight / minShapeSizeRatio);
    33.         }
    34.         Vector2 randomOffset = new Vector2(Random.Range(0, 2), Random.Range(0,2));
    35.         Vector3 shapePosition = getRandomPointInCircle(startCircleRadius, circleCenter) + randomOffset;
    36.         Data shape = Instantiate(shapeObject, shapePosition, Quaternion.identity).GetComponent<Data>();
    37.         shape.SetSize(newWidth, newHeight);
    38.         shape.id = i;
    39.         shape.name = Convert.ToString(i);
    40.         shapeList.Add(shape);
    41.     }
    42. }
    43.  
    44. private void moveShapesInMultipleDirections() {
    45.     while(hasOverlaps(ShapeList)) {
    46.         for(int i = 0; i < ShapeList.Count; i++) {
    47.             int IntersectingShapeNumber = i == ShapeList.Count ? 0 : i;
    48.             if(Intersect(ShapeList[i], ShapeList[IntersectingShapeNumber])) {
    49.                 float amount = 1;
    50.                 Vector2 direction = (Vector2)ShapeList[i].transform.position - circleCenter;
    51.                 direction = amount * direction.normalized;
    52.                 Vector3 Position = direction;
    53.                 ShapeList[i].transform.position += Position;
    54.             }
    55.         }
    56.     }
    57. }
    58.  
    59. private bool hasOverlaps(List<Data> shapes) {
    60.     bool isOverlapping = false;
    61.     foreach(Data shape in shapes) {
    62.         foreach(Data _shape in shapes) {
    63.             Bounds bounds = _shape.GetComponent<Collider2D>().bounds;
    64.             if(shape.GetComponent<Collider2D>().bounds.Intersects(bounds)) {
    65.                 isOverlapping = true;
    66.             }
    67.         }
    68.     }
    69.     return isOverlapping;
    70. }
    71.  
    72. private bool Intersect(Data shape, Data ShapeIntersecting) {
    73.     Bounds bounds = ShapeIntersecting.GetComponent<Collider2D>().bounds;
    74.     bool overlapping = shape.GetComponent<Collider2D>().bounds.Intersects(bounds);
    75.     return overlapping;
    76. }
    77.  
    78. private float NextGaussianDouble() {
    79.     float u, v, S;
    80.     do
    81.     {
    82.         u = 2.0f * Random.value - 1.0f;
    83.         v = 2.0f * Random.value - 1.0f;
    84.         S = u * u + v * v;
    85.     }
    86.     while (S >= 1.0);
    87.    
    88.     float fac = Mathf.Sqrt(-2.0f * Mathf.Log(S) / S);
    89.     return u * fac;
    90. }

    Data script:
    Code (CSharp):
    1. public class Data : MonoBehaviour
    2. {
    3.     public float defaultScale;
    4.     public int id;
    5.     public int width;
    6.     public int height;
    7.     private BoxCollider2D boxCollider2D;
    8.     private SpriteRenderer spriteRenderer;
    9.  
    10.     private void Awake()
    11.     {
    12.         spriteRenderer = GetComponent<SpriteRenderer>();
    13.         boxCollider2D = GetComponent<BoxCollider2D>();
    14.     }
    15.     public void SetSize(int _width, int _height) {
    16.         width = _width;
    17.         height = _height;
    18.         SetRoomSize();
    19.     }
    20.     private void SetShapeSize() {
    21.         transform.localScale = new Vector3(defaultScale * width, defaultScale * height, 0f);
    22.     }
    23. }
     
  14. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Thanks, but this is not the full code because you never call
    moveShapesInMultipleDirections
    .

    Preliminary, you have all kinds of small mistakes.

    For example
    Code (csharp):
    1. newHeight = newWidth < ShapeMinSize ? ShapeMinSize : newHeight;
    You probably meant
    newHeight < ShapeMinSize


    Then in
    moveShapesInMultipleDirections

    This line
    Code (csharp):
    1. int IntersectingShapeNumber = i == ShapeList.Count ? 0 : i;
    is redundant because
    i
    will never be equal to
    ShapeList.Count


    Here, a cosmetic one
    Code (csharp):
    1. Vector3 Position = direction;
    2. ShapeList[i].transform.position += Position;
    this is not
    Position
    , it is
    offset
    (notice
    +=
    and small caps). Also it's a little too late to rename
    direction
    into
    offset
    . In my code it was
    r
    (as ray) throughout, because it was self-explanatory. But don't mislead yourself with fabricated names.

    This is how I would name these phases in the original code
    Code (csharp):
    1. void nudgeShapesInAllDirections(GameObject[] shapes, Vector2 nudgeCenter, float nudgeAmount) {
    2.   for(int i = 0; i < shapes.Length; i++) {
    3.     var delta = (Vector2)shapes[i].transform.position - nudgeCenter;
    4.     var offset = nudgeAmount * delta.normalized;
    5.     shapes[i].transform.position += offset;
    6.   }
    7. }
    But if you don't care particularly for this delta, why waste a variable?
    You can also write this as one-liner, but I wanted it to be more readable so that you can understand what I'm doing.
    Code (csharp):
    1. void nudgeShapesInAllDirections(GameObject[] shapes, Vector2 nudgeCenter, float nudgeAmount) {
    2.   for(int i = 0; i < shapes.Length; i++) {
    3.     var pos = (Vector2)shapes[i].transform.position;
    4.     shapes[i].transform.position += nudgeAmount * (pos - nudgeCenter).normalized;
    5.   }
    6. }
    Observe how readable it is (or well, if it isn't you should really step back a little, and get some practice with vectors in general). I always pivot my code around so that it's easy to parse first and foremost.

    In
    hasOverlaps

    this is not a good way to write this
    Code (csharp):
    1. foreach(Data shape in shapes) {
    2.   foreach(Data _shape in shapes) {
    First the names are horrible. (Don't use _ for local variables. By convention we use this only for class fields and in scenarios when it implies some sort of special use.)

    Next, I showed you an algorithm that explicitly relies on indexed lists. This is what I said before
    The reason behind it is the actual method of iterating through the pairs. And you already have shapes coming in as a
    List<Data>
    . Finally, if you have
    Intersect
    method already, why didn't you just use it?

    So let's begin by doing this properly. I'm not claiming it will work after this, but it's a start.
    (Btw, you have completely ignored the point of snapping on the grid, so I will ignore it as well.)
    Code (csharp):
    1. bool hasOverlaps(List<Data> shapes) {
    2.   if(shapes.Count < 2) return false;
    3.   for(int a = 0; a < shapes.Count - 1; a++) {
    4.     for(int b = a + 1; b < shapes.Count; b++) {
    5.       if(Intersect(shapes[a], shapes[b])) return true;
    6.     }
    7.   }
    8.   return false;
    9. }
    This is how I would write
    Intersect
    , because your code is very messy.
    Code (csharp):
    1. bool Intersect(Data shape1, Data shape2) {
    2.   var bounds1 = shape1.GetComponent<Collider2D>().bounds;
    3.   var bounds2 = shape2.GetComponent<Collider2D>().bounds;
    4.   return bounds1.Intersects(bounds2);
    5. }
    Again this is something that has to be tested, but I will pretend that it works. It should work on paper. And it should be Intersects. In general you want to name bool methods like they pose a question.
    hasOverlaps
    is okay, this should probably be
    hasOverlap
    to stay in line with the general idea.
     
    Last edited: Jan 31, 2022
  15. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I can help you with visualizing that part of the code doing nudging, if that would help you understand what's going on. I'm not sure if you're versed enough with the vectors. Maybe it could help if you would see a picture of what exactly is computed and why.

    In any case, try this out and tell me where you're at, we'll make this work.

    Don't forget to fix the code so that it actually uses
    moveShapesInMultipleDirections
    somehow.
     
  16. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Now let's talk about the general algorithm
    First we had this (in pseudo-code)
    Code (csharp):
    1. while(any of the snapped shapes had any overlaps) {
    2.   nudge all shapes
    3. }
    4. snap all shapes
    Then we decided to go for this instead
    Code (csharp):
    1. foreach shape in list {
    2.   while(snapped shape overlaps) {
    3.     nudge this one shape
    4.   }
    5.   snap this one shape
    6. }
    Currently, you have a mixed approach, and this is likely why it doesn't work.
    Try to keep this big picture in mind when making changes.