|
// 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<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."); |
|
} |
|
|
|
} |
That's pretty neat, it's kinda the crowd manager. Probably the best solution I ve found so far, at free access, for zombielike behaviour, thanks.