We will make the bullets usable with the following six steps:
Bullet
class.Bullet
instances.Add the include directive to make the Bullet class available:
#include "stdafx.h"
#include <SFML/Graphics.hpp>
#include "ZombieArena.h"
#include "Player.h"
#include "TextureHolder.h"
#include "Bullet.h"
using namespace sf;
Let's move on to the next step.
Here are some variables to keep track of bullets, clip sizes, bullets spare/remaining, bullets in the clip, the current rate of fire (starting at one per second), and the time when the last bullet was fired.
Add the highlighted code and we can move on to seeing all these variables in action in the rest of this section:
// Prepare for a horde of zombies
int numZombies;
int numZombiesAlive;
Zombie* zombies = NULL;
// 100 bullets should do
Bullet bullets[100];
int currentBullet = 0;
int bulletsSpare = 24;
int bulletsInClip = 6;
int clipSize = 6;
float fireRate = 1;
// When was the fire button last pressed?
Time lastPressed;
// The main game loop
while (window.isOpen())
Next, let's handle what happens when the player presses the R keyboard key, which is used for reloading a clip.
Now we handle the player input related to shooting bullets. First we will handle pressing the R key to reload the gun. We do so with an SFML event.
Add the code shown highlighted in the following code block. It is shown with lots of context to make sure the code goes in the right place. Study the code and then we can talk about it:
// Handle events
Event event;
while (window.pollEvent(event))
{
if (event.type == Event::KeyPressed)
{
// Pause a game while playing
if (event.key.code == Keyboard::Return &&
state == State::PLAYING)
{
state = State::PAUSED;
}
// Restart while paused
else if (event.key.code == Keyboard::Return &&
state == State::PAUSED)
{
state = State::PLAYING;
// Reset the clock so there isn't a frame jump
clock.restart();
}
// Start a new game while in GAME_OVER state
else if (event.key.code == Keyboard::Return &&
state == State::GAME_OVER)
{
state = State::LEVELING_UP;
}
if (state == State::PLAYING)
{
// Reloading
if (event.key.code == Keyboard::R)
{
if (bulletsSpare >= clipSize)
{
// Plenty of bullets. Reload.
bulletsInClip = clipSize;
bulletsSpare -= clipSize;
}
else if (bulletsSpare > 0)
{
// Only few bullets left
bulletsInClip = bulletsSpare;
bulletsSpare = 0;
}
else
{
// More here soon?!
}
}
}
}
}// End event polling
The previous code is nested within the event handling part of the game loop (while(window.pollEvent)
), within the block that only executes when the game is actually being played (if(state == State::Playing)
). It is obvious that we don't want the player reloading when the game has finished or is paused and wrapping the new code as described achieves this.
In the new code itself, the first thing we do is test for the R key being pressed, with if (event.key.code == Keyboard::R)
. Once we have detected that the R key was pressed the remaining code is executed. Here is the structure of the if
, else if
, and else
blocks:
if(bulletsSpare >= clipSize) ... else if(bulletsSpare > 0) ... else ...
The previous structure allows us to handle three possible scenarios.
R
and they have more bullets spare than the clip can take. In this scenario, the clip is refilled and the number of spare bullets is reduced.else
block ready.Let's actually shoot a bullet at last.
Next we can handle the left mouse button being clicked to actually fire a bullet. Add the highlighted code and study it carefully:
if (Keyboard::isKeyPressed(Keyboard::D))
{
player.moveRight();
}
else
{
player.stopRight();
}
// Fire a bullet
if (Mouse::isButtonPressed(sf::Mouse::Left))
{
if (gameTimeTotal.asMilliseconds()
- lastPressed.asMilliseconds()
> 1000 / fireRate && bulletsInClip > 0)
{
// Pass the center of the player
// and the center of the crosshair
// to the shoot function
bullets[currentBullet].shoot(
player.getCenter().x, player.getCenter().y,
mouseWorldPosition.x, mouseWorldPosition.y);
currentBullet++;
if (currentBullet > 99)
{
currentBullet = 0;
}
lastPressed = gameTimeTotal;
bulletsInClip--;
}
}// End fire a bullet
}// End WASD while playing
All of the previous code is wrapped in an if
statement, which executes whenever the left mouse button is pressed, if (Mouse::isButtonPressed(sf::Mouse::Left))
. Note that the code will execute repeatedly, even if the player just holds down the button. The code we will go through now controls the rate of fire.
In the previous code, we then check whether the total time elapsed in the game (gameTimeTotal
), minus the time the player last shot a bullet (lastPressed
), is greater than 1000
divided by the current rate of fire, and that the player has at least one bullet in the clip. We use 1000
because this is the number of milliseconds in a second.
If this test is successful, the code that actually fires a bullet is executed. Shooting a bullet is easy because we did all the hard work in the Bullet
class. We simply call shoot
on the current bullet from the bullets
array. We pass in the player's and crosshair's current horizontal and vertical locations. The bullet will be configured and set in flight by the code in the shoot
function of the Bullet
class.
All we have to do is keep track of the array of bullets. First we increment the currentBullet
variable. Then we check to see whether we fired the last bullet (99
) with the statement if (currentBullet > 99)
. If it was the last bullet, we set currentBullet
to zero. If it wasn't the last bullet, then the next bullet is ready to go whenever the rate of fire permits it and the player presses the left mouse button.
Finally, for the previous code, we store the time that the bullet was fired in lastPressed
and decrement bulletsInClip
.
Now we can update every bullet, each frame.
Add the highlighted code to loop through the bullets array, check whether the bullet is in flight, and if it is, call its update function:
// Loop through each Zombie and update them
for (int i = 0; i < numZombies; i++)
{
if (zombies[i].isAlive())
{
zombies[i].update(dt.asSeconds(), playerPosition);
}
}
// Update any bullets that are in-flight
for (int i = 0; i < 100; i++)
{
if (bullets[i].isInFlight())
{
bullets[i].update(dtAsSeconds);
}
}
}// End updating the scene
And lastly, we can draw all the bullets.
Add the highlighted code to loop through the bullets
array, check whether the bullet is in flight and if it is, draw it:
/*
**************
Draw the scene
**************
*/
if (state == State::PLAYING)
{
window.clear();
// set the mainView to be displayed in the window
// And draw everything related to it
window.setView(mainView);
// Draw the background
window.draw(background, &textureBackground);
// Draw the zombies
for (int i = 0; i < numZombies; i++)
{
window.draw(zombies[i].getSprite());
}
for (int i = 0; i < 100; i++)
{
if (bullets[i].isInFlight())
{
window.draw(bullets[i].getShape());
}
}
// Draw the player
window.draw(player.getSprite());
}
Run the game to try out the bullets. Notice you can fire six shots before you need to press R to reload. The obvious things that are missing are some visual indicator of the number of bullets in the clip and the number of spare bullets. Another problem is that the player can very quickly run out of bullets, especially as the bullets have no stopping power whatsoever. They fly straight through the zombies. Add to this that the player is expected to aim at a mouse pointer instead of a precision crosshair, and it is plain we have work to do.
In the next chapter, we will give visual feedback through a HUD. We will replace the mouse cursor with a crosshair next and then spawn some pickups to replenish bullets and health after that. Finally, in this chapter, we will handle collision detection to make the bullets and the zombies do damage and make the player able to actually get the pickups.
18.216.40.129