public override void Update(/* inout */ SteeringBlender steering, Actor owner, IAgentStateManager agent) { if (!agent.HasProperty(AgentPropertyName.ActiveOpponent)) { ZombieWaitState zws = new ZombieWaitState(6.0f); agent.CurrentState = zws; return; } Actor opponent = GameResources.ActorManager.GetActorById(agent.GetProperty <int>(AgentPropertyName.ActiveOpponent)); BipedControllerComponent opponentBcc = opponent.GetComponent <BipedControllerComponent>(ActorComponent.ComponentType.Control); steering.Target = BepuConverter.Convert(opponentBcc.Controller.Body.Position); BipedControllerComponent bcc = owner.GetComponent <BipedControllerComponent>(ActorComponent.ComponentType.Control); BepuVec3 toOpponent = opponentBcc.Controller.Body.Position - bcc.Controller.Body.Position; float distance = toOpponent.Length(); ZombieSkillSet zss = owner.GetBehaviorThatImplementsType <ZombieSkillSet>(); BipedWeapon chosenAttack = distance <= zss.MeleeSkill.EffectiveRangeMax ? zss.MeleeSkill : zss.RangedSkill; Matrix attackTransform = Matrix.CreateTranslation(chosenAttack.MuzzleOffset) * Matrix.CreateWorld( BepuConverter.Convert(bcc.Controller.Body.Position), BepuConverter.Convert(bcc.Controller.ViewDirection), Vector3.Up); BepuVec3 bulletPath = opponentBcc.Controller.Body.Position - BepuConverter.Convert(attackTransform.Translation); // If we don't have a shot, we need to specify what kind of movement we need to remedy that. ZombieTacticalMovementState.MovementType movement = ZombieTacticalMovementState.MovementType.None; if (distance < chosenAttack.EffectiveRangeMin) { movement = ZombieTacticalMovementState.MovementType.Retreat; } else if (distance > chosenAttack.EffectiveRangeMax) { movement = ZombieTacticalMovementState.MovementType.Close; } else { BepuRay loeRay = new BepuRay(BepuConverter.Convert(attackTransform.Translation), bulletPath); LOSFilter filter = new LOSFilter(bcc.Controller.Body.CollisionInformation, opponentBcc.Controller.Body.CollisionInformation); RayCastResult loeResult; GameResources.ActorManager.SimSpace.RayCast(loeRay, chosenAttack.EffectiveRangeMax * 1.5f, filter.Test, out loeResult); EntityCollidable otherEntityCollidable = loeResult.HitObject as EntityCollidable; if (otherEntityCollidable != null && otherEntityCollidable.Entity != null && otherEntityCollidable.Entity.Tag != null && (int)(otherEntityCollidable.Entity.Tag) == opponent.Id) { toOpponent.Y = 0.0f; toOpponent.Normalize(); float aimTheta = (float)(Math.Acos(MathHelper.Clamp(BepuVec3.Dot(toOpponent, bcc.Controller.ViewDirection), 0.0f, 1.0f))); const float AIM_CONE_RADIANS = MathHelper.Pi / 12.0f; if (aimTheta <= AIM_CONE_RADIANS) { // TODO: P2: Add some wander to this value: bcc.WorldAim = BepuConverter.Convert(bulletPath); //chosenAttack.CurrentOperation = WeaponFunctions.TriggerPulled; BipedWeapon otherAttack = chosenAttack == zss.MeleeSkill ? zss.RangedSkill : zss.MeleeSkill; otherAttack.UpdateInputState(false, bcc); chosenAttack.UpdateInputState(true, bcc); return; } } else { movement = ZombieTacticalMovementState.MovementType.Lateral; } } if (movement != ZombieTacticalMovementState.MovementType.None) { ZombieTacticalMovementState ztms = new ZombieTacticalMovementState(movement); agent.CurrentState = ztms; } }
// Steer away from obstacles in the way. This method returns a zero vector if no correction is required. // It should be high priority and the steering from other behaviors should blend into the remaining space. // So if this returns a length 1.0f vector, avoiding the obstacle is most urgent and there is no room for other // steering. private Vector2 AvoidObstacles(Actor owner) { BipedControllerComponent bcc = owner.GetComponent <BipedControllerComponent>(ActorComponent.ComponentType.Control); // Conditions where we do not want to use this steering force. if (GetAngleFromVertical(bcc.Controller.Body.LinearVelocity) < MathHelper.PiOver4 || // We're probably falling... !bcc.Controller.SupportFinder.HasSupport || !bcc.Controller.SupportFinder.HasTraction) { return(Vector2.Zero); } // Sphere cast ahead along facing. List <RayCastResult> obstacles = new List <RayCastResult>(); SphereShape probe = new SphereShape(bcc.Controller.BodyRadius * 1.1f); RigidTransform probeStartPosition = new RigidTransform(bcc.Controller.Body.Position); // Add a small constant to the probe length because we want a minimum amount of forward probing, even if we are not moving. float probeLength = Math.Max(BepuVec3.Dot(bcc.Controller.Body.LinearVelocity, bcc.Controller.ViewDirection), 0.0f) + 1.0f; BepuVec3 probeSweep = bcc.Controller.ViewDirection * probeLength; ObstacleFilter filter = new ObstacleFilter(bcc.Controller.Body.CollisionInformation); GameResources.ActorManager.SimSpace.ConvexCast(probe, ref probeStartPosition, ref probeSweep, filter.Test, obstacles); RayCastDistanceComparer rcdc = new RayCastDistanceComparer(); obstacles.Sort(rcdc); BEPUutilities.Vector3 cross = BEPUutilities.Vector3.Zero; int obstacleIndex = 0; do { if (obstacles.Count == obstacleIndex) { return(Vector2.Zero); } cross = BEPUutilities.Vector3.Cross(bcc.Controller.ViewDirection, -obstacles[obstacleIndex++].HitData.Normal); }while (cross.X > 0.7f); // if cross.X > 0.7f, the obstacle is some kind of gentle ramp; ignore it. // dot will typically be negative and magnitude indicates how directly ahead the obstacle is. float dot = BEPUutilities.Vector3.Dot(bcc.Controller.ViewDirection, -obstacles[0].HitData.Normal); if (dot >= 0.0f) // The obstacle won't hinder us if we touch it. { return(Vector2.Zero); } // When cross.Y is positive, the object is generally to the right, so veer left (and vice versa). float directionSign = cross.Y >= 0.0f ? -1.0f : 1.0f; BEPUutilities.Vector2 result = BEPUutilities.Vector2.UnitX * directionSign * -dot; // Also scale response by how close the obstacle is. float distance = (obstacles[0].HitData.Location - bcc.Controller.Body.Position).Length(); result *= MathHelper.Clamp((1.0f - distance / probeLength), 0.0f, 1.0f); // / Math.Abs(dot); // So far the result is in terms of 'velocity space'. Rotate it to align with the controller facing. float velocityTheta = (float)(Math.Atan2(-probeSweep.X, -probeSweep.Z)); BEPUutilities.Matrix2x2 velocityWorld = SpaceUtils.Create2x2RotationMatrix(velocityTheta); float facingTheta = (float)(Math.Atan2(-bcc.Controller.HorizontalViewDirection.X, -bcc.Controller.HorizontalViewDirection.Z)); BEPUutilities.Matrix2x2 facingWorldInv = SpaceUtils.Create2x2RotationMatrix(facingTheta); facingWorldInv.Transpose(); // We want the transpose/inverse of the facing transform because we want to transform the movement into 'facing space'. return(BepuConverter.Convert(SpaceUtils.TransformVec2(SpaceUtils.TransformVec2(result, velocityWorld), facingWorldInv))); }