public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (currentCover != null) { Debug.Log("Enemy is taking cover"); if (AIFunction.LineOfSight(currentCover.position, attacker, coverCriteria)) { Debug.Log("Cover is compromised"); // Reset and find a new cover point currentCover = null; //currentCover = FindCover(attacker, ai.na, coverCheckRadius, numberOfChecks, coverCriteria); } } if (currentCover == null) { currentCover = FindCover(attacker, ai.na, coverCheckRadius, numberOfChecks, coverCriteria); } if (currentCover != null) { Debug.Log("Setting destination for " + ai.name + " from TakeCover behaviour"); ai.na.SetDestination(currentCover.position); } }
Character AcquireTarget() { // Use Physics.OverlapSphere Collider[] thingsInEnvironment = Physics.OverlapSphere(LookOrigin, viewRange); foreach (Collider thing in thingsInEnvironment) { /* * if (AIFunction.LineOfSightCheckWithExceptions(LookOrigin, thing.transform.position, viewDetection, characterData.HealthData.hitboxes, thing)) * { * * } */ if (AIFunction.LineOfSight(LookOrigin, thing.transform, viewDetection)) { //print("Line of sight established between agent and " + thing.name); Character targetCharacter = thing.transform.root.GetComponent <Character>(); if (targetCharacter != null && characterData.HostileTowards(targetCharacter)) { return(targetCharacter); } } } return(null); }
public NullableVector3 FindCover(Transform attacker, NavMeshAgent na, float coverCheckRadius, int numberOfChecks, LayerMask coverCriteria) { NullableVector3 cover = null; NavMeshPath coverPath = null; for (int i = 0; i < numberOfChecks; i++) { // Obtains a random position within a certain vicinity of the agent Vector3 randomPosition = ai.transform.position + Random.insideUnitSphere * coverCheckRadius; NavMeshHit coverCheck; // Checks if there is an actual point on the navmesh close to the randomly selected position if (NavMesh.SamplePosition(randomPosition, out coverCheck, na.height * 2, NavMesh.AllAreas)) { if (AIFunction.LineOfSight(coverCheck.position, attacker, coverCriteria) == false) // If line of sight is not established { // Ensures that the agent can actually move to the cover position. NavMeshPath newPathToTest = new NavMeshPath(); if (na.CalculatePath(coverCheck.position, newPathToTest)) { // Checks if the new cover position is easier to get to than the old one. if (cover == null || AIFunction.NavMeshPathLength(newPathToTest) < AIFunction.NavMeshPathLength(coverPath)) // Use OR statement, and check navmesh path cost between transform.position and the cover point currently being checked. { // If so, new cover position is established, and navmesh path is stored for next comparison cover = new NullableVector3(coverCheck.position); coverPath = newPathToTest; } } } } } return(cover); }
void PursueTargetUpdate() { if (currentTarget == null) // If the AI has not acquired a target, check for one { currentTarget = AcquireTarget(); } if (currentTarget != null) // If the AI has a target, check if said target is still worth pursuing { // If the AI cannot find their target (out of range or line of sight broken) //if (Vector3.Distance(transform.position, currentTarget.transform.position) > pursueRange || AIFunction.SimpleLineOfSightCheck(currentTarget.transform.position, LookOrigin, viewDetection) == false) bool lineOfSightBroken = !AIFunction.LineOfSightCheckWithExceptions(currentTarget.transform.position, LookOrigin, viewDetection, characterData.health.hitboxes, currentTarget.health.hitboxes); bool outOfRange = Vector3.Distance(transform.position, currentTarget.transform.position) > pursueRange; if (outOfRange || lineOfSightBroken) { // Count up a timer and continue pursuing until the timer expires patienceTimer = 0; } patienceTimer += Time.deltaTime; if (patienceTimer >= pursuePatience || currentTarget.health == null || currentTarget.health.IsDead) { print("Target out of range, cannot be attacked or is dead"); currentTarget = null; } } }
private void StartWander() { if (GetWanderLocation(out Destination)) { Agent.destination = Destination; CurrentAI = Wander; NextAI = MoveToWorkTarget; } }
private void Idle() { timer += Time.deltaTime; if (timer > currentTimer) { CurrentAI = NextAI; timer = 0; } }
public static AttackMessage Ranged(Character attacker, Vector3 origin, Vector3 direction, float maxRange, float projectileDiameter, float coneAngle, float velocity, LayerMask hitDetection) { AttackMessage m = new AttackMessage(); m.attacker = attacker; m.type = AttackType.Ranged; m.timeInitiated = Time.time; m.origin = origin; m.direction = direction; m.maxRange = maxRange; m.projectileDiameter = projectileDiameter; m.coneAngle = coneAngle; m.velocity = velocity; m.hitDetection = hitDetection; #region Get characters at risk List <Character> list = new List <Character>(); // Perform a vision cone check RaycastHit[] thingsInLineOfFire = AIFunction.VisionCone(origin, direction, Vector3.up, coneAngle, maxRange, damageableThings, hitDetection); /* * string debugString = "Characters at risk from " + m.attacker.name + " on frame " + Time.frameCount + ":"; * if (thingsInLineOfFire.Length < 1) * { * debugString = "Attack from " + m.attacker.name + " at time " + m.timeInitiated + " will not hit anything."; * //Misc.PauseForDebuggingPurposes(); * } */ for (int i = 0; i < thingsInLineOfFire.Length; i++) { // Check raycasthit collider to see if it is a character with a faction Character c = Character.FromObject(thingsInLineOfFire[i].collider.gameObject); // If there is a character class // If the character class is not already in the list // If the character class is considered an enemy of the attacker if (c != null && list.Contains(c) == false && attacker.HostileTowards(c)) { // If so, the character is in the attack's danger zone //Debug.Log(c.name + " is in the line of fire of " + attacker.name + "'s attack"); list.Add(c); //debugString += "\n" + c.name; } } //Debug.Log(debugString); m.charactersAtRisk = list.ToArray(); // Performs a calculation to find all enemies within the attack's boundaries. DO THIS LAST, after all the proper variables have been established for accurate calculations #endregion return(m); }
private void Wander() { if (!Agent.hasPath) { timer += Time.deltaTime; if (timer > WanderIdleTime) { timer = 0; CurrentAI = NextAI; } } }
// Checks if a position is in the line of fire public bool IsPositionSafe(Vector3 position, DamageHitbox[] characterColliders) { switch (type) { case AttackType.Ranged: // Check if the character is inside the cone of fire // If inside range if (Vector3.Distance(origin, position) < maxRange) { // If inside angle if (Vector3.Angle(direction, position - origin) < coneAngle) { // If inside line of sight if (AIFunction.LineOfSightCheckWithExceptions(position, origin, hitDetection, characterColliders)) { return(true); } } } break; case AttackType.Melee: // Save this until I have an actual melee attack system break; case AttackType.AreaOfEffect: // Check if the character is inside the blast radius and behind cover break; case AttackType.ExplosiveRanged: // ?? Somehow combine a vision cone check with a blast radius check break; default: break; } return(false); }
private void CheckIsBlocked(Map map, int[,] hazardMap) { // If the AI is blocked if (map.CollisionLayer[Engine.VectorToCell(_aiNextPosition).X, Engine.VectorToCell(_aiNextPosition).Y] || hazardMap[Engine.VectorToCell(_aiNextPosition).X, Engine.VectorToCell(_aiNextPosition).Y] >= 2) { Sprite.IsAnimating = false; IsMoving = false; // We define a new goal Path = AIFunction.MakeAWay( CellPosition, AIFunction.SetNewGoal(CellPosition, map.Board, map.CollisionLayer, hazardMap, map.Size), map.CollisionLayer, hazardMap, map.Size); } }
public void AttackUpdate() { // If the attack has ended, cool down if (wielder.currentAttack == null) { cooldownTimer += Time.deltaTime; } // Stores a bool that only needs to be altered if the following if statement returns true bool lineOfSight = false; // If the AI has an assigned target if (wielder.currentTarget != null) { // If the AI has not started telegraphing yet, or it is but has not locked a specific position to aim at if (wielder.currentAttack == null || lockOntoTargetWorldPosition == false) { targetPosition = DetermineEnemyPosition(); // Determine the correct position for the AI to be aiming at } //Debug.DrawLine(wielder.LookOrigin, targetPosition, Color.black); Debug.DrawRay(targetPosition, Vector3.up * 5, Color.black); Debug.DrawRay(wielder.LookOrigin, wielder.LookDirection * Vector3.Distance(targetPosition, wielder.LookOrigin), Color.black); // Perform a line of sight check, making sure to ignore the AI and target's hitboxes since those obviously aren't obstacles lineOfSight = AIFunction.LineOfSightCheckWithExceptions(targetPosition, wielder.LookOrigin, wielder.viewDetection, wielder.characterData.health.hitboxes, wielder.currentTarget.health.hitboxes); if (lineOfSight) { // Aim for player wielder.RotateLookTowards(targetPosition, currentAimDegreesPerSecond); // If the AI is successfully aiming at the target and their attack is off cooldown if (wielder.IsLookingAt(targetPosition, aimAngleThreshold) && cooldownTimer >= cooldown) { ExecuteAttack(); } } } if (wielder.currentTarget == null || lineOfSight == false) { if (wielder.currentAttack != null) { CancelAttack(); } wielder.ReturnToNeutralLookPosition(); } }
private void WorkTarget() { timer += Time.deltaTime; //time between works if (timer < TimeBetweenWorks) { return; } timer = 0; //work upgrade til work is done if (CurrentWorkTarget.WorkUpgrade(CalculateWork())) { //collect resrouce CarriedMaterial = CurrentWorkTarget.TakeMaterial(BuildingMaterialCarryPos); CurrentAI = NextAI; CurrentWorkTarget = null; } }
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { #region Check validity of destination, and return null if no longer valid if (currentDestination != null) { float distance = Vector3.Distance(currentDestination.position, TargetLocation); // Obtains distance between agent and target Debug.DrawLine(TargetLocation, currentDestination.position, new Color(1, 0.5f, 0)); // Checks if the AI can no longer see the target from their desired position, or if they are too close or too far bool lineOfSightLost = !AIFunction.LineOfSightCheckWithExceptions(TargetLocation, currentDestination.position, coverCriteria, ai.characterData.health.hitboxes, ai.currentTarget.health.hitboxes); bool tooClose = distance < minimumMoveRange; bool tooFar = distance > maximumMoveRange; if (lineOfSightLost || tooClose || tooFar) { // The AI cannot engage with the current target, position is nulled. currentDestination = null; } //Debug.Log(ai.name + " pathfinding status: " + lineOfSightLost + ", " + tooClose + ", " + tooFar); } #endregion #region Find new destination if there is none // If there is no position assigned, search for one. if (currentDestination == null) { //Debug.Log("Finding destination normally"); currentDestination = FindFollowPosition(TargetLocation, minimumDestinationRange, maximumDestinationRange, numberOfChecks); //Debug.Log("Current destination = " + currentDestination); } #endregion #region Travel to destination // If a valid position is found the agent must travel to it. if (currentDestination != null) { Debug.DrawLine(ai.transform.position, currentDestination.position, Color.magenta); ai.na.SetDestination(currentDestination.position); Debug.DrawLine(currentDestination.position, ai.na.destination, new Color(1, 0.75f, 0), 1); } #endregion }
public NullableVector3 FindAvoidPosition() // Should I have separate versions for dodging vs. taking cover? I might need this based on whether the enemy is aggressive or skittish { NullableVector3 newSafeLocation = null; float maxPathDistance = maxMoveDistance; Vector3[] test = AIFunction.PositionsAroundPointInSpiral(ai.transform.position + ai.transform.up, ai.transform.up, minCheckRadius, maxCheckRadius, 3, 15); for (int i = 0; i < numberOfChecks; i++) { // Samples a random position around the target, normalises it, and randomises the magnitude to a point in betwen the min and max radii. // If I simply multiply by the max check radius, the position may be too close. Vector3 randomPosition = ai.transform.position + Random.insideUnitSphere.normalized * Random.Range(minCheckRadius, maxCheckRadius); // Normalising the Random.insideUnitSphere ensures the magnitude (and therefore distance value) is always 1, and the distance is calculated correctly. NavMeshHit followCheck; // Checks if there is an actual point on the navmesh close to the randomly selected position if (NavMesh.SamplePosition(randomPosition, out followCheck, ai.na.height * 2, NavMesh.AllAreas)) { // Checks if the location is safe from the attack if (ai.attackToDodge.IsPositionSafe(followCheck.position, ai.characterData.health.hitboxes) == false) { // Creates a new path for reference NavMeshPath nmp = new NavMeshPath(); // If the agent can actually move to the location if (ai.na.CalculatePath(followCheck.position, nmp) && nmp.status == NavMeshPathStatus.PathComplete) { float distance = AIFunction.NavMeshPathLength(nmp); // Check navmesh path cost between transform.position and the cover point currently being checked. if (distance < maxPathDistance) // If the NPC is willing to travel that distance to the dodge zone, or if this distance is shorter than that of the previous route. { // If so, new cover position is established, and navmesh path is stored for next comparison newSafeLocation = new NullableVector3(followCheck.position); maxPathDistance = distance; } } } } } return(newSafeLocation); }
private void MoveToWorkTarget() { //get resource from priority list //HACK: just use trees for testing if (CurrentWorkTarget) { //check if pathing is done if (!Agent.hasPath) { CurrentAI = WorkTarget; NextAI = StartWander; } return; } CurrentWorkTarget = LandMan.Instance.GetNearbyUpgrade(this, GetDesiredResource()); if (!CurrentWorkTarget) { CurrentAI = StartWander; return; } Agent.SetDestination(CurrentWorkTarget.GetWorkLocation()); }
protected override void Move(GameTime gameTime, Map map, int[,] hazardMap) { #region Walk // If he hasn't reach his goal => we walk to this goal if (_aiNextPosition != new Vector2(-1, -1) && !AIFunction.HasReachNextPosition(Position, Speed, _aiNextPosition)) { IsMoving = true; Sprite.IsAnimating = true; CheckIsBlocked(map, hazardMap); Walk(); ComputeWallCollision(map); } #endregion #region Search a goal // Otherwise => we find another goal else { // We place the player at the center of its cell Position = Engine.CellToVector(CellPosition); #region Bomb => AI // Try to put a bomb // Put a bomb if (!HasBadEffect || (HasBadEffect && BadEffect != BadEffect.NoBomb)) { if (AIFunction.TryToPutBomb(CellPosition, BombPower, map.Board, map.CollisionLayer, hazardMap, Config.MapSize)) { if (CurrentBombAmount > 0) { Bomb bo = FinalBomber.Instance.GamePlayScreen.BombList.Find( b => b.CellPosition == CellPosition); if (bo == null) { CurrentBombAmount--; var bomb = new Bomb(Id, CellPosition, BombPower, BombTimer, Speed); // We define a new way (to escape the bomb) Path = AIFunction.MakeAWay( CellPosition, AIFunction.SetNewDefenseGoal(CellPosition, map.CollisionLayer, hazardMap, Config.MapSize), map.CollisionLayer, hazardMap, Config.MapSize); FinalBomber.Instance.GamePlayScreen.AddBomb(bomb); } } } } #endregion if (Path == null || Path.Count == 0) { Sprite.IsAnimating = false; IsMoving = false; // We define a new goal Path = AIFunction.MakeAWay( CellPosition, AIFunction.SetNewGoal(CellPosition, map.Board, map.CollisionLayer, hazardMap, Config.MapSize), map.CollisionLayer, hazardMap, Config.MapSize); if (Path != null) { _aiNextPosition = Engine.CellToVector(Path[Path.Count - 1]); Path.Remove(Path[Path.Count - 1]); CheckIsBlocked(map, hazardMap); } } else { // We finish the current way _aiNextPosition = Engine.CellToVector(Path[Path.Count - 1]); Path.Remove(Path[Path.Count - 1]); /* * // Update the way of the AI each time it changes of cell => usefull to battle against players (little bug here) * aiWay = AI.MakeAWay( * CellPosition, * AI.SetNewGoal(CellPosition, map.Board, map.CollisionLayer, hazardMap), * map.CollisionLayer, hazardMap); */ } } #endregion UpdatePlayerPosition(map); }
public NullableVector3 FindFollowPosition(Vector3 targetPosition, float minimumRange, float maximumRange, int numberOfChecks) { NullableVector3 newFollowPosition = null; float currentPathLength = float.MaxValue; /* * int noValidPosition = 0; * int noLineOfSight = 0; * int noValidPath = 0; * int incompletePath = 0; * int inefficient = 0; */ for (int i = 0; i < numberOfChecks; i++) { // Samples a random position around the target, outside minimumRange and inside maximumRange. // Normalising the Random.insideUnitSphere magnitude then multiplying it again by another random value allows me to ensure that the distance of the point is random but still within certain distance requirements. Vector3 randomPosition = targetPosition + Random.insideUnitSphere.normalized * Random.Range(minimumRange, maximumRange); NavMeshHit followCheck; // Checks if there is an actual point on the navmesh close to the randomly selected position if (NavMesh.SamplePosition(randomPosition, out followCheck, ai.na.height * 2, NavMesh.AllAreas)) { Debug.DrawLine(randomPosition, followCheck.position, Colours.darkGreen); // Checks if line of sight is established between the new position and target. The agent is still pursuing and attacking the target, but they are just staying cautious. if (AIFunction.LineOfSightCheckWithExceptions(targetPosition, followCheck.position, coverCriteria, ai.characterData.health.hitboxes, ai.currentTarget.health.hitboxes)) { // Ensures that a path can be sampled to the destination. NavMeshPath nmp = new NavMeshPath(); if (ai.na.CalculatePath(followCheck.position, nmp)) { // Checks to make sure a whole path can be found. // This if statement could probably be added to the previous one with the && operator if (nmp.status == NavMeshPathStatus.PathComplete) { // Checks if the new cover position is a shorter route to get to than the old one. // Use OR statement, and check navmesh path cost between transform.position and the cover point currently being checked. float length = AIFunction.NavMeshPathLength(nmp); if (newFollowPosition == null || length < currentPathLength) { // If so, new cover position is established, and navmesh path is stored for next comparison newFollowPosition = new NullableVector3(followCheck.position); currentPathLength = length; } /* * else * { * inefficient++; * } */ } /* * else * { * incompletePath++; * } */ } /* * else * { * noValidPath++; * } */ } /* * else * { * noLineOfSight++; * } */ } /* * else * { * noValidPosition++; * } */ } //Debug.Log("Path result - " + (newFollowPosition != null).ToString() + ", " + noValidPosition + " sampling errors, " + noLineOfSight + " line of sight fails, " + noValidPath + " pathing fails, " + incompletePath + " incomplete paths, and " + inefficient + " inefficient paths."); return(newFollowPosition); }