Algorithm: AOI vision algorithm in the game (cross linked list)

Time:2022-5-26

Why this algorithm?

For entities in a game scene, when fighting is frequent, there may be data synchronization of thousands of entities, such as coordinate synchronization when thousands of entities move, synchronization of attributes of entities in the whole scene in a large battle scene, etc., which will cause a problem. The amount of synchronized data is very large, but for the client, the size of screen field of view we can see is fixed, We don’t need to know all the data of entities outside the field of vision, so we don’t need to synchronize. At this time, we need an algorithm to quickly locate the entities within my field of vision, and then synchronize relevant messages for these players.

Algorithm data structure design

1. In order to ensure the search speed of linked list elements, the linked list is realized by jumping list.
2. You need two linked lists, xlist and ylist. You need to store the player’s X and Y coordinates in order on these two linked lists.

Implementation details

Note: the current design is only based on two-dimensional

public AoiZone(float xLinksLimit, float yLinksLimit)
{
    _ xLinks = new AoiLinkedList(limit: xLinksLimit);// X-axis linked list
    _ yLinks = new AoiLinkedList(limit: yLinksLimit);// Y-axis linked list
}

public AoiLinkedList(int maxLayer = 8, float limit = 0)
{
    _ limit = limit;// Fault tolerance value during coordinate comparison
    _ maxLayer = maxLayer;// Number of skip layers
    Add(float.MinValue);
}

public AoiNode Add(float target, AoiEntity entity = null)
{
    var rLayer = 1;

    if (_header == null)
    {
        //Create jump table
        rLayer = _maxLayer;

        var tempHeader = _ header = new AoiNode(rLayer, target, entity);// Head node

        for (var layer = _maxLayer - 1; layer >= 1; --layer)
        {
            _ header = _ header. Down = new AoiNode(layer, target, top: _header);  // Top: pre pointer down: next pointer
        }

        _header = tempHeader;
        return null;
    }
    
    //Randomly select a layer to insert nodes
    while (rLayer < _maxLayer && _random.Next(2) == 0) ++rLayer;    
    //The head node of the highest layer (the layer with the least nodes)
    AoiNode cur = _header, insertNode = null, lastLayerNode = null;

    for (var layer = _maxLayer; layer >= 1; --layer)
    {
        while (cur.Right != null && cur.Right.Value < target) cur = cur. Right;// Compare the size with the X / y value of the position to be inserted (there may be new inserted data, which must be compared every time)

        if (layer <= rLayer)    //
        {
            insertNode = new AoiNode(layer, target, entity: entity, left: cur, right: cur.Right);   //

            if (cur.Right != null) cur.Right.Left = insertNode;
            cur. Right = insertNode;// Node insertion

            If (lastlayernode! = null) // skip table upper and lower layer pointer maintenance
            {
                lastLayerNode.Down = insertNode;
                insertNode.Top = lastLayerNode;
            }

            lastLayerNode = insertNode;
        }

        cur = cur. Down;// Next floor
    }

    Count++;// Increase the number of nodes
    return insertNode;
}

Implementation of update function in the game

//Key: entity ID
//Area: field of view
//Enter: the player in the player coordinate field of view in this frame
public AoiEntity Refresh(long key, Vector2, out HashSet<long> enter)
{
    var entity = Refresh(key, area);
    enter = entity?.ViewEntity;
    return entity;
}

public AoiEntity Refresh(long key, float x, float y, Vector2 area)
{
    if (!_entityList.TryGetValue(key, out var entity)) return null;

    var isFind = false;

    if (Math.Abs(entity.X.Value - x) > 0)
    {
        isFind = true;
        _xLinks.Move(entity.X, ref x);
    }

    if (Math.Abs(entity.Y.Value - y) > 0)
    {
        isFind = true;
        _yLinks.Move(entity.Y, ref y);
    }

    if (isFind) Find(entity, ref area);
    return entity;
}

//This function is mainly the process of changing the linked list into order after the player moves each frame and changes the X and Y coordinates
public void Move(AoiNode node, ref float target)
{
    var cur = node;

    #region Left

    If (target > cur. Value) // the value after moving is greater than the value of the current node
    {
        while (cur != null)
        {
            if (cur.Right != null && target > cur.Right.Value)
            {
                var findNode = cur;
                // Find the target node to be moved to.
                //If there are many nodes here, traversal may consume some performance
                while (findNode.Right != null && findNode.Right.Value < target) findNode = findNode.Right;    
                // Fuse the current node.
                CircuitBreaker(cur);// Remove from current location
                // Move to the target node location
                cur.Left = findNode;
                cur. Right = findNode. Right;// Add to new location
                if (findNode.Right != null) findNode.Right.Left = cur;
                findNode.Right = cur;
            }

            cur.Value = target;
            cur = cur. Top;// Adjust the node order of the upper layer of the jump table
        }

        return;
    }

    #endregion Left

    #region Right

    While (cur! = null) // / the value after moving is less than the value of the current node
    {
        if (cur.Left != null && target < cur.Left.Value)
        {
            // Find the target node to be moved to.
            var findNode = cur;
            while (findNode.Left != null && findNode.Left.Value > target) findNode = findNode.Left;
            // Fuse the current node.
            CircuitBreaker(cur);
            // Move to the target node location
            cur.Right = findNode;
            cur.Left = findNode.Left;
            if (findNode.Left != null) findNode.Left.Right = cur;
            findNode.Left = cur;
        }

        cur.Value = target;
        cur = cur.Top;
    }

    #endregion Right
}
private void Find(AoiEntity node, ref Vector2 area)
{
    //Save the visual entities of the previous frame to the backup HashSet set
    SwapViewEntity(ref node.ViewEntity, ref node.ViewEntityBak);

    #region xLinks
    
    //Locate this entity, then traverse the left and right pointers on xlist and ylist respectively, and store the entity with distance within the specified range in viewentity of this frame
    for (var i = 0; i < 2; i++)
    {
        var cur = i == 0 ? node.X.Right : node.X.Left;

        while (cur != null)
        {
            if (Math.Abs(Math.Abs(cur.Value) - Math. Abs(node.X.Value)) > area. X)    //cur. Value is the value of AOI grid node
            {
                break;
            }

            if (Math.Abs(Math.Abs(cur.Entity.Y.Value) - Math. Abs(node.Y.Value)) <= area. Y)  //cur. Entity. Y. Value is the entity value on the current grid node
            {
                if (Distance(
                    new Vector2(node.X.Value, node.Y.Value),
                    new Vector2(cur.Entity.X.Value, cur.Entity.Y.Value)) <= area.X)
                {
                    node. ViewEntity. Add(cur.Entity.Key);// Within the scope
                }
            }

            cur = i == 0 ?  cur. Right : cur. Left; // Traverse on the left and right respectively
        }
    }

    #endregion xLinks

    #region yLinks

    for (var i = 0; i < 2; i++)
    {
        var cur = i == 0 ? node.Y.Right : node.Y.Left;

        while (cur != null)
        {
            if (Math.Abs(Math.Abs(cur.Value) - Math.Abs(node.Y.Value)) > area.Y)
            {
                break;
            }

            if (Math.Abs(Math.Abs(cur.Entity.X.Value) - Math.Abs(node.X.Value)) <= area.X)
            {
                if (Distance(
                    new Vector2(node.X.Value, node.Y.Value),
                    new Vector2(cur.Entity.X.Value, cur.Entity.Y.Value)) <= area.Y)
                {
                    node.ViewEntity.Add(cur.Entity.Key);
                }
            }

            cur = i == 0 ? cur.Right : cur.Left;
        }
    }

    #endregion yLinks
}
//For function callback
//People out of view in this frame relative to the previous frame
public IEnumerable<long> Leave => ViewEntityBak.Except(ViewEntity);
//The person who enters the field of view in this frame relative to the previous frame
public IEnumerable<long> NewEnter => ViewEntity.Except(ViewEntityBak);//The person who enters the field of view in this frame relative to the previous frame

Test code

private static void Main(string[] args)
  {
  var zone = new AoiZone(.001f, .001f);
  var area = new Vector2(3, 3);

  //Add 500 players.

  for (var i = 1; i <= 500; i++) zone.Enter(i, i, i);

  //Refresh the information with key 3.

  zone.Refresh(3, area, out var enters);

  Console. Writeline ("----------------- list of players whose ID is 3 players' current field of view ----------------");

  foreach (var aoiKey in enters)
  {
      var findEntity = zone[aoiKey];
      Console.WriteLine($"X:{findEntity.X.Value} Y:{findEntity.Y.Value}");
  }

  //Update the coordinates of key 3 to (20,20).

  var entity = zone.Refresh(3, 20, 20, new Vector2(3, 3), out enters);

  Console. Writeline ("---------------- ID is the list of players who leave the player's field of vision after 3 players move ------------");

  foreach (var aoiKey in entity.Leave)
  {
      var findEntity = zone[aoiKey];
      Console.WriteLine($"X:{findEntity.X.Value} Y:{findEntity.Y.Value}");
  }

  Console. Writeline ("---------------- ID is the list of players newly added to the player's field of vision after 3 players move ----------------");

  foreach (var aoiKey in entity.NewEnter)
  {
      var findEntity = zone[aoiKey];
      Console.WriteLine($"X:{findEntity.X.Value} Y:{findEntity.Y.Value}");
  }

  //Leave current AOI

  zone.Exit(50);}

test result

---------------List of players whose ID is 3 players' current field of view--------------
X:4 Y:4
X:5 Y:5
X:2 Y:2
X:1 Y:1
---------------List of players whose ID is 3 who leave the player's field of vision after moving--------------
X:4 Y:4
X:5 Y:5
X:2 Y:2
X:1 Y:1
---------------List of players whose ID is 3 and who are newly added to the player's field of vision after moving--------------
X:20 Y:20
X:21 Y:21
X:22 Y:22
X:19 Y:19
X:18 Y:18

Existing problems

At present, there is a problem in the cross linked list design: when the number of entities in the project is very large and the entities move frequently in each frame, the X and Y linked list coordinates will be sorted in each frame, which still consumes a lot of performance. I’m still thinking about how to solve this problem by using the linked list alone.

quote:https://github.com/qq362946/A…