Ammo Class and FireMode enum (Release)

lxXTaCoXxl -1 Tallied Votes 245 Views Share

I've developed a simple class and enum that will make ammunition creation and usage simpler in game development for those who are interested. I will be making a tutorial video on how I made this so that you will fully understand how the code works if you do not. Simply implement it into your game by declaring it, and using the methods and variables to your advantage. For example:

private Ammunition myAmmo = new Ammunition(30);

private void button1_Click(object s, EventArgs e)
{
// fire the gun, and output the remaining bullets to the screen for display
myAmmo.Fire(FireMode.ThreeShotBurst, false); // leave as false unless the user is firing their weapon on automatic.

label1.Text = "Ammo Left: " + myAmmo.ClipContents.ToString() + "/" + myAmmo.ClipCapacity.ToString();
}

Happy shootings,
Jamie

public enum FireMode
{
	SingleShot,
	TwoShotBurst,
	ThreeShotBurst,
	Automatic
}

public class Ammunition
{
	public int ClipCapacity {get; private set;}
	public int ClipContents {get; private set;}

	public FireMode FireMode = FireMode.SingleShot;

	public Ammunition(int Capacity)
	{
		this.ClipCapacity = Capacity;
		this.ClipContents = Capacity;
	}

	public void Fire(FireMode FireMode, bool IsPlayerCurrentlyFiring)
	{
		if (this.ClipContents > 0)
		{
			if (FireMode == FireMode.SingleShot)
				this.ClipContents--;

			else if (FireMode == FireMode.TwoShotBurst)
				this.ClipContents -= 2;

			else if (FireMode == FireMode.ThreeShotBurst)
				this.ClipContents -= 3;

			else if (FireMode == FireMode.Automatic)
			{
				while (IsPlayerCurrentlyFiring)
					this.ClipComponents--;
			}
		}

		else
			Reload();
	}

	public void Reload()
	{
		this.ClipContents = this.ClipCapacity;
	}
}
skatamatic 371 Practically a Posting Shark

Unless this code is running multithreaded, if the player is firing in automatic mode the code will be completely blocked forever, since the bool argument will not change once inside the while loop. If it is multithreaded, this class instance will lock up in automatic mode, eventually throwing an overflow exception when this.ClipComponents (which isn't actually a member, by the way) reaches the low limit of an integer.

This.ClipCompenents doesn't exist as a member so that will cause an error, though this is likely a typo and not a design flaw. Also, there's a bit of naming conflicting going on - theres an enum called FireMode, a member called FireMode, and an argument to Fire() that is called FireMode of type FireMode. This will cause quite a few errors.

Have you actually used this code in an application?

commented: Good code review +11
skatamatic 371 Practically a Posting Shark

Also, if the gun is fired with 1 bullet left in the clip, with a double or triple shot, the current ammo will drop below 0.

lxXTaCoXxl 26 Posting Whiz in Training

Well, I have updated this class quite a bit since posting it and I have taken care of the ammo dropping below zero problem. However, about multi threading, this is mainly just for XNA development and it works just fine in my game. If you want a windows forms style of the class then let me know and I'll work on one. As I stated, it's just a simple ammo class to give the basic idea of how ammunition works inside of a video game. Thank you for the input though.

P.S. - I may have implemented it in a forms application, but that's beside the point I was trying to make.

skatamatic 371 Practically a Posting Shark

I don't see how it could possibly work >.<. In automatic mode the only way to get out of that while loop is to throw an underflow exception.

Momerath 1,327 Nearly a Senior Poster Featured Poster

I'm guessing that in his game he's fixed the bugs, but he hasn't posted the fixed code here.

skatamatic 371 Practically a Posting Shark

I'm guessing that in his game he's fixed the bugs, but he hasn't posted the fixed code here.

That's not what he made it sound like. He said that it works fine for XNA development, but he was unsure about winforms.

lxXTaCoXxl 26 Posting Whiz in Training
using System.Threading;

public enum FireMode
{
	SingleShot,
	TwoShotBurst,
	ThreeShotBurst,
	Automatic
}

public class Ammunition
{
	public int ClipCapacity {get; private set;}
	public int ClipContents {get; private set;}

	public FireMode FireMode = FireMode.SingleShot;

	public Ammunition(int Capacity)
	{
		this.ClipCapacity = Capacity;
		this.ClipContents = Capacity;
	}

	public void Fire(FireMode FireMode, bool IsPlayerCurrentlyFiring)
	{
		if (this.ClipContents > 0)
		{
			if (FireMode == FireMode.SingleShot)
				this.ClipContents--;

			else if (FireMode == FireMode.TwoShotBurst)
			{
				if (this.ClipContents < 2)
					Fire(FireMode.SingleShot, false);

				else
					this.ClipContents -= 2;
			}

			else if (FireMode == FireMode.ThreeShotBurst)
			{
				if (this.ClipContents < 3)
					Fire(FireMode.SingleShot, false);

				else
					this.ClipContents -= 3;

			else if (FireMode == FireMode.Automatic)
			{
				while (IsPlayerCurrentlyFiring)
				{
					if (this.ClipComponents <= 0)
						IsPlayerCurrentlyFiring = false;

					else
						this.ClipComponents--;

					Thread.Sleep(10);
				}
			}
		}

		else
			Reload();
	}

	public void Reload()
	{
		this.ClipContents = this.ClipCapacity;
	}
}

This snippet takes care of the dropping below zero bug, as well as fixes the while loop. Change line 58's millisecond value to modify how rapidly your ammunition will fire on automatic.

skatamatic 371 Practically a Posting Shark

Well that looks quite a bit better. It's still flawed though - automatic mode now empties the entire clip regardless of user input. Is this by design?

skatamatic 371 Practically a Posting Shark

I put this together in my down-time at work (lol). I tried to address some of the issues in it. It uses a really simple threading construct to allow for proper automatic weapon fire.

public abstract class Ammo
    {
        public enum AmmoType
        {
            SingleShot,
            DoubleShot,
            TripleShot,
            Automatic
        }
        public AmmoType _AmmoType { get; protected set; }
        public uint _ClipSize { get; protected set; }
        public uint _ClipContents {get; protected set; }

        protected Ammo(AmmoType aT, uint ClipSize)
        {
            _AmmoType = aT;
            _ClipSize = ClipSize;
            _ClipContents = ClipSize;
        }
        protected static uint GetAmmoCost(AmmoType aT)
        {
            switch (aT)
            {
                case AmmoType.SingleShot:
                    return 1;
                case AmmoType.DoubleShot:
                    return 2;
                case AmmoType.TripleShot:
                    return 3;
                case AmmoType.Automatic:
                    return 1;
                default:
                    return 0;
            }
        }
        public virtual void Fire()
        {
            uint AmmoCost = GetAmmoCost(this._AmmoType);
            _ClipContents -= (_ClipContents - AmmoCost) >= 0 ? AmmoCost : _ClipContents;
        }
        public void Reload()
        {
            _ClipContents = _ClipSize;
        }
    }
    public class PistolAmmo : Ammo
    {
        public PistolAmmo(uint ClipSize)
            : base(AmmoType.SingleShot, ClipSize)
        { }
    }
    public class ARAmmo : Ammo
    {
        protected uint _RateOfFire = 0;
        volatile protected bool _IsFiring { get; private set; }
        Thread _FiringThread;   //allows async timing of firing
        delegate void FireCb(); //Call back from the firing timer thread

        public ARAmmo(uint ClipSize, uint RateOfFire)
            : base(AmmoType.Automatic, ClipSize)
        {
            _RateOfFire = RateOfFire;
        }
        public override void Fire()
        {
            if (_FiringThread == null || !_FiringThread.IsAlive)
            {
                _FiringThread = new Thread(new ParameterizedThreadStart(FireThread));
                _IsFiring = true;
                _FiringThread.Start(new FireCb(base.Fire));
            }

        }
        protected void FireThread(object myFireCB)
        {
            FireCb myCallBack = (FireCb)myFireCB;
            while (this._IsFiring && this._ClipContents > 0)
            {
                myCallBack.Invoke();
                Thread.Sleep((int)_RateOfFire);
            }
        }
        public void StopFiring()
        {
            _IsFiring = false;
        }
    }
lxXTaCoXxl 26 Posting Whiz in Training

Yeah the while loop was originally by design but I realized earlier today that not everyone wants extreme rapid fire auto-built into thier game, the automatic section was built to test a "cheat" that I'm custom building into my game. I just forgot to switch it out and didn't realize it was the cheat until today. However, the version I modified it to today uses TimeSpan variables to determine reload times, fire rates, and whether or not the gun should fire again while the button is held down on single shot, and two or three shot bursts. So I've modified it further and further. I just kind of inspect my code as the days go by and fix it more and more. However, I should probably do this before releasing it huh? LOL

Thanks for posting your modification, it gives me a chance to study how others code. However, the code seems to be a little sloppy (not in terms of how it will function, but just in sheer orginization). That's my own personal opinion though. I'm slightly OCD so all my code has to be organized in particular ways. LOL Go figure, an OCD programmer / physicist. HAHA


Could you do me a favor and, explain this line of code to me? I know it's a conditional but don't fully understand how it works.

_ClipContents -= (_ClipContents - AmmoCost) >= 0 ? AmmoCost : _ClipContents;

Thanks,
Jamie

skatamatic 371 Practically a Posting Shark

Thanks for posting your modification, it gives me a chance to study how others code. However, the code seems to be a little sloppy (not in terms of how it will function, but just in sheer orginization). That's my own personal opinion though. I'm slightly OCD so all my code has to be organized in particular ways. LOL Go figure, an OCD programmer / physicist. HAHA

Lol. Uncommented yes, sloppy - not even half as sloppy as yours was lol, but I guess that's a matter of opinions.

Could you do me a favor and, explain this line of code to me? I know it's a conditional but don't fully understand how it works.

_ClipContents -= (_ClipContents - AmmoCost) >= 0 ? AmmoCost : _ClipContents;

Thanks,
Jamie

This is equivalent to:

if (_ClipContents - AmmoCost >= 0) 
   _ClipContents -= AmmoCost;     //Subtract ammo cost from clip
else
   _ClipContents -= _ClipContents;//Empty remaining bullets from gun ( _ClipContents=0 )
lxXTaCoXxl 26 Posting Whiz in Training

So if your explanation serves me correctly then:

if (x > 0)
doSomething();

else
doSomethingElse();

// is equivalent to:

x > 0 ? doSomething() : doSomethingElse();

That's my basic understanding, that the '?' signifies a conditional and the first argument is true, ':' replaces else, and then the second argument is false?

Or, does this only work with assignments?

skatamatic 371 Practically a Posting Shark

Yep that's valid too. But I only really use it for assignment, things can get pretty ugly and confusing if you replace all your if/case structures with the conditional operator. There is also the ?? operator (null-coalescing) which can sometimes be useful.

myObject ob = OtherObject ?? DefaultObject;

Which is the same as

myObject ob;
if (OtherObject != null)
   ob = OtherObject;
else
   ob = DefaultObject;

Which is also the same as

myObject ob = (OtherObject != null) ? OtherObject : DefaultObject;
lxXTaCoXxl commented: thank you +4
lxXTaCoXxl 26 Posting Whiz in Training

That would be very useful in terms of assigning default values to things in XNA. I've actually never seen '??' anywhere before so thank you for including that. :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.