/* * Handle a collision between two units */ public void CheckUnitCollision(GameUnit g1, GameUnit g2) { Vector2 normal = g1.Position - g2.Position; float distance = normal.Length(); normal.Normalize(); if (distance < g1.Size) { g1.Position += normal * (g1.Size - distance) / 2; g2.Position -= normal * (g2.Size - distance) / 2; Vector2 relVel = g1.Vel - g2.Vel; float impulse = (-(1 + COLL_COEFF) * Vector2.Dot(normal, relVel)) / (Vector2.Dot(normal, normal) * (1 / g1.Mass + 1 / g2.Mass)); g1.Vel += (impulse / g1.Mass) * normal; g2.Vel -= (impulse / g2.Mass) * normal; } }
/* * Handle a collision between two units */ public void CheckUnitCollision(GameUnit g1, GameUnit g2) { Vector2 normal = g1.Position - g2.Position; float distance = normal.Length(); // Don't check collision between player and ally if (g1.Faction == UnitFaction.ALLY && g2.Type == UnitType.PLAYER || g2.Faction == UnitFaction.ALLY && g1.Type == UnitType.PLAYER || distance == 0) { return; } normal.Normalize(); if (distance < (g1.Size + g2.Size)/2) { //System.Diagnostics.Debug.WriteLine("1 pos: " + g1.Position + " 2pos: " + g2.Position + // " 1size: " + g1.Size + " 2size: " + g2.Size + " norm: " + normal + " dist: " + distance); if (g1.Static) { g2.Position -= normal * ((g2.Size + g1.Size) / 2 - distance); } else if (g2.Static) { g1.Position += normal * ((g1.Size + g2.Size) / 2 - distance); } else { g1.Position += normal * ((g1.Size + g2.Size) / 2 - distance) / 2; g2.Position -= normal * ((g2.Size + g1.Size) / 2 - distance) / 2; } //System.Diagnostics.Debug.WriteLine(g1.Position + " " + g2.Position); Vector2 relVel = g1.Vel - g2.Vel; float impulse = (-(1 + COLL_COEFF) * Vector2.Dot(normal, relVel)) / (Vector2.Dot(normal, normal) * (1 / g1.Mass + 1 / g2.Mass)); if(!g1.Static) g1.Vel += (impulse / g1.Mass) * normal; if(!g2.Static) g2.Vel -= (impulse / g2.Mass) * normal; } }
/* * Add a unit to the game */ public void AddUnit(GameUnit unit) { unit.ID = Units.Count; Units.Add(unit); }
private void UpdateUnit(GameUnit unit, Level level) { if (!unit.Exists) return; // Infection vitality update if (unit.Lost) { unit.InfectionVitality -= INFECTION_RECOVER_SPEED; unit.InfectionVitality = MathHelper.Clamp( unit.InfectionVitality, 0, unit.max_infection_vitality); } else { unit.InfectionVitality += INFECTION_RECOVER_SPEED; unit.InfectionVitality = MathHelper.Clamp( unit.InfectionVitality, 0, unit.max_infection_vitality); } // If infection vitality is 0, convert the unit, or defeat the boss if (unit.InfectionVitality == 0) { if (unit.Type == UnitType.BOSS) { level.BossesDefeated++; unit.Exists = false; } else { ConvertedUnits.Add(unit); } } // Attack cooldown unit.AttackCoolDown = (int)MathHelper.Clamp( --unit.AttackCoolDown, 0, ATTACK_COOLDOWN); // Apply ally attrition if they are outside of range if (unit.Faction == UnitFaction.ALLY && Player != null && !unit.inRange(Player, ALLY_FOLLOW_RANGE)) { unit.Health -= ALLY_ATTRITION; } // Check health if (unit.Health <= 0) { DeadUnits.Add(unit); } }
/* * Sets the velocity of the unit based on its next move */ private void setVelocity(GameUnit unit) { if (unit.HasNextMove()) { // Calculate direction of acceleration Vector2 vel = unit.Vel; Vector2 vel_mod = unit.NextMove - unit.Position; // If the unit is close enough to target don't move if (unit.NextMove == unit.Target && vel_mod.Length() < TARGET_STOP_DIST) { vel_mod = Vector2.Zero; } else { vel_mod.Normalize(); vel_mod *= unit.Accel; } //TEMP if (unit.Faction == UnitFaction.ALLY && Player != null) { if ((unit.Position - unit.Target).Length() < 50) { unit.Speed = (int)(Player.Speed * 1.2); } else { unit.Speed = Player.Speed * 2; } } // Clamp values to max speeds vel += vel_mod; if (vel.Length() > unit.Speed) { vel.Normalize(); vel *= unit.Speed; } unit.Vel = vel; } }
/* * Process combat between two units */ private void ProcessCombat(GameUnit unit) { if (unit.AttackCoolDown > 0) return; if (unit.Attacking != null && unit.inRange(unit.Attacking, unit.AttackRange + unit.Size/2 + unit.Attacking.Size/2)) { Attack(unit, unit.Attacking); unit.Attacking = null; } // Attack other unit. If no other, attack player /* if(closest != null) { Attack(unit, closest); } else if (Player.Exists && unit.AttackCoolDown == 0 && unit.Faction == UnitFaction.ENEMY && unit.inRange(Player, unit.AttackRange+Player.Size/2)) { Attack(unit, Player); }*/ }
/* * Move this unit according to its current velocity vector */ private void moveUnit(GameUnit unit) { // Damping Vector2 vel = unit.Vel; Vector2 vel_mod = vel; if (vel_mod.Length() != 0) { vel_mod.Normalize(); vel_mod *= unit.Decel; vel -= vel_mod; } // Apply drag if ((vel - Vector2.Zero).Length() < unit.Decel * 3/4) { vel = Vector2.Zero; } else if (Math.Abs(vel.X) < unit.Decel * 3 / 4) vel.X = 0; else if (Math.Abs(vel.Y) < unit.Decel * 3 / 4) vel.Y = 0; unit.Vel = vel; unit.Position += unit.Vel; }
/* * A* to target */ private bool findTarget(GameUnit unit, Vector2 target, Map map, int limit) { List<Vector2> path = Pathfinder.findPath(map, unit.Position, unit.Target, limit, false); // Set the next move to the last node in the path with no obstacles in the way if (path != null) { for (int i = path.Count - 1; i > 0; i--) { if (!map.rayCastHasObstacle(unit.Position, path[i], unit.Size / 2)) { unit.NextMove = path[i]; unit.Lost = false; return true; } } return true; } else { double d = unit.distance(Player); if (unit.Faction == UnitFaction.ALLY && (Player == null || unit.distance(Player) > LOST_ALLY_DISTANCE)) { unit.Lost = true; } return false; } }
/* * Returns the closest unit of a different faction from the specified one */ private GameUnit findClosestEnemyInRange(GameUnit unit, int range) { UnitFaction faction = unit.Faction; int x_index = (int)MathHelper.Clamp((unit.Position.X / COMBAT_GRID_CELL_SIZE), 0, combatRangeGrid.GetLength(1) - 1); int y_index = (int)MathHelper.Clamp((unit.Position.Y / COMBAT_GRID_CELL_SIZE), 0, combatRangeGrid.GetLength(0) - 1); // Find the closeset unit according to the combatRangeGrid. Performance optimized. GameUnit closest = null; double closestDistance = Double.MaxValue; List<Point> adjacent = getAdjacent(new Point(x_index, y_index)); foreach (Point loc in adjacent) { foreach (GameUnit other in combatRangeGrid[loc.Y, loc.X]) { //double distance_sq = unit.distance_sq(other); float xdiff = other.Position.X - unit.Position.X; float ydiff = other.Position.Y - unit.Position.Y; double distance_sq = xdiff * xdiff + ydiff * ydiff; if (unit != other && faction != other.Faction && other.Type != UnitType.BOSS && distance_sq < (range + unit.Size/2 + other.Size/2)*(range + unit.Size/2 + other.Size/2) && (closest == null || distance_sq < closestDistance)) { closest = other; closestDistance = distance_sq; } } } return closest; }
/* * Converts an enemy to an ally or vice versa, handling stat changes as necessary */ private void Convert(GameUnit unit) { if (Player == null) return; if (unit.Faction == UnitFaction.ALLY) { unit.Faction = UnitFaction.ENEMY; Player.NumAllies--; if (Player.NumAllies < MAX_ALLIES) { Player.MaxAllies = false; } } else if (unit.Faction == UnitFaction.ENEMY) { unit.Faction = UnitFaction.ALLY; Player.NumAllies++; if (Player.NumAllies >= MAX_ALLIES) { Player.MaxAllies = true; } } Units.Remove(unit); AddUnit(factory.createUnit(unit.Type, unit.Faction, unit.Level, unit.Position, unit.Immune)); // Change stats like speed etc as necessary }
private void Attack(GameUnit aggressor, GameUnit victim) { aggressor.AttackCoolDown = ATTACK_COOLDOWN; victim.Health -= Math.Max(aggressor.Attack - victim.Defense, 0); }
/* * Determine the next move for this unit with * targeting specific to each unit type AI */ public void setNextMove(GameUnit unit, Level level, bool playerFrontBlocked) { if (unit.Position.X < 0 || unit.Position.Y < 0) return; Vector2 prev_move = unit.NextMove; UnitFaction faction = unit.Faction; // Unit faction, only called once // If an ally unit is lost, do this if (faction == UnitFaction.ALLY && unit.Lost) { if (rand.NextDouble() < 0.01 && Player != null) { unit.Target = Player.Position; findTarget(unit, Player.Position, level.Map, MAX_ASTAR_DIST); } else if(rand.NextDouble() < 0.05) { unit.Target = unit.Position + new Vector2(rand.Next(600) - 300, rand.Next(600) - 300); unit.NextMove = unit.Target; } return; } // Select target bool random_walk = false; switch (unit.Type) { case UnitType.TANK: // tank AI if (Player != null && Player.Exists && faction == UnitFaction.ENEMY && Player.inRange(unit, ENEMY_CHASE_RANGE)) { unit.Target = Player.Position; } else if (rand.NextDouble() < 0.05) { // Random walk random_walk = true; unit.Target = unit.Position + new Vector2(rand.Next(600)-300, rand.Next(600)-300); } if (faction == UnitFaction.ALLY && Player != null) { if (playerFrontBlocked) { unit.Target = Player.Position; } else { unit.Target = Player.Front; } } // Chase the closest enemy in range GameUnit closest = findClosestEnemyInRange(unit, ATTACK_LOCK_RANGE); if (closest != null) { unit.Target = closest.Position; unit.Attacking = closest; } if(faction == UnitFaction.ALLY && Player != null && !Player.inRange(unit, ALLY_FOLLOW_RANGE)) { unit.Target = Player.Position; } break; case UnitType.RANGED: // ranged AI break; case UnitType.FLYING: // flying AI break; default: // Player case, do nothing break; } unit.NextMove = unit.Target; if (unit.HasTarget()) { /* // If the target is the player, use the player location map if (unit.Target.Equals(Player.Position) && Math.Abs(unit.TilePosition.X - Player.TilePosition.X) + Math.Abs(unit.TilePosition.Y - Player.TilePosition.Y) < PLAYER_PATHFIND_FIELD_SIZE) { Vector2 moveToPlayerTile = findMoveToPlayer(unit, level.Map); unit.NextMove = new Vector2(moveToPlayerTile.X * Map.TILE_SIZE, moveToPlayerTile.Y * Map.TILE_SIZE); }*/ // Pathfind to target if necessary if (!random_walk && level.Map.rayCastHasObstacle(unit.Position, unit.Target, unit.Size / 2)) { if (rand.NextDouble() < 0.1) { findTarget(unit, unit.Target, level.Map, MAX_ASTAR_DIST); } else { unit.NextMove = prev_move; } } } }
// Returns an instance of a unit of the given type and faction public GameUnit createUnit(UnitType type, UnitFaction faction, int level, Vector2 pos, bool immune) { GameUnit enemy; switch (type) { case UnitType.TANK: enemy = new GameUnit(faction == UnitFaction.ALLY? textures[ALLY_TANK_l] : textures[ENEMY_TANK_l], faction == UnitFaction.ALLY? textures[ALLY_TANK_r] : textures[ENEMY_TANK_r], type, faction, level, immune); break; case UnitType.RANGED: enemy = new GameUnit(faction == UnitFaction.ALLY ? textures[ALLY_RANGED] : textures[ENEMY_RANGED], faction == UnitFaction.ALLY ? textures[ALLY_TANK_r] : textures[ENEMY_TANK_r], type, faction, level, immune); break; case UnitType.FLYING: enemy = new GameUnit(faction == UnitFaction.ALLY ? textures[ALLY_FLYING] : textures[ENEMY_FLYING], faction == UnitFaction.ALLY ? textures[ALLY_TANK_r] : textures[ENEMY_TANK_r], type, faction, level, immune); break; case UnitType.BOSS: enemy = new GameUnit(textures[PLASMID], textures[PLASMID], type, faction, level, immune); break; default: enemy = null; break; } if (enemy != null) { enemy.Position = pos; } return enemy; }
/* * Handle a collision between a unit and wall */ public void CheckWallCollision(GameUnit unit, Map map) { List<Vector2> dirs = new List<Vector2>(); dirs.Add(new Vector2(0, 1)); dirs.Add(new Vector2(1, 0)); dirs.Add(new Vector2(0, -1)); dirs.Add(new Vector2(-1, 0)); dirs.Add(new Vector2(1, 1)); dirs.Add(new Vector2(1, -1)); dirs.Add(new Vector2(-1, 1)); dirs.Add(new Vector2(-1, -1)); foreach (Vector2 dir in dirs) { if(!map.canMoveToWorldPos(unit.Position + dir * unit.Size/2)) { int i = 0; while (i++ < unit.Size && !map.canMoveToWorldPos(unit.Position + dir * unit.Size/2)) { unit.Position -= dir; } } } }
/* * Use point location field to find the player (NOT IN USE) */ private Vector2 findMoveToPlayer(GameUnit unit, Map map) { return playerLocationField[(int)unit.Position.Y / Map.TILE_SIZE, (int)unit.Position.X / Map.TILE_SIZE]; }
/* * Handle a collision between a unit and wall */ public void CheckWallCollision(GameUnit unit, Map map) { }
/* * Process collisions for every unit */ public void ProcessCollisions(GameUnit unit, Map map) { if (unit == null) return; int x_index = (int)MathHelper.Clamp((unit.Position.X / CELL_SIZE), 0, cellGrid.GetLength(1) - 1); int y_index = (int)MathHelper.Clamp((unit.Position.Y / CELL_SIZE), 0, cellGrid.GetLength(0) - 1); List<Point> adjacent = getAdjacent(new Point(x_index, y_index)); foreach (Point loc in adjacent) { foreach (GameUnit other in cellGrid[loc.Y, loc.X]) { // Don't check collision for the same units or if they are in the same position (will crash) if (unit != other && unit.Position != other.Position && !unit.Ghost) { CheckUnitCollision(unit, other); } } } CheckWallCollision(unit, map); }