// src*: https://gist.github.com/andrew-raphael-lukasik/95ee4c1ee27a0087e7e8dc1785b6c542 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 _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 quick and dirty approximation [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) ); } /// Calculates local spot position [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; } /// Calculates ring index from spot index [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."); } }