Tactics RPG Status Effects

During our last lesson I suggested possible implementations for a few status effects, such as Haste, Slow and Stop. In this lesson we will actually add them. We will also learn how to manage the way multiple conditions might be keeping a status active. For some variation we will also add a Poison status effect and see how it can be tied to an item as an equip feature – a cursed sword.

Refactoring

It turns out I was only mostly right with the way I suggested we implement our status effects in the last lesson. The current implementation of our ValueModifier classes only modify the overall value, not the amount of change of a value.

Imagine the following scenario: we have a unit with a stat CTR value of 500, and on an update “tick” will increment the value by 100. If we “catch” the notification and attach a MultValueModifier with a multiplier of 2 in an attempt to implement haste, then the actual result is “(500 + 100) * 2 = 1200”. What I wanted for haste is “500 + (100 * 2) = 700”.

In order to allow us a way to modify the amount of change of a value we will have to refactor our code. Open the VauleModifier script and change the signature of our Modify method to the following:

public abstract float Modify (float fromValue, float toValue);

Each of the subclasses will also need to use the new signature. As you update the signatures and implementation bodies, use the “toValue” parameter to replace the original “value” parameter. If you try to “Build” you will see errors until you fix each subclass instance. Refer to the code in my repository if you get stuck.

Now that we have modified the ValueModifiers, we will also need to modify the way that a ValueChangeException determines the modified value. Use the following:

public float GetModifiedValue ()
{
	if (modifiers == null)
		return toValue;

	float value = toValue;
	modifiers.Sort(Compare);
	for (int i = 0; i < modifiers.Count; ++i)
		value = modifiers[i].Modify(fromValue, value);
	
	return value;
}

By knowing the value we were changing “from” and the value we are changing “to”, it is a simple matter to determine the delta. Because of this I can now add modifiers which modify the result based on that delta. Add a new script named MultDeltaModifier to the Scripts/Exceptions/Modifiers folder.

using UnityEngine;
using System.Collections;

public class MultDeltaModifier : ValueModifier 
{
	public readonly float toMultiply;
	
	public MultDeltaModifier (int sortOrder, float toMultiply) : base (sortOrder)
	{
		this.toMultiply = toMultiply;
	}
	
	public override float Modify (float fromValue, float toValue)
	{
		float delta = toValue - fromValue;
		return fromValue + delta * toMultiply;
	}
}

Status Conditions

There might be multiple reasons why a status is active on a Unit. Note that this is different than the cause of the status. For example, you could apply Blind by casting a magic spell or by hitting something with a special item – these are the cause of the status. When one of these causes occur, we add the status effect and add a “condition” for how long the effect remains active. In these cases we might say that the condition is some sort of timer or “duration” and once the time requirement is met, the condition for the status is removed, which also removes the status effect itself assuming that no other conditions were still active.

Normally I create an “abstract” base class, but in this case I decided to leave the base class as “usable” – it will be a “manual” condition which doesn’t take care of removing itself, and instead something else will decide when to add and remove it. Later, this base class will be used as part of an Equip Feature, where the condition is that a status will be applied for as long as the item is equipped.

Add a new script named StatusCondition to the following folder path Scripts/View Model Component/Status/Conditions.

using UnityEngine;
using System.Collections;

public class StatusCondition : MonoBehaviour
{
	public virtual void Remove ()
	{
		Status s = GetComponentInParent<Status>();
		if (s)
			s.Remove(this);
	}
}

The only thing this script does is to know how to remove itself. That’s not much, but its really the sole purpose of this class. It is there as a sort of “lock” to keep a status effect applied, but it doesn’t care what the status effect is, it only needs to know about itself and how to remove itself. The Status component manages the relationship between these “locks” and the effects, but we will get to that later.

Subclasses of the StatusCondition class will be able to remove themselves through some sort of more specific event. For example, time – add another script named DurationStatusCondition to the same folder.

using UnityEngine;
using System.Collections;

public class DurationStatusCondition : StatusCondition 
{
	public int duration = 10;

	void OnEnable ()
	{
		this.AddObserver(OnNewTurn, TurnOrderController.RoundBeganNotification);
	}

	void OnDisable ()
	{
		this.RemoveObserver(OnNewTurn, TurnOrderController.RoundBeganNotification);
	}

	void OnNewTurn (object sender, object args)
	{
		duration--;
		if (duration <= 0)
			Remove();
	}
}

This subclass listens for notifications that a new round has begun, and with each new round reduces a duration counter by one. Note that you can specify how many rounds a particular effect will last because the duration field is public.

Status Effects

Create a new script named StatusEffect in the Scripts/View Model Component/Status/Effects folder.

using UnityEngine;
using System.Collections;

public abstract class StatusEffect : MonoBehaviour 
{

}

Yep – its a completely empty script. Why would I ever do such a thing? Even though this base class has no functionality whatsoever, I wanted to make sure that all status effects share a common base class. This way, if as I am implementing them I do see some common functionality, it will be easy to move it to the base class and allow it to be reused. In addition, it makes the intent of my code more clear in other classes. For example, the Status component which I am about to create will work based on pairs of StatusEffect and StatusCondition components. If I didn’t specify a base class for the StatusEffect, then there would be nothing stopping a particularly “clever” user from adding “any” MonoBehaviour he wanted which may lead to unexpected consequences. Because I did specify a base class, the intentions of the architecture are much more obvious.

Haste

Add another script named HasteStatusEffect to the same folder as before.

using UnityEngine;
using System.Collections;

public class HasteStatusEffect : StatusEffect 
{
	Stats myStats;

	void OnEnable ()
	{
		myStats = GetComponentInParent<Stats>();
		if (myStats)
			this.AddObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}

	void OnDisable ()
	{
		this.RemoveObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}

	void OnCounterWillChange (object sender, object args)
	{
		ValueChangeException exc = args as ValueChangeException;
		MultDeltaModifier m = new MultDeltaModifier(0, 2);
		exc.AddModifier(m);
	}
}

In this case we register for the WillChangeNotification of the CTR stat. In the notification handler we add our brand new MultDeltaModifier to the ValueChangeException with a multiplier of “2”. This will cause the amount by which the stat changes to be doubled.

Slow

Add another script named SlowStatusEffect to the same folder:

using UnityEngine;
using System.Collections;

public class SlowStatusEffect : StatusEffect 
{
	Stats myStats;

	void OnEnable ()
	{
		myStats = GetComponentInParent<Stats>();
		if (myStats)
			this.AddObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}
	
	void OnDisable ()
	{
		this.RemoveObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}
	
	void OnCounterWillChange (object sender, object args)
	{
		ValueChangeException exc = args as ValueChangeException;
		MultDeltaModifier m = new MultDeltaModifier(0, 0.5f);
		exc.AddModifier(m);
	}
}

The Slow status effect is nearly identical to the Haste status effect. The only difference (besides the name of the class) is the multiplier value. At this point you should probably be thinking “Oh no, I’ve just repeated myself!” and if you are then you should give yourself a gold star.

There are a few ways we could have reduced the amount of code here. One way is that the Haste and Slow status effects could share a common base class. Another is that we could simply use a single script with a public field for the multiplier value to use. Perhaps this script would be called ModifyCounterSpeedStatusEffect. Unfortunately it isn’t quite as intuitive that this single component would be used for both the implementation of Haste and Slow – I could see myself forgetting how or where it was implemented. Furthermore, as this game is fleshed out more in the future I still might prefer they be separate classes due to new implementation details such as the different ways that visual aids (the things on-screen which indicate that a unit is under the haste or slow status effect) might appear. I’ll leave the final decision on this sort of architecture up to you.

Stop

Add another script named StopStatusEffect to the same folder:

using UnityEngine;
using System.Collections;

public class StopStatusEffect : StatusEffect 
{
	Stats myStats;

	void OnEnable ()
	{
		myStats = GetComponentInParent<Stats>();
		if (myStats)
			this.AddObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}
	
	void OnDisable ()
	{
		this.RemoveObserver( OnCounterWillChange, Stats.WillChangeNotification(StatTypes.CTR), myStats );
	}
	
	void OnCounterWillChange (object sender, object args)
	{
		ValueChangeException exc = args as ValueChangeException;
		exc.FlipToggle();
	}
}

This script also looks very similar to both Haste and Slow. In fact, it would have been possible to use the same script for all three if I had used a public field for the multiplier value. In this case, I would just use a multiplier of 0. However, because I mentioned in the last post that you could simply flip the toggle, I wanted to show that implementation. Also, flipping a toggle is more “strict” than multiplying by zero. For example, if I had multiple value modifiers in play, one might multiply by zero, and another could add some other amount so that the final result was still non-zero. When the toggle is flipped, it doesn’t matter what the final value would have been, a change simply isn’t allowed.

Poison

Add our final status effect named PoisonStatusEffect to the same folder.

using UnityEngine;
using System.Collections;

public class PoisonStatusEffect : StatusEffect 
{
	Unit owner;

	void OnEnable ()
	{
		owner = GetComponentInParent<Unit>();
		if (owner)
			this.AddObserver(OnNewTurn, TurnOrderController.TurnBeganNotification, owner);
	}

	void OnDisable ()
	{
		this.RemoveObserver(OnNewTurn, TurnOrderController.TurnBeganNotification, owner);
	}

	void OnNewTurn (object sender, object args)
	{
		Stats s = GetComponentInParent<Stats>();
		int currentHP = s[StatTypes.HP];
		int maxHP = s[StatTypes.MHP];
		int reduce = Mathf.Min(currentHP, Mathf.FloorToInt(maxHP * 0.1f));
		s.SetValue(StatTypes.HP, (currentHP - reduce), false);
	}
}

This status effect operates on a different notification, one that we haven’t actually added yet (but will in a moment). I had originally allowed it to work on the beginning of each new round, but then I realized there were several rounds before our units build up enough CTR to actually take a turn. I decided that it felt better to see the effect of the poison just before the unit takes a turn.

When the notification handler executes it gets a reference to the Stats component and reduces HP by one-tenth of the MHP or the current HP of the unit, whichever is less. Note that I could have relied on a Clamp Value Modifier which existed elsewhere (like a Health component) to ensure that HP never goes below zero (or above MHP). However, in this case I used the SetValue method with the AllowExceptions parameter set to false. I decided that the effect of Poison would be unalterable, but this also may not be a design you agree with. Feel free to modify things as you desire.

Don’t forget that we will need to add the new notification to the TurnOrderController script. It would look like the following:

public const string TurnBeganNotification = "TurnOrderController.TurnBeganNotification";

And it would be posted immediately before the yield statement in the Round method:

...
if (CanTakeTurn(units[i]))
{
	bc.turn.Change(units[i]);
	units[i].PostNotification(TurnBeganNotification); // ADDED
	yield return units[i];
...

Extensions

In Unity, when you Destroy a GameObject or Component, the thing which you destroyed is still there until the next frame. Imagine for example, that I have added a component, destroy it, and then do a GetComponent from somewhere else. The GetComponent call can find the component which is being destroyed and this can lead to some unfortunate problems. In addition, Unity doesn’t provide any sort of field which can be referenced to know that the object is scheduled for destruction.

In order to fix the problem mentioned above, and also for the sake of clear debugging in the hierarchy, I will use a system where I add components to children objects. When I want to destroy an object, I can first unparent the transform so that calls to GetComponentInChildren will not succeed in finding the object which is going to be destroyed.

A good polish step in the future might be to use the GameObjectPoolController and reuse GameObjects rather than constantly creating and destroying them. I’m not that worried at the moment because the frequency of the creation and destruction of these objects is so sporadic.

Add a new script named GameObjectExtensions to the Scripts/Extensions folder. This script will make it easy to create a new child object which is parented to the indicated game object, and attach the component of type you specify with generics.

using UnityEngine;
using System.Collections;

public static class GameObjectExtensions
{
	public static T AddChildComponent<T> (this GameObject obj) where T : MonoBehaviour
	{
		GameObject child = new GameObject( typeof(T).Name );
		child.transform.SetParent(obj.transform);
		return child.AddComponent<T>();
	}
}

Status

Add a new script named Status to the Scripts/View Model Component/Status folder. This component will be responsible for determining how long a status effect should remain applied to a unit. It handles this by checking for the existance of status conditions. As long as there is a condition tied to an effect, the effect remains applied. Note that the script itself doesn’t know or need to know anything specific about the status effects or status conditions themselves.

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

public class Status : MonoBehaviour
{
	public const string AddedNotification = "Status.AddedNotification";
	public const string RemovedNotification = "Status.RemovedNotification";

	public U Add<T, U> () where T : StatusEffect where U : StatusCondition
	{
		T effect = GetComponentInChildren<T>();

		if (effect == null)
		{
			effect = gameObject.AddChildComponent<T>();
			this.PostNotification(AddedNotification, effect);
		}

		return effect.gameObject.AddChildComponent<U>();
	}

	public void Remove (StatusCondition target)
	{
		StatusEffect effect = target.GetComponentInParent<StatusEffect>();

		target.transform.SetParent(null);
		Destroy(target.gameObject);

		StatusCondition condition = effect.GetComponentInChildren<StatusCondition>();
		if (condition == null)
		{
			effect.transform.SetParent(null);
			Destroy(effect.gameObject);
			this.PostNotification(RemovedNotification, effect);
		}
	}
}

Add Status Feature

So far I have shown examples of “what” a status effect can do, and “when” it should be active. I haven’t shown examples of “how” to actually apply something. I did suggest a common way would be to use a magic spell or attack to deliver the status effect along with a duration condition, but now I want to do something a little different. We will use our Feature component so that we can make the addition of a status effect one of the features of equipping an item.

Most of the time you will choose something nice, like special shoes which provide haste or something like that. Sometimes it can be interesting to mix things in an unexpected way, such as a sword which is exceedingly powerful, but which is also cursed so equipping it causes you to be poisoned.

Add a new script named AddStatusFeature to the Scripts/View Model Component/Features folder.

using UnityEngine;
using System.Collections;

public abstract class AddStatusFeature<T> : Feature where T : StatusEffect
{
	#region Fields
	StatusCondition statusCondition;
	#endregion

	#region Protected
	protected override void OnApply ()
	{
		Status status = GetComponentInParent<Status>();
		statusCondition = status.Add<T, StatusCondition>();
	}
	
	protected override void OnRemove ()
	{
		if (statusCondition != null)
			statusCondition.Remove();
	}
	#endregion
}

Next you can add a subclass called AddPoisonStatusFeature:

using UnityEngine;
using System.Collections;

public class AddPoisonStatusFeature : AddStatusFeature<PoisonStatusEffect> 
{

}

One thing to consider with this architecture, is the potential for an “explosion of classes” by which I mean that the more kinds of status effects we add, the more kinds of specific “Add Status Feature” subclasses we might also add. If the method of deliveries also included something like an “OnHitAddStatus” class then we may likewise have subclasses of that for each type of subclassed status effect. The classes themselves are empty but it is unfortunate to need so many.

An alternative architecture pattern could be to simply provide a public System.Type field which determines what type of feature to add. A single class would be able to be used no matter how many types of status effects we wanted to add. Unfortunately, you lose some of the readability and constraints that generics provided. Also, there isn’t a good method for attaching a “Type” through the inspector for your prefabs. You could use a string and get a type from the string, but that is also vulnerable to abuse and lacks compile time checking. Furthermore, you can’t directly use the “Type” with generics, but would need to use Reflection and that also feels a little wrong to me. Just my opinions – feel free to pick whatever feels best to you.

Demo

Let’s continue to use our Battle Scene, but this time as each unit moves, we will do “something” regarding status effects. One of our units will equip our cursed sword (and therefore get poisoned), and each of the units will get one of the CTR based status effects as well. Experiment with moving the pieces on the board, but for now don’t actually attack. Simply observe that one of the units will be faster than the others (Haste), one will get turns but very slowly in comparison (Slow), and another wont be getting turns at all (Stop). Eventually the status effects will all wear off, and each of the unit’s speeds will return to normal. Also note that even though no attacking took place, the unit that had equipped the poison sword will have lost hit points.

Before we begin the demo, add a Status and Equipment component to the Hero prefab in the project pane.

Next, add a tempory script named Demo and attach it to the Battle Controller GameObject in the scene.

using UnityEngine;
using System.Collections;

public class Demo : MonoBehaviour 
{
	Unit cursedUnit;
	Equippable cursedItem;
	int step;

	void OnEnable ()
	{
		this.AddObserver(OnTurnCheck, TurnOrderController.TurnCheckNotification);
	}

	void OnDisable ()
	{
		this.RemoveObserver(OnTurnCheck, TurnOrderController.TurnCheckNotification);
	}

	void OnTurnCheck (object sender, object args)
	{
		BaseException exc = args as BaseException;
		if (exc.toggle == false)
			return;

		Unit target = sender as Unit;
		switch (step)
		{
		case 0:
			EquipCursedItem(target);
			break;
		case 1:
			Add<SlowStatusEffect>(target, 15);
			break;
		case 2:
			Add<StopStatusEffect>(target, 15);
			break;
		case 3:
			Add<HasteStatusEffect>(target, 15);
			break;
		default:
			UnEquipCursedItem(target);
			break;
		}
		step++;
	}

	void Add<T> (Unit target, int duration) where T : StatusEffect
	{
		DurationStatusCondition condition = target.GetComponent<Status>().Add<T, DurationStatusCondition>();
		condition.duration = duration;
	}

	void EquipCursedItem (Unit target)
	{
		cursedUnit = target;

		GameObject obj = new GameObject("Cursed Sword");
		obj.AddComponent<AddPoisonStatusFeature>();
		cursedItem = obj.AddComponent<Equippable>();
		cursedItem.defaultSlots = EquipSlots.Primary;

		Equipment equipment = target.GetComponent<Equipment>();
		equipment.Equip(cursedItem, EquipSlots.Primary);
	}

	void UnEquipCursedItem (Unit target)
	{
		if (target != cursedUnit || step < 10)
			return;

		Equipment equipment = target.GetComponent<Equipment>();
		equipment.UnEquip(cursedItem);
		Destroy(cursedItem.gameObject);

		Destroy(this);
	}
}

Note that I wont have saved the scene changes or demo script to my repository. They are merely fun little snippets to verify that our code works and to help you understand how things work together.

Summary

In this lesson we refactored some of our code to support new ways to work with value change exceptions. Using the new method of value modifications, we were easily able to implement several status effects including Haste, Slow, and Stop. For variety we also added Poison. Next, we provided a system which could manage the “lifespan” of a status effect by keeping it active for as long as any status condition was also applied. Finally, we showed a means of actually applying a status effect. We went for a specialty route and applied a status effect by making it a Feature of an equipped weapon.

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.

21 thoughts on “Tactics RPG Status Effects

  1. If you are taking requests for upcoming lessons I have a few requests. Would you cover what you feel is the best way to implement AI for out battles and how to scale the difficulty of that AI? Also, would you cover how to make a shop that is capable of sorting items by various fields like cost, atk dmg, lvl requirement etc? Keep up the great work! I’m learning tons!

    1. I’m always glad to hear suggestions for topics. AI is definitely something I want to cover, but I haven’t even completed a “true” attack yet, much less a variety of the other abilities, so it will still be a bit down the road. On the other hand, I would suggest that AI isn’t necessarily as hard as many make it out to be – especially with an RPG. Many people could be fooled into believing the AI got “smarter” simply by boosting the count of enemies or by boosting their stats.

  2. Can you please point me to which section we created the Hero prefab in the Battle Scene? I can’t seem to find it. I’m doing this with a partner and apparently we both missed it so…strange things, circle K. Thanks!

  3. Hi, thanks a lot for this tutorial, it’s been a great ride so far.
    For some reason I’ve started stumbling a bit in this or the last post with a nullref exception.

    I am trying to attack a hero clone at this point, and it fails with a nullref on this line: return hr.Calculate(turn.actor, target);

    Im starting to think i’ve missed a post where we generate enemies, but i cannot seem to find where.

    Anyone have any suggestions, or experienced the same issue and been able to figure out where they went wrong?

    1. This is almost always due to an incorrectly structured object, such as forgetting to add the script, or a reference to a script etc. Your best bet will be to compare your setup against the one on the repository, but to help you determine what the problem is, I would first determine which of the objects on that line is the null ref. You can set a break point, or use debug logs before that line where each one prints a bool on whether or not the item is null.

  4. I’m trying to create a burn status, in which the damage over time is dependent on the stats of the caster and not the target affected. (let’s say some percentage of their MAtk is reduced from currentHP periodically) Is there a way to store that information on initial application of the status and still have it maintained in the status effect script each successive turn, or would I have to find another solution.

    1. I think you could find a way to modify the code and add caster stats to the burn, such as by observing a notification etc. However, the proposed design goes outside the scope of this architecture. The status ailments are more like tags – either you have them or you dont, rather than each one being unique.

      For example, imagine you have two mages, one strong and one weak. The first strong mage casts the fire spell causing a “burn” status to be applied to the target. You then modify the burn status to have a high damage because of his high stats. Next the weaker mage casts a fire spell – do you add a second “burn” (and if so how do you differentiate them), or do you potentially lower the stats of the “burn” based on the weaker mage that attacked second? There is a lot to think about here.

      A simpler approach that might fit more in line with the current architecture would be that if your mage is stronger, he should use a stronger spell. As a result, a different status could be applied. A weak fire spell might cause “singe”, a stronger one “burn”, and the strongest “charr”. You could easily add rules to say that stronger variants clear weaker variants, and that weaker variants cant be added along with stronger ones. It still isnt as much variety as directly taking stats from the caster, but it might be enough.

  5. I like the burn/singe/charr idea on the backend of it, but would it be fairly trivial to just name them all as “burn” to the player anyway? There isn’t really going to be character leveling in the game I’m trying to make, and adding multiple visible statuses with really similar effect might seem confusing the more statuses I add.

  6. I’m fairly new to coding – so please pardon my silly question:

    I wanted every unit to only act once per turn, so I scrapped the CTR stat completely, and made the turn order controller arrange according to the unit’s SPD stat instead.

    With that, I needed to change the Haste/Slow script as well. I had no problem in increasing/decreasing the unit’s SPD when the relevant status is applied, but I had troubles trying to change it back when the duration reached 0. I tried to make the script listen to Status.RemovedNotification and applying the relevant changes to change it back. However, if the same status ailment is applied to multiple units, there will be multiple notifications and the SPD stat won’t change back to normal.

    Is there any way to only listen to one notification, or any other way that can solve the problem?

    1. It’s not a silly question, and I am glad to see you trying to change things up, because that will be a very useful way to learn! You can do what you want, and it’s all easily available through the notification system as is. You would probably benefit from reading the post where the notification system was originally created (even though it has changed a bit over time):
      Social Scripting Part 3

      But here are some quick points that might be helpful to you:

      First, when a status is removed, there will only be ONE notification sent. This might have been confusing because there can be potentially MANY observers of the single notification. It’s like a teacher in a classroom where the teacher gives one lesson, and many students learned from it.

      Second, a notification usually includes two very helpful bits of information: a sender and some sort of information that could be anything. In this case, the sender is the `Status` component, and the information is the `StatusEffect` component.

      Because of the way that you can traverse a GameObject hierarchy from any component, you can determine many other things. You should be able to determine what Unit the component was attached to, and therefore what stat should be modified. Each observer of the notification can check whether or not the sender of the component, is attached to the same GameObject that the observer wants to manage. Does that make sense?

      Third, any object can observe any notification – it wouldn’t need to be on the same object hierarchy. So if you don’t like the previous suggestion of multiple observers, this means you could make a single object (usually called a system, controller or manager) that handles the logic for all objects of a particular type. For example, you could make a Haste System, where the system observes the Status Removed notification, verifies that the notification is referring to the removal of a Haste Status Effect, and then traverse the object’s hierarchy to modify its speed stat accordingly.

      1. I….somewhat get what you’re talking about, but I still have several questions:

        1. How do you determine the unit that the Haste Status Effect component was attached to when it has already been removed? In fact, this is my main problem at the moment – I can’t use the GetComponentInParent command to get the Stats component on the unit because the Haste Status Effect component has already been removed. I want the script to change the stats first before being removed, not change the stats after being removed, but I don’t know how to do that.

        2. I also understand that another method involves retrieving the sender that sent out the notification, and from there, getting the game object that the sender belongs to, and modifying the stats from there, but I…..don’t really know the command to retrieve it.

        Also, I’m not sure whether it’ll help, but this is my current HasteStatusEffect script. I’m not all too good at this sort of stuff so pardon me if the coding looks bad, or if I’m overlooking some obvious command that everyone already knows.

        using UnityEngine;
        using System.Collections;

        public class HasteStatusEffect : StatusEffect
        {
        Stats myStats;

        private void Start()
        {
        myStats = GetComponentInParent();
        myStats.SetValue(StatTypes.SPD, myStats[StatTypes.SPD] * 2, false);
        }

        void Remove(object sender, object args)
        {
        Stats myStats = GetComponentInParent();
        myStats.SetValue(StatTypes.SPD, myStats[StatTypes.SPD] / 2, false);
        }

        void OnEnable()
        {
        myStats = GetComponentInParent();
        this.AddObserver(Remove, Status.RemovedNotification, gameObject);
        }

        private void OnDisable()
        {
        myStats = GetComponentInParent();
        this.RemoveObserver(Remove, Status.RemovedNotification, gameObject);
        }
        }

  7. I think the GetModifiedValue method should group the modifier by their by order otherwise there is a “error” if multiple modifiers with the same order are added or am I missing something?
    Let’s say you have a base value of 500 and you add 200 to it while you have 3 modifiers on the stat.
    An percentage modifier which both add 50% boost to the delta.
    Currently the loop works like this:
    Step 1, FromValue 500, ToValue 700 (500 + 200) Delta 200, ModValue 100 (50% of 200) , NewValue 800
    Step 2, FromValue 500, ToValue 800, Delta 300, Mod Value 150 (50% of 300), NewValue 950

    But since both of the modifiers have the same order it would make more sense to “stack” them to a total of 100% which would result to the value 500 + (200 + 200) = 900 or maybe a third parameter to pass the “current modified group” value (by group I mean all modifier of the same order), in the example above the “group value” would be 700 in both cases.

    1. Sorry, I made some mistakes, I mean 2 modifiers (both with the same order and both add 50% to the delta). I hope you can still understand what I mean, otherwise I can try to explain it with a different example.

    2. I was imagining that the various modifiers would be designed such that if two modifiers had the same sort order that it wouldn’t matter the order you evaluate them, the final result should be the same. For example, if you had two addition and or subtraction modifiers. If one of the modifiers did not fit that goal such as multiplication vs addition, then you would need to use a different sort order. I suppose in a way you can imagine this being one long equation and the sort order is intended to be a way to indicate where the parenthesis would be to make sure those bits get evaluated first.

      Beyond that, I didn’t take the implementation of this system very far. If it is not able to obtain the result you desire, feel free to modify it to your hearts content. Good luck!

  8. Thanks a lot for these tutorials. I’ve been learning quite a bit. I think I found a bug in the demo code for this lesson. On line 76 you run a destroy command (Destroy(this)) which will destroy the BattleController. This will fire off the OnDisable notification into an infinite loop, I think, eventually resulting in a stack overflow exception. This happens when step is 11. It’s not a big deal since this is a demo script so it’s completely throw away code but I just wanted to understand the point of the Destroy command. Is it intended to kill the controller or should it have destroyed something else? I don’t think I have a bug elsewhere in my code but it’s certainly possible I do.

    1. In a script, “this” refers to the script itself. So in this case, we would be destroying the Demo script, not the Battle Controller GameObject. I would need to say “Destroy(this.gameObject)” to destroy that. While it has been a while since I wrote this, I believe I was thinking something along the lines that as we step through turns, the final step, we could delete the demo, because it is no longer needed. There are other ways to have handled it, such as disabling the script, calling remove component, or just adding an extra “case” statement that took no action.

      1. Ah. I see. I was reading the tool tip incorrectly. It was noting that this script is on the battle controller. I still get a constant run of the RemoveObserver function on the NotificationExtensions and I’m not sure why. It’s this line here that complains of the overflow and it’s started by the demo scripts OnDisable call.

        public static void RemoveObserver(this object obj, Handler handler, string notificationName) =>
        NotificationCenter.instance.RemoveObserver(handler, notificationName);

        This is a demo script and I couldn’t seem to replicate it elsewhere so it’s not a concern but more just trying to understand it. I appreciate your response.

Leave a Reply to thejon2014 Cancel reply

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