AI component for a bot character. It finds the nearest other CharacterController, moves toward it, faces the movement direction, uses a PushAbility when within attack range, and occasionally dashes while airborne; when no target it wanders by choosing random directions every 2–4 seconds.
using Sandbox;
using System;
using System.Linq;
public sealed class BotAI : Component
{
[Property] public float Speed { get; set; } = 220f;
[Property] public float AttackRange { get; set; } = 220f;
private CharacterController _cc;
private PushAbility _push;
private ManeuverAbility _maneuver;
private TimeUntil _nextDecision;
private Vector3 _moveDir;
protected override void OnAwake()
{
_cc = Components.Get<CharacterController>();
_push = Components.Get<PushAbility>();
_maneuver = Components.Get<ManeuverAbility>();
}
protected override void OnUpdate()
{
if ( IsProxy ) return;
var target = FindTarget();
if ( target.IsValid() )
{
_moveDir = (target.WorldPosition - WorldPosition).WithZ( 0 ).Normal;
if ( _cc.IsValid() )
{
_cc.Velocity = _moveDir * Speed;
WorldRotation = Rotation.LookAt( _moveDir );
}
float dist = Vector3.DistanceBetween( WorldPosition, target.WorldPosition );
if ( dist <= AttackRange && _push.IsValid() && _push.IsReady )
{
_push.TryExecuteBotPush();
}
}
else
{
if ( _nextDecision )
{
_nextDecision = Game.Random.Float( 2f, 4f );
float angle = Game.Random.Float( 0f, MathF.PI * 2f );
_moveDir = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f );
}
if ( _cc.IsValid() )
{
_cc.Velocity = _moveDir * (Speed * 0.6f);
}
}
if ( _cc.IsValid() && !_cc.IsOnGround && _maneuver.IsValid() && _maneuver.IsDashReady )
{
_maneuver.TryExecuteBotDash( -_cc.Velocity.Normal );
}
}
private GameObject FindTarget()
{
return Scene.GetAllComponents<CharacterController>()
.Where( c => c.GameObject != GameObject && c.GameObject.IsValid() )
.OrderBy( c => Vector3.DistanceBetween( WorldPosition, c.WorldPosition ) )
.FirstOrDefault()?.GameObject;
}
}