A simplified Black Hole

BlackHole

For Space Duel, I wanted to make a black hole… one that would bend the trajectory of the shots in the game and attract or swallow the player’s spaceship.

This is what I created to make a simple arcade-style gravity well.

I did some digging around (and with some help from NCarter on the IRC Channel*, iirc), I came up with this little piece of code that went into the “Update” function of the moving objects, or object controller if there was a controller responsible for a collection of objects like shots:

Transform thisTransform;
GameObject blackHole;
float gravityRadius;
float gravityConstant;

if (blackHole.active) {
      Vector3 gravityVector = blackHole.transform.position - thisTransform.position;
      float gravityDistance = 
            Vector3.Distance(blackHole.transform.position, thisTransform.position);
      Vector3 gravityStrength = Vector3.zero;
      gravityStrength.x = gravityConstant / Mathf.Pow(gravityDistance, 2);
      gravityStrength.z = gravityConstant / Mathf.Pow(gravityDistance, 2);
      if (gravityDistance < gravityRadius) {
            currentVelocity = 
                  currentVelocity + (Vector3.Scale(gravityStrength, gravityVector));
      }
}

 

I have also put this code up on pastebin.

This was implemented in a very simple manner.

Each object is aware of the black hole, rather than the black hole acting on each object.

thisTransform is set in Awake or Start and is a reference to the object’s own transform which is cached for performance purposes.

The GameObject blackHole is a reference to the black hole, so we can find the distance between and angle to the black hole from the object. This can be found or referenced in a number of different ways.

gravityRadius limits how far we want the black hole to effect objects. This was implemented in an arcade style game, so I needed this control over the radius, rather than just making a mass and letting it affect things naturally.

Lastly, gravityConstant is how strong the black hole is. A name such as “mass” would also work. I chose this name for the variable, as the actual gravityStrength – or how much pull is being exerted on the object – is dependent on the distance between the object and the black hole. This can be seen in the code as: Vector3 gravityStrength = Vector3.zero; which is then calculated per frame per object.

I’ve had a few people ask for this code, or code to make a gravity well of some sort, so I hope this helps!

*The Unity community driven IRC channel can be found on freenode.net in the room #unity3d. Please use a registered nick for admittance. For more information please see the community page on the Unity site. This IRC channel is not supported by Unity Technologies.

6 Comments

Jason

about 5 years ago Reply

Just found your blog from your unity tutorials on Youtube and I'm binge reading them all! I know this is a post from last year but its new to me. Is there any reason why you would have each object reference the black hole rather than have it the other way around? Just last week I was playing around with a similar script. I decided to use a trigger for my radius and use an on trigger stay to influence any objects within the trigger. It works although I think it would probably be better to change from using a stay trigger to maybe an on enter then add the object to a list and if the object leaves the radius remove them from the list. What do you think? The code looks like this:

public float portalStrength=5;

void OnTriggerStay2D(Collider2D other) {
   if(other.tag.Equals("Actor"))
   {
      Vector2 position = other.transform.position;
      float distance = Vector2.Distance(position,transform.position);
      Vector2 direction = 
         (((Vector2)transform.position) - position).normalized;
      other.gameObject.rigidbody2D.velocity += 
         (direction*portalStrength/(distance+.001f));
   }
}
Thanks, Jason

Adam

about 5 years ago Reply

Yes, that would work! First off, this is a very simplified case to show the forces involved, and the basic calculations. This isn't the full code. Also, in my case, this was used in a very simple game with all of the objects being affected by the black hole held on a controller in a list (or two, I can't remember...) and the controller iterated through all of the items in the list and applied the gravity forces upon them, so the list had a reference to the black hole. Essentially, the black hole was simply a transform moving in the game and the controller used it to apply gravity around that point. It really depends upon your architecture and which way you want to skin your cat!

Adam

about 5 years ago Reply

Now, specifically to your code... Yes, if it were me, I'd have something like the "blackhole.isActive" boolean that's turned on by an OnTriggerEnter2D and then turned off by an OnTriggerExit2D... Or turned on by the OnTriggerEnter2D, and then as part of the calculation have a test (e.g.: a test to see if the forces are below a threshold or the distance between greater than a value - whatever one feels is best) and make "blackhole.isActive = false" when the test tells us we've escaped the black hole! OnTriggerStay2D does work, but (for whatever reason) it wouldn't have been my first choice. One thing you can do is try some tests and see if one is more performant than another. The two best rules are "Does it work?" and "Can I understand it?". If your code passes Test1 and Test2, you need a big epic phail before you need to change it.

Joacim

about 4 years ago Reply

You mention that: "thisTransform is set in Awake or Start and is a reference to the object’s own transform which is cached for performance purposes". I don't really get that. What would possibly be the advantage of that compared to just using this.transform? Aren't they just two references to the same object? What performance advantage do you get by using this caching?

Adam

about 4 years ago Reply

We get the performance optimisation of not having to look it up each time we use it, which could be every frame. I can't remember the details about how it all works under the hood, and this changes slightly with Unity 5, but even though there are some optimisations to "transform" over the obsolete helper references in versions up to 4.x (e.g.: rigidbody, collider, etc.), there is still an overhead to look it up in the hierarchy when compared to a cached reference. Now, all that being said, for standalone builds or deployment to the web? The performance difference is probably negligible. On the other hand, getting into the habit of good cross platform code that could run on mobiles is not a bad idea either.

Joacim

about 4 years ago Reply

Well, I can understand that there is some overhead in looking up things like rigidBody and collider (I didn't know these were deprecated, what else is deprecated of the property references you get from GameObject?), but why would you need to look-up the transform? All game objects have one. Even if it needs to be looked up the first time you use it, isn't it automatically cached after that? If not, why not?

Leave a Comment

Please be polite. We appreciate that. Your email address will not be published and the required fields are marked.