Posted: 16th Feb 2022 17:53
Hello all together, i have a question and would be happy, if someone helps.
I am working on an rts in 2D and thinking of a what is the best method for me to shot if the units reach a certain distance.

One method is to work with vectors and calculate the root of (vectorx1#*vectorx1#)+(vectory1#*vectory1#).

A second method i have tried is with collision commands (getspriteincircle) and the third one with (getspritedistance).

I think if i have many units that the app has a lot to calculate (Everytime calculating the distance between the different units). I guess that the getspritedistance calculates similar to the root on the vectors.

Is the (getspriteincircle funktion) a faster method or is the code behind similar to the first method?

Thank you in advance.
Posted: 16th Feb 2022 21:59
this doesn't answer your question directly (Distance vs GetSpriteInCircle - which you can test yourself) but a notion of culling which distances to calculate/compare, instead:
+ Code Snippet
// Project: 2D Ranges 
// Created: 2022-02-16
// By: Virtual Nomad
// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "2D Ranges" )
SetWindowSize( 640,360, 0 )
SetWindowAllowResize( 1 )

// set display properties
SetVirtualResolution( 1280,720)
SetOrientationAllowed( 1, 1, 1, 1 )
SetSyncRate( 0, 0 ) 
SetScissor( 0,0,0,0 )
UseNewDefaultFonts( 1 ) 

Type Entity
	ID, SensorID, JointID
EndType
GLOBAL Allies as Entity []

MakeAlly(10) //Scatter Allies

GLOBAL Enemies as Integer []
Summon(20)

Type Target
	Distance#, ID //Distance# first so sorted by Distance#
EndType

GLOBAL Red, Green
Red = MakeColor(255,0,0)
Green = MakeColor(0,255,0)

SetPhysicsGravity(0,0)
Debug = -1
SetViewOffset(0,0) //Delete Physics Walls

do
    If GetRawKeyState(27) then End //[ESC] to End
    
	If GetPointerPressed()
		MakeAlly(10)
		Summon(10)
	EndIf
	
	If GetRawMouseRightPressed() //Toggle Debug
		Debug = -Debug
		If Debug = 1 then SetPhysicsDebugOn() else SetPhysicsDebugOff()
	Endif
	
	InRange()
	
    Print( ScreenFPS() )
    StepPhysics(GetFrameTime())
    Sync()
loop

Function InRange()
	For a = 0 to Allies.Length
		If GetSpriteFirstContact(Allies[a].SensorID)
			Targets as Target []
			Repeat
				ThisTarget as Target
				ThisTarget.ID = GetSpriteContactSpriteID2()
				ThisTarget.Distance# = GetSpriteDistance(Allies[a].ID, ThisTarget.ID)
				Targets.InsertSorted(ThisTarget) //Sorting by Distance# (First element in Target Type)
			Until GetSpriteNextContact() = 0
			
			For b = 0 to Targets.Length 
				x  = GetSpriteXByOffset(Allies[a].ID)	: 	y  = GetSpriteYByOffset(Allies[a].ID)
				ex = GetSpriteXByOffset(Targets[b].ID)	:	ey = GetSpriteYByOffset(Targets[b].ID)
				If b = 0 then Color = Green Else Color = Red //Since Targets already sorted by Distance, 0 = Closest
				DrawLine(x,y,ex,ey,Color,Color)
			Next b
			Targets.Length = -1
		EndIf
	NExt a
EndFunction


Function Summon(Num)
	For a = 1 to Num
		ThisEnemy = CreateSprite(0)	:	SetSpriteColor(ThisEnemy,255,0,0,255)
		x = Random(100,1180)		:	y = Random(100,620)
		SetSpritePositionByOffset(ThisEnemy,x,y)
		SetSpritePhysicsOn(ThisEnemy,2)
		Enemies.Insert(ThisEnemy)
	next a
EndFunction

Function MakeAlly(Num)
	For a = 1 to Num
	ThisAlly as Entity
		ThisAlly.ID = CreateSprite(0)		:	SetSpriteColor(ThisAlly.ID,0,255,0,255)
		x = Random(100,1180)	:	y = Random(100,620)
		SetSpritePositionByOffset(ThisAlly.ID, x,y)
		SetSpritePhysicsOn(ThisAlly.ID,2)	:	SetSpriteGroup(ThisAlly.ID,-10)
		
		ThisAlly.SensorID = CreateSprite(0)	:	SetSpriteVisible(ThisAlly.SensorID,0)
		SetSpritePositionByOffset(ThisAlly.SensorID,x,y)
		SetSpritePhysicsOn(ThisAlly.SensorID,3)
		ThisAlly.JointID = CreateWeldJoint(ThisAlly.ID, ThisAlly.SensorID, x,y,0)
		SetSpritePhysicsIsSensor(ThisAlly.SensorID,1)
		SetSpriteShapeCircle(ThisAlly.SensorID,0,0,96)
		SetSpriteGroup(ThisAlly.SensorID,-10) // Sensor ignores Ally sprites
		Allies.Insert(ThisAlly)
	Next a
EndFunction


[LMB] = Add Allies/Enemies | [RMB] = Toggle Debug (view ranges)

then, only checking for enemies within a given sprite's range when they are ready to fire/attack will further "cull", minimizing Distance checks even more-so.

add, i guess i'm suggesting that you won't find a faster distance check for all allies vs all enemies than Box2D (sensors) followed by the sweeps above
Posted: 17th Feb 2022 11:57
Ok, I happen to see this problem twice for now and
since my first ever released game 'vpop' (listed in the agk showcase, would appreciate much if andorid user fella's lend a hand that lol)
did not downloaded as much as I expected and set me sad about game programming, I can spare time for codes.

At the topic 'inverse square root' I'd suggested to not to use sqrt.

So, if you only check if a unit is in range or not (bool), you definetly do not have to use sqrt.

You check the absoulte value of x differences, if it is below the ranged distance then you check the absolute value of y distances and if it is below the ranged distance then the unit is in range.

So, instead of;

Distance = Sqrt((Ax - Bx) * (Ax - Bx) + (Ay - By) * (Ay - By))
If Distance <= Range Then InRange = True

If Abs(Ax - Bx) <= Range
If Abs(Ay - By) <= Range Then InRange = True
EndIf


Then, another problem occurs, do we have to check all units against each other?
No!
We may subdivide the map into grids and group the units according to their current grids.
And then we check only the units which are in the grid_range.
Let's say;
gridsize = 100
range = 190
xpos = 543
ypos = 409

so, our unit is in the grid 5, 4 and, our grid_range is +-2 (range = 190) -> (1 + Floor(Range/GridSize))
we check the units which are on the grids, from (3, 2) To (7, 6).


So, every grid will hold the ID of the unit which are on it and the number of total units on it.

About the performance, I compared @Virtual Nomad's code in equalavent circumtances (unit count, resolution) and saw that, when the unit count on the map is low, my code is slower but when it goes like 1024, my code is faster, besides no sprite usages.

The grid size and unit's ranges directly affects the performance on my code. And there is a limit which a grid can hold maximum units.

And If I was making a 2D game with sprites, I'd choose @Virtual Nomad's code for sure.

Hope this helps...




Simply drag the mouse on the screen to set our hero's new position, it is fun to watch, like a spider is walking on it's web.

This code may not be the best solution and is avaible for optimization for better performance;




+ Code Snippet
// Project: enemy distance 
// Created: 2022-02-16

// show all errors
SetRawWritePath(GetReadPath())
SetErrorMode(2)
#option_explicit

// set window properties
SetWindowTitle( "enemy distance" )
SetWindowSize( 1920, 1080, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1920, 1080 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 0, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts

#Constant _GRID_SIZE_ 64
#Constant _X_TILES_ 20
#Constant _Y_TILES_ 16
#Constant _UNIT_RADIUS_ 7
#Constant _MAX_UNITS_IN_A_GRID_ 32





Global _UNIT_GRID_MAP_ As Integer[_X_TILES_, _Y_TILES_, _MAX_UNITS_IN_A_GRID_]
//This Arrays first index will hold the unit count on it
//_UNIT_GRID_MAP_[gX, gY, 0] = UnitCount
//_UNIT_GRID_MAP_[gX, gY, 1] = First Unit Index



Type _UNIT_TYPE_
	Status			As Integer
	xPos			As Float
	yPos			As Float
	xTile			As Integer
	yTile			As Integer
	Range			As Float
	GridRange		As Integer
EndType


#Constant _MAX_UNITS_ 256


Global _UNIT_ As _UNIT_TYPE_[_MAX_UNITS_]


_CREATE_UNITS_()
//randomly place units to the map
//calculate their current grid positions
//randomy give them some ranges
//calculte how much grids will be checked depending on the range


Local mB As Integer, mX As Float, mY As Float
Do
    

    Print( ScreenFPS() )
    mB = GetPointerState()
    If mB
		mX = GetPointerX()
		mY = GetPointerY()
		If mX &lt; _X_TILES_ * _GRID_SIZE_
			If mY &lt; _Y_TILES_ * _GRID_SIZE_
				If mX &gt;= 0
					If mY &gt;= 0
						_UNIT_[0].xPos = mX
						_UNIT_[0].yPos = mY
					EndIf
				EndIf
			EndIf
		EndIf
	EndIf
    _UPDATE_UNITS_()
    _CHECK_RANGES_()
    _DRAW_()
    Sync()
Loop

Function _CHECK_RANGES_()
	Local U As Integer, kX As Integer, kY As Integer, lX As Integer, lY As Integer, i As Integer, j As Integer, E As Integer, Z As Integer
	Local Ax As Float, Ay As Float, Range As Float
	Local GridRange As Integer, X0 As Integer, X1 As Integer, Y0 As Integer, Y1 As Integer
	For U = 0 To _MAX_UNITS_ - 1
		kX = _UNIT_[U].xTile
		kY = _UNIT_[U].yTile
		Ax = _UNIT_[U].xPos
		Ay = _UNIT_[U].yPos
		
		Range = _UNIT_[U].Range
		GridRange = _UNIT_[U].GridRange
		
		//Precalculate the grids index limits, which will be checked
		X0 = kX - GridRange
		If X0 &lt; 0 Then X0 = 0
		X1 = kX + GridRange
		If X1 &gt;= _X_TILES_ Then X1 = _X_TILES_ - 1

		Y0 = kY - GridRange
		If Y0 &lt; 0 Then Y0 = 0
		Y1 = kY + GridRange
		If Y1 &gt;= _Y_TILES_ Then Y1 = _Y_TILES_ - 1
		
		
		For j = Y0 To Y1
			For i = X0 To X1
				For E = 0 To _UNIT_GRID_MAP_[i, j, 0] - 1   //unit count on that grid
					Z = _UNIT_GRID_MAP_[i, j, E + 1]
					If Z = U Then Continue  //dont check ourselves
					//If _UNIT_[U].Status = _UNIT_[Z].Status Then Continue //
					If Abs(Ax - _UNIT_[Z].xPos) &lt;= Range
						If Abs(Ay - _UNIT_[Z].yPos) &lt;= Range
							//I N   R A N G E ! ! !
						DrawLine(Ax, Ay, _UNIT_[Z].xPos, _UNIT_[Z].yPos, -1, -1)
						EndIf
					EndIf
				Next
			Next
		Next
	Next
		
		
EndFunction


Function _DRAW_()
	Local X As Integer, Y As Integer, kX As Integer, kY As Integer, Z As Integer
	Local dX As Float, dY As Float
	
	For Y = 0 To _Y_TILES_
		DrawLine(0, Y * _GRID_SIZE_, _X_TILES_ * _GRID_SIZE_, Y * _GRID_SIZE_, 0xFF808080, 0xFF808080)
	Next
	For X = 0 To _X_TILES_
		DrawLine(X * _GRID_SIZE_, 0, X * _GRID_SIZE_, _Y_TILES_ * _GRID_SIZE_ - 1, 0xFF808080, 0xFF808080)
	Next
	
	For X = 0 To _MAX_UNITS_ - 1
		Y = 0xFFFF8000
		If _UNIT_[X].Status = 1 Then Y = 0xFF0000FF
		If X = 0 Then Y = 0xFF00FF00
		DrawEllipse(_UNIT_[X].xPos, _UNIT_[X].yPos, _UNIT_RADIUS_, _UNIT_RADIUS_, Y, Y, 1)
	Next
	
	
EndFunction



Function _UPDATE_UNITS_()
	Local E As Integer, tGrids As Integer, z As Integer, kX As Integer, kY As Integer
	Local Ax As Float, Ay As Float, Bx As Float, By As Float, i As Float, j As Float
	
	For E = 0 To _Y_TILES_ - 1
		For Z = 0 To _X_TILES_ - 1
			_UNIT_GRID_MAP_[Z, E, 0] = 0  //The first index holds the unit count in that grid
		Next
	Next
	
	For E = 0 To _MAX_UNITS_ - 1
		Ax = _UNIT_[E].xPos
		Ay = _UNIT_[E].yPos
		kX = Floor(Ax/_GRID_SIZE_)
		kY = Floor(Ay/_GRID_SIZE_)
		_UNIT_[E].xTile = kX
		_UNIT_[E].yTile = kY
		tGrids = _UNIT_GRID_MAP_[kX, kY, 0] + 1
		_UNIT_GRID_MAP_[kX, kY, 0] = tGrids
		_UNIT_GRID_MAP_[kX, kY, tGrids] = E
	Next
EndFunction





Function _CREATE_UNITS_()
	Local E As Integer, j As Integer
	Local xPos As Float, yPos As Float
	For E = 0 To _MAX_UNITS_ - 1
		Do
			xPos = (Random() * _GRID_SIZE_ * _X_TILES_)/65535
			yPos = (Random() * _GRID_SIZE_ * _Y_TILES_)/65535
			For j = 0 To E - 1
				If Abs(_UNIT_[j].xPos - xPos) &lt;= _UNIT_RADIUS_
					If Abs(_UNIT_[j].yPos - yPos) &lt;= _UNIT_RADIUS_ Then Exit
				EndIf
			Next
			If j = E Then Exit
		Loop
		_UNIT_[E].xPos = xPos
		_UNIT_[E].yPos = yPos
		j = Random()
		_UNIT_[E].Status = 1 + (j &amp;&amp; 1)
		_UNIT_[E].Range = _GRID_SIZE_ + (Random() * _GRID_SIZE_/65535)
		_UNIT_[E].GridRange = Floor(_UNIT_[E].Range/_GRID_SIZE_)
	Next
	_UNIT_[0].Range = _GRID_SIZE_ * 3
	_UNIT_[0].GridRange = 1 + Floor(_UNIT_[0].Range/_GRID_SIZE_)
	
EndFunction



P.S. Also @Virtual Nomad's units are sorted due to their distances.
Posted: 17th Feb 2022 23:19
Use a shooting timer.

Just inc a timer then stop the bullet unless it hits collision.

Why?

Because it is more real life.

What I do is use a array for my timer for each bullet.
Posted: 18th Feb 2022 17:33
Many thanks for the postings. I thought about a good solution for many days, without success. The codes look very complex, so i will try to read and work with them this weekend. Now i have new ideas.


Game_Code_here: Thanks, but this is well known. I am using arrays and timers for the shots. I only was searching for a good solution to check the distance.
Posted: 18th Feb 2022 23:58
get_distance=GetSpriteDistance(sprite Id,Sprite id)

It is the best way.
Posted: 26th Feb 2022 14:16
t is the best way.


I disagree. If you're only looking for objects that are within a certain distance, then you don't need the actual distance. You can drop the square root from the calculation and compare it to the squared value of the range you wish to check and speed things up greatly.

+ Code Snippet
range = 75

for i = 1 to 1000
	if (enemy[i].x - player.x)^2 + (enemy[i].y - player.y)^2 &lt;= range * range
		// Enemy is within 75 units of player
	endif
next i
Posted: 26th Feb 2022 21:42
Phaelax

I love your way of getting distance.

I will try this for myself.