Tactics RPG Ability Effects

Over the past several lessons we have implemented tempory implementations for the effect of an ability, such as manually implementing an attack damage estimation and application of the damage itself. In this lesson we will be providing a much more complete implementation which allows for very different effects ranging from applying damage to inflicting status ailments and which supports just about anything you would want to do.

Bugs & Fixes

There are a few bug fixes in this check-in, several of which were pointed out by my readers – thanks!

  • TransformAnimationExtensions – was referencing MoveTo in the ScaleTo overloaded methods.
  • Point – should implement IEquatable in order to use the correct Equals method
  • BlindStatusEffect – needed to inherit from StatusEffect

Refactoring

I did a little refactoring which I don’t plan to cover in detail since you can refer to the code in the repository, but I want to point it out. In HitRate I modified the signature of the Calculate method.

// This was the old signature
public abstract int Calculate (Unit attacker, Unit target);

// This is the new signature
public abstract int Calculate (Tile target);

I decided I didn’t need to pass the attacker parameter because the “ability” itself will always be in the hierarchy of its own Unit and therefore can get (and optionally cache) its own reference without me needing to pass it along. I changed the target parameter from a Unit to a Tile because you can get a reference to the unit which occupies a tile from the tile, but there might be occasions where I actually want an ability to have an effect which works with something that isn’t a Unit. For example, maybe one ability allows you to place traps on empty tiles. I wanted this idea to be reflected through all of the ability related code.

Base Ability Effect

Let’s begin by creating a new abstract base class called BaseAbilityEffect and place it in a Scripts/View Model Component/Ability/Effects/ directory. This script will provide two functions – it will finish filling out the second part of our Hit Success Indicator with a damage “prediction” and it will provide an interface through which we can apply whatever it is that the ability actually does.

using UnityEngine;
using System.Collections;

public abstract class BaseAbilityEffect : MonoBehaviour
{
	public abstract int Predict (Tile target);
	public abstract void Apply (Tile target);
}

Damage Ability Effect

Our first concrete subclass will be used in many abilities, and is one which applies damage to a targeted unit. Create a script named DamageAbilityEffect and place it in the same folder as the base class.

I created this script while referencing the “Final Fantasy Tactics Advance: Mechanics Guide” by TFergusson so that I felt fairly confident I was going to be able to support a very complex damage algorithm (not that one is always needed). The algorithm in that guide is twenty steps long, with mutliple sub-steps in most of them. I don’t specifically outline each step of the process, but because of the way I architected this component, you could include each of those steps and more.

The damage algorithm starts with the Predict method. The Apply method actually uses the value calculated by the prediction and then merely adds a little random variance. The final calculated value is subtracted from the target’s hit points.

The “trick” to supporting large amounts of control in this algorithm is in the use of notifications which pass along a list of Value Modifiers in an argument. Any object can listen to those notifications and add a modifier at any step thanks to the sorting order ability of the modifier objects. The great thing is that my script doesn’t need to know about any of the objects which create special cases of value modification. For example, if I got around to implementing a feature where you carry along mission items, and by possessing a mission item you got an offensive bonus, then I can allow that mission item to listen for the appropriate notification, insert a value modifer into the list, and I am done. I can add (or remove) these kinds of little rules to my hearts content all without ever needing to modify this script in any way.

As an example, when I want to get the “base attack” stat of the attacker, I call my “GetStat” method. There I create an Info object which holds relevant information such as who the attacker and defender are, as well as that List of Value Modifiers I mentioned. The “GetAttackNotification” will be posted along with the info object. Any object which listens to the notification can then insert new modifiers into the list. Next, the list of value modifiers are sorted and applied to a value.

The very first modifier in the list would be the true “base attack stat” which would come from either the physical or magical attack stat of the unit, depending on what kind of ability is being used. It would be “added” to the formula with an “Add Value Modifier” to take the stat from zero to whatever it should be. Most other changes would multiply the final result with a “Mult Value Modifier”. Some of these modifiers could include:

  1. Mission item bonus
  2. Support checks like Turbo MP or Doublehand
  3. Status checks like Berserk or Boost
  4. Equipment checks

The same basic idea is utilized in each step of the process. When we get the defense stat for example, we can also check Mission Items, Support checks and Status checks, but keep in mind that each implementor would be different based on whether the attacker or defender has a particular status. For example, if the attacker is under Frog status then the attack would become weaker as a result of reducing the attack stat, but if the defender is under Frog status then the attack would become stronger as a result of reducing the defense stat.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DamageAbilityEffect : BaseAbilityEffect 
{
	#region Consts & Notifications
	public const string GetAttackNotification = "DamageAbilityEffect.GetAttackNotification";
	public const string GetDefenseNotification = "DamageAbilityEffect.GetDefenseNotification";
	public const string GetPowerNotification = "DamageAbilityEffect.GetPowerNotification";
	public const string TweakDamageNotification = "DamageAbilityEffect.TweakDamageNotification";

	const int minDamage = -999;
	const int maxDamage = 999;
	#endregion

	#region Public
	public override int Predict (Tile target)
	{
		Unit attacker = GetComponentInParent<Unit>();
		Unit defender = target.content.GetComponent<Unit>();

		// Get the attackers base attack stat considering
		// mission items, support check, status check, and equipment, etc
		int attack = GetStat(attacker, defender, GetAttackNotification, 0);

		// Get the targets base defense stat considering
		// mission items, support check, status check, and equipment, etc
		int defense = GetStat(attacker, defender, GetDefenseNotification, 0);

		// Calculate base damage
		int damage = attack - (defense / 2);
		damage = Mathf.Max(damage, 1);

		// Get the abilities power stat considering possible variations
		int power = GetStat(attacker, defender, GetPowerNotification, 0);

		// Apply power bonus
		damage = power * damage / 100;
		damage = Mathf.Max(damage, 1);

		// Tweak the damage based on a variety of other checks like
		// Elemental damage, Critical Hits, Damage multipliers, etc.
		damage = GetStat(attacker, defender, TweakDamageNotification, damage);

		// Clamp the damage to a range
		damage = Mathf.Clamp(damage, minDamage, maxDamage);
		return damage;
	}
	
	public override void Apply (Tile target)
	{
		Unit defender = target.content.GetComponent<Unit>();

		// Start with the predicted damage value
		int value = Predict(target);

		// Add some random variance
		value *= Mathf.FloorToInt(UnityEngine.Random.Range(0.9f, 1.1f));

		// Clamp the damage to a range
		value = Mathf.Clamp(value, minDamage, maxDamage);

		// Apply the damage to the target
		Stats s = defender.GetComponent<Stats>();
		s[StatTypes.HP] -= value;
	}
	#endregion
	
	#region Private
	int GetStat (Unit attacker, Unit target, string notification, int startValue)
	{
		var mods = new List<ValueModifier>();
		var info = new Info<Unit, Unit, List<ValueModifier>>(attacker, target, mods);
		this.PostNotification(notification, info);
		mods.Sort();

		float value = startValue;
		for (int i = 0; i < mods.Count; ++i)
			value = mods[i].Modify(startValue, value);

		int retValue = Mathf.FloorToInt(value);
		retValue = Mathf.Clamp(retValue, minDamage, maxDamage);
		return retValue;
	}
	#endregion
}

Inflict Ability Effect

I wanted to show a variation in ability effects so the second effect I created doesn’t do any damage at all, it merely applies a status effect. Create a new script named InflictAbilityEffect in the same folder as the base class.

In the Status Effects post I discussed some architectural ideas for implementing an “Add Status Feature” which is quite similar to an “Inflict Ability Effect”. In that post I used a generic implementation which would require a new subclass for every type of status we would want to add. Now that I am doing almost the exact same thing with Ability Effects, I feel that the “explosion of classes” problem I was worried about is a definite issue. This time I decided to go ahead and try the reflection route. I begin with the use of a string (since it is serializeable and you can use it in the editor) which maps to a Type of StatusEffect. I used some error handling to verify that we both have a legitimate class type and that the type is also a subclass of StatusEffect. If both of those are true, I am able to generate and invoke the appropriate Generic Method dynamically. I’m still not crazy about reflection, but at least I can reuse this single component for any StatusEffect that I might want to add.

using UnityEngine;
using System.Collections;
using System;
using System.Reflection;

public class InflictAbilityEffect : BaseAbilityEffect 
{
	public string statusName;
	public int duration;

	public override int Predict (Tile target)
	{
		return 0;
	}

	public override void Apply (Tile target)
	{
		Type statusType = Type.GetType(statusName);
		if (statusType == null || !statusType.IsSubclassOf(typeof(StatusEffect)))
		{
			Debug.LogError("Invalid Status Type");
			return;
		}

		MethodInfo mi = typeof(Status).GetMethod("Add");
		Type[] types = new Type[]{ statusType, typeof(DurationStatusCondition) };
		MethodInfo constructed = mi.MakeGenericMethod(types);

		Status status = target.content.GetComponent<Status>();
		object retValue = constructed.Invoke(status, null);

		DurationStatusCondition condition = retValue as DurationStatusCondition;
		condition.duration = duration;
	}
}

Base Ability Power

When we created the Damage Ability Effect, I mentioned ability “power” and also created several notifications that nothing listens to yet. Create a new script named BaseAbilityPower and place it in the Scripts/View Model Component/Ability/Power/ directory.

This base class implementation will handle subscribing to the various notifications, and in the handler methods will add value modifiers with values based on abstract methods which will need to be implemented by the concrete subclasses.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public abstract class BaseAbilityPower : MonoBehaviour
{
	protected abstract int GetBaseAttack ();
	protected abstract int GetBaseDefense (Unit target);
	protected abstract int GetPower ();

	void OnEnable ()
	{
		this.AddObserver(OnGetBaseAttack, DamageAbilityEffect.GetAttackNotification);
		this.AddObserver(OnGetBaseDefense, DamageAbilityEffect.GetDefenseNotification);
		this.AddObserver(OnGetPower, DamageAbilityEffect.GetPowerNotification);
	}

	void OnDisable ()
	{
		this.RemoveObserver(OnGetBaseAttack, DamageAbilityEffect.GetAttackNotification);
		this.RemoveObserver(OnGetBaseDefense, DamageAbilityEffect.GetDefenseNotification);
		this.RemoveObserver(OnGetPower, DamageAbilityEffect.GetPowerNotification);
	}

	void OnGetBaseAttack (object sender, object args)
	{
		var info = args as Info<Unit, Unit, List<ValueModifier>>;
		if (info.arg0 != GetComponentInParent<Unit>())
			return;

		AddValueModifier mod = new AddValueModifier(0, GetBaseAttack());
		info.arg2.Add( mod );
	}

	void OnGetBaseDefense (object sender, object args)
	{
		var info = args as Info<Unit, Unit, List<ValueModifier>>;
		if (info.arg0 != GetComponentInParent<Unit>())
			return;
		
		AddValueModifier mod = new AddValueModifier(0, GetBaseDefense(info.arg1));
		info.arg2.Add( mod );
	}

	void OnGetPower (object sender, object args)
	{
		var info = args as Info<Unit, Unit, List<ValueModifier>>;
		if (info.arg0 != GetComponentInParent<Unit>())
			return;
		
		AddValueModifier mod = new AddValueModifier(0, GetPower());
		info.arg2.Add( mod );
	}
}

Physical Ability Power

Create a new script named PhysicalAbilityPower in the same directory as the base class. This script will (obviously) provide the implementation for abilities which are physical in nature. It references the Attack stat of the attacker and the Defense stat of the defender. It also holds an int field called level which can be different for each ability. Weak units performing a strong ability might still do a lot of damage in this way. Along the same vein, a unit can have multiple abilities that might do different amounts of damage thanks to the power “level”, even though the attack stat hasn’t changed.

using UnityEngine;
using System.Collections;

public class PhysicalAbilityPower : BaseAbilityPower 
{
	public int level;
	
	protected override int GetBaseAttack ()
	{
		return GetComponentInParent<Stats>()[StatTypes.ATK];
	}

	protected override int GetBaseDefense (Unit target)
	{
		return target.GetComponent<Stats>()[StatTypes.DEF];
	}
	
	protected override int GetPower ()
	{
		return level;
	}
}

Magical Ability Power

The magical ability power is very much like the physical one, but it uses the magic attack and magic defense stats instead:

using UnityEngine;
using System.Collections;

public class MagicalAbilityPower : BaseAbilityPower
{
	public int level;

	protected override int GetBaseAttack ()
	{
		return GetComponentInParent<Stats>()[StatTypes.MAT];
	}

	protected override int GetBaseDefense (Unit target)
	{
		return target.GetComponent<Stats>()[StatTypes.MDF];
	}

	protected override int GetPower ()
	{
		return level;
	}
}

Weapon Ability Power

For a third and final variation of the ability power types, I will cause the power level to be determined by the equipped weapon. If there is no weapon equipped, it will fallback to using the default attack strength of whatever job the unit has taken.

using UnityEngine;
using System.Collections;

public class WeaponAbilityPower : BaseAbilityPower 
{
	protected override int GetBaseAttack ()
	{
		return GetComponentInParent<Stats>()[StatTypes.ATK];
	}

	protected override int GetBaseDefense (Unit target)
	{
		return target.GetComponent<Stats>()[StatTypes.DEF];
	}
	
	protected override int GetPower ()
	{
		int power = PowerFromEquippedWeapon();
		return power > 0 ? power : UnarmedPower();
	}

	int PowerFromEquippedWeapon ()
	{
		int power = 0;
		Equipment eq = GetComponentInParent<Equipment>();
		Equippable item = eq.GetItem(EquipSlots.Primary);
		StatModifierFeature[] features = item.GetComponentsInChildren<StatModifierFeature>();

		for (int i = 0; i < features.Length; ++i)
		{
			if (features[i].type == StatTypes.ATK)
				power += features[i].amount;
		}
		
		return power;
	}

	int UnarmedPower ()
	{
		Job job = GetComponentInParent<Job>();
		for (int i = 0; i < Job.statOrder.Length; ++i)
		{
			if (Job.statOrder[i] == StatTypes.ATK)
				return job.baseStats[i];
		}
		return 0;
	}
}

I added a new method to the Equipment script in order to easily determine what the primary item would be:

public Equippable GetItem (EquipSlots slots)
{
	for (int i = _items.Count - 1; i >= 0; --i)
	{
		Equippable item = _items[i];
		if ( (item.slots & slots) != EquipSlots.None )
			return item;
	}
	return null;
}

Confirm Ability Target State

Open the ConfirmAbilityTargetState. Add a new field to the class:

AbilityEffectTarget[] targeters;

and use that field in the FindTargets method instead of the local declaration. I removed the CalculateHitRate and EstimateDamage methods and modified the UpdateHitSuccessIndicator so that I am actually using the correct HitRate and Damage prediction based on what is actually able to be targeted by an ability effect.

void UpdateHitSuccessIndicator ()
{
	int chance = 0;
	int amount = 0;
	Tile target = turn.targets[index];

	for (int i = 0; i < targeters.Length; ++i)
	{
		if (targeters[i].IsTarget(target))
		{
			HitRate hitRate = targeters[i].GetComponent<HitRate>();
			chance = hitRate.Calculate(target);

			BaseAbilityEffect effect = targeters[i].GetComponent<BaseAbilityEffect>();
			amount = effect.Predict(target);
			break;
		}
	}

	hitSuccessIndicator.SetStats(chance, amount);
}

Perform Ability State

Finally, let’s open the PerformAbilityState script and replace the TemporaryAttackExample method with a more complete implementation. This time we aren’t hard-coding an ability effect but are letting the ability perform the effect for itself. It also takes into account the hit rate – it rolls a random number to see whether or not it actually hits.

void ApplyAbility ()
{
	BaseAbilityEffect[] effects = turn.ability.GetComponentsInChildren<BaseAbilityEffect>();
	for (int i = 0; i < turn.targets.Count; ++i)
	{
		Tile target = turn.targets[i];
		for (int j = 0; j < effects.Length; ++j)
		{
			BaseAbilityEffect effect = effects[j];
			AbilityEffectTarget targeter = effect.GetComponent<AbilityEffectTarget>();
			if (targeter.IsTarget(target))
			{
				HitRate rate = effect.GetComponent<HitRate>();
				int chance = rate.Calculate(target);
				if (UnityEngine.Random.Range(0, 101) > chance)
				{
					// A Miss!
					continue;
				}
				effect.Apply(target);
			}
		}
	}
}

Demo

At this point you should be able to build a nice variety of abilities. We will still need to add Elemental attributes and magic point costs in the future, as well as some constraints on the ability effects, but we have enough for a good demo already.

If I were to create an Ability, I would want to begin by adding an Empty GameObject as a child of the Unit which would be able to use it. I would name the GameObject the same name as the Ability (like you would see in the menu to select it). On this object I would add a component for the types of “Ability Power”, “Ability Range”, and “Ability Area”. When we add Magic Point costs and Elemental attributes I would also add those here.

Each effect of the ability (you can have multiple effects per ability) would be added to another child GameObject of the Ability GameObject. For example, to implement “Attack” I could add another GameObject named “Damage”. On this child object I would add a type of “Hit Rate”, “Effect Target”, and “Ability Effect” component. When we add constraints to the Ability Effect (like only adding a status effect if a previous effect hit, or implementing drain where you absorb the amount of hit points you attacked for) I would also add them here.

For now, let’s test out a pair of effects. Modify the Hero prefab’s “Attack” ability to cause Damage and Inflict Blind as follows:

Have one unit hit another. If the attack connects, there is a good chance the target will also have been blinded. You can tell whether or not it succeeded by using the Editor’s hierarchy pane to look at the targeted unit for a child GameObject named “BlindStatusEffect”. Once a unit has been blinded, it should have a very low chance of attacking another unit – particularly from the front. Check for a “miss” to see that our HitRate code is functioning. You can tell we missed if the hit points of the target haven’t changed.

Summary

In this lesson we implemented a couple of Ability Effects including the ability to apply damage to a target and the ability to inflict a status effect. We provided a means of powering abilities both by physical, magical, and weapon power. All of these pieces are components which can be mixed and matched to create a large variety of abilities.

When implementing the inflict status ability effect, we took a look at using Reflection as a way to work around an inability to use generics dynamically. In this way we were able to avoid an “explosion of classes” where we would need a new subclass for every kind of status effect we would wish to inflict.

Don’t forget that the project repository is available online here. If you ever have any trouble getting something to compile, or need an asset, feel free to use this resource.

47 thoughts on “Tactics RPG Ability Effects

  1. Hey, great tutorial again, thanks for your work!

    I have a question about Weapon Ability Power. I want to make sure I got this right, so when equipping an Equippable, its features will be applied, but the Weapon Ability Power increases the power by the amount of the increase of the ATK of the features of the primary weapon so that we could say that the increased ATK of the weapon is applied twice to the resulting power?

    I’m not sure I understand this correctly and if this is expected behaviour. Thanks for telling me if I’m wrong!

    1. The algorithms are admittedly a bit hard to keep straight. I think you understand but might have said it a little wrong. When considering the “Weapon Ability Power”, the ATK of the weapon would have already been added to the character’s ATK stat when evaluating his “BaseAttackStat” and would also be used as the “Power” stat. Those stats are different and are used at different places in the damage algorithm. So the ATK of the weapon would play a part twice during the damage calculation algorithm, but would not modify the “Power” stat twice.

      Each of the different component types have a “Power” value to use. Weapon based skills would be great while your character’s level was less than the attack value of your equipped weapon. At higher character levels it may turn the tables the other direction. Everything is designed in such a way as to require you to have to constantly change your approach and strategy so the gameplay is engaging for longer.

      1. Thanks for the clarification, I understand now!

        I also have a comment regarding Hitrate, I hope that asking it here isn’t a problem, if it is, you can move/delete my comment:

        In the ATypeHitRate, for example, we return and divide an int value by an int: “return rate / 2;”.
        Isn’t this not gonna work since dividing ints isn’t supposed to happen?
        I will try to use floats since facings aren’t really working how I expect them to in my code right now.

        Thanks for any help!

        1. There is nothing wrong with dividing integers as long as you understand that the result is floored. In other words “7/2” is not “3.5” but returns “3” instead. A lot of the algorithms I looked at chose to use integer division, and so I followed suit. I don’t think it will hurt anything to use better precision but it also wouldn’t surprise me, given the complexity of the algorithms, if even this small change could makes things feel wrong.

          What is wrong with the facings? Are you making something new based on my code or are you having a hard time implementing one of my examples?

  2. Ah I see, I had that feeling that it could be a problem but I was wrong.

    Since I’m using hexagon tiles, sometimes I have to change things a bit, but for facings, I get correct dot values when changing direction but the hitrate doesn’t change. It probably is something I overlooked when doing the hitrate lesson.

    Keep up the good work.

  3. Hey, i have problems just refactoring the Calculate method in the HitRate class. Because when it is override in the other classes. And the project directory doesn’t help because it doesn’t have the HitRate classes.

    1. Hey Draco, sorry you got stuck on the refactoring. I’m not sure I understand exactly what you are trying to say – it sounds like you can’t find the HitRate class in the project. The HitRate class was added in the previous lesson to this one (number 18 on the project page) so if you are missing it then you should go back a lesson and re-read it.

      Once you find it, then modify the function using the definition I provided in this post. When you build, it should produce errors, but those errors will tell you exactly which classes and even which lines need to be updated. If you are unable to complete the refactor on your own, then I would recommend trying to download the project with a source control program such as SourceTree. You can select the commit for this lesson and see exactly where code was changed and what it was changed to, etc.

      If none of that works for you, let me know (with as specific of a question as you can) and I will try to help some more.

  4. I just tried to demo this lesson today and got this error when I attacked:

    “TargetException: Non-static method requires a target.
    System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:236)
    System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MethodBase.cs:115)
    InflictAbilityEffect.Apply (.Tile target) (at Assets/Scripts/View Model Component/Ability/Effects/InflictAbilityEffect.cs:35)
    PerformAbilityState.ApplyAbility () (at Assets/Scripts/Controller/Battle States/PerformAbilityState.cs:64)
    PerformAbilityState+c__Iterator6.MoveNext () (at Assets/Scripts/Controller/Battle States/PerformAbilityState.cs:22)”

    I compared the repository scripts to my InflictAbilityEffect and PerformAbilityState scripts, and it looks like the only differences are several “return 0” you added to the InflictAbilityEffect (which caused even more errors) and a refactoring of the ApplyAbility function in PerformAbilityState (which I haven’t gotten to yet). It seems like it’s a problem with reflection, as when I remove the inflict blind component from the hero, I can attack just fine.

    1. It is worth pointing out that those “return 0” statements are very important. A difference in a line like that is definitely worth fixing. For example, I might use a return 0 to exit early in the case where I don’t have something I need in order to continue with the rest of the method. You can think of them as “guards” against allowing code to operate under false assumptions which can lead to crashes in the game, which might be exactly what you have experienced.

      Both of the classes you mentioned, “InflictAbilityEffect” and “PerformAbilityState”, receive some code updates in upcoming lessons. If you want to check your code against the code in the repository you need to make sure you are referring to the code at commit ‘aca7035’ and have not simply downloaded the full project. Otherwise you could expect to see a bunch of extra errors. If you aren’t sure how to do that or what I am talking about with commits etc, you are probably better off closely examining the code listed on the blog post here. If you remain stuck, let me know.

      1. Hey Jon,

        I’m getting a similar error too and I cant seem to get it right even though I’ve looked at the scripts at the correct commit. I’m not familiar with reflections so I don’t know whats wrong

        It seems that the problem starts in this bit:
        Status status = target.content.GetComponent();
        //Debug.Log(target.content);
        object retValue = constructed.Invoke(status, null);

        Where the status variable returned null. Is it supposed to be like this?

      2. Found my problem, apparently I missed the step of putting the Status and Equipment component in the ally Hero prefab. I put the missing pieces accordingly and it now works fine. Thanks for the hint!

  5. Ok, 2 weeks and I think I finally figured it out lol. I skipped to the next lesson after getting this error, and noticed that there were alot of code changes around this point in the tutorial, so it definitely gets really confusing. I was still getting errors and getting nowhere on the “Magic” tutorial, so I downloaded the repository. Had a problem opening that up, but finally figured out that I had to open the Battle scene, thanks to your comment in lesson 1 “Introduction.” Long story short, I think I found the 2 main issues causing my problems. First off, in the “Magic” tutorial you added an Ability script, but only when I looked through the final project’s Attack gameobject (GO) did I realize I needed to add an Ability component to the Attack GO on the hero prefab. Secondly, I noticed that in addition to the Unit component on the hero prefab, you also had Status and Equipment components, which either I glossed over, or it wasn’t implicit that they needed to be on the hero prefab, so I added them. The Status component seemed to be the cause of the “TargetException: Non-static method requires a target” error, as when I added it everything worked, and when I removed it I got that error again. Finally, the attack range and attack area of effect are too big for a normal attack, which for a melee unit would be 1 Tile over, with that Tile being the area of effect, so I changed Constant Ability Range to x=1 and y=max int, and Specify Ability Area to x=0 and y=1. I hope this helps any future readers as it was very frustrating for me when I couldn’t get it to work initially.

  6. I noticed that after the first successful attack, the BlindStatusEffect is applied to all 3 hero units. Is this working as currently intended or did I screw up somewhere? Thanks and cheers!

    1. I had this same issue. For some reason the ExpandSearch function of SpecifyAbilityArea would result in true, leading the Board to return three tiles, which happened to be the all the tiles that the existing units were occupying. This is even the case when the SpecifyAbilityArea properties were horizontal = 1 and vertical = 1.

      To target a single unit, I replaced it with Unit Ability Area. Maybe as I push forward through this series (which I love) I’ll understand better why that happened.

  7. The InflictAbilityEffect should reference a database to avoid the use of reflection or i am missing something? i dont like the reflection idea. A scriptable object can work to like data base.

    1. You’ll have to be more specific – there are tons of ways to tackle any problem. If you want to share a new solution on the forums I bet other readers would be interested to compare. I’d say that if you don’t like reflection then don’t use it, but what exactly is the concern? This isn’t something that will need to be performed 60 times per second across tons of objects. It will only need to be used at the time it is relevant and isn’t likely to cause any issues.

  8. Well i mean for me implementation its not suitable i am making a turn system like chrono trigger where there its possible a unit can do quickly 2 turns so there its framerate involved.

    But me suggestions its simple instead reflection just reference all the effects to a database in this case a scriptable object.

    So i just iterate for all the elements of that list for the specified effect just like reflection or that i think. So me answer its there is some benefit of the use of reflection?

    1. The purpose of this code is to call the generic “Add” method on a Status component where a type of Status Effect and a type of Status Condition are the generic types. You can’t really invoke a generic method without knowing the desired class at compile time. Since the ability effect is intended to be dynamic here (applied by whatever string you pass it), then generics weren’t an option outside of reflection. I can’t simply use a database or scriptable object because the effect to add is actually created inside of the “Add” method. It is designed to be a component that is added to a game object, and there are limited ways that Unity has given us to achieve this.

  9. I’m a bit confused. When would you want to add the Weapon Ability Power component to an ability if the weapon itself already applies its ATK to the stats of the unit? I’ve seen a response to a similar question but didn’t understand it.

    1. Abilities have their own stats. Think of a magic ability like Fire vs Firaga from Final Fantasy games – both are magic spells but one is weaker than the other and also costs less magic points than the other. An ability that draws its power from an equipped weapon will be stronger if the weapon itself is good or bad if the weapon is weak, this should be separate from the hero’s ATK stat, because that stat might be high simply because the hero itself is of a high level.

  10. As a little extra convenience, I set up a StatusEffects enum containing each status effect script’s name, and exposed an instance of it in InflictStatusEffect called “inflictStatus”. Then I set up statusName as a private property like so:

    string statusName {get { inflictStatus.ToString();}}.

    I figured this would prevent any misspellings and generally save time in implementing each instance of the Inflict Status Effect component. Figured I’d share to make sure it’s not a mistake to do so in some manner, though it appears to work as intended.

    1. There are pros and cons with this approach like there are with any approach. Some pros are that it is fairly intuitive and that it is something you could even configure within a Unity inspector, or save as an asset, put in a database etc.

      Some cons are that the more types of status effects you add, the more your switch statements will grow, and if you use the enum in multiple places then that could really bloat your code. If you treat the Status as a class of its own (could be a serialized object if you want to work with Unity inspectors) then you can use polymorphism to have more elegant code. For example, all status types could contain a property that returns its own name, as well as could provide some sort of interface for “applying” or “removing” itself to a target, etc.

      1. Makes sense, for sure. For now, the enum itself is only being used for the manner in which it’s serialized in the Inspector as a dropdown menu, and used simply for string conversion. I’ll definitely take that to heart should I use it for anything more involved, absolutely. Thanks!

  11. Im struggling to understand these methods:

    void OnGetBaseAttack (object sender, object args)

    Where is the args coming from?

    the getstat method seems to be used this signature:

    public void PostNotification (string notificationName, System.Object sender)

    in here: this.PostNotification(notification, info)

    But the handlers seem to be working with this signature:

    public void PostNotification (string notificationName, System.Object sender)

    The other more rational thing that ocurred me halfway trhoug writing this, is that it is using this signature:

    public static void PostNotification (this object obj, string notificationName, object e)

    In wich case, im not exactly sure how this works. Does it mean that there is some sort of syntatic sugar at work here?? and the this keyword is allowing you to bypass having to pass the sender as a parameter?? sorry if this is a dumb question.

    1. Correction: I meant this:

      But the handlers seem to be working with this signature:

      public void PostNotification (string notificationName, System.Object sender, System.Object e)

  12. For some reason I am having a really hard time understanding/following how the damage notifications are working.

    For example, let’s say I have an element bonus for characters. I have a script for the unit that tells his element, and his weakness and resistance is set automatically by a switch.

    When I attack I want to add 10% damage for having a strength against the enemy element. (Fore over grass for example.)

    I have a similar set up for weapon type and armor types to match specific weaknesses.

    Should the element script have the listener and adjust the damage when someone attacks? What notification should I listen for, the tweakdamage notificiation I assume, but I don’t understand how to make a priority for the items.

    For example I want base atk -def
    I use a armor % system so at least 1 damage is always dealt.

    Next check for global element changing skill. (Someone casted Gaia giving earth a global +5%)

    If attacker is using own element spell, + 10% or weakness spell – 10%.

    Next check defender, +\- 10% if his weakness or resistance.

    Then the weapon type and armor are matched.

    And so on.

    If these are all in tweakdamage, how do I be sure which one effects when?

    1. The part you should focus on understanding can be seen in the “DamageAbilityEffect” script’s “GetStat” method. This method creates a “List” of “ValueModifier” that is posted along with a notification. Observers of the notification append their own modifier to this list without needing to worry about order yet, because the calculation wont actually be performed until later.

      Back to the “GetStat” method, after the notification is posted (and all observers have responded) then we call “Sort” on the list of modifiers. This is how you determine the order of when your modifiers come into the formula. Then we can simply loop over the list applying each modifier in order.

      As far as knowing where to place your observer code, that is going to depend on a variety of design elements. For example, you “could” have an item such as armor or a weapon observe the notification, but then it might “listen” even while attached to a unit that is not participating in the current combat attack. Even worse, it might “listen” while it isn’t even equipped at all and is merely in the player’s inventory. As an alternative, you could create some sort of “system” object that handles the job of applying elemental effects to a battle. It could observe the notification and then analyze the weapons and armor of the units that are fighting and apply modifiers accordingly.

      1. I think I will make a new controller to track the changes then and add it into the damage calculation area as a function.

        Thank you for the idea!

  13. Hello!

    I have a question about the mods.Sort() call used in the GetStat method of the DamageAbilityEffect class.

    How does the mods list know how to sort itself?

    I looked things up on the internet a bit and found that list.Sort() uses a default value to sort lists, given that its elements have the interface IComparable. Our Modifier script doesn’t have that interface and we never set up sortOrder as our default comparer. How does the program know what to do?

    Thanks for clarifying

    1. Looks like you may have found a bug 🙂
      The Modifier should probably be IComparable based on its sortOrder.

      1. OK, I will try to implement that then 🙂

        I guess I didn’t actually checked if it worked, I simply assumed it did haha

        Thanks

          1. Can you explain that a bit more? So as it is the sorting never takes place actually? It seemed like the numbers changed

          2. I don’t know that I ever tested this code, even when I made it, so I cant remember if it ever worked, or works now in the demo. But from a quick glance is does appear that I missed some implementation details on how the Sort should be explicitly performed. It is possible that Sort was doing “something”, but was not actually using the sort order that we were expecting it to use. You don’t need to set up an entire game to test this. Just create a demo script that creates a bunch of value modifiers, each with a unique sort order. Add them to a list out-of-order, and call sort. Print the list to a debug console and check if it worked or not. I expect it wont work until you either add comparable conformance like Toucam suggested, or use one of the other overloads of Sort such as passing a comparison delegate method.

  14. Hello!

    I noticed while going through and doing some modifications to the job class to include a job tree, changing jobs, etc etc that the Weapon Ability Power script is checking the base stat of the job. Wouldn’t this mean that the Weapon Ability Power script doesn’t take into account the players actual ATK stat if they had levels in different jobs? Also doesn’t that mean that it doesn’t take into the account a players stats if they were a higher level than the base stats for the job?

    I made some modifications to the script to get what I think should be the players base stats before equipment.

    int UnarmedPower()
    {
    var unit = GetComponentInParent();
    var equips = unit.GetComponentsInChildren();
    var currentAttack = unit.stats[StatTypes.ATK];
    foreach (var equip in equips)
    {
    foreach (var feature in equip.GetComponentsInChildren())
    {
    if(feature.type == StatTypes.ATK)
    {
    currentAttack -= feature.amount;
    }
    }
    }
    return currentAttack;
    }

    1. You are correct in your understanding. By design, the Weapon Ability Power script was intended to provide a power stat based on an equipped weapon. If no weapon is equipped then the skill that uses it is presumably hindered, and so the fall back stat was merely the default attack strength of the job, not the total attack stat of the unit using it. Also, keep in mind that the damage formula can still take into account a variety of stats beyond the skill’s power level, so things like the scaling of the units strength could still apply.

      With the solution you have proposed, be careful in design phase to make sure that the strength stat of the unit was not rivaling (and especially not surpassing) the attack stat of weapons that would otherwise be equipped and used in the formula, unless of course that is something you intended to happen.

      1. Got it, I understand. Thanks!

        Is there possibly a cleaner implementation than that loop I implemented there? It doesn’t seem terrible but I wasn’t sure if it was optimal.

        Was also curious about implementing something like a passive effect? Similar to a status effect I would imagine right?

        In Final Fantasy tactics there’s something like the Ninja’s passive ability that allows it to attack twice with one attack action.

        1. What you have done is fine, as far as the loop itself. A bigger concern, to me, is when more and more features in the game can modify a stat. For example, in addition to weapons modifying the attack stat, maybe the unit is under a berserk status, or the party has a unique boost item, etc. I wouldn’t want to have to “undo” all of that manually. To handle all of that, I may go the notification route. Look at the way I use “GetAttackNotification” so I could let any component that modifies a stat listen to a new type of notification so you can determine how much to remove from the total.

          Another option, if you find yourself wanting even easier access to the unmodified stats, it might be worth making a separate entry in the stat table. Base stats as one entry, and the total modified stat as another entry.

          Passive effects would be implemented the same was as other status effects. In the end, it just needs to listen for some kind of notification, and modify the game logic in some way or other.

  15. hey, i’m having trouble with the ConfirmAbilityTargetState script. the name ‘targeters’ does not exist in the current context. i’ve checked into the repository and am still unsure how to fix this issue

    1. There is a section on this post under the name “Confirm Ability Target State” where you are instructed to add the new field:
      AbilityEffectTarget[] targeters;

Leave a Reply to Void Cancel reply

Your email address will not be published. Required fields are marked *