Skip to content

Creating Custom Projectiles

Alex Neargarder edited this page Jun 29, 2025 · 11 revisions

Types of Projectiles

The first thing you'll want to do is decide what type of projectile you want to create. The three main projectile classes in Broforce are Projectile, Grenade, and SachelPack.

The Projectile class is what all bullet projectiles inherit from. By default they are deleted on contact but you can make them pierce instead if you want.

Grenades don't actually inherit from the Projectile class, and they have some other weird properties. They have a color trail, start flashing when they are about to explode, and can be rethrown. All of these properties can be changed however.

SachelPack inherits from Projectile, so they are pretty similar, but they have some extra code for sticking to enemies and walls that can be useful.

Creating a Custom Projectile

Creating custom projectiles isn't too difficult. BroMaker provides the following classes that you can inherit from:

  • BroMakerLib.CustomObjects.Projectiles.CustomProjectile
  • BroMakerLib.CustomObjects.Projectiles.CustomGrenade
  • BroMakerLib.CustomObjects.Projectiles.CustomSachelPack

These classes provide some additional utilities to ease the creation of custom projectiles, but using them is not required. You can instead inherit from one of the base game's classes, which may be a good idea if you want to mostly copy an existing projectile with some slight modifications. But keep in mind you may need to add some additional code to handle some of the things that BroMaker's custom classes do.

Your Projectile class should look something like this probably:

    class YourProjectileName: Projectile
    {
                public static Material storedMat;
		public SpriteSM storedSprite;
		protected override void Awake()
		{
			MeshRenderer renderer = this.gameObject.GetComponent<MeshRenderer>();

			string directoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		        storedMat = ResourcesController.GetMaterial(directoryPath, "YourProjectileSprite.png");

			renderer.material = storedMat;

			SpriteSM sprite = this.gameObject.GetComponent<SpriteSM>();
                        // Replace (16, 16) with the (width, height) of your actual sprite file
                        // or if you're making an animated projectile then this should be the size of one frame of your animation
			sprite.lowerLeftPixel = new Vector2(0, 16);
			sprite.pixelDimensions = new Vector2(16, 16);

			sprite.plane = SpriteBase.SPRITE_PLANE.XY;
                        // These width and heights can be anything, they control how big the sprite appears in game.
			sprite.width = 16;
			sprite.height = 16;
			sprite.offset = new Vector3(0, 0, 0);

			storedSprite = sprite;

			base.Awake();
		}
    }

Creating a Custom Projectile Prefab

After you've created your custom Projectile class, you'll want to create a prefab of the object that you can continually spawn. So you'll want to have something like this in the Awake function or Start function of your bro, and you should make sure you store the prefab you're creating for use in other functions:

            projectile = new GameObject("YourProjectileName", new Type[] { typeof(Transform), typeof(MeshFilter), typeof(MeshRenderer), typeof(SpriteSM), typeof(YourProjectileName) }).GetComponent<YourProjectileName>();
            projectile.soundHolder = (HeroController.GetHeroPrefab(HeroType.Rambro) as Rambro).projectile.soundHolder;
            projectile.enabled = false;

The "YourProjectileName" string just controls the name of the GameObject, and can actually be whatever. It doesn't have to match the name of your class, but the typeof(YourProjectileName) and GetComponent() do have to match the name of your class.

The Transform, MeshFilter, MeshRenderer, SpriteSM, and YourProjectileName, are all the components you're attaching to your Projectile GameObject. Some projectiles in the base game may have some other slightly different components like an AnimatedTexture, or a BoxCollider, but for the most part, most projectiles just use the ones I just listed, minus YourProjectileName, which would obviously be replaced with your own Projectile class. If you're modeling your own Projectile after a specific one that already exists in game, I would just check which components that Projectile uses by viewing it in UnityRuntimeEditor, and then use the same ones.

Setting the SoundHolder to some other SoundHolder is usually a good idea just to make sure that the Projectile doesn't try to use the SoundHolder to play a sound. If it does and you don't set it to something, it will crash because it'll be accessing a null pointer. Setting it to something is a good idea to prevent this, but you can avoid using the SoundHolder when you want to add your own sounds, and just save them in AudioClip variables.

By default I think GameObjects are enabled when you create them, and this object is meant to just be a blueprint for all your future Projectiles, so we set enabled to false to make sure it's not running for no reason (which can lead to it getting destroyed and causing errors when you try to use it later).

Spawning a Custom Projectile

Finally when you want to actually spawn the Projectile when your character does a specific action, like shooting, using their special, or meleeing, you'll want to do something like this:

Projectile lastFiredProjectile = ProjectileController.SpawnProjectileLocally(this.projectile, this, x, y, xSpeed, ySpeed, base.playerNum) as Projectile;
lastFiredProjectile.enabled = true;

The this.projectile should be replaced with whatever you've called the variable that is storing your Projectile prefab. It's important to make sure you set enabled to true after you've fired the Projectile, because I think with the way we create them, for some reason they just don't get set to enabled when you fire them. This problem doesn't seem to exist with the base game Projectiles for whatever reason.

x and y should be replaced with the location you want the projectile to spawn, which is usually an offset of your character's position, for example:

base.X + base.transform.localScale.x * 10f, base.Y + 8f

xSpeed and ySpeed should be replaced by the speed of your object when it spawns, for example:

base.transform.localScale.x * 300f, 100f

If you want to add spread to your projectiles you could do something like this for the ySpeed:

(float)UnityEngine.Random.Range(-20, 20)

Note that the SpawnProjectileLocally function won't work for custom Grenades, instead you'll want to use the SpawnGrenadeLocally function.

It's important to use the Spawn___Locally functions because custom Projectiles and custom Bros don't work with functions that try to spawn them over the network. This also means that you'll want to override the default FireWeapon method to make sure it isn't trying to spawn your Projectile over the network.

Something to note is that if you spawn a Projectile using a disabled prefab, meaning that you set .enabled to false, the prefab will have its Awake() and Start() functions called immediately, even before the Fire function. However if you create it using a prefab with a deactivated GameObject, meaning that you called SetActive(false), the Awake() and Start() functions won't be called until you call SetActive on the GameObject. So generally you'll probably want to use disabled prefabs in order to ensure Awake() and Start() are called before Fire().

Clone this wiki locally