> For the complete documentation index, see [llms.txt](https://kinematicsoup.gitbook.io/reactor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kinematicsoup.gitbook.io/reactor/tutorials/virtual_players.md).

# Virtual Player Bots

## Summary

Create bots using virtual players that can control entities using a player controller just like real players, except their inputs are set in server scripts instead of being sent over the network from a client. The virtual player bots will move in random directions. If they get out of bounds they will move back in bounds. They will use physics queries to find nearby players and will shoot at the nearest player. This tutorial uses Tutorial [3](/reactor/tutorials/physics.md) or [5](/reactor/tutorials/syncgroups.md) as a base.

### Requirements

* [Reactor Requirements](/reactor/overview.md#requirements)
* Completion of Tutorials [1](/reactor/tutorials/basics.md), [2](/reactor/tutorials/player_controllers.md), and [3](/reactor/tutorials/physics.md).

## Setup

1. Start with the scene you created in Tutorial [3](/reactor/tutorials/physics.md) ([5](/reactor/tutorials/syncgroups.md)).

### Create a Static Cube in the Center of the Platform

The bots will not shoot at players if the static cube blocks their line-of-site to the player.

1. Create a cube.
2. Set the scale to (3, 3, 3).
3. Set the position to (0, 2, 0).
4. Add a *ksEntityComponent*.
5. In the inspector for the *ksEntityComponent*, set the collision filter to 'Terrain'.

## Scripting

### Create a Class for Collision Constants

We will create a class to define constants for the collision groups we created in Tutorial [3](/reactor/tutorials/physics.md).

1. In *'Assets/ReactorScripts/Common'*, create a script named 'Collision'.
   * The collision consts values are based on the order the collision groups are defined int. The first collision group is value 1, the second is 1 << 1, the third is 1 << 2 and so on.

*Collision.cs*

```c#
public class Collision
{
    public const uint PLAYER = 1;
    public const uint BULLET = 1 << 1;
    public const uint TERRAIN = 1 << 2;
}
```

### Create a Server Bot Player script

The server bot player script runs bot logic and sets virtual input for virtual players. Virtual input is input set from server scripts that is used in player controllers for entities controlled by virtual players. Only virtual players have virtual input and can have their inputs set on the server.

The server bot script moves in random directions for random intervals. If it gets out of bounds, it will move back in bounds. It uses an overlap query to look for nearby players and picks the closest player it has line-of-site to to shoot at. It uses a raycast query for line-of-site checks.

1. Select the 'Room' object.
2. In the **'Add Component'** menu, select **'Reactor->New Server Player Script'**.
3. Name the script 'ServerBot' and edit it as follows.
4. Set the 'Bounds' property in the inspector to be a little bit smaller than the platform. Use (9, 9) if you are using the scene from Tutorial [3](/reactor/tutorials/physics.md), or (24, 24) if you are using the scene from Tutorial ([5](/reactor/tutorials/syncgroups.md).

*ServerBotPlayer.cs*

```c#
using KS.Reactor.Server;
using KS.Reactor;

public class ServerBotPlayer : ksServerPlayerScript
{
    //X-Z bounds centered on the origin. If the bot moves outside the bounds it will try to move back in bounds.
    [ksEditable] private ksVector2 m_bounds;

    // Minimum and maximum times in seconds before choosing a new direction to move.
    [ksEditable] private float m_minMoveTime = .5f;
    [ksEditable] private float m_maxMoveTime = 4f;

    // Time in seconds before choosing a new target to shoot at.
    [ksEditable] public float m_chooseTargetInterval = .5f;

    // How far away the bot can see other players.
    [ksEditable] private float m_visibleRadius = 8f;

    // Max value in degrees for random error added to aim.
    [ksEditable] public float m_maxAimError = 15f;

    private float m_moveTimer;
    private float m_targetTimer;
    private float m_aimError;
    private ksIServerEntity m_entity;
    private ksIServerEntity m_target;
    private ksOverlapParams m_overlapParams;
    private ksRaycastParams m_raycastParams;

    private static ksRandom m_rand = new ksRandom();

    // Called after all other scripts on all entities are attached.
    public override void Initialize()
    {
        // If the player is a real player (not virtual/not a bot), remove this script.
        if (!Player.IsVirtual)
        {
            Scripts.Detach(this);
        }
        else
        {
            Room.OnUpdate[0] += Update;

            // Create the overlap parameters used to check for nearby players within the visible radius.
            m_overlapParams = new ksOverlapParams()
            {
                Shape = new ksSphere(m_visibleRadius),
                // Only check for entities with a collision filter that belongs to the player group.
                Filter = new ksGroupMaskFilter(Collision.PLAYER),
            };
            // Create the raycast parameters used to check for line-of-sight to a target player.
            m_raycastParams = new ksRaycastParams()
            {
                // Only check for entities with a collision filter that belongs to the terrain group.
                Filter = new ksGroupMaskFilter(Collision.TERRAIN)
            };
            ChooseDirection();
        }
    }
    
    // Called when the script is detached.
    public override void Detached()
    {
        if (Player.IsVirtual)
        {
            Room.OnUpdate[0] -= Update;
        }
    }
    
    // Called during the update cycle
    private void Update()
    {
        if (Player.OwnedEntities.Count == 0)
        {
            return;
        }
        m_entity = Player.OwnedEntities[0];

        // Decrement the move timer and choose a new direction to move if it reaches zero.
        m_moveTimer -= Time.Delta;
        if (m_moveTimer <= 0)
        {
            ChooseDirection();
        }

        // If the player's entity is out of bounds, move back in bounds.
        ksVector3 position = m_entity.Transform.Position;
        if (position.X <= -m_bounds.X && Player.VirtualInput.GetAxis(Axes.X) <= 0)
        {
            Player.VirtualInput.SetAxis(Axes.X, 1);
        }
        else if (position.X >= m_bounds.X && Player.VirtualInput.GetAxis(Axes.X) >= 0)
        {
            Player.VirtualInput.SetAxis(Axes.X, -1);
        }
        if (position.Z <= -m_bounds.Y && Player.VirtualInput.GetAxis(Axes.Y) <= 0)
        {
            Player.VirtualInput.SetAxis(Axes.Y, 1);
        }
        else if (position.Z >= m_bounds.Y && Player.VirtualInput.GetAxis(Axes.Y) >= 0)
        {
            Player.VirtualInput.SetAxis(Axes.Y, -1);
        }

        // Decrement the target timer and choose a new target to shoot at if it reaches zero. Always picks the closest
        // visible target.
        m_targetTimer -= Time.Delta;
        if (m_targetTimer <= 0f)
        {
            ChooseTarget();
            m_targetTimer += m_chooseTargetInterval;
        }

        // Set the rotation axes to the target aim.
        if (m_target != null && !m_target.IsDestroyed)
        {
            // Get the direction to the target.
            ksVector2 direction = position.XZ - m_target.Transform.Position.XZ;
            // Convert the direction to degrees and add the aim error.
            // ksRange.Degrees.Wrap will keep the value in the range [0, 360), and values outside the range will wrap
            // around.
            float degrees = ksRange.Degrees.Wrap(direction.ToDegrees() + m_aimError);
            // divide by 180 and subtract 1 to get a value between -1 and 1.
            float value = degrees / 180 - 1;
            // Set the rotation input value. Axis values must be between -1 and 1.
            Player.VirtualInput.SetAxis(Axes.ROTATION, value);
        }

        // Set the shoot button down (true = down, false = up) if we have a target.
        Player.VirtualInput.SetButton(Buttons.SHOOT, m_target != null && !m_target.IsDestroyed);
    }

    private void ChooseDirection()
    {
        // Set the X and Y axes values randomly to -1, 0, or 1, then pick a random amount of time to wait before we do
        // this again.
        Player.VirtualInput.SetAxis(Axes.X, m_rand.Next(-1, 2));
        Player.VirtualInput.SetAxis(Axes.Y, m_rand.Next(-1, 2));
        m_moveTimer = m_rand.NextFloat(m_minMoveTime, m_maxMoveTime);
    }

    private void ChooseTarget()
    {
        // Use an overlap query to find the nearby player entities within the visible radius.
        m_overlapParams.ExcludeEntity = m_entity;// Exclude this entity from the results.
        m_overlapParams.Origin = m_entity.Transform.Position;
        m_target = null;
        float nearestDistSq = float.MaxValue;
        foreach (ksOverlapResult overlap in Physics.Overlap(m_overlapParams))
        {
            // Check if we have an unobstructed line-of-site to the player.
            if (IsVisible(overlap.Entity))
            {
                // Check if this is the nearest player we have found and make them the target.
                float distSq = (m_entity.Transform.Position - overlap.Entity.Transform.Position).MagnitudeSquared();
                if (distSq < nearestDistSq)
                {
                    nearestDistSq = distSq;
                    m_target = (ksIServerEntity)overlap.Entity;
                }
            }
        }

        // If we have a target, add some random error to our aim.
        if (m_target != null)
        {
            m_aimError = m_rand.NextFloat(-m_aimError, m_aimError);
        }
    }

    private bool IsVisible(ksIEntity entity)
    {
        // Use a raycast to check if we have an unobstructed line-of-site to another entity.
        m_raycastParams.Origin = m_entity.Transform.Position;
        m_raycastParams.End = entity.Transform.Position;
        // The raycast uses a ksGroupMaskFilter to only check for hits with entities with a collision filter that belong
        // to the terrain group. If it doesn't hit anything, the entity is visible.
        return !Physics.RaycastAny(m_raycastParams);
    }
}
```

### Modify the Server Room Script to Create Virtual Players

The server room script will create virtual players in Initialize.

1. Open ServerRoom.cs from [Tutorial 3](/reactor/tutorials/physics.md).
2. Add a `NumBots` field.
3. Create virtual players to be bots in the `Initialize` function.

*ServerRoom.cs*

```c#
    ...
    [ksEditable]
    public int NumBots = 2;
    ...
    // Initialize the script. Called once when the script is loaded.
    public override void Initialize()
    {
        // Register a event handler that will be called when a new player joins the server.
        Room.OnPlayerJoin += OnPlayerJoin;

        // Register a event handler that will be called when a player leaves the server.
        Room.OnPlayerLeave += OnPlayerLeave;

        // Register an update event to be called at every frame at order 0.
        Room.OnUpdate[0] += Update;
        
        // Create virtual players (bots).
        for (int i = 0; i < NumBots; i++)
        {
            Room.CreateVirtualPlayer();
        }
    }
    ...
```

## Testing

1. Build your scene config (**CTRL + F2**).
2. Start a local server.
3. Enter play mode.

There will be 2 bots (or whatever number you set for 'Num Bots' in your 'ServerRoom' script) moving randomly around the platform. They will shoot at the closest player they have line-of-site to.
