/// <summary> /// /// </summary> /// <param name="side"></param> /// <returns></returns> private static Vector2 GetSideNormalCollision(EnemiesCollisionSide side) { switch (side) { case EnemiesCollisionSide.Top: return(new Vector2(0, -1)); case EnemiesCollisionSide.Bottom: return(Vector2.UnitY); case EnemiesCollisionSide.Left: return(Vector2.UnitX); case EnemiesCollisionSide.Right: return(new Vector2(-1, 0)); default: return(Vector2.Zero); } }
/// <summary> /// Check where and which direction should the enemies collide to /// </summary> /// <param name="timeStep"></param> /// <param name="screenWidth"></param> /// <param name="screenHeight"></param> /// <param name="firstObjectVelocity"></param> /// <param name="secondObjectVelocity"></param> /// <param name="firstObjectRectangle"></param> /// <param name="secondObjectRectangle"></param> /// <returns></returns> public static EnemiesCollisionInfor CheckCollisions(int timeStep, int screenWidth, int screenHeight, Vector2 firstObjectVelocity, Vector2 secondObjectVelocity, Rectangle firstObjectRectangle, Rectangle secondObjectRectangle) { Rectangle initialFirstObject; Rectangle initialSecondObject; Rectangle finalfirstObject; Rectangle finalSecondObject; Rectangle collisionRectangle; //checking if they are overlap bool detectedCollision = firstObjectRectangle.Intersects(secondObjectRectangle); if (detectedCollision) { // getting the same size of the rectangle finalfirstObject.Width = firstObjectRectangle.Width; finalfirstObject.Height = firstObjectRectangle.Height; finalSecondObject.Width = secondObjectRectangle.Width; finalSecondObject.Height = secondObjectRectangle.Height; // Getting the information right at the moment they overlap float firstXSpeed = (firstObjectVelocity.X * timeStep); float firstYSpeed = (firstObjectVelocity.Y * timeStep); initialFirstObject.X = (int)(firstObjectRectangle.X - firstXSpeed); initialFirstObject.Y = (int)(firstObjectRectangle.Y - firstYSpeed); initialFirstObject.Width = firstObjectRectangle.Width; initialFirstObject.Height = firstObjectRectangle.Height; float secondXSpeed = (secondObjectRectangle.X * timeStep); float secondYSpeed = (secondObjectRectangle.Y * timeStep); initialSecondObject.X = (int)(secondObjectRectangle.X - secondXSpeed); initialSecondObject.Y = (int)(secondObjectRectangle.Y - secondYSpeed); initialSecondObject.Width = secondObjectRectangle.Width; initialSecondObject.Height = secondObjectRectangle.Height; // Constantly, the time step is 60 frames per second, time will increament by the power of 2 int timeIncrement = timeStep / 2; int collisionDeltaT = timeStep; // rate of change of time int DeltaT = timeIncrement; // rate of change of time at a certain moment while (timeIncrement > 0) { // Move the objects firstXSpeed = firstObjectVelocity.X * DeltaT; firstYSpeed = firstObjectVelocity.Y * DeltaT; secondXSpeed = secondObjectVelocity.X * DeltaT; secondYSpeed = secondObjectVelocity.Y * DeltaT; // Updating current Location for the objects finalfirstObject.X = (int)(initialFirstObject.X + firstXSpeed); finalfirstObject.Y = (int)(initialFirstObject.Y + firstYSpeed); finalSecondObject.X = (int)(initialSecondObject.X + secondXSpeed); finalSecondObject.Y = (int)(initialSecondObject.Y + secondYSpeed); // decreasing the time increament timeIncrement /= 2; // check if they bound again or not detectedCollision = finalfirstObject.Intersects(finalSecondObject); if (detectedCollision) { // if the collision happens again collisionDeltaT = DeltaT; DeltaT -= timeIncrement; Rectangle.Intersect(ref finalfirstObject, ref finalSecondObject, out collisionRectangle); } else { DeltaT += timeIncrement; } } // Start the Collision int collisionStartTime = collisionDeltaT; firstXSpeed = firstObjectVelocity.X * collisionStartTime; firstYSpeed = firstObjectVelocity.Y * collisionStartTime; secondXSpeed = secondObjectVelocity.X * collisionStartTime; secondYSpeed = secondObjectVelocity.Y * collisionStartTime; finalfirstObject.X = (int)(initialFirstObject.X + firstXSpeed); finalfirstObject.Y = (int)(initialFirstObject.Y + firstYSpeed); finalSecondObject.X = (int)(initialSecondObject.X + secondXSpeed); finalSecondObject.Y = (int)(initialSecondObject.Y + secondYSpeed); // Check the side of the collision Rectangle collisionIntersection = Rectangle.Intersect(finalfirstObject, finalSecondObject); EnemiesCollisionSide possibleSide = checkCollisionSide(finalSecondObject, collisionIntersection, firstObjectVelocity, secondObjectVelocity); // Let the object move over the time Step int preCollisionTime = collisionStartTime - 1; int postCollisionTime = timeStep - preCollisionTime; EnemiesCollisionInfor enemiesBounce = getBounceObjects(firstObjectVelocity, initialFirstObject, secondObjectVelocity, initialSecondObject, preCollisionTime, postCollisionTime, possibleSide); enemiesBounce.firstOutBounds = IsOutOfBounds(firstObjectRectangle, screenWidth, screenHeight); enemiesBounce.SecondOutBounds = IsOutOfBounds(secondObjectRectangle, screenWidth, screenHeight); return(enemiesBounce); } else { // no collision happens -> no enemiesCollisionInfor return(null); } }
/// <summary> /// Get the new velocity for each object after collision /// </summary> /// <param name="firstVelocity"></param> /// <param name="secondVelocity"></param> /// <param name="side"></param> /// <param name="newFirstVelocity"></param> /// <param name="newSecondVelocity"></param> private static void getNewVelocities(Vector2 firstVelocity, Vector2 secondVelocity, EnemiesCollisionSide side, out Vector2 newFirstVelocity, out Vector2 newSecondVelocity) { switch (side) { case EnemiesCollisionSide.Top: if (firstVelocity.Y > 0 && secondVelocity.Y > 0) { // first object caught up to second object, change the first y velocity newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = secondVelocity; } else if (firstVelocity.Y < 0 && secondVelocity.Y < 0) { // second object caught up to first object, change the second y velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } else { // normal collision, change both velocities of two objects newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } break; case EnemiesCollisionSide.Bottom: if (firstVelocity.Y > 0 && secondVelocity.Y > 0) { // second object caught up to first object, change the second y velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } else if (firstVelocity.Y < 0 && secondVelocity.Y < 0) { // first object caught up to second object, change the first y velocity newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = secondVelocity; } else { // normal collision, change both velocities of two objects newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } break; case EnemiesCollisionSide.Left: if (firstVelocity.X > 0 && secondVelocity.X > 0) { // first object caught up to second object, change the first x velocity newFirstVelocity = new Vector2(firstVelocity.X * -1, firstVelocity.Y); newSecondVelocity = secondVelocity; } else if (firstVelocity.X < 0 && secondVelocity.X < 0) { // second object caught up to first object, change the second x velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X * -1, secondVelocity.Y); } else { // normal collision, change both velocities of two objects newFirstVelocity = new Vector2(firstVelocity.X * -1, firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X * -1, secondVelocity.Y); } break; case EnemiesCollisionSide.Right: if (firstVelocity.X > 0 && secondVelocity.X > 0) { // second object caught up to first object, change the second x velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X * -1, secondVelocity.Y); } else if (firstVelocity.X < 0 && secondVelocity.X < 0) { // first object caught up to second object, change the first x velocity newFirstVelocity = new Vector2(firstVelocity.X * -1, firstVelocity.Y); newSecondVelocity = secondVelocity; } else { // normal collision, change both velocities of two objects newFirstVelocity = new Vector2(firstVelocity.X * -1, firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X * -1, secondVelocity.Y); } break; default: newFirstVelocity = firstVelocity; newSecondVelocity = secondVelocity; break; } }
/// <summary> /// /// </summary> /// <param name="firstObjectVelocity"></param> /// <param name="firstObjectRectangle"></param> /// <param name="secondObjectVelocity"></param> /// <param name="secondObjectRectangle"></param> /// <param name="preCollisionTime"></param> /// <param name="postCollisionTime"></param> /// <param name="side"></param> /// <returns></returns> private static EnemiesCollisionInfor getBounceObjects(Vector2 firstObjectVelocity, Rectangle firstObjectRectangle, Vector2 secondObjectVelocity, Rectangle secondObjectRectangle, int preCollisionTime, int postCollisionTime, EnemiesCollisionSide side) { // make a temp variables for the initial speeds float firstSpeed = firstObjectVelocity.Length(); float secondSpeed = secondObjectVelocity.Length(); // get the properties in order to move the object forward Rectangle newFirstRectangle = moveObjects(firstObjectVelocity, firstObjectRectangle, preCollisionTime); Rectangle newSecondRectangle = moveObjects(secondObjectVelocity, secondObjectRectangle, preCollisionTime); // Change the velocity Vector2 newFirstVelocity; Vector2 newSecondVelocity; getNewVelocities(firstObjectVelocity, secondObjectVelocity, side, out newFirstVelocity, out newSecondVelocity); // move the object forward moveObjects(newFirstVelocity, newFirstRectangle, postCollisionTime); moveObjects(newSecondVelocity, newSecondRectangle, postCollisionTime); // If the objects are still colliding MoveCollidingObjects(newFirstVelocity, newFirstRectangle, newSecondVelocity, newSecondRectangle, out newFirstRectangle, out newSecondRectangle); return(new EnemiesCollisionInfor(newFirstVelocity, newFirstRectangle, false, newSecondVelocity, newSecondRectangle, false)); }
/// <summary> /// Returns the collision resolution info object associated with a detected collision or null if no collision /// is detected /// /// Ref: David M. Bourg, Physics for Game Developers, pages 207 and 209 /// </summary> /// <param name="timeStep">the time step, in milliseconds</param> /// <param name="windowWidth">the width of the game window</param> /// <param name="windowHeight">the height of the game window</param> /// <param name="firstVelocity">the velocity of the first object</param> /// <param name="firstDrawRectangle">the draw rectangle of the first object</param> /// <param name="secondVelocity">the velocity of the second object</param> /// <param name="secondDrawRectangle">the draw rectangle of the second object</param> /// <returns>the collision resolution info object for a detected collision or null if no collision is detected</returns> public static EnemiesCollisionInfor CheckCollision(int timeStep, int windowWidth, int windowHeight, Vector2 firstVelocity, Rectangle firstDrawRectangle, Vector2 secondVelocity, Rectangle secondDrawRectangle) { Rectangle initialFirstAabr; Rectangle initialSecondAabr; Rectangle currentFirstAabr; Rectangle currentSecondAabr; Rectangle collisionRectangle; // overlap test bool collisionDetected = firstDrawRectangle.Intersects(secondDrawRectangle); if (collisionDetected) { // initialize non-changing properties currentFirstAabr.Width = firstDrawRectangle.Width; currentFirstAabr.Height = firstDrawRectangle.Height; currentSecondAabr.Width = secondDrawRectangle.Width; currentSecondAabr.Height = secondDrawRectangle.Height; // back up both objects to their locations before the time step float firstDx = (firstVelocity.X * timeStep); float firstDy = (firstVelocity.Y * timeStep); initialFirstAabr.X = (int)(firstDrawRectangle.X - firstDx); initialFirstAabr.Y = (int)(firstDrawRectangle.Y - firstDy); initialFirstAabr.Width = firstDrawRectangle.Width; initialFirstAabr.Height = firstDrawRectangle.Height; float secondDx = (secondVelocity.X * timeStep); float secondDy = (secondVelocity.Y * timeStep); initialSecondAabr.X = (int)(secondDrawRectangle.X - secondDx); initialSecondAabr.Y = (int)(secondDrawRectangle.Y - secondDy); initialSecondAabr.Width = secondDrawRectangle.Width; initialSecondAabr.Height = secondDrawRectangle.Height; // at fixed time step of 60 fps, time increment can only be 8, 4, 2, or 1 int timeIncrement = timeStep / 2; int collisionDt = timeStep; // we know we have a collision or we wouldn't be here int dt = timeIncrement; while (timeIncrement > 0) { // move both objects forward by dt from their initial positions firstDx = firstVelocity.X * dt; firstDy = firstVelocity.Y * dt; secondDx = secondVelocity.X * dt; secondDy = secondVelocity.Y * dt; // update axis-aligned bounding rectangles currentFirstAabr.X = (int)(initialFirstAabr.X + firstDx); currentFirstAabr.Y = (int)(initialFirstAabr.Y + firstDy); currentSecondAabr.X = (int)(initialSecondAabr.X + secondDx); currentSecondAabr.Y = (int)(initialSecondAabr.Y + secondDy); // cut time increment in half as we search for the time of collision timeIncrement /= 2; collisionDetected = currentFirstAabr.Intersects(currentSecondAabr); if (collisionDetected) { // collision detected, so save collision dt and reduce dt to make it earlier collisionDt = dt; dt -= timeIncrement; // save the collision rectangle in case we don't find any other collisions Rectangle.Intersect(ref currentFirstAabr, ref currentSecondAabr, out collisionRectangle); } else { // no collision detected, so increase dt to make it later dt += timeIncrement; } } // get rectangle locations at start of collision int collisionStartTime = collisionDt; firstDx = firstVelocity.X * collisionStartTime; firstDy = firstVelocity.Y * collisionStartTime; secondDx = secondVelocity.X * collisionStartTime; secondDy = secondVelocity.Y * collisionStartTime; currentFirstAabr.X = (int)(initialFirstAabr.X + firstDx); currentFirstAabr.Y = (int)(initialFirstAabr.Y + firstDy); currentSecondAabr.X = (int)(initialSecondAabr.X + secondDx); currentSecondAabr.Y = (int)(initialSecondAabr.Y + secondDy); // use square collision normals Rectangle intersection = Rectangle.Intersect(currentFirstAabr, currentSecondAabr); EnemiesCollisionSide collisionSide = GetCollisionSide(currentSecondAabr, intersection, firstVelocity, secondVelocity); // move objects through complete time step int preCollisionDuration = collisionStartTime - 1; int postCollisionDuration = timeStep - collisionStartTime + 1; EnemiesCollisionInfor colInfor = BounceObjects(firstVelocity, initialFirstAabr, secondVelocity, initialSecondAabr, preCollisionDuration, postCollisionDuration, collisionSide); // check out of bounds and return colInfor.firstOutBounds = OutOfBounds(firstDrawRectangle, windowWidth, windowHeight); colInfor.SecondOutBounds = OutOfBounds(secondDrawRectangle, windowWidth, windowHeight); return(colInfor); } else { // no collision return(null); } }
/// <summary> /// Gets the new velocity vectors for the collider (first object) and /// collidee (second object) in a collision. Need to be careful if /// the collider caught up to the collidee when both were going in /// the same direction /// </summary> /// <param name="firstVelocity">velocity of the first object</param> /// <param name="secondVelocity">velocity of the second object</param> /// <param name="collisionSide">the collision side</param> /// <param name="newFirstVelocity">the new first object velocity</param> /// <param name="newSecondVelocity">the new second object velocity</param> private static void GetNewVelocities(Vector2 firstVelocity, Vector2 secondVelocity, EnemiesCollisionSide collisionSide, out Vector2 newFirstVelocity, out Vector2 newSecondVelocity) { switch (collisionSide) { case EnemiesCollisionSide.Top: if (firstVelocity.Y > 0 && secondVelocity.Y > 0) { // first object caught up to second object, only change first y velocity newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = secondVelocity; } else if (firstVelocity.Y < 0 && secondVelocity.Y < 0) { // second object caught up to first object, only change second y velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } else { // normal top collision, change both y velocities newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } break; case EnemiesCollisionSide.Bottom: if (firstVelocity.Y > 0 && secondVelocity.Y > 0) { // second object caught up to first object, only change second y velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } else if (firstVelocity.Y < 0 && secondVelocity.Y < 0) { // first object caught up to second object, only change first y velocity newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = secondVelocity; } else { // normal bottom collision, change both y velocities newFirstVelocity = new Vector2(firstVelocity.X, -1 * firstVelocity.Y); newSecondVelocity = new Vector2(secondVelocity.X, -1 * secondVelocity.Y); } break; case EnemiesCollisionSide.Left: if (firstVelocity.X > 0 && secondVelocity.X > 0) { // first object caught up to second object, only change first x velocity newFirstVelocity = new Vector2(-1 * firstVelocity.X, firstVelocity.Y); newSecondVelocity = secondVelocity; } else if (firstVelocity.X < 0 && secondVelocity.X < 0) { // second object caught up to first object, only change second x velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(-1 * secondVelocity.X, secondVelocity.Y); } else { // normal left collision, change both x velocities newFirstVelocity = new Vector2(-1 * firstVelocity.X, firstVelocity.Y); newSecondVelocity = new Vector2(-1 * secondVelocity.X, secondVelocity.Y); } break; case EnemiesCollisionSide.Right: if (firstVelocity.X > 0 && secondVelocity.X > 0) { // second object caught up to first object, only change second x velocity newFirstVelocity = firstVelocity; newSecondVelocity = new Vector2(-1 * secondVelocity.X, secondVelocity.Y); } else if (firstVelocity.X < 0 && secondVelocity.X < 0) { // first object caught up to second object, only change first x velocity newFirstVelocity = new Vector2(-1 * firstVelocity.X, firstVelocity.Y); newSecondVelocity = secondVelocity; } else { // normal right collision, change both x velocities newFirstVelocity = new Vector2(-1 * firstVelocity.X, firstVelocity.Y); newSecondVelocity = new Vector2(-1 * secondVelocity.X, secondVelocity.Y); } break; default: // should never get here newFirstVelocity = firstVelocity; newSecondVelocity = secondVelocity; break; } }
/// <summary> /// Bounces the two objects off each other. This method makes sure the speed /// of each object after the bounce is set to the given speed values /// </summary> /// <param name="firstVelocity">the velocity of the first object</param> /// <param name="firstDrawRectangle">the draw rectangle of the first object</param> /// <param name="secondVelocity">the velocity of the second object</param> /// <param name="secondDrawRectangle">the draw rectangle of the second object</param> /// <param name="preCollisionDuration">the duration before the collision</param> /// <param name="postCollisionDuration">the duration after the collision</param> /// <param name="collisionSide">the collision side</param> /// <returns>the collision resolution info object</returns> private static EnemiesCollisionInfor BounceObjects(Vector2 firstVelocity, Rectangle firstDrawRectangle, Vector2 secondVelocity, Rectangle secondDrawRectangle, int preCollisionDuration, int postCollisionDuration, EnemiesCollisionSide collisionSide) { // save speeds for later float firstSpeed = firstVelocity.Length(); float secondSpeed = secondVelocity.Length(); // move forward up to collision Rectangle newFirstDrawRectangle = MoveForward(firstVelocity, firstDrawRectangle, preCollisionDuration); Rectangle newSecondDrawRectangle = MoveForward(secondVelocity, secondDrawRectangle, preCollisionDuration); // change velocities as appropriate Vector2 newFirstVelocity; Vector2 newSecondVelocity; GetNewVelocities(firstVelocity, secondVelocity, collisionSide, out newFirstVelocity, out newSecondVelocity); // move objects forward after collision MoveForward(newFirstVelocity, newFirstDrawRectangle, postCollisionDuration); MoveForward(newSecondVelocity, newSecondDrawRectangle, postCollisionDuration); // may still need to move objects apart if they're still colliding MoveCollidingObjectsApart(newFirstVelocity, newFirstDrawRectangle, newSecondVelocity, newSecondDrawRectangle, out newFirstDrawRectangle, out newSecondDrawRectangle); return(new EnemiesCollisionInfor(newFirstVelocity, newFirstDrawRectangle, false, newSecondVelocity, newSecondDrawRectangle, false)); }