• Advertisement
Sign in to follow this  

Some AI advice needed

This topic is 2301 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So I have a AI class that allows me to initialize a certain number of enemies and that's the number of enemies I have in the game. However, it doesn't allow me to do more interesting techniques with my AI such as spawning two new enemies when one enemy dies or just spawning an enemy on the fly. Basically I want to change the class to give me the ability to create new enemies at any time in the game. My current class has a initialization function that initialized the initial positions of the enemies, sets up the shader, and the enemy object itself. Most of this could be moved outside of the class, except there will be a problem with setting the enemies initial position. I thought of some other ways to do it without modifying my class since creating and destroying objects can be expensive. One of these ways is to make a pool of enemies. I could create a set number of enemies, put them in two vectors or something based on rather or not they are used and then swap the objects around. It seems like this wouldn't work in some cases since you would eventually run out of enemy objects. Does anyone have some advice to gvie me on a good method to set up creating new enemies at any time while the game is running?

Share this post


Link to post
Share on other sites
Advertisement
Just give the AI class a container of enemies. It could be contained inside the object itself, or you could hold a reference/smart pointer to a container owned by, say, the "Game logic" code, or whatever. Then you can arbitrarily expand, contract, and manipulate the container of foes to your heart's content.

Share this post


Link to post
Share on other sites
Whats the difference in adding a container inside of the class and just adding a container of enemy objects in say the main game file, for instance main.cpp. Maybe I'm misunderstanding, but it seems I would also have to due away with the initialization function of the enemy class.

Share this post


Link to post
Share on other sites
The difference is in accessibility. If you need to access the list of enemies from places other than just your AI code - say, for rendering purposes - then you should put the container someplace that is accessible to those areas of code. Generally, put it in the most localized, constrained location possible; this helps keep your code clean.

I don't see why you would need to remove enemy initialization to make this work.

Share this post


Link to post
Share on other sites
Thanks for explaining. The reason I mentioned moving the initialization code was because I'm calling the initialization function of the enemy class from my games Initialization function which of course gets called once. When I want to randomly add another enemy I would have to call that enemies initialize function, just not constantly like it would in the game loop.

Share this post


Link to post
Share on other sites
Even if you have your code for creating an enemy called in the game loop, nothing is forcing you to initialize it more than once, let alone every frame; for example:

CreateEnemies(container)

while(GameIsPlaying())
{
while(NeedToSpawn())
{
AddNewEnemy(container) // Creates the enemy, calls Initialize, puts it in container
}
ProcessAI(container)
ProcessPlayerInput()
Render()
}

Share this post


Link to post
Share on other sites
Thanks I managed to get that part working, however I would like some advice on optimization techniques to use, since after adding 2-3 enemies the FPS is dropping dramatically. There's a few things I'm currently doing to cause the issue, such as using the distance formula to calculate the distance between all the enemies and the player ever frame. All the enemies also draw no matter if they are in the view area or not. I'm still making optimizations such as adding bounding volumes and calculated collisions instead of distance. I thought about adding only frustum culling but that wouldn't solve the issue since my AI is designed zergling-like and you could have many enemies in your view at a time. I also thought about adding in some type of grid or tree, do you have any advice on what to add to solve my optimization issues?

Share this post


Link to post
Share on other sites
Could it be the rendering? Are you using glBegin/End or VBOs?

You could make the distance calculation to only run every x frame to see if thats the problem.

I guess im supposed to say "Profile, profile, PRRROOOFFIIILLLEEe1!!!!!!1!!1111" here like everyone else all the time :P

Share this post


Link to post
Share on other sites
Yep, I'd suggest running a profiler to see where your time is going. That will point you at optimizations that are actually worth investing in, versus stuff that'll make you 0.001% faster - still faster, technically, but you'd never notice ;-)

Share this post


Link to post
Share on other sites
umm could you explain a little on profiling, I did a little research and it looks like Visual Studio Ultimate and Premium have a in-built profiler, but I'm using Professional edition ;o.

I think the profiling idea is the best option as well, even with using the sqrt function calculating distance it seems kinda extreme that spawning a single enemy jumps the FPS from 400+ to 60 and at times goes as low as 30.

Edit: This is being done with DirectX10, not OpenGL. I currently have it set up so I press a keyboard key that tells that a new enemy should be made. I initialize that enemy. The init function creates the actual object which right now is just some vertices that make up a square. Then calls D3DX10CreateMesh to make a mesh with the vertex and index info and sets initial enemy position. Then I have a basic update function that controls the AI and tells it when to do the different AI tasks such as wandering and chasing. Then the render function just sets the enemies transforms and sends the mesh off to the color shader class for rendering.

Editx2: It isn't the distance formula, I can comment out all of the AI movements and just have the box rendering and the FPS drops. Maybe it's because I'm creating new vertices and a new mesh for every enemy in the Initialize function?

Share this post


Link to post
Share on other sites
Some free profilers are out there; I personally like Luke Stackwalker and Very Sleepy.

They can take some getting used to, so I'd suggest downloading one (or both) and fooling around with it, and if you run into any specific questions, feel free to ask!


Sounds to me like your rendering method is the biggest problem, but it's hard to say without concrete numbers.

Share this post


Link to post
Share on other sites
alright sooo i downloaded both of those. I was looking through the results Very Sleepy gave me and on enemy update function it was showing 12.96s in the inclusive column. I clicked there and it said CQuadTree::GetHeightAtPosition was 12.95s and the %calls was at 99.93%. I went into my game code and just completely commented it out and the FPS went back up to normal. However, That function of course is used to set the enemy y position to the height of the terrain where the enemy is at that time.


void CEnemy::Update(CQuadTree* terrain, D3DXVECTOR3 playerPos)
{
//y position on top of the terrain
float height;

//if the enemy is alive
if (m_bAlive)
{
//Get the distance between the player and the enemy
float dist = sqrt((playerPos.x - m_vPos.x)*(playerPos.x - m_vPos.x) + (playerPos.z - m_vPos.z)*(playerPos.z - m_vPos.z));

//If the distance is less than 20
if (dist <= 20.0f)
{
//Chase the player
m_bEngaged = true;
}

//If not engaged
if (!(m_bEngaged))
{
//Chase randomly generated vector
Search();
}
else
{
//Chase player
Chase(playerPos);
}

/*PROBLEM AREA*/
//Get the height of the terrain at the current enemy position and
//set the enemies height to be slightly higher.
//if (terrain->GetHeightAtPosition(m_vPos.x, m_vPos.z, height))
//{
// m_vPos.y = height + 2.5f;
//}

//Update the bounding sphere around the enemy
m_sHitSphere.c = m_vPos;
m_sHitSphere.r = 2.0f;
}
}


Edit: It would appear my game just isn't a big fan of the GetHeightAtPosition function in my quadtree class. I use the same function for my camera/player to be able to walk on top of the terrain. When commenting out I notice a increase in FPS, not as drastic as what is happening with my enemies though. The code for this is pretty large and is taking to much time to be called every frame. I may try to only call every so many frames or something.

Share this post


Link to post
Share on other sites
It's... very large. Why I didn't consider this being the problem sooner is beyond me. I was thinking of maybe calling it after so many frames instead of every frame.. or maybe redoing it completely. It basically does a line-triangle intersection test on triangles in the current node and then determines the height of the triangle. It's based from a tutorial on terrain quadtree's since I was learning them at the time.

Edit: Not calling it every frame seems to improve the performance considerably. I found a decent frame number to keep the position transition smooth and have a much higher frame rate. I still may do some work on the GetHeightAtPosition function though. Also, thanks for the info on the profiling, this thread was worth it just to learn about those. Sure was easy narrowing the problem down with Very Sleepy ;o

bool CQuadTree::GetHeightAtPosition(float positionX, float positionZ, float& height)
{
float meshMinX, meshMaxX, meshMinZ, meshMaxZ;


meshMinX = m_parentNode->positionX - (m_parentNode->width / 2.0f);
meshMaxX = m_parentNode->positionX + (m_parentNode->width / 2.0f);

meshMinZ = m_parentNode->positionZ - (m_parentNode->width / 2.0f);
meshMaxZ = m_parentNode->positionZ + (m_parentNode->width / 2.0f);

// Make sure the coordinates are actually over a polygon.
if((positionX < meshMinX) || (positionX > meshMaxX) || (positionZ < meshMinZ) || (positionZ > meshMaxZ))
{
return false;
}

// Find the node which contains the polygon for this position.
FindNode(m_parentNode, positionX, positionZ, height);

return true;
}



void CQuadTree::FindNode(NodeType* node, float x, float z, float& height)
{
float xMin, xMax, zMin, zMax;
int count, i, index;
float vertex1[3], vertex2[3], vertex3[3];
bool foundHeight;


// Calculate the dimensions of this node.
xMin = node->positionX - (node->width / 2.0f);
xMax = node->positionX + (node->width / 2.0f);

zMin = node->positionZ - (node->width / 2.0f);
zMax = node->positionZ + (node->width / 2.0f);

// See if the x and z coordinate are in this node, if not then stop traversing this part of the tree.
if((x < xMin) || (x > xMax) || (z < zMin) || (z > zMax))
{
return;
}

// If the coordinates are in this node then check first to see if children nodes exist.
count = 0;

for(i=0; i<4; i++)
{
if(node->nodes != 0)
{
count++;
FindNode(node->nodes, x, z, height);
}
}

// If there were children nodes then return since the polygon will be in one of the children.
if(count > 0)
{
return;
}

// If there were no children then the polygon must be in this node. Check all the polygons in this node to find
// the height of which one the polygon we are looking for.
for(i=0; i<node->triangleCount; i++)
{
index = i * 3;
vertex1[0] = node->vertexArray[index].x;
vertex1[1] = node->vertexArray[index].y;
vertex1[2] = node->vertexArray[index].z;

index++;
vertex2[0] = node->vertexArray[index].x;
vertex2[1] = node->vertexArray[index].y;
vertex2[2] = node->vertexArray[index].z;

index++;
vertex3[0] = node->vertexArray[index].x;
vertex3[1] = node->vertexArray[index].y;
vertex3[2] = node->vertexArray[index].z;

// Check to see if this is the polygon we are looking for.
foundHeight = CheckHeightOfTriangle(x, z, height, vertex1, vertex2, vertex3);

// If this was the triangle then quit the function and the height will be returned to the calling function.
if(foundHeight)
{
return;
}
}

return;
}




bool CQuadTree::CheckHeightOfTriangle(float x, float z, float& height, float v0[3], float v1[3], float v2[3])
{
float startVector[3], directionVector[3], edge1[3], edge2[3], normal[3];
float Q[3], e1[3], e2[3], e3[3], edgeNormal[3], temp[3];
float magnitude, D, denominator, numerator, t, determinant;


// Starting position of the ray that is being cast.
startVector[0] = x;
startVector[1] = 0.0f;
startVector[2] = z;

// The direction the ray is being cast.
directionVector[0] = 0.0f;
directionVector[1] = -1.0f;
directionVector[2] = 0.0f;

// Calculate the two edges from the three points given.
edge1[0] = v1[0] - v0[0];
edge1[1] = v1[1] - v0[1];
edge1[2] = v1[2] - v0[2];

edge2[0] = v2[0] - v0[0];
edge2[1] = v2[1] - v0[1];
edge2[2] = v2[2] - v0[2];

// Calculate the normal of the triangle from the two edges.
normal[0] = (edge1[1] * edge2[2]) - (edge1[2] * edge2[1]);
normal[1] = (edge1[2] * edge2[0]) - (edge1[0] * edge2[2]);
normal[2] = (edge1[0] * edge2[1]) - (edge1[1] * edge2[0]);

magnitude = (float)sqrt((normal[0] * normal[0]) + (normal[1] * normal[1]) + (normal[2] * normal[2]));
normal[0] = normal[0] / magnitude;
normal[1] = normal[1] / magnitude;
normal[2] = normal[2] / magnitude;

// Find the distance from the origin to the plane.
D = ((-normal[0] * v0[0]) + (-normal[1] * v0[1]) + (-normal[2] * v0[2]));

// Get the denominator of the equation.
denominator = ((normal[0] * directionVector[0]) + (normal[1] * directionVector[1]) + (normal[2] * directionVector[2]));

// Make sure the result doesn't get too close to zero to prevent divide by zero.
if(fabs(denominator) < 0.0001f)
{
return false;
}

// Get the numerator of the equation.
numerator = -1.0f * (((normal[0] * startVector[0]) + (normal[1] * startVector[1]) + (normal[2] * startVector[2])) + D);

// Calculate where we intersect the triangle.
t = numerator / denominator;

// Find the intersection vector.
Q[0] = startVector[0] + (directionVector[0] * t);
Q[1] = startVector[1] + (directionVector[1] * t);
Q[2] = startVector[2] + (directionVector[2] * t);

// Find the three edges of the triangle.
e1[0] = v1[0] - v0[0];
e1[1] = v1[1] - v0[1];
e1[2] = v1[2] - v0[2];

e2[0] = v2[0] - v1[0];
e2[1] = v2[1] - v1[1];
e2[2] = v2[2] - v1[2];

e3[0] = v0[0] - v2[0];
e3[1] = v0[1] - v2[1];
e3[2] = v0[2] - v2[2];

// Calculate the normal for the first edge.
edgeNormal[0] = (e1[1] * normal[2]) - (e1[2] * normal[1]);
edgeNormal[1] = (e1[2] * normal[0]) - (e1[0] * normal[2]);
edgeNormal[2] = (e1[0] * normal[1]) - (e1[1] * normal[0]);

// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v0[0];
temp[1] = Q[1] - v0[1];
temp[2] = Q[2] - v0[2];

determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));

// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}

// Calculate the normal for the second edge.
edgeNormal[0] = (e2[1] * normal[2]) - (e2[2] * normal[1]);
edgeNormal[1] = (e2[2] * normal[0]) - (e2[0] * normal[2]);
edgeNormal[2] = (e2[0] * normal[1]) - (e2[1] * normal[0]);

// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v1[0];
temp[1] = Q[1] - v1[1];
temp[2] = Q[2] - v1[2];

determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));

// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}

// Calculate the normal for the third edge.
edgeNormal[0] = (e3[1] * normal[2]) - (e3[2] * normal[1]);
edgeNormal[1] = (e3[2] * normal[0]) - (e3[0] * normal[2]);
edgeNormal[2] = (e3[0] * normal[1]) - (e3[1] * normal[0]);

// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v2[0];
temp[1] = Q[1] - v2[1];
temp[2] = Q[2] - v2[2];

determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));

// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}

// Now we have our height.
height = Q[1];

return true;
}

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement