Weapons, in a lot of games they are very important. And although they might seem quite simple (you just push a button and it works), there is quite a bit more to them. To illustrate this, I’ll give a detailed explanation of how I programmed the shotgun in my arcade game.

The shotgun having shot some bullets.

The bullet

Firing the bullet

There are three main parts to this gun, the gun itself (which isn’t that interesting as it is just a sprite), the bullet and the shell that flies out of it. Everytime the player pulls the trigger, eight bullets come out that all fly off in a “random” direction. This direction is actually a speed vector , of which the y-component is randomly chosen between and . But as every bullet must have the same speed, which in this case means a speed vector of equal length, we cannot simply set the x-component of the speed vector, as this has to be calculated:

Translated to the beautiful C++ language, it looks something like this:

yspeed = RandomRange(-2.5, 2.5);
xspeed = sqrt(100.0f - pow(yspeed, 2));

OK, simple enough so far, you might think. But no, it gets better. Every shotgun bullet also has horizontal drag , so it will slow down, and for the effect, the drag increases every update cycle. OK, still not that hard, we’ll just update the horizontal speed:

But now the direction of the vector has changed, and as that’ll look weird, we’ll also have to update the vertical component:

After recalculating the speed vector we can also recalculate the position vector (but only if the speed vector is still positive):

So, we’ll put this in our C++ code as well:

//facing is a variable that equals 1 for right and -1 for left.
// we apply it here so we don't have to bother with directions in all the other calculations
x += xspeed*facing;
y += yspeed;
if(xspeed > drag) {
  yspeed -= (yspeed/xspeed)*drag;
  xspeed -= drag;
} else {
  xspeed = 0;
}
drag += 0.01; 

Also, before we can fire a bullet, we need to make sure we haven’t just already fired one. In order to do this, the Shotgun class contains a cooldown variable. For every step this cooldown is updated:

Every time we fire, we set this variable to , so the player will have to wait about two seconds before being able to fire again. Firing then is quite straightforward:

void Shotgun::FirePressed(LevelBase* level, Player* player) {
  //Check if we can fire
  if(cooldown == 0) {
    //Check the direction the player is facing
    if(player->facing == 1) {
      //Repeat 8 times
      for(int i = 0; i < 8; i++) {
        //Create a bullet
        ShotgunBullet* b = new ShotgunBullet(tex_bullet, player->facing);
        b->Init(player->x+52, player->y+26, level);
        level->AddObject(b);
      }
    } else {
      //Do the same if the player is facing the other side
      for(int i = 0; i < 8; i++) {
        ShotgunBullet* b = new ShotgunBullet(tex_bullet, player->facing);
        b->Init(player->x-16, player->y+26, level);
        level->AddObject(b);
      }
    }   

    //Set cooldown timer and play sound effect
    cooldown = 60;
    audio->PlayEffect("./assets/sfx/weapons/shotgun.wav");
  }
}

Making the bullet hit something

So, when the bullet is away, we’ll have to check if the bullet hit something. First, we have to check for hits with the level, which is quite easy as the level itself has a global Collide* method, which returns the x-position of the hit, or -1 if there was no hit:

if(facing == 1) {
  if(level->CollideRight(Round(x), Round(y), 12, 12) != -1)
    dead = true;
} else {
  if(level->CollideLeft(Round(x), Round(y), 12, 12) != -1)
    dead = true;
}

We then have to check for a hit with an enemy, but for this there is another method implemente,d IntersectEnemy, which returns a pointer to the intersecting enemy, or NULL if there was no enemy. If there was a hit, the enemy needs to be hit with a damage of 1 HP, and with bullet (kBullet)-damage (which is used for the death animation)

EnemyBase* enemy = level->IntersectEnemy(MakeRect(x, y, 12, 12));
if(enemy != 0) {
  enemy->Hit(1, facing, 0, kBullet);
  dead = true;
}

We then render the bullet:

RenderTexture(texture, renderer, Round(x), Round(y), 3);

The shell

Though the shell flying away from the gun after firing is just a simple effect, it has quite some code behind it. Just like the bullet it has a speed vector and a position vector , but as it spins, it also has an angle and an angular velocity . The bullet is also under the influence of the gravity which exerts a constant pull of 0.8 (which is just a random number :sweat_smile:)

Every step we first update the position vector, which is simple for the shell, as it has no drag:

Then comes the angle:

And then we update the speed vector according to the gravity:

Translated to C++, it looks like this:

x += xspeed;
y += yspeed;
yspeed += 0.8;
angle += omega;

Rendering works just like the ShotgunBullet, except a different function is used to render a rotated texture:

RenderTextureRotated(texture, renderer, Round(x), Round(y), 3, angle);


And like that, you see how complicated programming a simple shotgun in a 2D platformer can be. I’m already imagining the fun programming the flamethrower must bring me :confused: