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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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.
1 | GameObject arm; |
In LoadContent(...) we'll initialize it.
1 | arm = new GameObject(Level.Content.Load<Texture2D>("Sprites/Player/Arm_Gun")); |
Now in Update(...) add this to the bottom.
1 2 3 4 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //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.
1 2 3 4 5 6 7 8 9 10 11 12 13 | if(IsAlive) { 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.
1 | 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.
1 2 3 4 5 6 | 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.
1 | GamePadState previousGamePadState; |
and at the bottom of GetInput(...) add this.
1 | 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.
1 2 3 | //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.
1 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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.
1 2 3 4 5 6 7 8 9 | //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:
1 | private List<Enemy> enemies = new List<Enemy>(); |
and you need to make it public instead of private.
1 | 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.
1 2 3 4 5 6 | public bool Alive { get { return alive; } set { alive = value; } } bool alive; |
and in our constructor let's initialize it.
1 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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.
1 | private Animation dieAnimation; |
and in LoadContent(...) add this after the other animations.
1 | 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.
1 2 | else if(!alive || ((waitTime > 0) && !alive)) sprite.PlayAnimation(dieAnimation); |
Now onto Player.cs. In our UpdateBullets(...) method lets add this if statement underneath where we declared Rectangle bulletRect...
1 2 3 4 5 6 7 8 | //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!
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.
March 7th, 2012 - 08:11
Hi Jeff, superb tutorial here. I am using XNA 4.0 and finding that the shooting wont work. I got it to shoot twice and now it refuses to, even restarting it hasn’t let me. I am running it through XBox, the code looks identical to yours, could it be a 4.0 difference or is it more likely that I’ve made an error?
Thanks
March 7th, 2012 - 08:15
I think I’ve figured out the error but I don’t know what to do about it. There is a point in my level where the bullets stop firing, if I jump over this block it lets me fire on the left, but not on the right, how insane is that? I have parralax scrolling set up on my game so maybe it is to do with setting the level boundaries?
March 7th, 2012 - 08:18
Last post on this matter – I found that I had to comment out the below code
//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;
//}
With that gone it lets me fire as many bullets as I like – obviously the down side to this is they don’t die, I will probably change the width to something much higher than 1280 or code in an “after 10 seconds kill the bullet” thing as most bullets will take 3 seconds to hit something if aiming for them.
March 7th, 2012 - 10:51
Do you have a camera that you’re using? If so, change the “screenRect” rectangle so it looks like this: Rectangle screenRect = new Rectangle(camera.position.X, camera.position.Y, 1280, 720). Right now your bullets will work anywhere in that initial 1280×720 area but when you move outside of it (I’m guessing you have a scrolling level with a camera) then the bullets get deleted as soon as they spawn. Also worth mentioning, if your game resolution is set to 1280×720 then you should be fine with this code but if it’s set to anything else then you’ll want to change the 1280×720 to match the resolution you set your game to.
April 28th, 2012 - 19:04
A fix – change this:
else if(!alive)
sprite.PlayAnimation(dieAnimation);
into this:
else if ((!alive && (waitTime > 0)) || !alive)
sprite.PlayAnimation(dieAnimation);
Otherwise, the enemy will retain idle animation if it is idle while killed
May 7th, 2012 - 02:39
Thanks for catching that, updated!
May 8th, 2012 - 11:13
How do I make the gun die with the player? I thought it was as simple as arm.alive = false but this did not work. Presenting my finished game tomorrow,very excited!
May 8th, 2012 - 11:21
I added in this if statement to do what I wanted.
if (isAlive == true)
{
spriteBatch.Draw(
arm.sprite,
arm.position,
null,
Color.White,
arm.rotation,
arm.center,
1.0f,
flip,
0);
}
May 8th, 2012 - 13:08
Good catch, I updated the post.