-
Notifications
You must be signed in to change notification settings - Fork 0
Glossary of Important Methods
This page provides an overview of commonly overridden methods when creating custom bros, organized by functionality.
Awake()
is called once when the Bro is created, before Start()
. You can use this method for initializing any variables / custom projectiles your bro uses, but generally I prefer to keep most of my initialization in the Start() method.
Note that your bro's sprite / gunsprite / avatar materials won't be loaded by BroMaker yet when Awake()
is called. If you want to store those, in case you wanted to swap to different materials, then you'll want to place that in Start()
instead.
Start()
is called once when the Bro is created, after Awake()
. You can use this method to set up any additional sprites that your bro uses, custom projectile prefabs, your bro's meleeType (needed if you want to have custom melee methods called), or any other variables that need initializing.
If you want to store your bro's original sprite materials (in case you'd like to swap them later) you should make sure you call base.Start() first, to allow BroMaker to load your sprites.
Update()
runs every frame during gameplay. You can use this method to handle ongoing logic, timers, and any mechanics that need constant updating.
Examples:
You might use it to track if a custom keybinding you have set is pressed:
// Switch Weapon Pressed
if ( playerNum >= 0 && playerNum < 4 && switchWeaponKey.IsDown( playerNum ) )
{
StartSwitchingWeapon();
}
Or you can use it to countdown any cooldowns / timers you have set:
// Count down to becoming sober
if ( this.drunk )
{
this.drunkCounter -= this.t;
// If drunk counter has run out, try to start becoming sober animation
if ( this.drunkCounter <= 0 )
{
this.TryToBecomeSober();
}
}
OnDestroy()
is called when your bro's GameObject is being destroyed. Override this if you want to clean up any spawned objects that you don't want to remain after your bro's death.
PreloadAssets()
is a BroMaker method that runs when the game starts up and the mod is being loaded. Use this to load custom sprites and sounds to avoid lag spikes when spawning in custom bros later.
Note:
The sprite, gunSprite, SpecialIcons, Avatar, cutscene sprites, and cutscene sounds that you have set in your bro's JSON file are automatically loaded, so you don't need to load them here. But they are cached, so it won't really be a problem if you do.
Example:
public override void PreloadAssets()
{
string directoryPath = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location );
CustomHero.PreloadSprites( directoryPath, new List<string> { "spriteStealth.png", "gunSpriteStealth.png", "avatar.png", "avatarStealth.png" } );
CustomHero.PreloadSprites( Path.Combine( directoryPath, "projectiles" ), new List<string> { "TranqDart.png", "Explosive.png", "ExplosiveGum.png" } );
directoryPath = Path.Combine( directoryPath, "sounds" );
CustomHero.PreloadSounds( directoryPath, new List<string> { "gun1.wav", "gun2.wav", "gun3.wav", "gun4.wav", "gun5.wav", "gun6.wav", "gun7.wav", "Click_Metal1.wav", "Click_Metal2.wav", "Click_Metal3.wav", "Click_Metal5.wav", "Click_Metal6.wav" } );
}
PrefabSetup()
is called when BroMaker creates your bro's prefab. This is only called once when your bro spawns for the first time, but after that BroMaker will use the cached prefab instead. So you can use this method to load assets once so you don't have to load them every time your bro spawns.
Example:
public override void PrefabSetup()
{
// Make sure you include this
base.PrefabSetup();
// Load Audio
string soundPath = Path.Combine( directoryPath, "sounds" );
this.crossbowSounds = new AudioClip[4];
this.crossbowSounds[0] = ResourcesController.GetAudioClip( soundPath, "crossbowShot1.wav" );
this.crossbowSounds[1] = ResourcesController.GetAudioClip( soundPath, "crossbowShot2.wav" );
this.crossbowSounds[2] = ResourcesController.GetAudioClip( soundPath, "crossbowShot3.wav" );
this.crossbowSounds[3] = ResourcesController.GetAudioClip( soundPath, "crossbowShot4.wav" );
}
Important Notes:
You need to make sure to include a call to base.PrefabSetup()
, because this method handles some initialization as well.
The base method also stores the path to your bro's directory in the this.directoryPath
variable, so you can use it for loading assets.
Any assets that you load here need to be assigned to public variables, otherwise Unity will ignore them when instantiating GameObjects using the prefab, which means they'll be null when your bro actually spawns. Another option to get around this is to use the SerializeField
attribute.
HarmonyPatches()
lets you apply harmony patches to your bro. Most bro's probably won't need to use any harmony patches, but if you want to create an effect that modifies the behavior of enemies then it can be useful. For example, Brostbuster's ghost trap and Furibrosa's grab both use them.
Parameters:
-
harmony
- Harmony instance
Example:
public override void HarmonyPatches( Harmony harmony )
{
Assembly assembly = Assembly.GetExecutingAssembly();
harmony.PatchAll( assembly );
}
UIOptions()
allows you to add custom options to the BroMaker menu for your bro. You can see this page for information on the different methods you have available.
Example:
public override void UIOptions()
{
GUILayout.Space( 10 );
// Only display tooltip if it's currently unset (otherwise we'll display BroMaker's tooltips
switchWeaponKey.OnGUI( out _, (GUI.tooltip == string.Empty) );
GUILayout.Space( 10 );
if ( doubleTapSwitch != ( doubleTapSwitch = GUILayout.Toggle( doubleTapSwitch, "Double Tap Down to Switch Weapons" ) ) )
{
// Settings changed, update json
this.SaveSettings();
}
}
Note:
A new instance of your bro is created to dispay these options, so it will be a different object from your actual bro in-game. Therefore you'll need to use static variables to have the variables set by your menu options available to your actual bro in-game.
If you'd like to have the options in this menu saved between game sessions, you can use the [SaveableSetting] attribute.
The primary fire system follows this pattern:
- Player presses fire button →
StartFiring()
called once. SetsfireCounter
tofireRate
iffireRate
< 0.3f -
Update()
→ callsRunFiring()
which checks if you're holding fire, and if so, incrementsfireCounter
-
RunFiring()
calls →UseFire()
whenfireCounter >= fireRate
-
UseFire()
calls →FireWeapon()
to spawn a projectile or deal damage - Player releases fire →
StopFiring()
andReleaseFire()
are called
Additionally:
-
RunGun()
runs everyUpdate()
to handle gun sprite animations -
RunGun()
calls →SetGunSprite()
to change the gun sprite frame -
SetGunPosition()
is usually called each frame to position the gun sprite
StartFiring()
is called once when the fire button is first pressed. It sets fireCounter
to fireRate
if fireRate
< 0.3f, otherwise sets it to 0. This basically means that if the fireRate is fairly quick, you'll instantly fire as soon as you press fire, but if it's longer then it'll have to charge up before you get your first shot. You can override this to reset any additional firing variables you've created or start playing a continuous sound effect.
RunFiring()
is called every update while the fire button is held. It increments fireCounter
by this.t
, which is the amount of time that has passed between the last update and the current one, and calls UseFire()
when fireCounter >= fireRate
. You can override this if you want to have your own custom logic for the firing timing, or if you wanted to have a continuous fire effect.
StopFiring()
is called when the fire button is released. You can override this to stop continuous firing effects, release charged shots, or clean up firing states.
ReleaseFire()
is called when the fire button is released. The only difference between this and StopFiring()
is that StopFiring()
is not called if your bro is dead, frozen, or impaled, whereas ReleaseFire()
doesn't have these checks.
UseFire()
is called to perform an attack. It cancels any active melee, calls FireWeapon()
to actually spawn the projectile or deal damage, and calls PlayAttackSound()
.
Examples: You can override this if you'd like to adjust the position that your projectiles spawn depending on your bro's state:
protected override void UseFire()
{
if ( this.doingMelee )
{
this.CancelMelee();
}
float num = base.transform.localScale.x;
if ( !base.IsMine && base.Syncronize )
{
num = (float)this.syncedDirection;
}
if ( Connect.IsOffline )
{
this.syncedDirection = (int)base.transform.localScale.x;
}
// Fire while on zipline / hanging
if ( this.IsHangingOneArmed() )
{
this.FireWeapon( base.X + num * 4f, base.Y + 5f, num * bulletSpeed, 0 );
}
// Fire while on grapple
else if ( this.grappleAttached || this.exitingGrapple )
{
this.FireWeapon( base.X + num * 14f, base.Y + 10f, num * bulletSpeed, 0 );
}
// Fire while wall dragging
else if ( this.WallDrag )
{
num *= -1;
this.FireWeapon( base.X + num * 14f, base.Y + 10f, num * bulletSpeed, 0 );
}
// Stealth fire
else if ( stealthActive )
{
this.FireWeapon( base.X + num * 12f, base.Y + 10f, num * bulletSpeed, 0 );
}
// Normal fire ducking
else if ( this.ducking )
{
this.FireWeapon( base.X + num * 14f, base.Y + 7f, num * bulletSpeed, 0 );
}
// Normal fire
else
{
this.FireWeapon( base.X + num * 12f, base.Y + 10f, num * bulletSpeed, 0 );
}
if ( !this.stealthActive )
{
Map.DisturbWildLife( base.X, base.Y, 60f, base.playerNum );
}
this.fireCooldown = this.fireRate - 0.12f;
}
Or you could use it to change the projectile / attack spawned depending on your character's state:
protected override void UseFire()
{
if ( !theThing )
{
if ( this.burstCooldown <= 0f )
{
this.FireWeapon( base.X + base.transform.localScale.x * 11f, base.Y + 10f, base.transform.localScale.x * 180f + this.xI, UnityEngine.Random.value * 30 - 15f );
this.FireWeapon( base.X + base.transform.localScale.x * 11f, base.Y + 11f, base.transform.localScale.x * 210f + this.xI, UnityEngine.Random.value * 40f - 20f );
--this.burstCount;
if ( this.burstCount <= 0 )
{
this.fireRate = originalFirerate;
this.burstCooldown = UnityEngine.Random.Range( 0.5f, 2f );
}
}
else
{
this.FireWeapon( base.X + base.transform.localScale.x * 11f, base.Y + 10f, base.transform.localScale.x * 30f + this.xI, UnityEngine.Random.value * 20f - 15f );
this.FireWeapon( base.X + base.transform.localScale.x * 11f, base.Y + 11f, base.transform.localScale.x * 40f + this.xI, UnityEngine.Random.value * 30f - 20f );
}
Map.DisturbWildLife( base.X, base.Y, 60f, base.playerNum );
}
else
{
this.ThingUseFire();
}
}
Note:
Called automatically by RunFiring()
when fireCounter >= fireRate
.
Some of the things that you could change in this method could also be done in FireWeapon()
instead, it's up to you how you'd like to organize your code.
FireWeapon()
is called to create the projectile for your bro's primary attack. It sets gunFrame
to the correct frame on your spritesheet for your gun's firing animation, creates a muzzle flash effect, and spawns the projectile. You can override this to spawn custom projectiles, implement non-projectile attacks, or vary the projectile you're spawning based on your bro's state.
Parameters:
-
x, y
- World position to spawn the projectile -
xSpeed, ySpeed
- Initial velocity (base uses 400 horizontal, random -20 to 20 vertical)
Example:
protected override void FireWeapon( float x, float y, float xSpeed, float ySpeed )
{
// Fire crossbow
if ( this.currentState == PrimaryState.Crossbow )
{
// Fire explosive bolt
if ( this.charged )
{
x = base.X + base.transform.localScale.x * 8f;
y = base.Y + 8f;
xSpeed = base.transform.localScale.x * 500 + ( this.xI / 2 );
ySpeed = 0;
this.gunFrame = 3;
this.SetGunSprite( this.gunFrame, 0 );
this.TriggerBroFireEvent();
EffectsController.CreateMuzzleFlashEffect( x, y, -25f, xSpeed * 0.15f, ySpeed, base.transform );
Bolt firedBolt = ProjectileController.SpawnProjectileLocally( explosiveBoltPrefab, this, x, y, xSpeed, ySpeed, base.playerNum ) as Bolt;
}
// Fire normal bolt
else
{
x = base.X + base.transform.localScale.x * 8f;
y = base.Y + 8f;
xSpeed = base.transform.localScale.x * 400 + ( this.xI / 2 );
ySpeed = 0;
this.gunFrame = 3;
this.SetGunSprite( this.gunFrame, 0 );
this.TriggerBroFireEvent();
EffectsController.CreateMuzzleFlashEffect( x, y, -25f, xSpeed * 0.15f, ySpeed, base.transform );
Bolt firedBolt = ProjectileController.SpawnProjectileLocally( boltPrefab, this, x, y, xSpeed, ySpeed, base.playerNum ) as Bolt;
}
this.PlayCrossbowSound( base.transform.position );
this.fireDelay = crossbowDelay;
}
else if ( this.currentState == PrimaryState.FlareGun )
{
Projectile flare = null;
if ( this.attachedToZipline == null )
{
x = base.X + base.transform.localScale.x * 12f;
y = base.Y + 8f;
xSpeed = base.transform.localScale.x * 450;
ySpeed = UnityEngine.Random.Range( 15, 50 );
EffectsController.CreateMuzzleFlashEffect( x, y, -25f, xSpeed * 0.15f, ySpeed, base.transform );
flare = ProjectileController.SpawnProjectileLocally( flarePrefab, this, x, y, xSpeed, ySpeed, base.playerNum );
}
// Move position of shot if attached to a zipline
else
{
x = base.X + base.transform.localScale.x * 4f;
y = base.Y + 8f;
xSpeed = base.transform.localScale.x * 450;
ySpeed = UnityEngine.Random.Range( 15, 50 );
EffectsController.CreateMuzzleFlashEffect( x, y, -25f, xSpeed * 0.15f, ySpeed, base.transform );
flare = ProjectileController.SpawnProjectileLocally( flarePrefab, this, x, y, xSpeed, ySpeed, base.playerNum );
}
this.gunFrame = 3;
this.PlayFlareSound( base.transform.position );
this.fireDelay = flaregunDelay;
}
}
RunGun()
is called every update to handle gun sprite animations. It decrements gunFrame
over time when > 0, calling SetGunSprite()
to update the sprite. You can override this to change any aspect of how your gun sprite is displayed.
Example:
You could use it to create new animations (as long as they're on your bro's gun sprite):
protected override void RunGun()
{
// Placing explosives
if ( this.stealthActive && this.currentExplosives.Count < MaxExplosives )
{
if ( this.gunFrame > 0 )
{
this.gunCounter += this.t;
if ( this.gunCounter > 0.0334f )
{
this.gunCounter -= 0.0334f;
this.gunFrame--;
this.SetGunSprite( this.gunFrame, 0 );
}
}
else
{
this.SetGunSprite( 0, 0 );
}
}
else
{
// Normal animation
}
}
protected override void RunGun()
{
// Other code
// Animate flaregun
else if ( this.currentState == PrimaryState.FlareGun )
{
if ( this.gunFrame > 0 )
{
this.gunCounter += this.t;
if ( this.gunCounter > 0.0334f )
{
this.gunCounter -= 0.0334f;
--this.gunFrame;
}
}
this.SetGunSprite( this.gunFrame, 0 );
}
// Animate switching
else
{
this.gunCounter += this.t;
if ( this.gunCounter > 0.07f )
{
this.gunCounter -= 0.07f;
++this.gunFrame;
if ( this.gunFrame == 3 )
{
this.PlaySwapSound( base.transform.position );
}
}
if ( this.gunFrame > 5 )
{
this.SwitchWeapon();
}
else
{
this.SetGunSprite( 25 + this.gunFrame, 0 );
}
}
}
SetGunSprite()
updates the current frame of the gun sprite. It checks if you're hanging from a zipline, and if so uses the gunSpriteHangingFrame
to offset the current frame to the one-handed animations on your gun sprite instead. You may need to change gunSpriteHangingFrame
to a different value depending on where you have these animations on your sprite sheet. It defaults to 6.
In most cases you won't need to override this method, and it's generally a good idea to use it rather than calling SetLowerLeftPixel()
on gunSprite manually like so:
this.gunSprite.SetLowerLeftPixel((float)(this.gunSpritePixelWidth * spriteFrame), (float)(this.gunSpritePixelHeight * (1 + spriteRow)));
because SetGunSprite()
has the additional handling for checking if you're on a zipline built-in.
Parameters:
-
spriteFrame
- Horizontal frame index on the gun sprite sheet (starts from 0) -
spriteRow
- Vertical row index on the gun sprite sheet (starts from 0)
ActivateGun()
and DeactivateGun()
control whether the gun sprite is currently visible. Usually you won't need to override these, you can just call them as needed to show / hide the gun sprite, but you may want to override them if you want to ensure the gun sprite isn't activated / deactived during any new animations you've created.
SetGunPosition()
positions the gun sprite relative to your bro. You shouldn't need to override this or call it generally. If you want to adjust the values for the offset, you can use the GunSpriteOffset parameter rather than overriding this method.
Parameters:
-
xOffset, yOffset
- Offset relative to your bro
The melee system follows this call hierarchy:
- Player presses melee →
StartMelee()
is called -
StartMelee()
calls →StartKnifeMelee()
orStartPunch()
orStartCustomMelee()
, depending on your bro'smeleeType
-
StartCustomMelee()
checks that a melee can be performed, if so, it performs melee initialization, and setsdoingMelee
to true. - While
doingMelee
is true →AnimateCustomMelee()
is called every frame change, andRunCustomMeleeMovement()
is called every update -
AnimateCustomMelee()
calls →AnimateKnifeMelee()
by default, but you'll end up completely overriding this though for your custom melee. -
AnimateKnifeMelee()
calls →PerformKnifeMeleeAttack()
when it reaches the damage frame (3 by default). -
AnimateKnifeMelee()
calls →CancelMelee()
when melee reaches final frame, or if cancelled early. -
CancelMelee()
resets melee variables and setsdoingMelee
to false.
StartMelee()
is called when the melee button is pressed. It sets currentMeleeType
to meleeType
, which determines what type of melee you perform. There are only 3 types of melees really, BroBase.MeleeType.Knife
, BroBase.MeleeType.Punch
/ BroBase.MeleeType.JetpackPunch
, and then all others. If you want to have a custom melee, you can set meleeType
to anything other than those three melee types that I listed, in which case StartMelee()
will call StartCustomMelee
. If you do that you shouldn't need to override this method, you can just override StartCustomMelee()
instead.
Note:
This method also checks if you are standing on a pig, in which case it ignores meleeType
, and sets currentMeleeType
to BroBase.MeleeType.Knife
instead. So keep in mind that you may still want to keep an animation on your bro's spritesheet for the knife melee, since all bros are capable of performing it. Or you can override this function to make it impossible to perform a knife melee, but then you won't be able to pig surf.
StartCustomMelee()
is called by StartMelee()
if your bro's meleeType
is something other than Knife, Punch, or JetpackPunch. It checks that a melee can be performed, and if so performs melee initialization and sets doingMelee to true
. You can override this if you need to initialize any additional variables related to your custom melee
Note: For custom melees to work, set this.meleeType
in Start()
to something other than BroBase.MeleeType.Knife
, BroBase.MeleeType.Punch
, or BroBase.MeleeType.JetpackPunch
.
AnimateCustomMelee()
is called every frame change during custom melee attacks. It defaults to calling AnimateKnifeMelee()
. You can override this to handle custom melee animations. You'll also want to handle the damage logic in this method for whatever damage / effect you want your melee to do.
Examples:
protected override void AnimateCustomMelee()
{
this.AnimateMeleeCommon();
// Release Slimer
if ( this.usingSlimerMelee )
{
base.frameRate = 0.06f;
int num = 11 + base.frame;
this.sprite.SetLowerLeftPixel( (float)( num * this.spritePixelWidth ), (float)( 9 * this.spritePixelHeight ) );
if ( base.frame == 0 && !this.playedSlimerAudio )
{
this.slimerPortalSource = this.sound.PlaySoundEffectAt( this.slimerTrapOpen, 0.4f, base.transform.position, 1f, true, false, true, 0f );
this.playedSlimerAudio = true;
}
else if ( base.frame == 8 && !this.alreadySpawnedSlimer )
{
base.counter -= 0.066f;
SpawnSlimer();
}
else if ( base.frame >= 9 )
{
base.frame = 0;
this.CancelMelee();
}
}
// Proton Bash
else
{
if ( !this.throwingMook )
{
base.frameRate = 0.04f;
}
int num = 25 + Mathf.Clamp( base.frame, 0, 6 );
int num2 = 1;
if ( !this.standingMelee )
{
if ( this.jumpingMelee )
{
num = 17 + Mathf.Clamp( base.frame, 0, 6 );
num2 = 6;
}
else if ( this.dashingMelee )
{
num = 17 + Mathf.Clamp( base.frame, 0, 6 );
num2 = 6;
if ( base.frame == 4 )
{
base.counter -= 0.0334f;
}
else if ( base.frame == 5 )
{
base.counter -= 0.0334f;
}
}
}
this.sprite.SetLowerLeftPixel( (float)( num * this.spritePixelWidth ), (float)( num2 * this.spritePixelHeight ) );
if ( base.frame == 3 )
{
base.counter -= 0.066f;
this.MeleeAttack( true, true );
}
else if ( base.frame > 3 && !this.meleeHasHit )
{
this.MeleeAttack( false, false );
}
if ( base.frame >= 6 )
{
base.frame = 0;
this.CancelMelee();
}
}
}
Here's what a melee attack could look like:
protected void MeleeAttack( bool shouldTryHitTerrain, bool playMissSound )
{
bool flag;
Map.DamageDoodads( 3, DamageType.Knock, base.X + (float)( base.Direction * 4 ), base.Y, 0f, 0f, 6f, base.playerNum, out flag, null );
this.KickDoors( 24f );
if ( Map.HitClosestUnit( this, base.playerNum, 4, DamageType.Knock, 14f, 24f, base.X + base.transform.localScale.x * 8f, base.Y + 8f, base.transform.localScale.x * 200f, 500f, true, false, base.IsMine, false, true ) )
{
this.sound.PlaySoundEffectAt( this.soundHolder.alternateMeleeHitSound, 1f, base.transform.position, 1f, true, false, false, 0f );
this.meleeHasHit = true;
}
else if ( playMissSound )
{
this.sound.PlaySoundEffectAt( this.soundHolder.missSounds, 0.3f, base.transform.position, 1f, true, false, false, 0f );
}
this.meleeChosenUnit = null;
if ( shouldTryHitTerrain && this.TryMeleeTerrain( 0, 2 ) )
{
this.meleeHasHit = true;
}
this.TriggerBroMeleeEvent();
}
RunCustomMeleeMovement()
is called every update during custom melees to control movement. It defaults to calling RunKnifeMeleeMovement()
. You can override this if you want to change how movement works during your custom melee. I'd recommend starting with the code from RunKnifeMeleeMovement()
and making minor adjustments to it as needed.
CancelMelee()
is called when the melee animation finishes, or if it's interrupted by something. It resets all melee variables and returns to the appropriate action state. You can override this if you have additional variables or things you need to clean up after your custom melee.
Example:
protected override void CancelMelee()
{
// Stop portal sound if melee was cancelled before slimer spawned
if ( this.slimerPortalSource != null && !this.alreadySpawnedSlimer && this.usingSlimerMelee )
{
this.slimerPortalSource.Stop();
}
base.CancelMelee();
this.usingSlimerMelee = false;
}
The special ability system follows this pattern:
- Player presses special →
PressSpecial()
called (if not using a pocketted special) and it setsusingSpecial
to true - While
usingSpecial
is true →AnimateSpecial()
is called each frame change -
AnimateSpecial()
calls →UseSpecial()
when it reaches the frame where the grenade should spawn (2 by default) -
UseSpecial()
checks if the player has enoughSpecialAmmo
, if so → spawns thespecialGrenade
and subtracts 1 fromSpecialAmmo
PressSpecial()
is called when the special button is first pressed, unless you have pocketted special ammo to use first. It checks that you're not covered in acid or doing a melee, and if so, sets usingSpecial
to true and resets frame
to 0. You may want to override this if you'd prefer to have the special animation not play out if you are out of special ammo. By default, the special ammo check happens in UseSpecial()
rather than PressSpecial()
, so the special animation will always play out, and if you don't have special ammo, UseSpecial()
will just flash the special icons red instead.
This method is also useful to override in case you just want to add additional variables to your special that need initializing.
AnimateSpecial()
handles the animation for special abilities. It's called each frame change, and it calls UseSpecial()
at the appropriate frame to spawn the special grenade. It sets usingSpecial
to false when the animation has finished. You can override this to change the number of frames in your special animation, change the framerate, or change what frame UseSpecial()
is called on.
Example:
protected override void AnimateSpecial()
{
this.SetSpriteOffset( 0f, 0f );
this.DeactivateGun();
this.invulnerable = true;
this.invulnerableTime = 0.3f;
// Animate drinking to become drunk
if ( !this.wasDrunk )
{
this.frameRate = 0.1f;
this.sprite.SetLowerLeftPixel( (float)( this.usingSpecialFrame * this.spritePixelWidth ), (float)( this.spritePixelHeight * 8 ) );
if ( this.usingSpecialFrame < 10 && this.IsWalking() )
{
this.speed = 0f;
}
else
{
this.speed = this.originalSpeed;
}
if ( this.usingSpecialFrame == 4 )
{
this.frameRate = 0.35f;
this.PlayDrinkingSound();
}
else if ( this.usingSpecialFrame == 6 )
{
this.frameRate = 0.15f;
this.UseSpecial();
}
else if ( this.usingSpecialFrame >= 7 )
{
this.frameRate = 0.2f;
this.StopUsingSpecial();
return;
}
this.usingSpecialFrame++;
}
}
UseSpecial()
checks that you have enough SpecialAmmo
, and if so, it executes the special ability. By default, it spawns whatever grenade you have set in specialGrenade
. If you don't have enough SpecialAmmo
, it flashes the special icons red. You can override this to change what happens when your special activates, if you'd rather have it be some sort of buff, some other type of projectile, or anything.
Examples:
protected override void UseSpecial()
{
if ( this.SpecialAmmo > 0 && this.specialGrenade != null )
{
this.SpecialAmmo--;
// Only clear reference, don't destroy existing War Rigs
this.ClearCurrentWarRig();
this.currentWarRig = UnityEngine.Object.Instantiate<WarRig>( warRigPrefab, DetermineWarRigSpawn(), Quaternion.identity );
this.currentWarRig.SetTarget( this, base.X + base.transform.localScale.x * 10f, new Vector3( base.transform.localScale.x, this.currentWarRig.transform.localScale.y, this.currentWarRig.transform.localScale.z ), base.transform.localScale.x );
this.currentWarRig.gameObject.SetActive( true );
if ( this.special )
{
this.holdingSpecial = true;
this.holdingSpecialTime = 0f;
}
}
else
{
HeroController.FlashSpecialAmmo( base.playerNum );
this.ActivateGun();
}
this.pressSpecialFacingDirection = 0;
}
If you have questions or need help with creating custom bros, you can join the Free Lives Discord Server and post your questions in the bf-mods channel.