The Last Signal. Post #2: adding multiple bullets

So far we were only able to have one bullet in the game at any time. One of the most important tasks I was working on during this sprint (16 Feb – 20 Feb) was to implement “overcharge” item which temporarily increases  rate of fire. When palyer’s rate of fire is increased, there will definitely be more than one bullet on the screen.

So this post is about how I added multiple bullets to the game. This is going to be a moderately technical post intended mostly to programmers.


Before we would dynamically allocate exactly one instance of a bullet object in the GameState. The bullet would be placedin location (0;0) and when the player presses fire, the bullet will be moved to player position, assume player’s rotation and velocity and continue going infinitely in that direction. When the player presses the fire again, the bullet is immediately returned to the player position and starts moving the new facing direction.


The first to do is to dynamically allocate more than one bullet, say 10. Since we have 10 instances of the same object, it would be really nasty to copy over the same code 10 times, especially when our current code isn’t all that pretty either. So I added a for-loop and added an std::vector<Bullet*>m_entites vector that would contain all dynamically allocated entities, bullets included:

for (int i = 0; i < 10; i++)
	{
		m_bullet = new Bullet(&m_transfer.bullet, 0, 0);
		m_entities.push_back(m_bullet);
	}

m_transfer.bullet is a single sprite that is used for all of the 10 bullets.

I additionally added a bool m_active to bullet class which is initialized to false by the constructor. GameState’s draw function only draws active bullets, those that aren’t active aren’t drawn.

The logic that makes bullets active is when the player is in overcharge state (has consumed the overcharge item) and has pressed fire, i.e. left mouse click in our game.

if (Transfer::GetShoot())
	{
		m_shoot_time = m_shoot_delay.getElapsedTime();
		if (m_shoot_time.asSeconds() >= m_cooldown)
		{
			m_shoot_delay.restart();
			for (int i = 0; i GetType() == ENTITY_BULLET)
				{
					Bullet* bullet = static_cast(m_entities[i]);
					if (!bullet->IsActive())
					{
						bullet->SetState(true);
						bullet->SetPosition(m_player->GetX(), m_player->GetY());
						bullet->SetAngle(m_player->GetAngle());
						bullet->SetDirection(m_player->GetDirection());
						bullet->SetRoom(m_room_active);
						break;
					}
				}
			}
		}
		Transfer::SetShoot(false);
	};

Currently it is the Engine that checks for user input, so GameState class has to have some way of accessing the information if fire button has been pressed. The current solution is temporary, to create a static class Transfer which stores the variable m_shoot. The engines sets this variable to true whenever fire has been pressed and the GameState class accesses this static class to determine if fire has been pressed. This is seen in line Transfer::GetShoot(). After the shot has been processed, GameState sets the variable back to false in Transfer::SetShoot(false).

The processing occurs as follows. We first check if the cooldown betwneen shots is over in line 4. If we’re still under cooldown, we skip processing and set m_shoot to false – this button press has failed to produce the shot since the cooldown isn’t over yet. If the cooldown is over and we can shoot, we process the shot by doing the following actions.

1. we reset the timer to begin the next cooldown
2. we scan the entire entities vector for bullet objects in it
3. when we hit a bullet object in the vector we static-cast it to bullet. Remember it was placed in the vector as an entity, so if you try to access its member functions, you will be accessing Entity class’s pure virtuals and that’s presumably a compilation error. After the static, welocally convert the Entity object to actual bullet object and can access bullet member functions.
4. we check if the bullet is not active. If a bullet is active, it is already flying somewhere in the game our outside the screen but is still present in the game; we don’t want to touch active bullets. We pick the first inactive bullet, i. e., a “free” one that can be activated.
5. we set its state to active, so that now this bullet too becomes “untouchable”. We then set its position, angle and room it is in to corresponding player properties. Off goes the bullet. It is spawned at player location and immediately begins flying in the direction the player is facing since it assumes player direction vector.
6. we break the loop since our job is done here – the bullet is spawned so no need to continue scanning the vector container.

Potential problem is that when we break the loop, the Transfer::SetShoot(false) is skipped; this I noticed at the time I was writing this post; it will need to be investigated later.

Next, we need to run Update functions for all active bullets. Here is this code:

for (int i = 0; i GetType() == ENTITY_BULLET)
		{
		Bullet* bullet = static_cast(m_entities[i]);
		if (m_room_active->EvaluateCoords(bullet->GetX(), bullet->GetY()) != CONFINED)
			bullet->SetState(false);
		bullet->Update(1.0f);
		}

Most of this code chunk is similar to the one above — it scans the vector, picks out bullets and static-casts them. It then invokes a special Room class function for the active room instance which checks if bullet is not outside the active room. If it has just crossed the room boundary, it should be removed so what we do we set their state to inactive. As a result, they won’t be drawn in the Draw function. We then run Update for every bullet; this needs to be fixed to include the check if a bullet is active and skip remaining iteration if it is.

Next comes a very important and difficult piece of code which has tripped me up for hours and hours until I figured out what was wrong. Here’s what we are doing now. We have deactivated bullets that are outside the current room (the one the player is in), we have updated all bullet positions. But that’s positions of bullets as abstract objects (see my previous post, #1 for a short coverage of abstract objects), the sprite of every bullet has to be updated as well.

Since we’re using the same sprite for all 10 bullets, updating it once will update it for all bullets, since all bullets are using that one sprite. We have to create an individual copy of that sprite for every bullet as we go and modify it individually for that bullet, seeting sprite position and rotation to bullet object position and rotation. Here is the correct code:

for (int i = 0; i GetType() == ENTITY_BULLET)
		{
		Bullet* bullet = static_cast(m_entities[i]);
		sf::Sprite temp = *bullet->GetSprite();
		temp.setPosition(bullet->GetX(), bullet->GetY());
		temp.setRotation(bullet->GetAngle());
		bullet->SetSprite(temp);
		}
	

We still scan the vector, pick out and static-cast bullet objects and then we create a new sprite, set its position and rotation to corresponding bullet properties and apply that sprite ot the bullet object (line 8). This requires that the sprite be copied not referenced, so I made it so in line 8 the sprite itself is passed and accepted on the other side (in the bullet object). The problem here was that I was passing a reference to the modified sprite; the same sprite was getting modified in every iteration of the loop, leaving it with values applicable for the last bullet. So all the bullets would behave as the one fired out last.

The remaining code is easy: draw just the active bullets:

for (int i = 0; i GetType() == ENTITY_BULLET)
		{
		Bullet* bullet = static_cast(m_entities[i]);
		sf::Sprite* temp = bullet->GetSpriteFull();
		if (bullet->IsActive())
			m_draw_manager->Draw(*temp, sf::RenderStates::Default);

And here is the result: our player shooting out several bullets with very little cooldown due to overcharge state:

Project 2015-02-19 23-29-46-55

About Rokas Paulauskas

2014  Programming