//===================================================================================== /// <summary> /// Collision query function. If an object is collideable it should implement it's collision /// testing code here and return the result of the collision. This collision test is for /// a bounding elipse against a piece of geometry, such as a collection of lines etc. /// </summary> /// <param name="elipsePosition"> Position of the bounding elipse </param> /// <param name="elipseDimensions"> Dimensions of the bounding elipse </param> /// <param name="elipseRotation"> Amount the elipse is rotated by in radians. </param> /// <param name="otherObject"> Object making the collision query, may be null. </param> /// <param name="results"> Array to save the results to. </param> /// <param name="results_index"> Index in the array to save the results to. </param> /// <returns> Number of results from the collision. </returns> //===================================================================================== public override int OnCollisionQuery( Vector2 elipsePosition , Vector2 elipseDimensions , float elipseRotation , GameObject otherObject , CollisionQueryResult[] results , int results_index ) { // Get height of the top texture (if any). the ground plane is at the bottom of this. int top_tex_height = 0; if ( m_top_texture != null ) top_tex_height = m_top_texture.Height; // Take two points along the ground surface: Vector2 ground_p1 = new Vector2( elipsePosition.X - 10 , PositionY + BoxDimensionsY - top_tex_height ); Vector2 ground_p2 = new Vector2( elipsePosition.X + 10 , PositionY + BoxDimensionsY - top_tex_height ); // Transform into the correct form so that the ellipse is a circle, it's rotations are undone and it's center is the center of the world ground_p1 = Vector2.Transform( ground_p1 , LevelCollisionQuery.CollisionCache.ToEllipseLocal ); ground_p2 = Vector2.Transform( ground_p2 , LevelCollisionQuery.CollisionCache.ToEllipseLocal ); /* OLD UNOPTIMIZED COLLISION CODE: WHICH IS MANUALLY DOING THESE TRANSFORMS // Get both relative to the center of the elipse: ground_p1 = ground_p1 - elipsePosition; ground_p2 = ground_p2 - elipsePosition; // Rotate them according to the elipse rotation: we are essentially rotating the world so the elipse is not rotated anymore Matrix rot = Matrix.CreateRotationZ( - elipseRotation ); ground_p1 = Vector2.Transform( ground_p1 , rot ); ground_p2 = Vector2.Transform( ground_p2 , rot ); // Scale the worlds y coordinates so that the elipse becomes a circle: float world_y_scale = elipseDimensions.X / elipseDimensions.Y; ground_p1.Y *= world_y_scale; ground_p2.Y *= world_y_scale; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ */ // Get the line normal: Vector2 ground_normal = new Vector2 ( - ( ground_p2.Y - ground_p1.Y ) , + ( ground_p2.X - ground_p1.X ) ); ground_normal.Normalize(); // Get distance to the ground: float distance = - Vector2.Dot( ground_p1 , ground_normal ); // See if we are within range for a collision: if ( distance < elipseDimensions.X ) { // Calculate penetration amount: float penetration = elipseDimensions.X - distance; // Calculate the collision point relative to the elipse center: Vector2 collision_point = - distance * ground_normal; // Calculate how much the elipse must move by to avoid collision: Vector2 resolve_vec = penetration * ground_normal; // Transform the collision point and resolve direction back into normal coordinates { Vector4 v = Vector4.Zero; v.X = resolve_vec.X; v.Y = resolve_vec.Y; v = Vector4.Transform( v , LevelCollisionQuery.CollisionCache.FromEllipseLocal ); resolve_vec.X = v.X; resolve_vec.Y = v.Y; } collision_point = Vector2.Transform ( collision_point , LevelCollisionQuery.CollisionCache.FromEllipseLocal ); /* OLD UNOPTIMIZED COLLISION CODE: WHICH IS MANUALLY DOING THESE TRANSFORMS // Now undo the previous scaling: resolve_vec.Y /= world_y_scale; collision_point.Y /= world_y_scale; // Undo the previous rotation: Matrix rot_inverse = Matrix.CreateRotationZ(elipseRotation); resolve_vec = Vector2.Transform(resolve_vec,rot_inverse); collision_point = Vector2.Transform(collision_point,rot_inverse); // Make collision point back to world coordinates collision_point += elipsePosition; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ */ // Get penetration amount penetration = resolve_vec.Length(); // If the penetration is very small then abort: if ( penetration < 0.001f ) return 0; // Normalise resolve direction: resolve_vec /= penetration; // Save the result: if ( results_index < results.Length ) { // Make up the result: CollisionQueryResult c; c.ValidResult = true; c.QueryObject = this; c.Point = collision_point; c.Penetration = penetration; c.ResolveDirection = resolve_vec; c.Normal = Vector2.UnitY; c.IsPointCollision = false; // Save the result results[results_index] = c; // Got a valid result: return 1; } } // If we got to here there was no result return 0; }
//========================================================================================= /// <summary> /// Does rumbles for when the player jumps and hits a surface, and also for when the player /// is hit. /// </summary> //========================================================================================= private void UpdateRumbles() { // Make sure the gampad is connected: if ( GamePad.GetState(PlayerIndex.One).IsConnected == false ) return; // If the player is dead then set impact rumbles to off: if ( Health <= 0 ) { GamePad.SetVibration( PlayerIndex.One , 0 , 0 ); return; } // Figure out the new amount of rumble due to velocity change: // Get our current total velocity: Vector2 total_velocity = Velocity + MoveVelocity; // See if we have left or just hit a surface: if ( FlattestContactSurface.ValidResult != m_last_contact_surface.ValidResult ) { // Jumped or left a surface: get the difference in velocity between the last frame Vector2 velocity_difference = total_velocity - m_last_total_velocity; // Add to the current impact rumbling by this amount: m_current_impact_rumble += velocity_difference.Length() * IMPACT_RUMBLE_SCALE; } // Slow down impact rumbling m_current_impact_rumble *= IMPACT_RUMBLE_SLOWDOWN; // Do impact rumble dead zoning: if ( m_current_impact_rumble < IMPACT_RUMBLE_DEAD_ZONE ) m_current_impact_rumble = 0; // Save the current contact surface as the last: m_last_contact_surface = FlattestContactSurface; // Save the current total velocity as the last: m_last_total_velocity = total_velocity; // Figure out the amount of rumble due to pain: // Get the difference in health from the last frame: float health_difference = Health - m_last_health; // Negate and if negative then zero since that means we got health rather than lose it health_difference *= -1; if ( health_difference < 0 ){ health_difference = 0; } // Increase pain rumbling due to this change: m_current_pain_rumble += health_difference * PAIN_RUMBLE_SCALE; // Slow down pain rumbling m_current_pain_rumble *= PAIN_RUMBLE_SLOWDOWN; // Do pain rumble dead zoning: if ( m_current_pain_rumble < PAIN_RUMBLE_DEAD_ZONE ) m_current_pain_rumble = 0; // Save current health as the last: m_last_health = Health; // Set the rumble on the gamepad: GamePad.SetVibration(PlayerIndex.One,m_current_pain_rumble,m_current_impact_rumble); }
//========================================================================================= /// <summary> /// Orients the character according to the surfaces they are in contact with. /// This will cause the characters to run slightly up walls and orient to underlying terrain. /// Also determines a surface to jump off while its at it. /// </summary> //========================================================================================= private void OrientToSurface() { // Clear the jump surface: m_jump_surface.ValidResult = false; // Makeup a new bounding elipse for the character and expand it slightly: Vector2 expanded_elipse_dimensions = EllipseDimensions; expanded_elipse_dimensions.X += SURFACE_ORIENT_DISTANCE; expanded_elipse_dimensions.Y += SURFACE_ORIENT_DISTANCE; // Shorter: LevelCollisionQuery c = Core.Level.Collision; // Do collision detection with the level: c.Collide( Position , expanded_elipse_dimensions , m_surface_rotation , this ); // If the character is not in contact with any surfaces then straighten it's orientation if ( c.CollisionResultCount <= 0 ) { // See which way the surface angle of the character is: if ( m_surface_rotation < 0 ) { // Straigten up: m_surface_rotation += AIR_ORIENT_STRAIGHTEN; // Don't over do it: if ( m_surface_rotation > 0 ) m_surface_rotation = 0; } else { // Straigten up: m_surface_rotation -= AIR_ORIENT_STRAIGHTEN; // Don't over do it: if ( m_surface_rotation < 0 ) m_surface_rotation = 0; } // Bail out after this: surface orientation is done return; } /******* OLD WAY OF DOING THINGS *********************************************************************************************** // Make a weighted average resolve direction for all the things collided with: Vector2 average_resolve_dir = Vector2.Zero; for ( int i = 0 ; i < c.CollisionResultCount ; i++ ) { // Ignore this surface if it is almost or is a ceiling: if ( c.CollisionResults[i].Normal.Y < MIN_ORIENT_SURFACE_NORMAL_Y ) continue; // - old way - // // Get the speed of the character in relation to the surface normal: // // float dot = Math.Abs( Vector2.Dot( - m_velocity - m_move_velocity , c.CollisionResults[i].ResolveDirection ) ); // // Get the distance to the collision point: float d = Vector2.Distance( Position , c.CollisionResults[i].Point ); // Save this surface also as the jump surface if there is none or if it is flatter than the current: if ( m_jump_surface.ValidResult == false || m_jump_surface.Normal.Y < c.CollisionResults[i].Normal.Y ) { // New jump surface: save m_jump_surface = c.CollisionResults[i]; } // Add to the results: // - old way - // // average_resolve_dir += c.CollisionResults[i].ResolveDirection * dot; // average_resolve_dir += c.CollisionResults[i].ResolveDirection; } // If the resolve dir is still zero then bail: if ( average_resolve_dir.Length() == 0 ) return; // Otherwise normalise: average_resolve_dir.Normalize(); // Turn it into a surface direction: Vector2 surf_dir = new Vector2 ( + ( average_resolve_dir.Y ) , - ( average_resolve_dir.X ) ); // Get an up vector for the character rotated by the current surface rotation: Vector2 char_up_vec = Vector2.UnitY; // Make a rotation matrix: Matrix rot = Matrix.CreateRotationZ(m_surface_rotation); // Do the rotation: char_up_vec = Vector2.Transform( char_up_vec , rot ); //''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' // Get the cosine of the angle between the average resolve direction and rotated character // up vector: should be zero when character is perfectly oriented with the surface //''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' float cos = Vector2.Dot( surf_dir , char_up_vec ); // Get this cosine as an angle: but use sine function instead to get angular diference from the two vectors being perpendicular float angle = (float) Math.Asin(cos); // Now rotate the player towards being perpendicular with the underlying suface { // Save this variable temporarily: float t = m_surface_rotation; // Orient towards new orientation: m_surface_rotation *= SURFACE_ORIENT_SPEED; m_surface_rotation += ( t + angle ) * ( 1.0f - SURFACE_ORIENT_SPEED ); } *****************************************************************************************************************************/ // Store the average angle difference between the player and each surface here: float average_angle_difference = 0; // Find the average angle difference: for ( int i = 0 ; i < c.CollisionResultCount ; i++ ) { // Ignore this surface if the resolve dir is not right if ( c.CollisionResults[i].ResolveDirection.Y < MIN_ORIENT_SURFACE_NORMAL_Y ) continue; // Save this surface also as the jump surface if there is none or if it is flatter than the current: if ( m_jump_surface.ValidResult == false || m_jump_surface.Normal.Y < c.CollisionResults[i].Normal.Y ) { // New jump surface: save m_jump_surface = c.CollisionResults[i]; } // Get the angle of this surfaces normal: float angle = (float) Math.Acos( c.CollisionResults[i].ResolveDirection.X ); // Make so that up is angle zero: angle -= MathHelper.PiOver2; // Add to the average angle difference: average_angle_difference += angle - m_surface_rotation; } // Find the average angle difference: average_angle_difference /= c.CollisionResultCount; // Need this temporary: float t = m_surface_rotation; // Interpolate towards our new orientation: m_surface_rotation *= SURFACE_ORIENT_SPEED; m_surface_rotation += ( t + average_angle_difference ) * ( 1.0f - SURFACE_ORIENT_SPEED ); }
//===================================================================================== /// <summary> /// Collision query function. If an object is collideable it should implement it's collision /// testing code here and return the result of the collision. This collision test is for /// a bounding elipse against a piece of geometry, such as a collection of lines etc. /// </summary> /// <param name="elipsePosition"> Position of the bounding elipse </param> /// <param name="elipseDimensions"> Dimensions of the bounding elipse </param> /// <param name="elipseRotation"> Amount the elipse is rotated by in radians. </param> /// <param name="otherObject"> Object making the collision query, may be null. </param> /// <param name="results"> Array to save the results to. </param> /// <param name="results_index"> Index in the array to save the results to. </param> /// <returns> Number of results from the collision. </returns> //===================================================================================== public override int OnCollisionQuery( Vector2 elipsePosition , Vector2 elipseDimensions , float elipseRotation , GameObject otherObject , CollisionQueryResult[] results , int results_index ) { // If the index is past the end of the results array then do nothing: if ( results_index >= results.Length ) return 0; // Check out the collision cache and see if the ellipse is within bounds of our lines: if ( LevelCollisionQuery.CollisionCache.EllipseBoxTopLeft.X > m_lines_bb_bottom_right.X ) return 0; if ( LevelCollisionQuery.CollisionCache.EllipseBoxBottomRight.X < m_lines_bb_top_left.X ) return 0; if ( LevelCollisionQuery.CollisionCache.EllipseBoxTopLeft.Y < m_lines_bb_bottom_right.Y ) return 0; if ( LevelCollisionQuery.CollisionCache.EllipseBoxBottomRight.Y > m_lines_bb_top_left.Y ) return 0; // Store the number of results here: int num_results = 0; // Run through all the lines looking for a collision: for ( int i = 0 ; i < m_collision_lines.Length ; i++ ) { // Store collision results here: Vector2 collide_point = Vector2.Zero; Vector2 resolve_dir = Vector2.Zero; Vector2 collide_normal = Vector2.Zero; float penetration = 0; bool point_collision = false; // Test against this line: bool collision = m_collision_lines[i].FastCollide ( elipseDimensions.X , ref collide_point , ref resolve_dir , ref collide_normal , ref penetration , ref point_collision ); // See if there was a collision if ( collision ) { // Increment number of results: num_results++; // Makeup the result: CollisionQueryResult c; c.ValidResult = true; c.QueryObject = this; c.Point = collide_point; c.Normal = collide_normal; c.Penetration = penetration; c.ResolveDirection = resolve_dir; c.IsPointCollision = point_collision; // Save the result: results[results_index] = c; // Increment results index: results_index++; // If past the end then return number of results: if ( results_index >= results.Length ) return num_results; } } // Return the number of collision results return num_results; }
//========================================================================================= /// <summary> /// Checks to see if the character is on the ground or touching a surface such as a wall. /// This fucntion saves both the flatest and steepest surfaces. /// </summary> //========================================================================================= private void FindContactSurfaces() { // Makeup a new bounding elipse for the character and expand it slightly: Vector2 expanded_elipse_dimensions = EllipseDimensions; expanded_elipse_dimensions.X += CONTACT_SURFACE_DISTANCE; expanded_elipse_dimensions.Y += CONTACT_SURFACE_DISTANCE; // Shorten things: LevelCollisionQuery c = Core.Level.Collision; // Do collision detection with the level: c.Collide( Position , expanded_elipse_dimensions , m_surface_rotation , this ); // See if there is any results: if ( c.CollisionResultCount > 0 ) { // Got results: save the result with the flatest surface. Use the normal to determine this. int flattest_s_index = -1; for ( int i = 0 ; i < c.CollisionResultCount ; i++ ) { // See if this surface is flatter if ( flattest_s_index == -1 || c.CollisionResults[i].Normal.Y > c.CollisionResults[flattest_s_index].Normal.Y ) { // Only do if we are travelling towards the surface: if ( Vector2.Dot( m_velocity + m_move_velocity , c.CollisionResults[i].Normal ) <= 0.001f ) { // Flatter: save it's index flattest_s_index = i; } } } // Save the flattest contact surface found: if ( flattest_s_index >= 0 ) { m_flattest_contact_surface = Core.Level.Collision.CollisionResults[flattest_s_index]; } else { m_flattest_contact_surface = CollisionQueryResult.NoResult; } // Now find the steepest surface: again use the normal int steepest_s_index = -1; for ( int i = 0 ; i < Core.Level.Collision.CollisionResultCount ; i++ ) { // See if this surface is steeper if ( steepest_s_index == -1 || c.CollisionResults[i].Normal.Y < c.CollisionResults[steepest_s_index].Normal.Y ) { // Only do if we are travelling towards the surface: if ( Vector2.Dot( m_velocity + m_move_velocity , c.CollisionResults[i].Normal ) <= 0.001f ) { // Steeper: save it's index steepest_s_index = i; } } } // Save the steepest surface found: if ( steepest_s_index >= 0 ) { m_steepest_contact_surface = Core.Level.Collision.CollisionResults[steepest_s_index]; } else { m_steepest_contact_surface = CollisionQueryResult.NoResult; } } else { // No collision results: not in contact with anything m_flattest_contact_surface = CollisionQueryResult.NoResult; m_steepest_contact_surface = CollisionQueryResult.NoResult; } }