//TODO: Make this account for attack range and entity size. //TODO: Maybe make this not allow them to leave their platform as well, just to avoid issues with pathing. private void FollowEntity(Entity e, GameTime Time) { var entity = this.Parent; Random r = new Random(); float attackRange = AttributesComponent.MeleeAttackRange.X; if (entity.Location.Contains((int)e.Location.Center.X, (int)e.Location.Center.Y)) { if (!PhysicsComponent.IsGrounded) return; // Do nothing, just wait for us to fall on our location. } else { bool MissingHorizontally = e.Location.Center.X - attackRange > entity.Location.Right || e.Location.Center.X + attackRange < entity.Location.Left; if (entity.Location.Bottom > e.Location.Bottom && !MissingHorizontally) { if (CanJumpToChase) MovementComponent.Jump(AllowMultiJump); } if (MissingHorizontally) { //Most of this code prevents the AI from jumping off their current platform to chase player. //Often results in death. float checkDistance = PhysicsComponent.VelocityX * Time.GetTimeScalar(); Vector2 leftPlatformVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Bottom + 1); Vector2 rightPlatformVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Bottom + 1); Vector2 leftWallVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Center.Y); Vector2 rightWallVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Center.Y); bool leftPossible = PhysicsSystem.IsLocationSolid(leftPlatformVector) && !PhysicsSystem.IsLocationSolid(leftWallVector); bool rightPossible = PhysicsSystem.IsLocationSolid(rightPlatformVector) && !PhysicsSystem.IsLocationSolid(rightWallVector); if (entity.Location.Center.X > e.Location.Center.X + attackRange) { if (leftPossible) MovementComponent.BeginWalking(Direction.Left); else MovementComponent.StopWalking(); } else if (entity.Location.Center.X < e.Location.Center.X - attackRange) { if (rightPossible) MovementComponent.BeginWalking(Direction.Right); else MovementComponent.StopWalking(); } } else { if (MovementComponent.IsWalking) MovementComponent.StopWalking(); } //Ensure they are facing the correct direction, if they move within AI rectangle. if (entity.Location.Center.X > e.Location.Center.X) { MovementComponent.CurrentDirection = Direction.Left; } else if (entity.Location.Center.X < e.Location.Center.X) { MovementComponent.CurrentDirection = Direction.Right; } } }
protected override void OnUpdate(GameTime Time) { base.OnUpdate(Time); if (AIEnabled) { //Begin process of death if entity has run out of health. if (((AttributesComponent.CurrentHealth / AttributesComponent.MaxHealth) * 100) < 10) { if (!DeathStarted) DeathTime = DateTime.Now; DeathStarted = true; } //Begin process of fleeing if entity's health is <25%. if (((AttributesComponent.CurrentHealth / AttributesComponent.MaxHealth) * 100) < 25) { FleeingStarted = true; } bool shouldFlee = FleeingStarted && FleeingEnabled; bool shouldDie = DeathStarted && DeathEnabled; if (!shouldFlee && !shouldDie) //NORMAL AI { bool foundEntity = false; bool projectileFlyingToMe = false; bool entityAttackingMe = false; bool entityAttackable = false; bool entityFollowable = false; Entity entityToFollow = null; //TODO: This could be very ineffecient. //Foreach entity in this entity's reaction box. foreach (Entity e in PhysicsSystem.GetEntitiesAtLocation(GetReactionBox())) { var clc = e.GetComponent<ClassificationComponent>(); var coc = e.GetComponent<CombatComponent>(); if (clc.Classification == EntityClassification.Player) //If Player { foundEntity = true; entityFollowable = true; entityToFollow = e; if (coc.IsAttacking && WithinEntitysAttackRange(e) && EntityFacingMe(e)) entityAttackingMe = true; if (!MovementComponent.IsWalking && EntityWithinAttackRange(e)) entityAttackable = true; } else if (clc.Classification == EntityClassification.Projectile) //If Projectile { foundEntity = true; //Basically, projectiles within our rectangle might hit us, so we'll just block. if (EntityGoingToMe(e)) projectileFlyingToMe = true; } } if (foundEntity) //If we found an entity. { //We're reacting to an entity. IsReacting = true; //If we're following a path and we're aggressive, stop following the path. if (PathComponent.PathingEnabled && Aggressive) { PathComponent.StopFollowing(); } //If we're able to follow an entity and we're aggressive, follow the entity. if (entityFollowable && entityToFollow != null && Aggressive) { FollowEntity(entityToFollow, Time); } //If an entity is attackable and if i (the enemy) am not blocking, attack. if (entityAttackable && !CombatComponent.IsBlocking) { CombatComponent.AttackAI(); } } else //If we found nothing. { IsReacting = false; //Resume following the path. if (!PathComponent.PathingEnabled) PathComponent.StartFollowing(); } //If a projectile is coming to us, or an entity is attacking us, block. if (entityAttackingMe || projectileFlyingToMe) { if (!CombatComponent.IsBlocking) CombatComponent.BeginBlock(); } //If there's no projectile coming to us, or no entity attacking us, end the block. if (!entityAttackingMe && !projectileFlyingToMe) { if (CombatComponent.IsBlocking) CombatComponent.EndBlock(); } } else if (shouldDie) //DYING AI { //Stop blocking if we were doing so at the moment death occured. if (CombatComponent.IsBlocking) CombatComponent.EndBlock(); Direction walkDirection = MovementComponent.WalkDirection == Direction.Left ? Direction.Right : Direction.Left; double walkTime = 200; if ((DateTime.Now - DeathTime).TotalMilliseconds > walkTime) { MovementComponent.BeginWalking(walkDirection); DeathTime = DateTime.Now; } } else if (shouldFlee) //FLEEING AI { //Stop blocking if we were doing so at the moment fleeing occured. if (CombatComponent.IsBlocking) CombatComponent.EndBlock(); float checkDistance = PhysicsComponent.VelocityX * Time.GetTimeScalar(); //Run back and forth on platform. Vector2 leftPlatformVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Bottom + 1); Vector2 rightPlatformVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Bottom + 1); Vector2 leftWallVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Center.Y); Vector2 rightWallVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Center.Y); bool leftPossible = PhysicsSystem.IsLocationSolid(leftPlatformVector) && !PhysicsSystem.IsLocationSolid(leftWallVector); bool rightPossible = PhysicsSystem.IsLocationSolid(rightPlatformVector) && !PhysicsSystem.IsLocationSolid(rightWallVector); //Set a location if one hasn't been set (default is Direction.Down). Very unlikely to happen here, but can't say impossible. if (MovementComponent.CurrentDirection == Direction.Down) MovementComponent.CurrentDirection = Direction.Left; if (MovementComponent.CurrentDirection == Direction.Left) { if (leftPossible) MovementComponent.BeginWalking(Direction.Left); else if (rightPossible) MovementComponent.BeginWalking(Direction.Right); } else if (MovementComponent.CurrentDirection == Direction.Right) { if (rightPossible) MovementComponent.BeginWalking(Direction.Right); else if (leftPossible) MovementComponent.BeginWalking(Direction.Left); } } } }
/// <summary> /// Decides which direction the AI should walk to follow the path. Simple x value check. /// </summary> /// <param name="Time"></param> protected override void OnUpdate(GameTime Time) { if (PathingEnabled) { if (Nodes.Count != 0) { var entity = this.Parent; var mc = entity.GetComponent<MovementComponent>(); var pc = entity.GetComponent<PhysicsComponent>(); var ps = Scene.GetSystem<PhysicsSystem>(); //Formerly Vector2.Distance(entity.Position, CurrentNode) for Y stuff, but not needed. if (entity.Location.Contains((int)CurrentNode.X, (int)CurrentNode.Y)) { if (!pc.IsGrounded) return; // Do nothing, just wait for us to fall on our location. AdvanceNode(); } else { bool MissingHorizontally = CurrentNode.X > entity.Location.Right || CurrentNode.X < entity.Location.Left; if (entity.Location.Top > CurrentNode.Y && /*(DateTime.Now - _LastJump).TotalMilliseconds > JumpDelay &&*/ !MissingHorizontally) { mc.Jump(AllowMultiJump); _LastJump = DateTime.Now; } if (MissingHorizontally) { if (entity.Location.Left > CurrentNode.X) { mc.BeginWalking(Direction.Left); // Check if we'll need to jump this frame. if (!ps.IsLocationSolid(new Vector2(entity.Location.Left - pc.VelocityX * Time.GetTimeScalar(), entity.Location.Bottom + 5))) mc.Jump(AllowMultiJump); } else if (entity.Location.Right < CurrentNode.X) { mc.BeginWalking(Direction.Right); if (!ps.IsLocationSolid(new Vector2(entity.Location.Right + pc.VelocityX * Time.GetTimeScalar(), entity.Location.Bottom + 5))) mc.Jump(AllowMultiJump); } } else mc.StopWalking(); } } else { float checkDistance = PhysicsComponent.VelocityX * Time.GetTimeScalar(); //Run back and forth on platform. //This is fairly close to fleeing AI. Vector2 leftPlatformVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Bottom + 1); Vector2 rightPlatformVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Bottom + 1); Vector2 leftWallVector = new Vector2(Parent.Location.Left + checkDistance, Parent.Location.Center.Y); Vector2 rightWallVector = new Vector2(Parent.Location.Right + checkDistance, Parent.Location.Center.Y); bool leftPossible = PhysicsSystem.IsLocationSolid(leftPlatformVector) && !PhysicsSystem.IsLocationSolid(leftWallVector); bool rightPossible = PhysicsSystem.IsLocationSolid(rightPlatformVector) && !PhysicsSystem.IsLocationSolid(rightWallVector); //Set a location if one hasn't been set (default is Direction.Down), which is likely when we have no path. if (MovementComponent.CurrentDirection == Direction.Down) MovementComponent.CurrentDirection = Direction.Left; if (MovementComponent.CurrentDirection == Direction.Left) { if (leftPossible) MovementComponent.BeginWalking(Direction.Left); else if (rightPossible) MovementComponent.BeginWalking(Direction.Right); } else if (MovementComponent.CurrentDirection == Direction.Right) { if (rightPossible) MovementComponent.BeginWalking(Direction.Right); else if (leftPossible) MovementComponent.BeginWalking(Direction.Left); } } } base.OnUpdate(Time); }
private void PerformStaticCollision(GameTime Time) { // This is safe to multi-thread because we're never modifying anything besides the Component itself. // But each iteration is so cheap that the overhead of threading outweighs performance benefits. List<Task> Tasks = new List<Task>(); foreach(var Component in GetFilteredComponents<PhysicsComponent>()) { var Parent = Component.Parent; if(!Component.IsGrounded) Component.VelocityY += Gravity * Time.GetTimeScalar() * Parent.GetComponent<PhysicsComponent>().GravityCoefficient; //Prolly not the best thing to do GetComponent, eventually make something else. //if(Component.IsMoving) { float CurrSign = Math.Sign(Component.VelocityX); Component.VelocityX += -CurrSign * HorizontalDrag * Time.GetTimeScalar() * Parent.GetComponent<PhysicsComponent>().HorizontalDragCoefficient; if(Math.Sign(Component.VelocityX) != CurrSign) Component.VelocityX = 0; //} Vector2 PositionDelta = Component.Velocity * Time.GetTimeScalar(); // First, handle falling. Check for collision a bit below bottom + PositionDelta, and put them back to the top of the tile if hit. if(Component.VelocityY >= 0 && CheckStaticCollision(Parent, new Vector2(0, (Parent.Size.Y / 2) + PositionDelta.Y + 1), (Tile) => new Vector2(Parent.Position.X, Tile.Location.Top - Parent.Size.Y))) { // We're going to hit a tile while falling, so stop falling. Component.IsGrounded = true; Component.VelocityY = 0; PositionDelta.Y = 0; } else { // Otherwise, we're not grounded, and check for collision above. Component.IsGrounded = false; if(Component.VelocityY < 0 && CheckStaticCollision(Parent, new Vector2(0, (-Parent.Size.Y / 2) + PositionDelta.Y - 1), (Tile) => new Vector2(Parent.Position.X, Tile.Location.Bottom))) { Component.VelocityY = 0; PositionDelta.Y = 0; } } // Now, check horizontal collision. if(Component.VelocityX >= 0.001f && CheckStaticCollision(Parent, new Vector2(Parent.Size.X / 2 + PositionDelta.X + 1, 0), (Tile) => new Vector2(Tile.Location.Left - Parent.Size.X, Parent.Position.Y))) { PositionDelta.X = 0; Component.VelocityX = 0; } else if(Component.VelocityX <= 0.001f && CheckStaticCollision(Parent, new Vector2(-Parent.Size.X / 2 + PositionDelta.X - 1, 0), (Tile) => new Vector2(Tile.Location.Right, Parent.Position.Y))) { PositionDelta.X = 0; Component.VelocityX = 0; } Parent.Position += PositionDelta; Parent.Y = Math.Max(0, Parent.Y); Parent.X = Math.Max(-Scene.TileSize.X / 2, Parent.X); Parent.X = Math.Min(Scene.MapSize.X - Scene.TileSize.X, Parent.X); // Special case: If we're at the bottom of the level, we should be considered grounded. if(Parent.Y > Scene.MapSize.Y - Parent.Size.Y) { Parent.Y = Scene.MapSize.Y - Parent.Size.Y; Component.VelocityY = 0; Component.IsGrounded = true; } } }
protected override void OnUpdate(GameTime Time) { base.OnUpdate(Time); if(_WalkDirection == Direction.Left) PhysicsComponent.VelocityX -= Math.Max(0, Math.Min(MaxWalkingSpeed + PhysicsComponent.VelocityX, WalkAcceleration * Time.GetTimeScalar())); else if(_WalkDirection == Direction.Right) PhysicsComponent.VelocityX += Math.Max(0, Math.Min(MaxWalkingSpeed - PhysicsComponent.VelocityX, WalkAcceleration * Time.GetTimeScalar())); if(!PhysicsComponent.IsGrounded && _WasGrounded) _LastUngrounded = DateTime.Now; _WasGrounded = PhysicsComponent.IsGrounded; if(PhysicsComponent.IsGrounded && !_JumpedThisFrame) // This may be called after the Jump, before PhysicsSystem updates position. _JumpedSinceUngrounded = false; _JumpedThisFrame = false; }