1
\$\begingroup\$

I'm trying to create an agar.io clone. In agar.io, each player can have multiple cells that "chase" the mouse pointer. The cells can also have multiple masses and the velocity is tied to the mass. The bigger the cell, the slower it moves. The first picture in this question is a good representation of how this plays out

The cells can't overlap (mostly), so I've already implemented

  1. check to see if they are touching
  2. calculate how much they overlap -> distance between circle centers, minus both circle's radius
  3. calculate a push vector -> subtract the two centers, normalize the resulting vector, and multiply by overlap value

The above code mostly works, but in the regular game if you have a smaller/faster blob moving behind a larger/slower blob, the faster blob will start to slide around the larger blob. I'm not sure how to implement that.

I think you should be able to calculate the normal to the point where the circles touch, and remove the magnitude of the "chase the mouse" velocity in the direction of the normal? I'm not sure how to do that. Math is not my strong suit.

I took the idea for the circle collision from this link and I think my question of how to remove the velocity from the circle normal is in this link which talks about how to calculate the reflection off of a circle, but I haven't been able to translate that to code.

I'm using C++, not that that matters.

There's also the open source agar.io clones, Ogar and Agar.io-clone, but I haven't found much digging through the code with how they handle that.

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

If both circles are headed to the same destination, but not in exactly the same line, then your logic for pushing them apart will actually already route the smaller circle around the larger one, slowly.

Step-by-step breakdown of a smaller circle overtaking a larger one

Here you can see three iterations of advancing the circles along their velocities, computing penetrations, and adding separating offsets. In the process, the smaller circle climbs from about the 10:00 position on the larger circle's clock face up toward the 12:00 position, even though we never "told" it to.

So, that might be why you're having trouble finding code in Ogar or Agar.io-clone that handles this routing-around behaviour: they might not code that behaviour explicitly. It can arise as an emergent result of applying offsets along the line separating the circles.

That said, you might find this effect is a bit too gradual on its own - especially when the circles are travelling directly in line with one another. There, pushing the small circle back along the line between the circles just reduces its net velocity along the same line, rather than displacing it above or below the obstacle. (You can think of it like all the little one's effort is going into pushing against the big circle, with no component sliding along the surface)

So we can give the small circle a little extra nudge to help it in these situations. I do something similar in this answer, though there the obstacles are stationary so we can plan the movement of the ball around them without order-of-update considerations. For many co-navigating balls, we'll want some type of iterative solution instead.

I don't know exactly what you have in mind with the proposal to 'remove the magnitude of the "chase the mouse" velocity in the direction of the normal', but here's how you can do that after you've resolved the collision between two circles:

toTarget = targetPosition - centerPosition

collisionNormal = normalize(centerPosition - obstacle.centerPosition)

// This is the component of the toTarget vector pointing into the obstacle.
// The min(, 0) discards cases where our toTarget actually points out of the collision.
intoCollision = min(dot(toTarget, normal), 0)

toTargetNonColliding = toTarget - normal * intoCollision

A few cautionary notes about doing this type of adjustment:

  • This subtraction can produce a zero vector when the collision normal is pointing exactly opposite the direction we want to go. In such a situation, you can fall back on a perpendicular to the collision normal (-normal.y, normal.x) to avoid a deadlock.

  • A circle might find itself wedged between two or more obstacles, in which case considering only one normal might not tell you enough about the nearby constraints, and considering all normals might leave you with nowhere to move at all. You'll need to decide what kind of behaviour you want in that scenario.

  • You may want to apply this type of adjustment a little at a time, as a nudge or persistent spring force over a range of nearby distances, rather than an all-or-nothing instantaneous velocity change.

    If you completely eliminate movement into collision on frames with collision, you'll tend to not encounter a collision in the next frame (if you're dealing with just the one obstacle), falling back on your default behaviour, which sends the velocity back into a colliding direction. This can make your following object appear to stutter or zig-zag on alternating frames as it switches between collision-avoiding and normal velocity calculations.

\$\endgroup\$
  • \$\begingroup\$ Wouldn't multiplying by the intoCollision product (if it's zero) completely clobber your toTargetNonColliding vector? It'd make it like, zero right? \$\endgroup\$ – Ben Hoff Jul 11 '18 at 11:54
  • \$\begingroup\$ See the very first bullet point: "This subtraction can produce a zero vector when the collision normal is pointing exactly opposite the direction we want to go" \$\endgroup\$ – DMGregory Jul 11 '18 at 11:56

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.