1
\$\begingroup\$

I'm developing a 2D game in Unity and have been tasked with building a particular feature I'm not sure how to accomplish with Unity's physics engine. To simplify, a bunch of objects of type X and O are filled into a pit:

XXXXXXXXXXXX
XXXXXXXXXXXX
XXXXXXXXXXXX
XXoXXXoXXXoX

Both objects have Rigidbody2Ds and colliders. Objects of type X have normal gravity and should settle into place. Objects of type O have inverse gravity and should float.

However, O should not be able to push X, and X should not be able to push O. Another way of looking at it is that O should only be able to move upward, and X should only be able to move downward. If we remove some of the Xs directly above an O, the O should float upwards as the Xs fall downwards until the Xs settle on top of the O and everything stops moving:

XXXXXXXXXXXX        XX XXXXXXXXX
XX XXXXXXXXX   -->  XXXXXXXXXXXX
XX XXXXXXXXX   -->  XXoXXXXXXXXX
XXoXXXoXXXoX        XX XXXoXXXoX

(In the actual game Xs and Os can be different sizes; their movement is continuous and they are not constrained into rows or columns).

The Os should be able to support the weight of any arbitrary number of Xs without sinking.

XXXXXXXXXXXX
XXXXXXXXXXXX
XooooooooooX
X          X

Lastly, the Os should be able to float diagonally if there is a space available:

XXXX XXXXXXX      XXX       XX
XXXXX     XX  --> XXXXX   XXXX
XXXX    XXXX  --> XXXXoXXXXXXX
XXXo XXXXXXX      XXXX XXXXXXX

I can't solve this problem simply by adjusting the mass of the Xs or Os since the number of Xs on top of an O may vary. I can't use the built-in RigidBody constraints since they don't allow you to disable only one direction along an axis (e.g. saying an object can move upward but not downward).

What's the proper way to handle this? I could do something like this:

void FixedUpdate() {
    if (movementDirection == Direction.UP) {
        if (transform.position.y < lastYCoordinate) {
            Vector3 position = transform.position;
            position.y = lastYCoordinate;
            transform.position = position;
        }
    }
    lastYCoordinate = transform.position.y;
}

but that feels hacky to me and I suspect it will keep the objects from sleeping. Are there better solutions here?

\$\endgroup\$
1
\$\begingroup\$

You could try to use a script to check the velocity of the rigid bodies, and in case they have a velocity in the Y axis in a direction you don't want, set it to zero.

For example:

OnlyDown.cs

void FixedUpdate()
{
    if (GetComponent<Rigidbody2D>().velocity.y > 0)
    {
        GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, 0);
    }
}

OnlyUp.cs

void FixedUpdate()
{
    if (GetComponent<Rigidbody2D>().velocity.y < 0)
    {
        GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, 0);
    }
}

Also, for the gameObjects that have to go up, just change the gravity scale to -1.

\$\endgroup\$
5
  • \$\begingroup\$ Does this have any functional difference from the sample code I posted at the end of the question? \$\endgroup\$
    – user45623
    Sep 15 '16 at 1:00
  • 1
    \$\begingroup\$ @user45623 Yes, it has: it's much more adequate (less hacky) to reset unwanted part of velocity than to manually update transform.position. \$\endgroup\$ Sep 15 '16 at 2:11
  • \$\begingroup\$ @MaximKamalov I tried it out and your solution is actually less effective, as it still allows some movement in the undesired direction. I'm not quite sure why though. My 'solution' isn't quite what I want, in any case. \$\endgroup\$
    – user45623
    Sep 16 '16 at 18:07
  • 1
    \$\begingroup\$ Did you assign the correct script to each type? Did you apply the changes in the fixedupdate funtion? I tested the solution and there was 0 movement in the undesired direction. Maybe my rb had less mass... ? \$\endgroup\$
    – Leo
    Sep 16 '16 at 18:27
  • \$\begingroup\$ Yes, I used the correct script and applied the change in FixedUpdate. Could be a mass issue, I'm not sure. \$\endgroup\$
    – user45623
    Sep 23 '16 at 21:05
1
\$\begingroup\$

You can use OnCollisionStay2D:

void OnCollisionStay2D (Collision2D coll) 
{
    if(coll.gameObject.tag == "Y" || coll.gameObject.tag == "X")
    {
        GetComponent<Rigidbody2D>().velocity = Vector2.zero;
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ 1.CompareTag() is a bit more efficient for checking tag values, 2. Don't forget to check if the collided object is above/below (or that the normal points down/up), lest we block movement for glancing scrapes along the left & right sides. \$\endgroup\$
    – DMGregory
    Sep 15 '16 at 2:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.