/// <summary> /// gets a list of all the tiles intersecting bounds. The returned list is ordered for collision detection based on the /// direction passed in so they can be processed in order. /// </summary> /// <returns>The colliding tiles.</returns> /// <param name="bounds">Bounds.</param> /// <param name="direction">Direction.</param> void populateCollidingTiles( Rectangle bounds, Edge direction ) { _collidingTiles.Clear(); var isHorizontal = direction.isHorizontal(); var primaryAxis = isHorizontal ? Axis.X : Axis.Y; var oppositeAxis = primaryAxis == Axis.X ? Axis.Y : Axis.X; var oppositeDirection = direction.oppositeEdge(); var firstPrimary = worldToTilePosition( bounds.getSide( oppositeDirection ), primaryAxis ); var lastPrimary = worldToTilePosition( bounds.getSide( direction ), primaryAxis ); var primaryIncr = direction.isMax() ? 1 : -1; var min = worldToTilePosition( isHorizontal ? bounds.Top : bounds.Left, oppositeAxis ); var mid = worldToTilePosition( isHorizontal ? bounds.getCenter().Y : bounds.getCenter().X, oppositeAxis ); var max = worldToTilePosition( isHorizontal ? bounds.Bottom : bounds.Right, oppositeAxis ); var isPositive = mid - min < max - mid; var secondaryIncr = isPositive ? 1 : -1; var firstSecondary = isPositive ? min : max; var lastSecondary = !isPositive ? min : max; for( var primary = firstPrimary; primary != lastPrimary + primaryIncr; primary += primaryIncr ) { for( var secondary = firstSecondary; secondary != lastSecondary + secondaryIncr; secondary += secondaryIncr ) { var col = isHorizontal ? primary : secondary; var row = !isHorizontal ? primary : secondary; _collidingTiles.Add( collisionLayer.getTile( col, row ) ); #if DEBUG_MOVER if( direction.isHorizontal() ) { var pos = tiledMap.tileToWorldPosition( new Point( col, row ) ); _debugTiles.Add( new Rectangle( (int)pos.X, (int)pos.Y, 16, 16 ) ); } #endif } } }
bool testMapCollision( Rectangle collisionRect, Edge direction, CollisionState collisionState, out int collisionResponse ) { collisionResponse = 0; var side = direction.oppositeEdge(); var perpindicularPosition = side.isVertical() ? collisionRect.Center.X : collisionRect.Center.Y; var leadingPosition = collisionRect.getSide( direction ); var shouldTestSlopes = side.isVertical(); populateCollidingTiles( collisionRect, direction ); for( var i = 0; i < _collidingTiles.Count; i++ ) { if( _collidingTiles[i] == null ) continue; // disregard horizontal collisions if the last tile we were grounded on was a slope. Our y movement will push us up on the slope. // this is not a fantastic solution if( direction.isHorizontal() && collisionState._lastGroundTile != null && collisionState._lastGroundTile.isSlope() && _collidingTiles[i].isSlope() ) return false; if( testTileCollision( _collidingTiles[i], side, perpindicularPosition, leadingPosition, shouldTestSlopes, out collisionResponse ) ) { // store off our last ground tile if we collided below if( direction == Edge.Bottom ) { collisionState._lastGroundTile = _collidingTiles[i]; collisionState.isGroundedOnOneWayPlatform = collisionState._lastGroundTile.isOneWayPlatform(); } return true; } // special case for sloped ground tiles if( collisionState._lastGroundTile != null && direction == Edge.Bottom ) { // if grounded on a slope and intersecting a slope or if grounded on a wall and intersecting a tall slope we go sticky. // tall slope here means one where the the slopeTopLeft/Right is 0, i.e. it connects to a wall var isHighSlopeNearest = _collidingTiles[i].getNearestEdge( perpindicularPosition ) == _collidingTiles[i].getHighestSlopeEdge(); if( ( collisionState._lastGroundTile.isSlope() && _collidingTiles[i].isSlope() ) || ( !collisionState._lastGroundTile.isSlope() && isHighSlopeNearest ) ) { // store off our last ground tile if we collided below collisionState._lastGroundTile = _collidingTiles[i]; return true; } } } return false; }
bool testMapCollision( Rectangle collisionRect, Edge direction, out int collisionResponse ) { collisionResponse = 0; var side = direction.oppositeEdge(); var perpindicularPosition = side.isVertical() ? collisionRect.Center.X : collisionRect.Center.Y; var leadingPosition = collisionRect.getSide( direction ); var shouldTestSlopes = side.isVertical(); populateCollidingTiles( collisionRect, direction ); for( var i = 0; i < _collidingTiles.Count; i++ ) { if( _collidingTiles[i] == null ) continue; // TODO: is this necessary? seems to work without it // disregard horizontal collisions if the last tile we were grounded on was a slope // this is not a fantastic solution if( direction.isHorizontal() && _lastGroundTile != null && _lastGroundTile.isSlope() ) continue; if( testTileCollision( _collidingTiles[i], side, perpindicularPosition, leadingPosition, shouldTestSlopes, out collisionResponse ) ) { // store off our last ground tile if we collided below if( direction == Edge.Bottom ) _lastGroundTile = _collidingTiles[i]; return true; } // special case for sloped ground tiles if( _lastGroundTile != null && direction == Edge.Bottom ) { // if grounded on a slope and intersecting a slope or if grounded on a wall and intersecting a tall slope we go sticky // tall slope here means one where the the slopeTopLeft/Right is 0, i.e. it connects to a wall if( ( _lastGroundTile.isSlope() && _collidingTiles[i].isSlope() ) || ( !_lastGroundTile.isSlope() && _collidingTiles[i].isSlope() /* should be tall slope check */ ) ) { // store off our last ground tile if we collided below _lastGroundTile = _collidingTiles[i]; return true; } } } return false; }
public void testCollisions( ref Vector2 motion, Rectangle boxColliderBounds, CollisionState collisionState ) { _boxColliderBounds = boxColliderBounds; // save off our current grounded state which we will use for wasGroundedLastFrame and becameGroundedThisFrame collisionState.wasGroundedLastFrame = collisionState.below; // reset our collisions state collisionState.reset( ref motion ); // reset rounded motion for us while dealing with subpixel movement so fetch the rounded values to use for our actual detection var motionX = (int)motion.X; var motionY = (int)motion.Y; // first, check movement in the horizontal dir if( motionX != 0 ) { var direction = motionX > 0 ? Edge.Right : Edge.Left; var sweptBounds = collisionRectForSide( direction, motionX ); int collisionResponse; if( testMapCollision( sweptBounds, direction, collisionState, out collisionResponse ) ) { // react to collision. get the distance between our leading edge and what we collided with motion.X = collisionResponse - boxColliderBounds.getSide( direction ); collisionState.left = direction == Edge.Left; collisionState.right = direction == Edge.Right; collisionState._movementRemainderX.reset(); } else { collisionState.left = false; collisionState.right = false; } } // next, check movement in the vertical dir { var direction = motionY >= 0 ? Edge.Bottom : Edge.Top; var sweptBounds = collisionRectForSide( direction, motionY ); sweptBounds.X += (int)motion.X; int collisionResponse; if( testMapCollision( sweptBounds, direction, collisionState, out collisionResponse ) ) { // react to collision. get the distance between our leading edge and what we collided with motion.Y = collisionResponse - boxColliderBounds.getSide( direction ); collisionState.above = direction == Edge.Top; collisionState.below = direction == Edge.Bottom; collisionState._movementRemainderY.reset(); if( collisionState.below && collisionState._lastGroundTile != null && collisionState._lastGroundTile.isSlope() ) collisionState.slopeAngle = MathHelper.ToDegrees( (float)Math.Atan( collisionState._lastGroundTile.getSlope() ) ); } else { collisionState.above = false; collisionState.below = false; collisionState._lastGroundTile = null; } // when moving down we also check for collisions in the opposite direction. this needs to be done so that ledge bumps work when // a jump is made but misses by the colliderVerticalInset if( direction == Edge.Bottom ) { direction = direction.oppositeEdge(); sweptBounds = collisionRectForSide( direction, 0 ); sweptBounds.X += (int)motion.X; sweptBounds.Y += (int)motion.Y; if( testMapCollision( sweptBounds, direction, collisionState, out collisionResponse ) ) { // react to collision. get the distance between our leading edge and what we collided with motion.Y = collisionResponse - boxColliderBounds.getSide( direction ); // if we collide here this is an overlap of a slope above us. this small bump down will prevent hitches when hitting // our head on a slope that connects to a solid tile. It puts us below the slope when the normal response would put us // above it motion.Y += 2; collisionState.above = true; } } } // set our becameGrounded state based on the previous and current collision state if( !collisionState.wasGroundedLastFrame && collisionState.below ) collisionState.becameGroundedThisFrame = true; }
/// <summary> /// gets a list of all the tiles intersecting bounds. The returned list is ordered for collision detection based on the /// direction passed in so they can be processed in order. /// </summary> /// <returns>The colliding tiles.</returns> /// <param name="bounds">Bounds.</param> /// <param name="direction">Direction.</param> void populateCollidingTiles( Rectangle bounds, Edge direction ) { _collidingTiles.Clear(); var isHorizontal = direction.isHorizontal(); var primaryAxis = isHorizontal ? Axis.X : Axis.Y; var oppositeAxis = primaryAxis == Axis.X ? Axis.Y : Axis.X; // if we are going up or left we subtract 1 from our opposite edge so that it doesnt get the column/row of tiles when we are against them var oppositeDirection = direction.oppositeEdge(); var minSidePad = direction.isMin() ? -1 : 0; var firstPrimary = worldToTilePosition( bounds.getSide( oppositeDirection ) + minSidePad, primaryAxis ); var lastPrimary = worldToTilePosition( bounds.getSide( direction ), primaryAxis ); var primaryIncr = direction.isMax() ? 1 : -1; var min = worldToTilePosition( isHorizontal ? bounds.Top : bounds.Left, oppositeAxis ); var mid = worldToTilePosition( isHorizontal ? bounds.getCenter().Y : bounds.getCenter().X, oppositeAxis ); // same as above here. we subtract 1 to not grab an extra column/row of tiles which will mess up collisions var max = worldToTilePosition( isHorizontal ? bounds.Bottom - 1 : bounds.Right - 1, oppositeAxis ); var isPositive = mid - min < max - mid; var secondaryIncr = isPositive ? 1 : -1; var firstSecondary = isPositive ? min : max; var lastSecondary = !isPositive ? min : max; for( var primary = firstPrimary; primary != lastPrimary + primaryIncr; primary += primaryIncr ) { for( var secondary = firstSecondary; secondary != lastSecondary + secondaryIncr; secondary += secondaryIncr ) { var col = isHorizontal ? primary : secondary; var row = !isHorizontal ? primary : secondary; _collidingTiles.Add( _collisionLayer.getTile( col, row ) ); } } }