Creating Custom Projectiles - Gorzontrok/Bro-Maker GitHub Wiki
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 custom projectiles isn't too difficult, although BroMaker doesn't really provide any additional support like it does for creating Custom Bros. First you'll want to create a class that inherits from Projectile, Grenade, or SachelPack. Or you can inherit from some other more specific Projectile if you want to make a similar Projectile to an existing one with slightly different behavior.
Your Projectile class should look something like this probably:
class TranqDart : 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, "TranqDarts1.png");
renderer.material = storedMat;
SpriteSM sprite = this.gameObject.GetComponent<SpriteSM>();
sprite.lowerLeftPixel = new Vector2(0, 16);
sprite.pixelDimensions = new Vector2(16, 16);
sprite.plane = SpriteBase.SPRITE_PLANE.XY;
sprite.width = 16;
sprite.height = 16;
sprite.offset = new Vector3(0, 0, 0);
storedSprite = sprite;
base.Awake();
this.damageType = DamageType.Normal;
this.damage = 3;
this.damageInternal = this.damage;
this.fullDamage = this.damage;
this.life = 0.30f;
}
}
If you want to change how big the sprite looks in game, you should change the sprite.width and sprite.height values. The sprite.pixelDimensions is for loading the actual sprite and should only be changed if you're using a different size sprite. sprite.offset can be changed to line up the sprite better with the hitbox of the object.
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("TranqDart", new Type[] { typeof(Transform), typeof(MeshFilter), typeof(MeshRenderer), typeof(SpriteSM), typeof(TranqDart) }).GetComponent<TranqDart>();
projectile.soundHolder = (HeroController.GetHeroPrefab(HeroType.Rambro) as Rambro).projectile.soundHolder;
projectile.enabled = false;
The "TranqDart" string just controls the name of the GameObject, and can actually be whatever.
The Transform, MeshFilter, MeshRenderer, SpriteSM, and TranqDart, 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 TranqDart, which would obviously be replaced with your own Projectile class. If you're modelling 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, find that it's null, and then throw an error. You can usually just avoid using the SoundHolder entirely when you want to add your own sounds though.
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.
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:
lastFiredTranq = ProjectileController.SpawnProjectileLocally(this.projectile, this, x, y, xSpeed, ySpeed, base.playerNum) as TranqDart;
lastFiredTranq.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.
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, 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, the Awake() and Start() functions won't be called until you call SetActive on the GameObject.