Hey! I’m working on a physics based 2D platformer in Unity 6. I am using the new rigidbody.Slide functionality to apply 2D movement. The code for this is pretty straight forward.
private void ApplyMovement()
{
if (!playerPaused)
{
var velocity = new Vector2(xInput * movementSpeed, 0f);
SlideResults = rb.Slide(velocity, Time.deltaTime, SlideMovement);
}
}
Everything works well, except for when I check for OnCollisionEnter2D. This doesn’t seem to get called at all if I “slide” into an object. It does get called when I land or jump into objects however (I’m using AddForce for jumping).
Am I doing something wrong? Is rb.Slide simply incompatible with CollisionEnter? Any ideas would be much appreciated! Thank you
EDIT: It seems that the rb.Slide component stops the player movement when they’re close to objects. You can see the gap between the player collider and the wall collider in the image below. I did a test switching the players movement to a purely velocity based movement and CollisionEnter worked fine then, so I’m 99% certain that rb.Slide is preventing collisions.
I’m hazarding a guess but I perhaps imagine its because the Slide functionality prevents intersections (thus, preventing what would trigger a collision). Whereas setting the velocity/adding forces will causes colliders to intersect - causing collision triggers - before getting corrected by the physics system.
Not sure what the best solution/workaround is here.
Thanks so much for the suggestions guys! I ended up making my own pseudo OnCollision Enter/Exit system using SlideHit like you mentioned Kurt. If anyone runs into this issue and needs help, here’s my code;
private GameObject currentCollisionObject;
private PlayerController playerController;
private void Start()
{
playerController = GetComponent<PlayerController>();
}
private void HandleCollisionEnter(GameObject hitObject)
{
// Trigger only if the collision object is new
if (currentCollisionObject != hitObject)
{
currentCollisionObject = hitObject;
//Do collision enter events here
}
}
private void HandleCollisionExit()
{
if (currentCollisionObject != null)
{
//Do collision exit events here
currentCollisionObject = null;
}
}
private void Update()
{
// Check if there is a current collision based on the slide result
if (playerController.SlideResults.slideHit)
{
var hitObject = playerController.SlideResults.slideHit.collider.gameObject;
if (hitObject != null)
{
HandleCollisionEnter(hitObject);
}
}
else if (currentCollisionObject != null)
{
HandleCollisionExit();
}
}
Was definitely an interesting problem. I would have swapped to just using velocity but Slide works seamlessly with the Physics system, and allows for tight, precise controls. If this is the one caveat, it’s a pretty damn great system.
In short, Slide isn’t disabling contacts but it’s avoiding them if possible or I should say, it’s not trying to contact the surfaces it’s moving to/along. If you touch something then the physics system will create a contact and you’ll get a report, be able to query that etc.
The Slide functionality is using standard physics queries to avoid actual contact with surfaces because interacting with surfaces and doing the movements you’re explicitly asking for can be incompatible in a lot of cases. Slide performs all sorts of queries then ensures it stays outside of the contact offset for the surface, or at least tries to. If it didn’t do this, you’ll likely hit things like vertex corners (ghost collisions) and also surface contact friction would take effect etc.
In the same sense as when devs set gravity/forces then continually issue a move position; you cannot do both and the external things need to be rolled into the position you ask to move to.
This is why it returns the results of the slide/surface hits to you should you wish to take further action on the contacts it found. You’re welcome to move to the actual contact point and interact with the surface, producing contacts but given the code you posted, you’re doing this per-frame and you won’t get a contact callback until the simulation runs, assuming it’s running (by default) in the FixedUpdate so wouldn’t make sense or at least it wouldn’t be in-sync with your actual movements.
Glad you’re finding it useful! Feel free to ask if you need any more specifics on it although I’d need to recap myself as I wrote it a few years ago now.
I ran into this same issues, but I really wanted the OnCollisionXX events to fire since I have other components on the GO that use them. I was also getting a weird scenario where the character would move a little closer to the wall after the move button was released. I assume this was because my character has deceleration so once the move button is released there is a final movement small enough to close the gap a little.
Anyway, I thought I’d share my solution in case it helps. This will do two things: 1) Close the gap to the obstacle and 2) Causes a collision and fires the OnCollisionEnter2D event.
This solution handles obstacles on slopes. If your game only uses horizontal surfaces then the code can be simplified since you won’t care about the ground angle.
I’m not sure it matters, but my character uses a kinematic rigidbody. I didn’t test this with a dynamic one, but I think it should still work.
Also, I call this method from FixedUpdate since it is moving a rigidbody.
private void SlideWithCollision(Vector2 velocity, float deltaTime, Rigidbody2D.SlideMovement movement)
{
var result = _rigidBody.Slide(velocity, deltaTime, movement);
// If we have hit a surface, cache its normal in a member variable since it won't happen every frame.
if (result.surfaceHit)
_surfaceNormal = result.surfaceHit.normal;
// If the slide stopped before hitting an obstacle, then we should close the gap to that obstacle.
if (result.slideHit)
{
// Calculate the vector to complete the movement to the obstacle.
// This will correctly handle obstacles on slopes.
var surfaceAngle = Vector2.Angle(_surfaceNormal, Vector2.up);
var directionX = Mathf.Sign(velocity.x);
var directionY = -Mathf.Sign(_surfaceNormal.x) * directionX;
var gapX = result.slideHit.distance * Mathf.Cos(surfaceAngle * Mathf.Deg2Rad) * directionX;
var gapY = result.slideHit.distance * Mathf.Sin(surfaceAngle * Mathf.Deg2Rad) * directionY;
// Using MovePosition ensures we close the gap during the next physics update.
// This will cause a collision and fire an OnCollisionEnter2D event.
_rigidBody.MovePosition(new Vector2(_rigidBody.position.x + gapX, _rigidBody.position.y + gapY));
}
}