/// <summary> /// Checks if a particular dodge is valid /// </summary> private bool isDodgeValid(DynamicObject callingAI,float dodgeAngle,int dodgeAngleMultiplierYaw,int dodgeAngleMultiplierPitch,PathInformation pathInfo, StaticObject closestObstruction,float dodgeDistance, ref Vector3 dodgeWp) { bool bFlag = false; //Define a conal area around the current path to choose another path from Quaternion qRot = Quaternion.CreateFromYawPitchRoll(dodgeAngle * dodgeAngleMultiplierYaw, dodgeAngle * dodgeAngleMultiplierPitch, 0); Vector3 choiceVector = Vector3.Normalize( Vector3.Transform(pathInfo.currentWaypoint.Position - callingAI.Position, Matrix.CreateFromQuaternion(qRot))); dodgeWp = callingAI.Position + choiceVector * dodgeDistance; if ((dodgeWp - closestObstruction.Position).Length() > dodgeDistance) { foreach (GridObjectInterface o in spatialGrid.checkNeighbouringBlocks(dodgeWp)) { if (o != callingAI) { if (o is StaticObject) { if ((o.Position - dodgeWp).Length() > (o.getBoundingSphere().Radius + callingAI.getBoundingSphere().Radius * (DODGE_DISTANCE_MULTIPLIER))) bFlag = true; } if (o is DynamicObject) { if (isObjectRegistered(o as DynamicObject)) { Node otherObjectsWaypoint = objectPaths[o as DynamicObject].currentWaypoint; if (otherObjectsWaypoint != null) { if ((otherObjectsWaypoint.Position - dodgeWp).Length() < (o.getBoundingSphere().Radius + callingAI.getBoundingSphere().Radius * (DODGE_DISTANCE_MULTIPLIER))) bFlag = false; } } } if (!spatialGrid.isInGrid(new Node(dodgeWp,-1))) bFlag = false; } } } return bFlag; }
/// <summary> /// Checks if there are any obstructions in close proximity which must be dodged /// </summary> /// <param name="AIObject">ai object to check around</param> /// <returns>list of possible obstructions</returns> private List<StaticObject> obstructionsInCloseProximity(DynamicObject AIObject) { List<StaticObject> results = new List<StaticObject>(); foreach (GridObjectInterface obj in this.spatialGrid.checkNeighbouringBlocks(AIObject)) { if (obj is StaticObject && !(obj is Bullet || obj is Missile)) { float radiusToCheck = (obj.getBoundingSphere().Radius + AIObject.getBoundingSphere().Radius) * DODGE_DISTANCE_MULTIPLIER; if ((AIObject.Position - obj.Position).Length() <= radiusToCheck) { results.Add(obj as StaticObject); } } } return results; }
/// <summary> /// Method to test if a collision will occur on the current route of the calling ai (and which collision will occur first). If such /// a possible collision is detected the callingAI will attempt to dodge the object. /// </summary> /// <param name="callingAI">AI that the test is performed for.</param> /// <param name="obstructionsList">A list of obstructions as returned by PathIntersectTest</param> private void avoidCollisions(DynamicObject callingAI, List<StaticObject> obstructionsList) { //find the closest obstruction and dodge it: if (obstructionsList.Count == 0) return; PathInformation pathInfo = objectPaths[callingAI]; StaticObject closestObstruction = obstructionsList.First(); float closestObstructionDistance = (closestObstruction.Position - callingAI.Position).Length(); for (int i = 1; i < obstructionsList.Count; ++i) { StaticObject obstruction = obstructionsList.ElementAt(i); float distanceToObstruction = (obstruction.Position - callingAI.Position).Length(); if (distanceToObstruction < closestObstructionDistance) { closestObstruction = obstruction; closestObstructionDistance = distanceToObstruction; } } if (closestObstruction is DynamicObject) if (isObjectRegistered(closestObstruction as DynamicObject)) if (objectPaths[closestObstruction as DynamicObject].currentWaypoint != null) if (objectPaths[closestObstruction as DynamicObject].currentWaypoint.connectedEdges.Count == 0 || closestObstruction is playerObject) return; // that object is dodging already don't make it stop //Make the obstruction yield for the next few steps: if (closestObstruction is DynamicObject) if (!movementYieldList.Keys.Contains(closestObstruction as DynamicObject)) movementYieldList.Add(closestObstruction as DynamicObject, YIELD_TICKS); //Now dodge it: dodgeObject(callingAI, closestObstruction); }
/// <summary> /// Method to set the path of an ai unit in order to dodge an object /// </summary> /// <param name="callingAI">ai to perform dodge</param> /// <param name="closestObstruction">closest obstruction</param> private void dodgeObject(DynamicObject callingAI, StaticObject closestObstruction) { if (!this.isObjectRegistered(callingAI)) return; PathInformation pathInfo = objectPaths[callingAI]; if (pathInfo.currentWaypoint == null) return; if (!dodgeInactiveCountDown.Keys.Contains(callingAI)) dodgeInactiveCountDown.Add(callingAI, RETAIN_DODGE_PATH_TICKS); else return; Vector3 dodgeWp = new Vector3(); bool bFlag = false; // Set the new path: float dodgeDistance = (callingAI.getBoundingSphere().Radius + closestObstruction.getBoundingSphere().Radius) * DODGE_DISTANCE_MULTIPLIER; float distanceToObject = (callingAI.Position - closestObstruction.Position).Length(); float distanceToCurrentWp = (callingAI.Position - pathInfo.currentWaypoint.Position).Length(); float dodgeAngle = (float)Math.Abs(Math.Atan2(distanceToObject, dodgeDistance)); for (int i = (int)Math.Ceiling(dodgeAngle); i * dodgeAngle < Math.PI; ++i) { for (int j = (int)Math.Ceiling(dodgeAngle); j * dodgeAngle < Math.PI; ++j) { if (isDodgeValid(callingAI, dodgeAngle, i, j, pathInfo, closestObstruction, dodgeDistance, ref dodgeWp)) { bFlag = true; break; } if (isDodgeValid(callingAI, -dodgeAngle, i, j, pathInfo, closestObstruction, dodgeDistance, ref dodgeWp)) { bFlag = true; break; } } if (bFlag) break; } List<Node> path = pathInfo.remainingPath; if (path.Count > 0) path.Remove(path.Last()); path.Add(new Node(dodgeWp, -1)); pathInfo.remainingPath = path; }
/// <summary> /// Sets a new path for an object if it is already registered /// </summary> /// <param name="AIObject">Any registered dynamic object</param> /// <param name="start">Start node (should be close to the object if possible)</param> /// <param name="end">End node</param> public void setNewPathForRegisteredObject(DynamicObject AIObject, Node start, Node end) { if (objectPaths.Keys.Contains(AIObject)) { List<Node> path = AStar(start, end, AIObject is Destroyer); if (path != null) objectPaths[AIObject].remainingPath = path; else //we just go the the start node { path = new List<Node>(); path.Add(start); objectPaths[AIObject].remainingPath = path; } } }
/// <summary> /// Turns the ai to face towards the a waypoint /// </summary> /// <param name="vWantDir"></param> /// <param name="vLookDir"></param> /// <param name="objectToTurn"></param> /// <param name="currentWaypoint"></param> /// <param name="gt"></param> internal void turnAI(ref Vector3 vWantDir, ref Vector3 vLookDir, DynamicObject objectToTurn, Vector3 currentWaypoint, GameTime gt) { //Calculate yaw and pitch for view direction and target direction vWantDir = Vector3.Normalize(currentWaypoint - objectToTurn.Position); vLookDir = Vector3.Normalize(-Matrix.CreateFromQuaternion(objectToTurn.rotation).Forward); float distance = (float)Math.Sqrt(vWantDir.Z * vWantDir.Z + vWantDir.X * vWantDir.X); float tpitch = distance == 0 ? (float)Math.Sign(-vWantDir.Y) * (float)Math.PI / 2 : -(float)Math.Atan2(vWantDir.Y, distance); float tyaw = (float)Math.Atan2(vWantDir.X, vWantDir.Z); distance = (float)Math.Sqrt(vLookDir.Z * vLookDir.Z + vLookDir.X * vLookDir.X); float cyaw = (float)Math.Atan2(vLookDir.X, vLookDir.Z); float cpitch = distance == 0 ? (float)Math.Sign(-vLookDir.Y) * (float)Math.PI / 2 : -(float)Math.Atan2(vLookDir.Y, distance); //now rotate towards the target yaw and pitch float diffy = tyaw - cyaw; float diffp = tpitch - cpitch; //get the direction we need to rotate in: if (Math.Abs(diffy) > Math.PI) if (tyaw > cyaw) diffy = -(float)(Math.PI * 2 - Math.Abs(diffy)); else diffy = (float)(Math.PI * 2 - Math.Abs(diffy)); if (Math.Abs(diffp) > Math.PI) if (tpitch > cpitch) diffp = -(float)(Math.PI * 2 - Math.Abs(diffp)); else diffp = (float)(Math.PI * 2 - Math.Abs(diffp)); if (Math.Abs(diffp) > Math.Abs(objectToTurn.getpitchSpeed) * (float)(gt.ElapsedGameTime.TotalSeconds)) diffp = Math.Sign(diffp) * Math.Abs(objectToTurn.getpitchSpeed) * (float)(gt.ElapsedGameTime.TotalSeconds); if (Math.Abs(diffy) > Math.Abs(objectToTurn.getYawSpeed) * (float)(gt.ElapsedGameTime.TotalSeconds)) diffy = Math.Sign(diffy) * Math.Abs(objectToTurn.getYawSpeed) * (float)(gt.ElapsedGameTime.TotalSeconds); //special case: deal with the pitch if its PI/2 or -PI/2, because if its slightly off it causes problems: if (Math.Abs(Math.Abs(tpitch) - Math.PI / 2) <= EPSILON_DISTANCE && !(Math.Abs(diffy) <= EPSILON_DISTANCE)) objectToTurn.rotation = Quaternion.CreateFromYawPitchRoll(tyaw, tpitch, 0); else objectToTurn.rotation = Quaternion.CreateFromYawPitchRoll(cyaw + diffy, cpitch + diffp, 0); }
/// <summary> /// Method to register an object for computer-based navigation /// </summary> /// <param name="AIObject">Any dynamic object capable of moving</param> public void registerObject(DynamicObject AIObject) { if (!objectPaths.Keys.Contains(AIObject)) objectPaths.Add(AIObject, new PathInformation()); }
/// <summary> /// Checks if an object is registered in the navigation computer /// </summary> /// <param name="AIObject">Object to check</param> /// <returns>true iff object is registered</returns> public bool isObjectRegistered(DynamicObject AIObject) { return objectPaths.Keys.Contains(AIObject); }
/// <summary> /// Method to get the current Path of a registered object /// </summary> /// <param name="o">registered object</param> /// <returns>path if it exists. Either an empty list or Null otherwise</returns> public List<Node> getPath(DynamicObject o) { return objectPaths[o].remainingPath; }
/// <summary> /// Performs a dogfight move against the opponent /// </summary> /// <param name="ti">Team on which the ai is registered</param> /// <param name="ai">Character to perform move</param> /// <param name="target">Opponent of the ai</param> public void doDogfightMove(TeamInformation ti, DynamicObject ai, StaticObject target) { if (!this.isObjectRegistered(ai)) return; float radiusToGoBehindTarget = (target.getBoundingSphere().Radius + ai.getBoundingSphere().Radius) * RADIUS_MULTIPLIER_TO_GO_BEHIND_TARGET; Vector3 wpPosition = target.Position + Vector3.Normalize(Matrix.CreateFromQuaternion(target.rotation).Forward) * radiusToGoBehindTarget; Vector3 wpDPosition = target.Position + Vector3.Normalize(Matrix.CreateFromQuaternion(target.rotation).Up) * radiusToGoBehindTarget / RADIUS_FACTOR_TO_GO_ABOVE_TARGET; if (Vector3.Dot(Vector3.Normalize(wpPosition - target.Position), Vector3.Normalize(ai.Position - target.Position)) < DOT_ANGLE_TO_STOP_DOGFIGHT_MOVE || (ai.Position - target.Position).Length() > CHASE_WHEN_FURTHER) { PathInformation fighterPath = objectPaths[ai]; List<Node> waypointList = fighterPath.remainingPath; //we clear the waypoint list and add new waypoints: if (waypointList.Count > 0) { bool shouldAddTopWaypt = (Vector3.Dot(Vector3.Normalize(Matrix.CreateFromQuaternion(target.rotation).Forward), Vector3.Normalize(target.Position - ai.Position)) > 0); if ((wpPosition - waypointList.ElementAt(0).Position).Length() > TARGET_WP_BUFFER || shouldAddTopWaypt) { waypointList.Clear(); if (shouldAddTopWaypt) waypointList.Insert(0, new Node(wpDPosition, -1)); waypointList.Insert(0, new Node(wpPosition, -1)); } } else { if (Vector3.Dot(Vector3.Normalize(Matrix.CreateFromQuaternion(target.rotation).Forward), Vector3.Normalize(target.Position - ai.Position)) > 0) waypointList.Insert(0, new Node(wpDPosition, -1)); waypointList.Insert(0, new Node(wpPosition, -1)); } fighterPath.remainingPath = waypointList; } else //stop navigation (we are behind the target so we can start shooting it) getPath(ai).Clear(); }
/// <summary> /// Method to deregister an object in order to stop computer-based navigation for that object /// </summary> /// <param name="AIObject">Currently registered dynamic object</param> public void deregisterObject(DynamicObject AIObject) { if (objectPaths.Keys.Contains(AIObject)) objectPaths.Remove(AIObject); }