Advertisement

RTS CD

Started by May 22, 2020 08:02 AM
20 comments, last by JoeJ 4 years, 8 months ago

I`m designing the collision detection system, it`s a basic thing but I forgot how it`s done (I had it once working). Do I need to check three out of four tile corners to get a collision?

CBody CollisionBodies[100];
int BodyCount =-1;
VOID AddCollisionBody(int posx, int posz, int tilewidth, CBody * Bodies)
{
BodyCount++;
Bodies[BodyCount].nwx = posx - tilewidth / 2;
Bodies[BodyCount].nex = posx + tilewidth / 2;
Bodies[BodyCount].sex = posx + tilewidth / 2;
Bodies[BodyCount].swx = posx - tilewidth / 2;
Bodies[BodyCount].nwz = posz + tilewidth / 2;
Bodies[BodyCount].nez = posz + tilewidth / 2;
Bodies[BodyCount].sez = posz - tilewidth / 2;
Bodies[BodyCount].swz = posz - tilewidth / 2;

}
VOID Detect(CBody * Bodies)
{
for (int ii = 0; ii < BodyCount + 1; ii++)
{
 for (int i = 0; i < BodyCount + 1; i++)
 {
  if ((Bodies[ii].nex > Bodies[i].nwx) &amp;&amp;
   (Bodies[ii].nex < Bodies[i].nex) &amp;&amp;
   (Bodies[ii].nez > Bodies[i].sez) &amp;&amp;
   (Bodies[ii].nez < Bodies[i].nez))
  {

  }
   
 }

}
}

My project`s facebook page is “DreamLand Page”

if you want to interact each item of an array with each other, you can do this:

for (int i = 0; i < count; i++)
for (int j = i+1; j < count; j++)
	DoSomething (i, j);

That's only half of work in comparison to your code. Pay attention to the second loop where we do not start from zero.

Advertisement

Your counting is odd. would be more intuitive this way:

int BodyCount = 0;
VOID AddCollisionBody(int posx, int posz, int tilewidth, CBody * Bodies)
{
Bodies[BodyCount].nwx = posx - tilewidth / 2;
Bodies[BodyCount].nex = posx + tilewidth / 2;
//...
BodyCount++;
}

After that BodyCount contains the number of bodies, not one less which is confusing, and if there is nothing it's zero.

Finally, it would be much faster if you would insert your bodies into a grid or other spatial structure, so you do not need to test collisions for each vs. every other but only nearby bodies.
But for less than X your simple solution is faster. (X might be somewhat like 100 in practice.)

Not sure what you mean with tile corners.

JoeJ said:
Your counting is odd

with BodyCount++ post array usage you need to remember that BodyCount has been incremented, with BodyCount++ prior you have a new page ready for use, without needing to worry what BodyCount might have a wrong value. It`s just a thing of convenience.

My project`s facebook page is “DreamLand Page”

No. In both cases you need to initialize the counter. The rest is not about convenience but just habits and convention.

And conventions are important. We have to be happy about every case that only has one convention. For array or list sizes this would be the case.
You are actually the only programmer in the world who uses -1 to indicate an array has 0 entries. If you ever end up working in a team, that's a big problem. They will force you with knifes to change your habit. ; )
It will also hit you if you start to use STL or libraries, even if you are solo programmer.

int CDResult[2];
VOID Detect(CBody * Bodies, int * CDResult)
{
for (int ii = 0; ii < BodyCount + 1; ii++)
{
 for (int i = 0; i < BodyCount + 1; i++)
 {
  if ((Bodies[ii].nex > Bodies[i].nwx) &amp;amp;&amp;amp;
   (Bodies[ii].nex < Bodies[i].nex) &amp;amp;&amp;amp;
   (Bodies[ii].nez > Bodies[i].sez) &amp;amp;&amp;amp;
   (Bodies[ii].nez < Bodies[i].nez)
   ||
   (Bodies[ii].nwx < Bodies[i].nex)&amp;amp;&amp;amp;
   (Bodies[ii].nwx > Bodies[i].nwx)&amp;amp;&amp;amp;
   (Bodies[ii].nwz < Bodies[i].nwz)&amp;amp;&amp;amp;
   (Bodies[ii].nwz > Bodies[i].swz)
   ||
   (Bodies[ii].sez > Bodies[i].sez) &amp;amp;&amp;amp;
   (Bodies[ii].sez < Bodies[i].nez) &amp;amp;&amp;amp;
   (Bodies[ii].sex > Bodies[i].swx) &amp;amp;&amp;amp;
   (Bodies[ii].sex < Bodies[i].sex)
   )
  {
   CDResult[0] = ii;
   CDResult[1] = i;
  }
  else
  {
   CDResult[0] = -1;
  }

   
 }

}
}

now I must see if it works.

JoeJ said:
The rest is not about convenience but just habits and convention

it`s a way that helps me at debugging. If I`d learn to do it the other way (by practicing a long period of time) it would be just as good.

My project`s facebook page is “DreamLand Page”

Advertisement

The rectangle overlap test seems too complicated, but hard to read becasue of NSEW notation. Mathematically it's more intuitive to use minimum and maximum per dimension instead:

bool TestOverlapLine1D (
	const int min1, const int max1, // first line
	const int min2, const int max2 // second line
)
{
	if (max1 < min2 || min1 > max2)
 return false;
 // if the leftmost coordinate of line 1 is right from rightmost coordinate of line 2 (or vice versa), the lanes do not overlap		
	return true;
}

Extending this to a 2D rectangle is just doing the same for the second dimension:

bool TestOverlapRectangle2D (
	int min1x, int min1y,   int max1x, int max1y,   // first rectangle
	int min2x, int min2y,   int max2x, int max2y,   // second rectangle
)
{
	if (max1x < min2x || min1x > max2x) return false;
	if (max1y < min2y || min1y > max2y) return false;
	return true;
}

That's a lot less operations than you have.

Ofc. your code would only record the last collision it finds.

And you still test each pair twice: Once A vs B and second B vs A.
I told you so, but did not really expect you would listen… that's not motivating to provide further help or feedback.

I`m trying to put the algorithm to test. I will have units moving towards each other. When the units run into each other do I have to take a snapshot for units position and save them to pathfinding chart so that they may find a new path.

Added a new function

VOID UpdateCollisionBodyTo(int posx, int posz, int tilewidth,int BodyID, CBody* Bodies)
{
Bodies[BodyID].nwx = posx - tilewidth / 2;
Bodies[BodyID].nex = posx + tilewidth / 2;
Bodies[BodyID].sex = posx + tilewidth / 2;
Bodies[BodyID].swx = posx - tilewidth / 2;
Bodies[BodyID].nwz = posz + tilewidth / 2;
Bodies[BodyID].nez = posz + tilewidth / 2;
Bodies[BodyID].sez = posz - tilewidth / 2;
Bodies[BodyID].swz = posz - tilewidth / 2;
}

I have this in my programs main loop

if (firstrun2)
  {
   UnitAid = AddCollisionBody(5 * tilesize,5 * tilesize ,20,&amp;amp;CollisionBodies[0]);
   UnitBid = AddCollisionBody(12 * tilesize, 5 * tilesize, 20, &amp;amp;CollisionBodies[0]);
   FindPath(5, 5, 12, 6, &amp;amp;VertexGroups[0],&amp;amp;FinalPathA[0],&amp;amp;PathCountA[0]);
   FindPath(12, 5, 2, 6, &amp;amp;VertexGroups[0],&amp;amp;FinalPathB[0],&amp;amp;PathCountB[0]);
   firstrun2 = false;

  }
  else
  {
   FollowPath(nms,&amp;amp;UA[0],&amp;amp;FinalPathA[0],PathCountA[0]);
   FollowPath(nms, &amp;amp;UB[0],&amp;amp;FinalPathB[0],PathCountB[0]);
   UpdateCollisionBodyTo(UA[0], UA[1],20,UnitAid,&amp;amp;CollisionBodies[0]);
   UpdateCollisionBodyTo(UB[0], UB[1], 20, UnitBid,&amp;amp;CollisionBodies[0]);
   //MoveGroupTo(careux, 0, careuz, 1, &amp;amp;VertexGroups[0]);
   //MoveGroupTo(careux, 0, careuz, 1, &amp;amp;VertexGroups[0]);
  }

and this in the global scope

PathTile FinalPathA[20];
PathTile FinalPathB[20];
int PathCountA[1];
int PathCountB[1];
int UnitAid;
int UnitBid;
float UA[2];
float UB[2];
That's a lot less operations than you have

You`re code is very concentrated, I like to keep my code loose.

My project`s facebook page is “DreamLand Page”

I`m trying to figure out collision response. nms is variable that holds time. This is what how the setup looks now

my updated code

PathTile FinalPathA[50];
PathTile FinalPathB[50];
int PathCountA[1];
int PathCountB[1];
int UnitAid;
int UnitBid;
float UA[2];
float UB[2];
int UAg;
int UBg;
UnitPathSetup Pathsetup[2];
CBody CollisionBodies[100];
int  Result[2];

 if (firstrun2)
		 {
			 UAg = AddGroup(&amp;amp;VertexGroups[0], &amp;amp;VertexGroupsCount[0], false); //actor
			 AddSquareTile(0, 0, 26, UAg, &amp;amp;VertexGroups[0]);
			 UBg = AddGroup(&amp;amp;VertexGroups[0], &amp;amp;VertexGroupsCount[0], false); //actor
			 AddSquareTile(0, 0, 26, UBg, &amp;amp;VertexGroups[0]);

			 UnitAid = AddCollisionBody(5 * tilesize,5 * tilesize ,20,&amp;amp;CollisionBodies[0]);
			 UnitBid = AddCollisionBody(12 * tilesize, 5 * tilesize, 20, &amp;amp;CollisionBodies[0]);
			 FindPath(5, 5, 15, 5, &amp;amp;VertexGroups[0],&amp;amp;FinalPathA[0],&amp;amp;PathCountA[0]);
			 FindPath(15, 5, 5, 5, &amp;amp;VertexGroups[0],&amp;amp;FinalPathB[0],&amp;amp;PathCountB[0]);
			 firstrun2 = false;
			 InitPathSetup(&amp;amp;Pathsetup[0], 0);
			 InitPathSetup(&amp;amp;Pathsetup[0], 1);

		 }
		 else
		 {
			
			 UpdateCollisionBodyTo(UA[0], UA[1],20,UnitAid,&amp;amp;CollisionBodies[0]);
			 UpdateCollisionBodyTo(UB[0], UB[1], 20, UnitBid,&amp;amp;CollisionBodies[0]);
			 Detect(&amp;amp;CollisionBodies[0], &amp;amp;Result[0]);

			 if (Result[0] != -1)
			 {
				
			 }

			 FollowPath(nms, &amp;amp;UA[0], &amp;amp;FinalPathA[0], PathCountA[0], &amp;amp;Pathsetup[0], 0);
			 FollowPath(nms, &amp;amp;UB[0], &amp;amp;FinalPathB[0], PathCountB[0], &amp;amp;Pathsetup[0], 1);
			 MoveGroupTo(UA[0], 0, UA[1], UAg, &amp;amp;VertexGroups[0]);
			 MoveGroupTo(UB[0], 0, UB[1], UBg, &amp;amp;VertexGroups[0]);
		 } 

My project`s facebook page is “DreamLand Page”

Because i have not played RTS a lot i do not really know how such games deal with collisions, i have to admit.
So i can not help much, but i can point out some different forms of dynamic bodies collision response:

Not resolving collisions at all, like in early games like Pacman: Collision → game over.
Modeling the response physically like physics engines do.
Or doing something in between like most games do, e.g. Super Mario.

Then there is an entire different approach of avoiding collisions. And i assume that's the way to go for RTS for most.
To achieve this there exist some options:

1. Make a proximity query to find nearby entities, average their velocity direction and use this for the entity in question. That's a form of ‘flock behavior’.
Because nearby entities walk at the same direction and speed, they do not collide. Intersection can be resolved using some local refinement to keep some minimum distance form each other if necessary.

2. Instead making the proximity query (which is costly), let each entity inject its velocity vector into a common grid like you have for path finding. Average the velocities per grid cell, and let the entities follow this averaged direction. (bilinear filter makes sense to avoid grid quantization, also for the injection)
People call this ‘flow fields’ i think. But mathematically it is just a vector field. It's simple and can model interesting behavior.

The two methods also correspond to SPH (1) or grid based (2) fluid simulation methods. You can imagine your entities are particles that ‘flow’ towards a goal, or keep close to each other, etc.
This can also replace path finding: Imagine we have a labyrinth and we put hot wax on the goal. The wax flows through the whole labyrinth. But most wax is still at the goal because we let the wax draining to the goal all the time. (A ‘diffusion’ process.)
After that, we can find the shortest path to the goal form anywhere, just by following the direction of increasing amount of wax. ('Local maxima search': Finding the path becomes super fast, the performance problem moves to the diffusion process.)
We can also move the goal. If we drain the wax elsewhere, units will find the new goal after some time when the new goal has more wax than the old one.

This should give you some inspiration, and you might be able to make better assumptions of how some of your favorite RTS games eventually work.
By combining multiple methods it becomes possible to do path finding over time, or only for one leader of an entire group, letting the rest just follow the leader, etc.

(You can also just ask specifically how RTS games work - some members here should know better than i do.)

This topic is closed to new replies.

Advertisement