Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save HajiyevEl/3a83a3d88ae2ca5c9d44508d3da43ab7 to your computer and use it in GitHub Desktop.

Select an option

Save HajiyevEl/3a83a3d88ae2ca5c9d44508d3da43ab7 to your computer and use it in GitHub Desktop.

Revisions

  1. @andrew-raphael-lukasik andrew-raphael-lukasik revised this gist Oct 5, 2023. 3 changed files with 4 additions and 2 deletions.
    2 changes: 2 additions & 0 deletions .AttackSpotProvider.cs.md
    Original file line number Diff line number Diff line change
    @@ -2,3 +2,5 @@ Answers a question: "How to make enemies surround player"

    - `AttackSpotProvider.cs` provides attack positions to attackers.
    - `Attacker.cs` simulates an attacker

    ![GIF 05 10 2023 20-52-29](https://user-images.githubusercontent.com/3066539/273015683-f8420d72-9dfb-47b4-bcd2-bf7c0649b374.gif)
    2 changes: 1 addition & 1 deletion AttackSpotProvider.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // src*:
    // src*: https://gist.github.com/andrew-raphael-lukasik/95ee4c1ee27a0087e7e8dc1785b6c542
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Assertions;
    2 changes: 1 addition & 1 deletion Attacker.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // src*:
    // src*: https://gist.github.com/andrew-raphael-lukasik/95ee4c1ee27a0087e7e8dc1785b6c542
    using UnityEngine;

    public class Attacker : MonoBehaviour
  2. @andrew-raphael-lukasik andrew-raphael-lukasik created this gist Oct 5, 2023.
    4 changes: 4 additions & 0 deletions .AttackSpotProvider.cs.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    Answers a question: "How to make enemies surround player"

    - `AttackSpotProvider.cs` provides attack positions to attackers.
    - `Attacker.cs` simulates an attacker
    127 changes: 127 additions & 0 deletions AttackSpotProvider.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,127 @@
    // src*:
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Assertions;
    using BurstCompile = Unity.Burst.BurstCompileAttribute;

    public class AttackSpotProvider : MonoBehaviour
    {

    [SerializeField][Min(0)] float _playerRadius = 1.0f;
    [SerializeField][Min(0)] float _enemyRadius = 0.5f;

    HashSet<int> _takenSpots = new ();

    #if UNITY_EDITOR
    void OnDrawGizmos ()
    {
    Vector2 origin = transform.position;
    UnityEditor.Handles.color = Color.yellow;
    UnityEditor.Handles.CircleHandleCap( 0 , origin , Quaternion.identity , _playerRadius , EventType.Repaint );
    foreach( int spotIndex in _takenSpots )
    {
    AttackSpotProviderUtilities.GetRing( spotIndex , _playerRadius , _enemyRadius , out int ring , out int firstSpotIndexForThisRing );
    float innerCircle = _playerRadius + ring * _enemyRadius * 2f;
    int cmax = AttackSpotProviderUtilities.NumSpotsAroundACircleApprox( circleRadius:innerCircle , spotRadius:_enemyRadius );
    int c = spotIndex - firstSpotIndexForThisRing;
    AttackSpotProviderUtilities.SpotOffset( circleIndex:c , circleRadius:innerCircle , spotRadius:_enemyRadius , cmax , out Vector2 spotOffset );
    UnityEditor.Handles.CircleHandleCap( 0 , origin + spotOffset , Quaternion.identity , _enemyRadius , EventType.Repaint );
    }
    }
    #endif

    public void RegisterAttacker ( Vector2 attackerPosition , out int assignedSpotIndex , out Vector2 assignedOffset )
    {
    Vector2 spotOrigin = transform.position;

    int spotIndex = -1;
    for( int ring=0 ; ring<10 ; ring++ )
    {
    float innerCircle = _playerRadius + ring * _enemyRadius * 2f;
    int cmax = AttackSpotProviderUtilities.NumSpotsAroundACircleApprox( circleRadius:innerCircle , spotRadius:_enemyRadius );

    int nearestSpotIndex = -1;
    float nearestDist = float.MaxValue;
    Vector2 nearestOffset = Vector2.zero;
    for( int c=0 ; c<cmax ; c++ )
    {
    spotIndex++;
    if( !_takenSpots.Contains(spotIndex) )
    {
    AttackSpotProviderUtilities.SpotOffset( circleIndex:c , circleRadius:innerCircle , spotRadius:_enemyRadius , numSpotsAroundCircle:cmax , out Vector2 spotOffset );
    float spotDistance = Vector2.Distance( attackerPosition , spotOrigin + spotOffset );
    if( spotDistance<nearestDist )
    {
    nearestSpotIndex = spotIndex;
    nearestDist = spotDistance;
    nearestOffset = spotOffset;
    }
    }
    }

    if( nearestSpotIndex!=-1 )
    {
    assignedSpotIndex = nearestSpotIndex;
    _takenSpots.Add( nearestSpotIndex );
    assignedOffset = nearestOffset;
    return;
    }
    }

    throw new System.Exception("Something went wrong here.");
    }

    public void UnregisterAttacker ( int spotIndex )
    {
    Assert.IsTrue( _takenSpots.Contains(spotIndex) );
    _takenSpots.Remove( spotIndex );
    }

    }

    // burst-compiled to make these calculations go brrrrr fast
    [BurstCompile]
    public static class AttackSpotProviderUtilities
    {

    /// <summary> quick and dirty approximation </summary>
    [BurstCompile]
    public static int NumSpotsAroundACircleApprox ( float circleRadius , float spotRadius )
    {
    Assert.IsTrue( circleRadius>0 , $"{circleRadius} > 0" );
    Assert.IsTrue( spotRadius>0 , $"{spotRadius} > 0" );
    return Mathf.FloorToInt( ( 2f*Mathf.PI*( circleRadius + spotRadius ) )/(2f*spotRadius) );
    }

    /// <summary> Calculates local spot position </summary>
    [BurstCompile]
    public static void SpotOffset ( int circleIndex , float circleRadius , float spotRadius , int numSpotsAroundCircle , out Vector2 spotOffset )
    {
    Assert.IsTrue( circleIndex>=0 , $"{circleIndex} >= 0" );
    Assert.IsTrue( circleRadius>0 , $"{circleRadius} > 0" );
    Assert.IsTrue( spotRadius>0 , $"{spotRadius} > 0" );
    Assert.IsTrue( numSpotsAroundCircle>0 , $"{numSpotsAroundCircle} > 0" );
    float angle = Mathf.PI*2f * circleIndex/numSpotsAroundCircle;
    Vector2 unitVector = new Vector2( Mathf.Cos(angle) , Mathf.Sin(angle) );
    spotOffset = unitVector*circleRadius + unitVector*spotRadius;
    }

    /// <summary> Calculates ring index from spot index </summary>
    [BurstCompile]
    public static void GetRing ( int spotIndex , float initialCircleRadius , float spotRadius , out int ring , out int firstSpotIndexForThisRing )
    {
    Assert.IsTrue( spotIndex>=0 , $"{spotIndex} >= 0" );
    Assert.IsTrue( initialCircleRadius>0 , $"{initialCircleRadius} > 0" );
    Assert.IsTrue( spotRadius>0 , $"{spotRadius} > 0" );
    int cmaxSum = 0;
    for( ring=0 ; ring<100 ; ring++ )
    {
    float innerCircle = initialCircleRadius + ring * spotRadius * 2f;
    firstSpotIndexForThisRing = cmaxSum;
    cmaxSum += NumSpotsAroundACircleApprox( circleRadius:innerCircle , spotRadius:spotRadius );
    if( cmaxSum>spotIndex) return;
    }
    throw new System.Exception("Something went wrong here.");
    }

    }
    57 changes: 57 additions & 0 deletions Attacker.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    // src*:
    using UnityEngine;

    public class Attacker : MonoBehaviour
    {
    [SerializeField] GameObject _attacked;
    [SerializeField] float _tickTime = 0.23f;// seconds

    AttackSpotProvider _attackSpotProvider;
    int _assignedSpotIndex;
    Vector2 _assignedOffset;

    #if UNITY_EDITOR
    void OnDrawGizmos ()
    {
    UnityEditor.Handles.color = Color.yellow;
    UnityEditor.Handles.CircleHandleCap( 0 , transform.position , Quaternion.identity , 0.1f , EventType.Repaint );

    if( _attackSpotProvider!=null )
    {
    Vector3 src = transform.position;
    Vector3 dst = _attackSpotProvider.transform.position + (Vector3)_assignedOffset;
    Vector3 dir = dst - src;

    Gizmos.color = Color.cyan;
    Gizmos.DrawLine( src , dst );
    UnityEditor.Handles.color = Gizmos.color;
    UnityEditor.Handles.ArrowHandleCap( 0 , dst-dir.normalized*0.6f , Quaternion.LookRotation(dir) , 0.6f , EventType.Repaint );
    }
    }
    #endif

    void OnEnable ()
    {
    _attackSpotProvider = _attacked.GetComponent<AttackSpotProvider>();
    if( _attackSpotProvider!=null )
    {
    _attackSpotProvider.RegisterAttacker( transform.position , out _assignedSpotIndex , out _assignedOffset );
    InvokeRepeating( nameof(Tick) , Random.Range(0,_tickTime) , _tickTime );
    }
    }

    void OnDisable ()
    {
    if( _attackSpotProvider!=null )
    {
    _attackSpotProvider.UnregisterAttacker( _assignedSpotIndex );
    }
    }

    void Tick ()
    {
    _attackSpotProvider.UnregisterAttacker( _assignedSpotIndex );
    _attackSpotProvider.RegisterAttacker( transform.position , out _assignedSpotIndex , out _assignedOffset );
    }

    }