Fancy Mansion: Patrol state
|
For this week’s discussion, I’ll run through how I started work on a form of pathfinding for our enemy AI. Working with finite states, I created some states to handle behaviours for our enemy, Otto von Fancy: he needed to chase the player, attack the player, patrol along a set route, and stand still between patrolling waypoints. I created four finite states to accomplish this: a patrolling state, an idle state, a chasing state, and an attacking state. By having the enemy object take in a pointer to the player object in its constructor, I am able to access the player’s datamembers, such as its position on the map. This way, I can specify the chasing state to activate when the enemy is within a certain pixel range of the player (set 400 pixels in any direction including diagonal, at the moment). The attacking state has a similar condition: it activates if the player is within 150 pixels of the enemy. This, coupled with a footstep sound effect that plays with every step the player takes, creates the illusion that Otto hears the player when the player is nearby. The finite states for the enemy ai are written in the enemy’s Update() function, since this continually updates the enemy’s positioning, sprite display, and so on. void Otto::Update(float deltatime)
{
//Räknar ut avståndet mellan den spriten och target-possitionen (delta-X och -Y)
float deltaX = p_player->GetPos().x - GetPos().x;
float deltaY = p_player->GetPos().y - GetPos().y;
//Räkar nu längden på vektorn med pytagoras sats C = roten ur(A^2 + B^2)
float hypotenuse = sqrtf(deltaX*deltaX + deltaY*deltaY);
//Om vectorn är 0 måste den bli 1, eftersom man inte kan dela med 0
if (hypotenuse == 0)
hypotenuse = 1;
// cosv = Närliggande / Hypotinusan
deltaX /= hypotenuse;
// sinv = Motstående / Hypotinusan
deltaY /= hypotenuse;
//AI for the enemy Otto, using finite states
switch (m_state)
{
case STATE_IDLE:
{
//start timer
m_idleTimer += deltatime;
//enter patrolling state after 2 seconds
if (m_idleTimer > 2.0f)
{
m_pathIndex++;
m_state = STATE_PATROL;
}
//If the player is within '' pixels of Otto, but further away than '' pixels, enter chasing state
if (hypotenuse m_attackRange)
{
m_state = STATE_CHASING;
}
//If the player is even closer, within x pixels of Otto, enter attacking state
if (hypotenuse < m_attackRange)
{
m_state = STATE_ATTACKING;
}
break;
}
case STATE_PATROL:
{
//If the player is within '' pixels of Otto, but further away than '' pixels, enter chasing state
if (hypotenuse m_attackRange)
{
m_state = STATE_CHASING;
}
//If the player is even closer, within '' pixels of Otto, enter attacking state
if (hypotenuse SetPosition(GetPos().x, GetPos().y);
p_light->SetRotation(1000);
//if timer is appropriate and there is currently no visible projectile already on screen
if (!p_projectile->IsVisible() && m_rifleChargeTimer > 2.0f)
{
//run shoot function, ie attack
Shoot();
p_light->SetPosition(-400, -400);
if (m_rifleChargeTimer > 3.0f)
{
//reset rifle charge timer if more than 3s have passed
m_rifleChargeTimer = 0.0f;
//and go back to patrol state
m_state = STATE_PATROL;
}
}
break;
//Otto re-enters chasing state if he is '' pixels from the player
if (hypotenuse m_attackRange);
{
m_rifleChargeTimer = 0.0f;
m_state = STATE_CHASING;
}
break;
}
//Chasing state
case STATE_CHASING:
{
//Otto moves
m_ottoSprite.move(m_speed*deltatime*deltaX, m_speed*deltatime*deltaY);
//If the player is even closer, within '' pixels of Otto, enter attacking state
if (hypotenuse m_chaseRange)
{
m_directionX = 0;
m_directionY = 0;
m_state = STATE_PATROL;
}
break;
}
//Update the collider for Otto
p_collider->SetPosition(m_x, m_y);
}
};
In order to approximate a patrol path, after consulting with our lead programmer I created a separate function to be called from the patrol finite state. This function takes in a two-dimensional vector in its constructor, a vector which contains the x and y coordinates of various waypoints for the patrolling path of the enemy. I set these coordinates manually myself in Otto’s ctor. Using the pythagorean theorum and basic trigonometry, the function calculates the fastest path between its current position and the next waypoint. A path index variable increments each time a waypoint is reached, and the patrol finite state uses this path index variable in order to look at each element in the 2d waypoint array. This way the enemy moves from one waypoint to the next.
void Otto::FindPath(sf::Vector2f vector2)
{
//Räknar ut avståndet mellan den spriten och target-positionen (delta-X och -Y), i detta fall en position i en 2d vector
pathDeltaX = vector2.x - GetPos().x;
pathDeltaY = vector2.y - GetPos().y;
//Räkar nu längden på vektorn med pytagoras sats C = roten ur(A^2 + B^2)
pathHypotenuse = sqrtf(pathDeltaX*pathDeltaX + pathDeltaY*pathDeltaY);
//If we are less than 5 pixels away from the target position, increase path index
if (pathHypotenuse 3)
{
m_pathIndex = 0;
}
};
//Om vectorn är 0 måste den bli 1, eftersom man inte kan dela med 0
if (pathHypotenuse == 0)
pathHypotenuse = 1;
// cosv = Närliggande / Hypotinusan
pathDeltaX /= pathHypotenuse;
// sinv = Motstående / Hypotinusan
pathDeltaY /= pathHypotenuse;
};
This code will be expanded with a more robust waypoint system, but these are the basics that will be used for the alpha. More on this next week! |