Adding a Gun to the Platform Starter Kit
Someone requested this for a tutorial and I think it's one that a lot of people are interested in so I'll show you how I did it for Nomis: Legacy Islands.
You can download the project by clicking on the zipped folder below.
Here's a video showing what it should look like when you're finished.
First you'll want to add this image into your HighResolutionContent>Sprites>Player folder. Right click and Save As...

A few notes about this image and the following code. I have the gun/arm as 1 image so they can rotate together at the pivot point (shoulder). The pivot point is exactly in the middle of the image so that way finding the origin of where to rotate is easy. The bottom of the arrow would be the shoulder of your character, and the tip of the arrow would be the end of your arm or where the gun is. I also have it pointing up so I know how to deal with it later. Make sure to add it to the folder above and include it in your project.
Now onto some code. I use the GameObject class (which, I believe, is from the 2D tutorial) to handle most of my objects that I need to rotate, move, set alive or not alive, etc. Add a new class to your project that looks like this: (Change the namespace)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace AddingGunToPSK
{
class GameObject
{
public Texture2D sprite;
public Vector2 position;
public float rotation;
public Vector2 center;
public Vector2 velocity;
public bool alive;
public Rectangle rectangle
{
get
{
int left = (int)position.X;
int width = sprite.Width;
int top = (int)position.Y;
int height = sprite.Height;
return new Rectangle(left, top, width, height);
}
}
public GameObject(Texture2D loadedTexture)
{
rotation = 0.0f;
position = Vector2.Zero;
sprite = loadedTexture;
center = new Vector2(sprite.Width / 2, sprite.Height / 2);
velocity = Vector2.Zero;
alive = false;
}
}
}
Now in Player.cs we'll add this to the top of the class.
GameObject arm;
In LoadContent(...) we'll initialize it.
arm = new GameObject(Level.Content.Load<Texture2D>("Sprites/Player/Arm_Gun"));
Now in Update(...) add this to the bottom.
if (flip == SpriteEffects.FlipHorizontally)
arm.position = new Vector2(position.X + 5, position.Y - 60);
else
arm.position = new Vector2(position.X - 5, position.Y - 60);
Add this to GetInput(...) so we can control rotating our arm with the right thumbstick.
//Arm rotation
arm.rotation = (float)Math.Atan2(gamePadState.ThumbSticks.Right.X, gamePadState.ThumbSticks.Right.Y);
if (flip == SpriteEffects.FlipHorizontally) //Facing right
{
//If we try to aim behind our head then flip the
//character around so he doesn't break his arm!
if (arm.rotation < 0)
flip = SpriteEffects.None;
//If we aren't rotating our arm then set it to the
//default position. Aiming in front of us.
if (arm.rotation == 0 && Math.Abs(gamePadState.ThumbSticks.Right.Length()) < 0.5f)
arm.rotation = MathHelper.PiOver2;
}
else //Facing left
{
//Once again, if we try to aim behind us then
//flip our character.
if (arm.rotation > 0)
flip = SpriteEffects.FlipHorizontally;
//If we're not rotating our arm, default it to
//aim the same direction we're facing.
if (arm.rotation == 0 && Math.Abs(gamePadState.ThumbSticks.Right.Length()) < 0.5f)
arm.rotation = -MathHelper.PiOver2;
}
Finally in Draw(...) add this AFTER sprite.Draw(...) so our arm draws on top of our player.
spriteBatch.Draw(
arm.sprite,
arm.position,
null,
Color.White,
arm.rotation,
arm.center,
1.0f,
flip,
0);
Build it and try it out! You should be able to rotate your arm (arrow) and see how it defaults to aiming in front of you whichever way you're facing.
Now we'll add a set of bullets that you can shoot. These use the same GameObject class and we'll be using the 'alive' function so we can reuse bullets instead of creating a new one each time we need one. Add this bullet image (little red dot) to HighResolutionContent>Sprites>Player.
![]()
Still in Player.cs add this under 'GameObject arm' at the top.
GameObject[] bullets;
Now in LoadContent(...) underneath the initialize of our 'arm' add this. I'm creating 12 bullets so there can be no more than 12 bullets on-screen at a time. You can increase/decrease this as you see fit.
bullets = new GameObject[12];
for (int i = 0; i < 12; i++)
{
bullets[i] = new GameObject(Level.Content.Load<Texture2D>(
"Sprites/Player/Bullet"));
}
Now we'll handle the input for shooting our bullets. First we need to add this to the top of our Player.cs class.
GamePadState previousGamePadState;
and at the bottom of GetInput(...) add this.
previousGamePadState = gamePadState;
The code above allows us to find individual button presses so each time we push a button it fires 1 bullet and not all of them. In GetInput(...) add this underneath the rotation code for our arm.
//Shoot = RightTrigger
if (previousGamePadState.Triggers.Right < 0.5 && gamePadState.Triggers.Right > 0.5)
FireBullet();
In Update(...) add this call. This allows us to keep our logic for updating our bullets in its own place.
UpdateBullets();
and underneath the Update(...) method add 2 new methods. First one is for firing our bullets and the second one is for updating each bullet.
private void FireBullet()
{
foreach (GameObject bullet in bullets)
{
//Find a bullet that isn't alive
if (!bullet.alive)
{
//And set it to alive.
bullet.alive = true;
if (flip == SpriteEffects.FlipHorizontally) //Facing right
{
float armCos = (float)Math.Cos(arm.rotation - MathHelper.PiOver2);
float armSin = (float)Math.Sin(arm.rotation - MathHelper.PiOver2);
//Set the initial position of our bullet at the end of our gun arm
//42 is obtained be taking the width of the Arm_Gun texture / 2
//and subtracting the width of the Bullet texture / 2. ((96/2)-(12/2))
bullet.position = new Vector2(
arm.position.X + 42 * armCos,
arm.position.Y + 42 * armSin);
//And give it a velocity of the direction we're aiming.
//Increase/decrease speed by changing 15.0f
bullet.velocity = new Vector2(
(float)Math.Cos(arm.rotation - MathHelper.PiOver2),
(float)Math.Sin(arm.rotation - MathHelper.PiOver2)) * 15.0f;
}
else //Facing left
{
float armCos = (float)Math.Cos(arm.rotation + MathHelper.PiOver2);
float armSin = (float)Math.Sin(arm.rotation + MathHelper.PiOver2);
//Set the initial position of our bullet at the end of our gun arm
//42 is obtained be taking the width of the Arm_Gun texture / 2
//and subtracting the width of the Bullet texture / 2. ((96/2)-(12/2))
bullet.position = new Vector2(
arm.position.X - 42 * armCos,
arm.position.Y - 42 * armSin);
//And give it a velocity of the direction we're aiming.
//Increase/decrease speed by changing 15.0f
bullet.velocity = new Vector2(
-armCos,
-armSin) * 15.0f;
}
return;
}
}
}private void UpdateBullets()
{
//Check all of our bullets
foreach (GameObject bullet in bullets)
{
//Only update them if they're alive
if (bullet.alive)
{
//Move our bullet based on it's velocity
bullet.position += bullet.velocity;
//Rectangle the size of the screen so bullets that
//fly off screen are deleted.
Rectangle screenRect = new Rectangle(0, 0, 1280, 720);
if (!screenRect.Contains(new Point(
(int)bullet.position.X,
(int)bullet.position.Y)))
{
bullet.alive = false;
continue;
}
//Collision rectangle for each bullet -Will also be
//used for collisions with enemies.
Rectangle bulletRect = new Rectangle(
(int)bullet.position.X - bullet.sprite.Width * 2,
(int)bullet.position.Y - bullet.sprite.Height * 2,
bullet.sprite.Width * 4,
bullet.sprite.Height * 4);
//Everything below here can be deleted if you want
//your bullets to shoot through all tiles.
//Look for adjacent tiles to the bullet
Rectangle bounds = new Rectangle(
bulletRect.Center.X - 6,
bulletRect.Center.Y - 6,
bulletRect.Width / 4,
bulletRect.Height / 4);
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// For each potentially colliding tile
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
TileCollision collision = Level.GetCollision(x, y);
//If we collide with an Impassable or Platform tile
//then delete our bullet.
if (collision == TileCollision.Impassable ||
collision == TileCollision.Platform)
{
if (bulletRect.Intersects(bounds))
bullet.alive = false;
}
}
}
}
}
}
And in Draw(..) add the following between our sprite and arm calls.
//Draw the bullets
foreach (GameObject bullet in bullets)
{
if (bullet.alive)
{
spriteBatch.Draw(bullet.sprite,
bullet.position, Color.White);
}
}
That's it for shooting bullets. They'll delete when they go off-screen and whenever they hit a Platform or Impassable tile. Next we'll make the bullets kill those pesky enemies.
Now lets make those bullets kill the enemies! In Level.cs you'll see a line like this:
private List<Enemy> enemies = new List<Enemy>();
and you need to make it public instead of private.
public List<Enemy> enemies = new List<Enemy>();
Lets jump to Enemy.cs so we can setup some variables so they can die. Near the top add this.
public bool Alive
{
get { return alive; }
set { alive = value; }
}
bool alive;and in our constructor let's initialize it.
this.alive = true;
Now when an enemy is created it will be set to alive.
We're jumping back to Level.cs again because that's where our enemies get updated at. When our enemies die we want them to stop updating so they don't keep running even though they look pretty undead! There's an UpdateEnemies(...) method that you need to change to look like this one.
private void UpdateEnemies(GameTime gameTime)
{
foreach (Enemy enemy in enemies)
{
if (enemy.Alive)
{
enemy.Update(gameTime);
// Touching an enemy instantly kills the player
if (enemy.BoundingRectangle.Intersects(Player.BoundingRectangle))
{
OnPlayerKilled(enemy);
}
}
}
}So this checks to see if the enemy is alive before it tries to update them. Since we set alive = true in the constructor of our enemy they'll always start out updating until we kill them.
Since the default PSK comes with death animations for the enemies lets add those now. Find 'private Animation idleAnimation' and add this after it.
private Animation dieAnimation;
and in LoadContent(...) add this after the other animations.
dieAnimation = new Animation(Level.Content.Load<Texture2D>(spriteSet + "Die"), 0.15f, false);
That last argument is set to 'false' because we don't want our dieAnimation to loop. We need to include the actual death animation texture since it's not included by default. Go through and include the 'Die' texture found at Sprites>MonsterA>Die. There is a Die texture for all 4 monsters (A-D) so make sure to include all 4 of them before you move on.
Now in the Draw(...) method still in Enemy.cs you'll see an if/else statement that deals with playing animations. Lets add this else if statement BETWEEN the if/else statement.
else if(!alive)
sprite.PlayAnimation(dieAnimation);
Now onto Player.cs. In our UpdateBullets(...) method lets add this if statement underneath where we declared Rectangle bulletRect...
//Check for collisions with the enemies
foreach (Enemy enemy in level.enemies)
{
if (bulletRect.Intersects(enemy.BoundingRectangle))
{
enemy.Alive = false;
}
}
And that's it! You can kill those pesky enemies with just a single bullet now and you have an infinite amount!! Happy undead killing. ![]()
There's lots of extra stuff you could do at this point like making the enemies disappear after they die or adding a death sound when they die (which is already in your project but not included called MonsterKilled.wma). You could also add a sound for each time you fire or an ammo count so you don't have infinite bullets!
November 29th, 2011 - 10:09
how would you flip the image of the bullet based on the position of the player (trying to throw a boomerang but when i’m facing left it looks like it’s being thrown backwards )
November 29th, 2011 - 15:51
In your Player.cs class there’s a check to see if the player is facing left/right called “SpriteEffects flip”. When drawing your boomerang use one of the longer methods that includes a SpriteEffects call. If you take a look at the Draw method in Player.cs you can see how it’s done in there.
December 11th, 2011 - 20:59
How can I get this to work under XNA 4.0?
December 11th, 2011 - 21:05
I haven’t tried it under XNA 4.0, but it should work since there’s nothing complicated going on. If you post the problems you’re having I can see if there’s something that was changed in 4.0.