Creating Your First Custom Ability - digocorbellini/EGaDS-Open-Project GitHub Wiki

Introduction

Hey contributor! This page will guide you through creating a movement ability for the game using the Ability System. Before trying this out on your own, please clone the project onto your computer and open it up in Unity. Once you have gone through this guide, be sure to also check out the main documentation for the ability system for more detailed info and a typical workflow!

In this tutorial, we will more specifically be creating an ability that allows the player to rise upwards for a certain amount of time by the press of a button. Whether or not this ability would actually be incorporated into the game is up to you contributors to figure out!

The Tutorial

1. Create your ability script

In Unity's project navigation panel, navigate to the Assets/Scripts/Abilities/ directory. This is where all custom abilities of the game are stored. Right click, then select Create > C# Script. Rename the new file to something like MyAbility, but in general naming conventions should follow: <AbilityName>Ability.

2. Set up your ability class

Double click on the file you just created in Unity to open it up in your text editor. You should see something like the following:

public class MyAbility : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Now, change MonoBehaviour into either TriggeredAbility or PassiveAbility, depending on which type of ability you will be creating. A triggered ability is one triggered by the press of a key, while a passive ability does not require any input from the player. For more information about the 2 types of abilities, check here. Since our ability for this tutorial, which allows the player to rise upwards, requires player input, we will be creating a triggered ability.

Secondly, since abilities are ScriptableObjects, they can be added to the project as an asset file. To allow for this, add [CreateAssetMenu(menuName = "Abilities/MyAbility")] above the class declaration.

And finally, since we do not need the Start() and Update() methods, you may delete those. Your resulting ability class should look like the following:

[CreateAssetMenu(menuName = "Abilities/MyAbility")]
public class MyAbility : TriggeredAbility
{
    
}

3. Set up useful fields

For our ability, we will be needing the following field variables:

[SerializeField]
private float _riseHeight = 8.0f; // in tiles

[SerializeField]
private float _riseSpeed = 5.0f; // in tiles per second

[SerializeField]
private float _cooldownTime = 0.5f; // in seconds

private bool _isRising;
private bool _isInCooldown;
private float _timer;
private float _savedFallSpeedMultiplier;

_riseHeight, _riseSpeed, and _cooldownTime are the settings for the ability, which would be able to be adjusted in the inspector panel in Unity (later in this tutorial) due to [SerializeField]. _isRising, _isInCooldown, _timer, and _savedFallSpeedMultiplier will be used by our script's logic in the next steps.

4. Create start logic

With the ability system, a player can remove an ability and add it back later. Due to this, it is recommended to use the Start() method, which is run when the player obtains the ability, to reset any fields:

// Inside MyAbility class
public override void AbilityStart(PlayerComponents player)
{
	_isRising = false;
	_isOnCooldown = false;
	_timer = -1;
}

5. Activate the ability

Since we want to activate the ability by a key press, we can check for keyboard input every frame using the AbilityUpdate() method, which is run every frame while the player holds the ability.

Next, we can use the GetKeyDown() method provided by the TriggeredAbility class (not available in PassiveAbility) to check if the player just started pressing down the desired key for our ability in the current frame. For more information on the input system for abilities, check here.

Once we detect a key press, we should perform some logic:

  • Set _isRising to true to mark that the ability has been activated.
  • Call player.abilityManager.AcquireFocus(this) to block the player from activating any other ability. For more info on focus, check here.
  • Set FallSpeedMultiplier to 0 so the script responsible for causing the player to fall doesn't interfere with our ability. For a list of the different modifiers abilities have access to, check the Ability class's list of properties.
  • Start a timer so we know when to stop the ability

Our code should now look like this:

// inside MyAbility class
public override void AbilityUpdate(PlayerComponents player)
{
	// also check that _isRising is false so player can't activate 
	// the ability while it is still running
	if (!_isRising && GetKeyDown())
	{
		// AcquireFocus() returns false if focus failed to be acquired, such as when 
		// another ability is in focus and doesn't want your ability to be activated
		if (player.abilityManager.AcquireFocus(this))
		{
			_isRising = true;

			// modifiers might have been set in the inspector, so you should save it
			// so you know what to set it back to after the ability finishes
			_savedFallSpeedMultiplier = FallSpeedMultiplier;
			FallSpeedMultiplier = 0;

			_timer = _riseHeight / _riseSpeed; // start the timer
		}
	}
}

6. Apply effects on player and deactivate the ability

Making the player rise is pretty simple. We can just use the player's transform.

We should also decrement the timer we set in the previous step, and perform some more logic to deactivate the ability:

  • Set _isRising to false
  • Reset FallSpeedMultiplier
  • Unaquire focus with player.abilityManager.UnaquireFocus(this)

Inside AbilityUpdate(), we should now also have this:

// inside AbilityUpdate()
if (_isRising)
{
	// move player up (don't forget to scale the speed with deltaTime!)
	player.transform.position += new Vector3(0, _riseSpeed*Time.deltaTime);

	_timer -= Time.deltaTime; // progress the timer
	
	if (_timer < 0) // deactivate the ability
	{
		if (player.abilityManager.UnacquireFocus(this))
		{
			_isRising = false;
			FallSpeedMultiplier = _savedFallSpeedMultiplier;
		}
	}
}

7. Create a cooldown

Of course, we don't want the player to keep spamming the rise key to continuously go up all the way into the sky! One way we can prevent this is by adding a cooldown system. To do this, we can use the same _timer we used for ability deactivation for this:

// in AbilityUpdate()
// check that ability isn't in cooldown
if (!_isInCooldown && !_isRising && GetKeyDown() && player.abilityManager.AcquireFocus(this))
{
	_isRising = true;
	_savedFallSpeedMultiplier = FallSpeedMultiplier;
	FallSpeedMultiplier = 0;

	_timer = _riseHeight / _riseSpeed;
}
if (_isRising)
{
	player.transform.position += new Vector3(0, _riseSpeed*Time.deltaTime);

	_timer -= Time.deltaTime;
	if (_timer < 0 && player.abilityManager.UnacquireFocus(this))
	{
		_isRising = false;
		FallSpeedMultiplier = _savedFallSpeedMultiplier;

		// start cooldown
		_isInCooldown = true;
		_timer = _cooldownTime;
	}
}
if (_isInCooldown)
{
	// progress timer and end cooldown
	_timer -= Time.deltaTime;
	if (_timer < 0)
	{
		_isInCooldown = false;
	}
}

8. Create ending logic

Since an ability could be removed from a player at any time, you should perform logic when it is removed, so things don't break with the player. To do this, use the AbilityEnd() method, which is called when the player loses the ability:

// Inside MyAbility class
public override void AbilityEnd(PlayerComponents player)
{
	if (_isRising)
	{
		// allow the player to start falling again
		FallSpeedMultiplier = _savedFallSpeedMultiplier;
		player.abilityManager.UnacquireFocus(this);
	}
}

9. Create an ability asset

Now you're all done with the programming! Go back to Unity (again into the Abilities directory), right click, and select Create > Abilities > MyAbility. Now rename the new asset to a name of your choosing such as "MyAbility". Click on the asset, and look at the inspector panel. You should see something like this:

You can now edit some of the values. Those values will be saved for the asset.

10. Add the ability to the player

For now, to add your ability to the player, we have created a script called AbilityAdder (found in Assets/Scripts/AbilitySystem/Example/). Simply add this script to the player as a component, then select your ability asset in the inspector. When the game starts, you should be able to test your ability.

If you want to add your ability to the player programmatically:

  1. Get a reference to the AbilityManager component on the player
  2. Create a SerializeField ability so you can get a reference to your ability through the inspector
  3. Call abilityManager.SetTriggeredAbilityCount(count) or abilityManager.setPassiveAbilityCount(count) to make sure there is enough space to add an ability
  4. Call abilityManager.AddAbility(ability, index) (make sure index < count)

Conclusion

Congrats! You have created your first ability! As a quick reminder, please do not commit your ability or any changes you made in the project related to it if you are only trying things out or testing.

If you would like to see the the finished product of this tutorial, check out Assets/Scripts/AbilitySystem/Example/. You're welcome to play around with it, just try not to commit your changes!

Also, again, check out here for a more detailed and general workflow!

⚠️ **GitHub.com Fallback** ⚠️