//========================================================================================= /// <summary> /// Creates the projectile with the specified settings. /// </summary> /// <param name="owner"> Object that created the projectile. </param> /// <param name="texture"> Texture to use for the projectile. </param> /// <param name="spark_texture"> Spark texture to use for a projectile spark. </param> /// <param name="effect"> Effect to render projectile with. </param> /// <param name="position"> Intiial position of the projectile. </param> /// <param name="live_time"> Amount of time the projectile has to live. </param> /// <param name="velocity"> Initial velocity of the projectile. </param> /// <param name="gravity"> Amount of gravity to apply to the projectile. </param> /// <param name="size"> Size of the projectile. </param> /// <param name="damage"> Amount of damage this projectile does </param> //========================================================================================= public EnemyProjectile( GameObject owner , Texture2D texture , Texture2D spark_texture , Effect effect , Vector2 position , Vector2 velocity , float live_time , float gravity , float size , float damage ) : base(owner, texture, spark_texture , effect, position, velocity, live_time, gravity, size, damage) { // This projectile has not been deflected m_isDeflected = false; }
//========================================================================================= /// <summary> /// Unregisters an object's type name with the scene. After this has finished the object will /// no longer be found when searching for objects with this type name. /// </summary> /// /// <param name="obj"> Object to unregister name for </param> //========================================================================================= private void UnregisterType( GameObject obj ) { // Abort if null object: if ( obj == null ) return; // Do nothing if the object has no ID or is not in the scene: if ( obj.Id == 0 || obj.ParentContainer != this || obj.ParentLevel != this.m_level ) return; // Get the object's type name: string typeName = obj.GetType().Name; // Make sure the hash contains the type name if ( m_types.ContainsKey( typeName ) ) { // Get the hash for this type name: Dictionary<int,GameObject> hash = m_types[typeName]; // See if this object exists in the hash if ( hash.ContainsKey( obj.Id ) ) { // Bingo: remove the object from the hash hash.Remove( obj.Id ); // If the hash is now empty then remove it from the parent hash if ( hash.Count == 0 ){ m_types.Remove( typeName ); } } } }
//========================================================================================= /// <summary> /// Registers an objects type name with the level data so the object can later be found by /// using this type name. Multiple objects can be registered with the same type. /// </summary> /// /// <param name="obj">Object to register type name for</param> //========================================================================================= private void RegisterType( GameObject obj ) { // Abort if null object: if ( obj == null ) return; // Do nothing if the object has no ID or is not in the scene: if ( obj.Id == 0 || obj.ParentContainer != this || obj.ParentLevel != this.m_level ) return; // Get the object's type name: string typeName = obj.GetType().Name; // Create a hash for objects with this type if not already in existance if ( m_types.ContainsKey( typeName ) == false ) { m_types.Add( typeName , new Dictionary<int,GameObject>() ); } // Add the object into the hash: m_types[typeName][obj.Id] = obj; }
//========================================================================================= /// <summary> /// Applys damage to the character. Virtual so that derived classes can implement special /// events for this action. /// </summary> /// <param name="amount"> Amount of damage to apply to the character. </param> /// <param name="damager"> Thing that damaged the character. May be null. </param> //========================================================================================= public override void Damage( float amount , GameObject damager ) { // Call base function: base.Damage(amount,damager); // Good: now get the AI sight module AI_Sight b_sight = (AI_Sight) m_behaviours.GetBehaviour("AI_Sight"); // If we have the sight module then suddenly jolt the player into visibilty: the ai should wake up after being hurt !!! if ( b_sight != null ) b_sight.AlertAI(); }
//===================================================================================== /// <summary> /// Intersect query function. If an object is collideable it should also implement code to /// do a line / line intersection test here. It should return all of the intersections /// found. /// </summary> /// <param name="lineStart"> Start point of the line involved. </param> /// <param name="lineEnd"> End point of the line involved. </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 intersection test. </returns> //===================================================================================== public override int OnIntersectQuery( Vector2 lineStart , Vector2 lineEnd , GameObject otherObject , IntersectQueryResult[] results , int results_index ) { // Abort if no room to save new result: if ( results_index >= results.Length ) return 0; // Store the result here: Vector2 intersect_point = Vector2.Zero; // Do it: bool intersection = m_line.IntersectInfinite( new Line(lineStart,lineEnd) , ref intersect_point ); // See if there was an intersection: if ( intersection ) { // Make up the result: IntersectQueryResult result; result.ValidResult = true; result.QueryObject = this; result.Point = intersect_point; result.Normal = Vector2.UnitY; result.PointDistance = Vector2.Distance(lineStart,intersect_point); // Save the result: results[results_index] = result; // Got a result: return 1 return 1; } else { // No result: return 0; } }
//========================================================================================= /// <summary> /// Preforms an overlap query on the level for the given rectangle. /// </summary> /// <param name="rectangle_position"> /// Center of the bounding rectangle to check for overlap with </param> /// <param name="rectangle_dimensions"> /// Dimensions of the bounding rectangle to check for overlap with, in +/- x and y directions /// from rectangle center. /// </param> /// <param name="caller"> /// Object making the overlap query, may be null. /// </param> //========================================================================================= public void Overlap( Vector2 rectangle_position , Vector2 rectangle_dimensions , GameObject caller ) { // Reset number of overlaps that have occured: m_overlap_result_count = 0; // Get enumerator for the list of overlapable level objects: Dictionary<int,GameObject>.Enumerator e = m_level.Data.OverlapableObjects.GetEnumerator(); // Run through the list of overlapable objects: while ( e.MoveNext() ) { // If this object is the same as the caller then skip it: if ( caller == e.Current.Value ) continue; // Otherwise do an overlap test and save the result: OverlapQueryResult result = e.Current.Value.OnOverlapQuery ( rectangle_position , rectangle_dimensions , caller ); // See if there was a result: if ( result.ValidResult ) { // Put into the overlaps list if not full already: if ( m_overlap_result_count < MAX_RESULTS ) { // On windows debug spit an error out if the object in the query is null: #if WINDOWS_DEBUG if ( result.QueryObject == null ) throw new NullReferenceException("Query result must have a valid query object !"); #endif // Put into the list m_overlap_results[m_overlap_result_count] = result; // Increment the number of intersections: m_overlap_result_count++; } } } }
//========================================================================================= /// <summary> /// Preforms a collision query on the level for the given elipse. /// </summary> /// <param name="ellipse_pos"> Center of the bounding elipse. </param> /// <param name="ellipse_scale"> Dimensions of the elipse in +/- x and y directions from elipse center. </param> /// <param name="ellipse_rot"> Amount the elipse is rotated by in radians. </param> /// <param name="caller"> Object performing the query. This object will not be included in the test. </param> //========================================================================================= public void Collide( Vector2 ellipse_pos , Vector2 ellipse_scale , float ellipse_rot , GameObject caller ) { //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- // Update the collision cache: { // Figure out how much the elipse must be scaled in the y direction to make it a circle float circlelizing_scale = ellipse_scale.X / ellipse_scale.Y; // Make the to ellipse local coordinate system transform matrix: CollisionCache.ToEllipseLocal = Matrix.CreateTranslation( - ellipse_pos.X , - ellipse_pos.Y , 0 ) * Matrix.CreateRotationZ( - ellipse_rot ) * Matrix.CreateScale( 1 , circlelizing_scale , 1 ); // Now make the opposite transform: CollisionCache.FromEllipseLocal = Matrix.CreateScale( 1 , 1.0f / circlelizing_scale , 1 ) * Matrix.CreateRotationZ( ellipse_rot ) * Matrix.CreateTranslation( ellipse_pos.X , ellipse_pos.Y , 0 ); // Rotate the four axis vectors of the ellipse: Vector2 v1 = Vector2.UnitX * ellipse_scale.X; Vector2 v2 = - Vector2.UnitX * ellipse_scale.X; Vector2 v3 = Vector2.UnitY * ellipse_scale.Y; Vector2 v4 = - Vector2.UnitY * ellipse_scale.Y; { Matrix rot = Matrix.CreateRotationZ( ellipse_rot ); v1 = Vector2.Transform( v1 , rot ); v2 = Vector2.Transform( v2 , rot ); v3 = Vector2.Transform( v3 , rot ); v4 = Vector2.Transform( v4 , rot ); } // Figure out the bounding box dimensions for the ellipse: CollisionCache.EllipseBoxTopLeft = v1; CollisionCache.EllipseBoxBottomRight = v1; if ( v2.X < CollisionCache.EllipseBoxTopLeft.X ) CollisionCache.EllipseBoxTopLeft.X = v2.X; if ( v3.X < CollisionCache.EllipseBoxTopLeft.X ) CollisionCache.EllipseBoxTopLeft.X = v3.X; if ( v4.X < CollisionCache.EllipseBoxTopLeft.X ) CollisionCache.EllipseBoxTopLeft.X = v4.X; if ( v2.X > CollisionCache.EllipseBoxBottomRight.X ) CollisionCache.EllipseBoxBottomRight.X = v2.X; if ( v3.X > CollisionCache.EllipseBoxBottomRight.X ) CollisionCache.EllipseBoxBottomRight.X = v3.X; if ( v4.X > CollisionCache.EllipseBoxBottomRight.X ) CollisionCache.EllipseBoxBottomRight.X = v4.X; if ( v2.Y > CollisionCache.EllipseBoxTopLeft.Y ) CollisionCache.EllipseBoxTopLeft.Y = v2.Y; if ( v3.Y > CollisionCache.EllipseBoxTopLeft.Y ) CollisionCache.EllipseBoxTopLeft.Y = v3.Y; if ( v4.Y > CollisionCache.EllipseBoxTopLeft.Y ) CollisionCache.EllipseBoxTopLeft.Y = v4.Y; if ( v2.Y < CollisionCache.EllipseBoxBottomRight.Y ) CollisionCache.EllipseBoxBottomRight.Y = v2.Y; if ( v3.Y < CollisionCache.EllipseBoxBottomRight.Y ) CollisionCache.EllipseBoxBottomRight.Y = v3.Y; if ( v4.Y < CollisionCache.EllipseBoxBottomRight.Y ) CollisionCache.EllipseBoxBottomRight.Y = v4.Y; // Bring box points into world coords: CollisionCache.EllipseBoxTopLeft += ellipse_pos; CollisionCache.EllipseBoxBottomRight += ellipse_pos; } // end of update collision cache //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- // Reset number of collisions that have occured: m_collision_result_count = 0; // Get enumerator for the list of collideable level objects: Dictionary<int,GameObject>.Enumerator e = m_level.Data.CollideableObjects.GetEnumerator(); // Run through the list of collideable objects: while ( e.MoveNext() ) { // If this object is the same as the caller then skip it: if ( caller == e.Current.Value ) continue; // Otherwise do a collision test: int num_results = e.Current.Value.OnCollisionQuery ( ellipse_pos , ellipse_scale , ellipse_rot , caller , m_collision_results , m_collision_result_count ); // Make sure number of results is positive: if ( num_results < 0 ) num_results = 0; // Increase number of collision results: m_collision_result_count += num_results; // Make sure in range: if ( m_collision_result_count > MAX_RESULTS ) m_collision_result_count = MAX_RESULTS; } }
//========================================================================================= /// <summary> /// Creates collision query result. /// </summary> /// <param name="validResult"> If this is a valid collision query result </param> /// <param name="queryObject"> Object involved in the collision query </param> /// <param name="point"> Point where the collision happened </param> /// <param name="normal"> Normal at the point of collision </param> /// <param name="penetration"> Amount of penetration into geometry by the elipse </param> /// <param name="resolveDirection"> Direction the elipse must move in to resolve collision </param> /// <param name="isPointCollision"> /// Tells if the collision is a point collision. If so then the elipse has collided with a /// point at the end of a line and not the line's plane itself. /// </param> //========================================================================================= public CollisionQueryResult( bool validResult , GameObject queryObject , Vector2 point , Vector2 normal , float penetration , Vector2 resolveDirection , bool isPointCollision ) { ValidResult = validResult; QueryObject = queryObject; Point = point; Normal = normal; Penetration = penetration; ResolveDirection = resolveDirection; IsPointCollision = isPointCollision; }
//===================================================================================== /// <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> /// Intersect query function. If an object is collideable it should also implement code to /// do a line / line intersection test here. It should return all of the intersections /// found. /// </summary> /// <param name="lineStart"> Start point of the line involved. </param> /// <param name="lineEnd"> End point of the line involved. </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 intersection test. </returns> //===================================================================================== public override int OnIntersectQuery( Vector2 lineStart , Vector2 lineEnd , GameObject otherObject , IntersectQueryResult[] results , int results_index ) { // Abort if there is no more room for results: 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.IntersectionCache.RayBoxTopLeft.X > m_lines_bb_bottom_right.X ) return 0; if ( LevelCollisionQuery.IntersectionCache.RayBoxBottomRight.X < m_lines_bb_top_left.X ) return 0; if ( LevelCollisionQuery.IntersectionCache.RayBoxTopLeft.Y < m_lines_bb_bottom_right.Y ) return 0; if ( LevelCollisionQuery.IntersectionCache.RayBoxBottomRight.Y > m_lines_bb_top_left.Y ) return 0; // Store the number of results here: int num_results = 0; // Make a new line to test with: Line line = new Line( lineStart , lineEnd ); // Collide the line with each of this object's lines: for ( int i = 0 ; i < m_collision_lines.Length ; i++ ) { // Store the collision point here: Vector2 intersection_point = Vector2.Zero; // Do the intersection: bool lines_intersect = m_collision_lines[i].Intersect( line , ref intersection_point ); // See if we got an intersection if ( lines_intersect ) { // Increment number of results: num_results++; // Get vector from line start to point of intersection Vector2 r = intersection_point - lineStart; // Get the distance to the point float intersection_distance = Vector2.Dot( r , line.Direction ); // Make up the result: IntersectQueryResult result; result.ValidResult = true; result.QueryObject = this; result.Point = intersection_point; result.Normal = m_collision_lines[i].Normal; result.PointDistance = intersection_distance; // Save the result: results[results_index] = result; // 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> /// Creates an intersect query result. /// </summary> /// <param name="validResult"> If this is a valid intersect query result </param> /// <param name="queryObject"> Object involved in the intersect query </param> /// <param name="point"> Point where the intersection happened </param> /// <param name="normal"> Normal at the point of intersection </param> /// <param name="pointDistance"> Distance from the start point of the ray cast the intersection point is </param> //========================================================================================= public IntersectQueryResult( bool validResult , GameObject queryObject , Vector2 point , Vector2 normal , float pointDistance ) { ValidResult = validResult; QueryObject = queryObject; Point = point; Normal = normal; PointDistance = pointDistance; }
//========================================================================================= /// <summary> /// Adds the given amount to the player's score. Note that every time this is done, the /// player's combat multiplier goes up in score. /// </summary> /// <param name="amount"> Amount to modify the player's score by. </param> /// <param name="caller"> Thing that is giving the score to the player. </param> //========================================================================================= public void GivePlayerScore( float amount , GameObject caller ) { // If amount is negative then abort if ( amount <= 0 ) return; // Increase our score: m_player_score += amount * m_player_combat_multiplier; // Don't allow score to be negative: if ( m_player_score < 0 ) m_player_score = 0; // Try and find the floating scores object: FloatingScores scores = (FloatingScores) Core.Level.Search.FindByType("FloatingScores"); // If found then make a new floating score at the position of the caller: if ( scores != null && caller != null ) { // Make new score: scores.AddScore( amount * m_player_combat_multiplier , caller.Position ); } // Increase combat multiplier: m_player_combat_multiplier++; }
//========================================================================================= /// <summary> /// Applys damage to the character. Virtual so that derived classes can implement special /// events for this action. /// </summary> /// <param name="amount"> Amount of damage to apply to the character. </param> /// <param name="damager"> Thing that damaged the character. May be null. </param> //========================================================================================= public override void Damage( float amount , GameObject damager ) { // Call base function: base.Damage(amount,damager); // If we can do blood then do it: if ( m_time_to_blood <= 0 ) { // Pick random time to next blood: float r = (float) Core.Random.NextDouble(); // Pick time till next blood: m_time_to_blood = BLOOD_INTERVAL_1 * r + ( 1.0f - r ) * BLOOD_INTERVAL_2; // See if there was an object given: if ( damager != null ) { // Get direction to the object: Vector2 dir = damager.Position - Position; // Randomnly rotate the direction: Matrix rot = Matrix.CreateRotationZ( 0.25f * (float) Core.Random.NextDouble() - 0.125f ); // Rotate direction randomnly: dir = Vector2.Transform( dir , rot ); // First blood color to use: Vector4 blood_color1 = Vector4.One; blood_color1.X = 1.0f; blood_color1.Y = 1.0f; blood_color1.Z = 1.0f; blood_color1.W = 0.35f; // Second blood color to use: Vector4 blood_color2 = Vector4.One; blood_color2.X = 0.25f; blood_color2.Y = 0.25f; blood_color2.Z = 0.25f; blood_color2.W = 0.65f; // Spawn blood: Core.Level.Emitter.CreateDirectedBurst ( 100 , m_blood_texture , Position , dir , 0.25f , 0.25f , 0.75f , 12.0f , 18.0f , blood_color1 , blood_color2 , 50 , 300 , 1000 , true ); } } }
//''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' /// <summary> /// Constructor for the pending level change structure. /// </summary> /// <param name="o"> Object in question. </param> /// <param name="t"> Type of level change. </param> /// <param name="n"> Name of the object at type of change. For name changes only!</param> //''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' public PendingLevelChange( GameObject o , PendingLevelChangeType t , string n ) { obj = o; changeType = t; objectName = n; }
//========================================================================================= /// <summary> /// Applys damage to the character. Virtual so that derived classes can implement special /// events for this action. /// </summary> /// <param name="amount"> Amount of damage to apply to the character. </param> /// <param name="damager"> Thing that damaged the character. May be null. </param> //========================================================================================= public override void Damage( float amount , GameObject damager ) { // Do base class function base.Damage(amount,damager); // Reset our combat multiplier: find the game rules object first though LevelRules rules = (LevelRules) Core.Level.Search.FindByType("LevelRules"); // See if there: if ( rules != null ) rules.ResetCombatMultplier(); }
//========================================================================================= /// <summary> /// Creates an overlap query result. /// </summary> /// <param name="validResult"> If this is a valid overlap query result </param> /// <param name="queryObject"> Object involved in the overlap query </param> /// <param name="regionPosition"> Center of the region where the overlap occured </param> /// <param name="regionDimensions"> Dimensions of the overlap region in +/- x and y directions from region center </param> /// <param name="regionArea"> Area of the overlap region. </param> //========================================================================================= public OverlapQueryResult( bool validResult , GameObject queryObject , Vector2 regionPosition , Vector2 regionDimensions , float regionArea ) { ValidResult = validResult; QueryObject = queryObject; RegionPosition = regionPosition; RegionDimensions = regionDimensions; RegionArea = regionArea; }
//========================================================================================= /// <summary> /// Adds a given game object into the level data block. /// </summary> /// /// <param name="obj"> GameObject to add </param> //========================================================================================= public void Add( GameObject obj ) { // Make sure object not in another scene: if ( obj.Id != 0 || obj.ParentLevel != null || obj.ParentContainer != null ) return; // If the level data is locked then just add to the list of pending level data changes: if ( m_lock_count != 0 ) { // Add this change to the list: m_pending_changes.AddLast ( new PendingLevelChange ( obj , PendingLevelChangeType.ADD_OBJECT , obj.Name ) ); // Abort: do not actually add the object into the list - we will do this later on Unlock() return; } // Choose an id for the object: int id = m_next_id; // If there are any free ids from deletion then use them: otherwise use the next free id and increment it if ( m_free_ids.Count > 0 ) { // Use the next free id gotten from deletion: id = m_free_ids.First.Value; m_free_ids.RemoveFirst(); } else { // No id's ready from deletion: use the next free id and increment it m_next_id++; } // Set ID, parent level and container: obj.Id = id; obj.ParentLevel = this.m_level; obj.ParentContainer = this; // Add to the appropriate dictionaries: m_objects.Add( obj.Id , obj ); if ( obj.Renderable ) m_renderables.Add ( obj.Id , obj ); if ( obj.Updateable ) m_updateables.Add ( obj.Id , obj ); if ( obj.Collideable ) m_collideables.Add ( obj.Id , obj ); if ( obj.Overlapable ) m_overlapables.Add ( obj.Id , obj ); // Register the objects name and type for later quick lookup RegisterName(obj,obj.Name); RegisterType(obj); }
//========================================================================================= /// <summary> /// Creates the projectile with the specified settings. /// </summary> /// <param name="owner"> Object that created the projectile. </param> /// <param name="texture"> Texture to use for the projectile. </param> /// <param name="spark_texture"> Spark texture to use for a projectile spark. </param> /// <param name="effect"> Effect to render projectile with. </param> /// <param name="position"> Intiial position of the projectile. </param> /// <param name="live_time"> Amount of time the projectile has to live. </param> /// <param name="velocity"> Initial velocity of the projectile. </param> /// <param name="gravity"> Amount of gravity to apply to the projectile. </param> /// <param name="size"> Size of the projectile. </param> /// <param name="damage"> Amount of damage this projectile does </param> //========================================================================================= public Projectile( GameObject owner , Texture2D texture , Texture2D spark_texture , Effect effect , Vector2 position , Vector2 velocity , float live_time , float gravity , float size , float damage ) : base(false,true,false) { // Get the character type: m_character_type = Type.GetType("NinjaGame.Character"); // Throw a wobbler in debug if not there: #if DEBUG if ( m_character_type == null ) throw new Exception("Code missing: no Character type got for projectile"); #endif // Save all the stuff m_owner = owner; Texture = texture; m_spark_texture = spark_texture; Effect = effect; Position = position; m_velocity = velocity; m_gravity = gravity; m_live_time = live_time; m_damage = damage; // Set the size of the projectile: BoxDimensionsX = size; BoxDimensionsY = size; // Set default depth: Depth = 4500; }
//========================================================================================= /// <summary> /// Registers an objects name with the scene so the object can later be found by using this /// name. The name does not have to be unique and multiple objects could be retrieved under /// the same name. /// </summary> /// /// <param name="obj"> Object to register name for </param> /// <param name="name"> Name to register the object under </param> //========================================================================================= public void RegisterName( GameObject obj , string name ) { // Abort if null object: if ( obj == null ) return; // Do nothing if the object has no ID or is not in the scene: if ( obj.Id == 0 || obj.ParentContainer != this || obj.ParentLevel != this.m_level ) return; // Do nothing if no name was given: if ( name == null || name.Length <= 0 ) return; // If the level data is locked then just add to the list of pending level data changes: if ( m_lock_count != 0 ) { // Add this change to the list: m_pending_changes.AddLast ( new PendingLevelChange ( obj , PendingLevelChangeType.REGISTER_NAME , name ) ); // Abort: do not actually add the object into the list - we will do this later on Unlock() return; } // Make the name lowercase: string nameLower = name.ToLower(); // Create a hash for objects with this name if not already in existance if ( m_names.ContainsKey( nameLower ) == false ) { m_names.Add( nameLower , new Dictionary<int,GameObject>() ); } // Add the object into the hash: m_names[nameLower][obj.Id] = obj; }
//========================================================================================= /// <summary> /// Preforms a intersection query on the level for the given line. /// </summary> /// <param name="line_start"> Start of the line </param> /// <param name="line_end"> End of the line </param> /// <param name="caller"> Object performing the query. This object will not be included in the test. </param> //========================================================================================= public void Intersect( Vector2 line_start , Vector2 line_end , GameObject caller ) { //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- // Update the intersection query cache: { // Figure out the bounds of the bounding box surrounding the ray: IntersectionCache.RayBoxTopLeft = line_start; IntersectionCache.RayBoxBottomRight = line_start; if ( line_end.X < IntersectionCache.RayBoxTopLeft.X ) { IntersectionCache.RayBoxTopLeft.X = line_end.X; } if ( line_end.X > IntersectionCache.RayBoxBottomRight.X ) { IntersectionCache.RayBoxBottomRight.X = line_end.X; } if ( line_end.Y > IntersectionCache.RayBoxTopLeft.Y ) { IntersectionCache.RayBoxTopLeft.Y = line_end.Y; } if ( line_end.Y < IntersectionCache.RayBoxBottomRight.Y ) { IntersectionCache.RayBoxBottomRight.Y = line_end.Y; } } // end of update collision cache //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- // Reset number of intersections that have occured: m_intersect_result_count = 0; // Get enumerator for the list of collideable level objects: Dictionary<int,GameObject>.Enumerator e = m_level.Data.CollideableObjects.GetEnumerator(); // Run through the list of collideable objects: while ( e.MoveNext() ) { // If this object is the same as the caller then skip it: if ( caller == e.Current.Value ) continue; // Otherwise do an intersection test and save the number of results: int num_results = e.Current.Value.OnIntersectQuery ( line_start , line_end , caller , m_intersect_results , m_intersect_result_count ); // Make sure it is in range: if ( num_results < 0 ) num_results = 0; // Add to current intersect result count: m_intersect_result_count += num_results; // Clamp to within range: if ( m_intersect_result_count > MAX_RESULTS ) m_intersect_result_count = MAX_RESULTS; } }
//========================================================================================= /// <summary> /// Removes the given GameObject from the level. /// </summary> /// /// <param name="obj"> GameObject to remove</param> //========================================================================================= public void Remove( GameObject obj ) { // Do nothing if objet is null: if ( obj == null ) return; // Do nothing if the object has no ID or is not in the scene: if ( obj.Id == 0 || obj.ParentContainer != this || obj.ParentLevel != this.m_level ) return; // See if object exists in scene: if ( m_objects.ContainsKey( obj.Id ) ) { // If the level data is locked then just add to the list of pending level data changes: if ( m_lock_count != 0 ) { // Add this change to the list: m_pending_changes.AddLast ( new PendingLevelChange ( obj , PendingLevelChangeType.REMOVE_OBJECT , obj.Name ) ); // Abort: do not actually add the object into the list - we will do this later on Unlock() return; } // Cleanup after the object: obj.OnDelete(); // Unregister object name and type: UnregisterName(obj,obj.Name); UnregisterType(obj); // Remove object from all containers: m_objects.Remove( obj.Id ); if ( m_renderables.ContainsKey(obj.Id) ) m_renderables.Remove(obj.Id); if ( m_updateables.ContainsKey(obj.Id) ) m_updateables.Remove(obj.Id); if ( m_collideables.ContainsKey(obj.Id) ) m_collideables.Remove(obj.Id); if ( m_overlapables.ContainsKey(obj.Id) ) m_overlapables.Remove(obj.Id); // Add this id to the list of free ids m_free_ids.AddLast(obj.Id); // Clear all the object's scene related variables obj.Id = 0; obj.ParentLevel = null; obj.ParentContainer = null; } }
//===================================================================================== /// <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> /// Unregisters an object's name with the scene. After this has finished the object will /// no longer be found when searching for objects with this name. /// </summary> /// /// <param name="obj"> Object to unregister name for </param> /// <param name="name"> Name that the object is registered under </param> //========================================================================================= public void UnregisterName( GameObject obj , string name ) { // Abort if null object: if ( obj == null ) return; // Do nothing if the object has no ID or is not in the scene: if ( obj.Id == 0 || obj.ParentContainer != this || obj.ParentLevel != this.m_level ) return; // Do nothing if no name was given: if ( name == null || name.Length <= 0 ) return; // If the level data is locked then just add to the list of pending level data changes: if ( m_lock_count != 0 ) { // Add this change to the list: m_pending_changes.AddLast ( new PendingLevelChange ( obj , PendingLevelChangeType.UNREGISTER_NAME , name ) ); // Abort: do not actually add the object into the list - we will do this later on Unlock() return; } // Make the name lowercase: string nameLower = name.ToLower(); // Make sure the hash contains the name if ( m_names.ContainsKey( nameLower ) ) { // Get the hash for this name: Dictionary<int,GameObject> hash = m_names[nameLower]; // See if this object exists in the hash if ( hash.ContainsKey( obj.Id ) ) { // Bingo: remove the object from the hash hash.Remove( obj.Id ); // If the hash is now empty then remove it from the parent hash if ( hash.Count == 0 ){ m_names.Remove( nameLower ); } } } }
//========================================================================================= /// <summary> /// Moves the camera towards an object so that it appears to follow the object. /// </summary> /// <param name="obj"> Object to follow </param> /// <param name="interpolateAmount"> /// Amount that the camera should move over to the object's position by. If this is 1 then /// the camera instantly assumes the position of the object. If this is 0 then the camera /// never will assume the position of the object. /// </param> //========================================================================================= public void Follow( GameObject obj , float interpolateAmount ) { // Clamp interpolate amount from 0-1 if ( interpolateAmount < 0 ) interpolateAmount = 0; if ( interpolateAmount > 1 ) interpolateAmount = 1; // Make the camera follow the object: Position = Position * ( 1.0f - interpolateAmount ) + obj.Position * interpolateAmount; }
//========================================================================================= /// <summary> /// Applys damage to the character. Virtual so that derived classes can implement special /// events for this action. /// </summary> /// <param name="amount"> Amount of damage to apply to the character. </param> /// <param name="damager"> Thing that damaged the character. May be null. </param> //========================================================================================= public virtual void Damage( float amount , GameObject damager ) { // If the amount is nothing then do not bother: if ( amount <= 0 ) return; // Damage the character: m_health -= amount; // If health is less than zero: make zero if ( m_health < 0 ) m_health = 0; }