We will use a new enum to hold the three basic facing angles that I care about for this game. Create a new script named Facings located in the Scripts/Enums folder.
In this image, the center square has an arrow pointing to the right. Imagine that this arrow is a target unit which we wish to attack and that it is facing toward the right. Our attacker could be located at any other square, and if it was, we need a good way to know what angle would the attack be from – the back, side, or front?
According to the image, if we place the unit on any yellow tile which is marked with an “F” then we would be attacking from the Front. I have similarly marked the Side (“S”) and Back (“B”) tile locations.
If you aren’t familiar with “advanced” math, your algorithm for implementing this might be a bit long and hard to read, because each tile could be any of the three Facings – it depends on the direction the target is facing as well as the direction of the attacker to the target.
If you are familiar with “advanced” math, then this problem is surprisingly easy. The solution is called a “dot product”. I thought about including the mathematical definition, but somehow I don’t find it very helpful at all, so as a non-mathematician have mercy on me making up my own. You can use a dot product (a single float value) to determine the relationship between two vectors – such as whether they face in the same direction (values greater than zero), a perpindicular direction (zero), or opposite directions (values less than zero), etc.
Before I lose everyone (hopefully I haven’t already), I’ll just get to the point. If you were to take the dot product of the normalized vector from our attacker to our defender, and the normalized vector representing the angle the defender was facing, then you would get numbers in the range of -1 (attacking from the front) to +1 (attacking from the back). These relationships are shown in the image below as the dog (attacker) approaches the cat (defender).
Create a new script called HitRate in the Scripts/View Model Component/Ability/Hit Rate folder. This will be our abstract base class for another type of component which we will add to each type of ability. There will be three concrete implementations based on the design of Final Fantasy Tactics, where we have one kind that is used for a standard attack, one that is used for applying status ailments, and one that is used for abilities which should always hit.
/// Includes a toggleable MatchException argument which defaults to false.
/// should modify the arg2 parameter.
The concrete subclasses of this component include several of the same “checks” but not in the same order. The base class provides the implementations of the shared checks, which are called in whatever order is important in the subclass in the Calculate method.
One such “check” to be performed is whether “something” will cause an ability to certainly succeed. For example, our ability might be a regular “Attack” and on a normal occassion the chance to hit needs to consider the target’s chance to evade. However, if the target were under the effects of a “Stop” or “Sleep” status effect, then the chance to evade would be “zero” and we would consider it an automatic hit type of event.
A similar but oppositie check is whether “something” can cause an ability to certainly fail. For example, if the ability is supposed to apply a status effect, but the target has “Immune” attributes for that status type, then the ability would need to fail no matter what. This would be particularly important on some boss fights to keep them from being too easy.
A final check modifies the chances of a hit, without forcing it to be “certain”. For example, if the Attacker is “Blind” then he can still hit an opponent, but his chances of hitting are worse.
When determining when an ability would have either an automatic hit or automatic miss exception, I posted a notification along with an instance of our next class, the MatchException. Create and add this class to the Scripts/Exceptions folder.
In order to determine these cases, it can be helpful to know who is attacking and who is being attacked This would provide access to any number of components you may want to check. Note that the sender would be a HitRate, which should be tied to the same game object as the ability being performed, and is also information you may need to know when determining whether or not to allow this particular exception.
I wanted to use a subclass of a BaseException because I like the way that a condition is normally a certain way and can only be toggled to the opposite way. This “safety net” will help to avoid situations where one condition flips a toggle one way and a different condition flips it an opposite way. The final result would be dependent upon the order which the scripts listened to and handled the notification and therefore could lead to some difficult to track down “bugs” in your code.
The hit rate also posted a status check notification to allow various status effects a chance to modify the evasion rates of an ability. This notification passes along an instance of an Info object which is just a generic class made up of one or more generic fields. I decided to use this class instead of needing to make a specific implementation every time I need to pass a few bits of information along with a notification.
Although it is nice not to have to make a ton of little info classes, note that this method does not provide any protections that I would have been able to specify in a manually created class. For example, in the implementation I pass along both the attacker and dender as the first two fields. Had I created this class manually, I would make those two fields readonly so that no “listener” would be able to modify them. The use of good code comments can alleviate this problem but it is still something to keep in mind.
Since we exposed a bit of functionality which could cause an ability to have an automatic hit case, let’s add it to our “Stop” status effect. Final Fantasy Tactics would do the same for a variety of other status effects like “Petrify”, “Hibernate”, and “Sleep”.
You could provide functionality for automatic misses in very much the same way, you would simply be observing a different notification under different circumstances.
Let’s add a new status effect to help demonstrate how the “StatusCheck” portion of our Hit Rate will work. Create a new script named BlindStatusEffect in the Scripts/View Model Component/Status/Effects folder.
Don’t forget to recreate the Job project assets using our Pre-Production tool. From the menu bar choose Pre Production->Parse Jobs.
In order to help the user make better informed decisions, let’s add a new UI element that will show the HitRate for the selected ability. Add a new script named HitSuccessIndicator to the Scripts/View Model Component folder.
Our UI element will be pretty simple – we will use the AttackArrowBacker and AttackArrowFill sprites to visually indicate the hit rate chance. In addition we will have a Text label beneath the arrow to more specifically show the chance of a hit as well as how much damage might be done (although we are not implementing the damage algorithm yet).
Hopefully it is obvious to you that EstimateDamage has a placeholder implementation for now. We wont be using fixed values in a more complete implementation.
There is one last step to take before we can test everything out – add the ATypeHitRate component to the “Attack” game object in the project assets “Hero” prefab. Assuming you have created the Hit Success Indicator and linked it up to the Battle Controller you should now see a hit rate appear in the confirm portion of your ability action.
Try targeting a unit from behind, from the side, and from the front to verify that the chance of hitting from each angle is different. If you use the demo from last week you can also test that targeting a unit with the “Stop” status effect is guaranteed to hit.
In this lesson we spent some time illustrating how a little bit of math can help simplify our code. By using a Dot Product we were able to determine the angle of an attack. We created a few different types of hit rate components which include the angle of attack to determine their hit rate chances. Since our hit rate components were flexible enough to use exceptions, we implemented some in the “Stop” status effect and even added a new “Blind” status effect to show how values could be modified. Finally, we added another simple UI element which displays the hit rate to the user.
Why do you use 0.45f as the condition threshold? I think that makes the angle to be about 63 degree(instead of 45).
When I originally implemented it as 0.45 it was because I wasn’t paying too close of attention and thought of it as degrees as you point out. Even after I thought about it I decided it was okay to leave it as it was because I wasn’t sure I could trust 0.5 to produce correct results due to floating point inaccuracy – it seems that Mathf.Approximately is really wonky particularly when using IL2CPP. I imagine that Unity will fix that in the future, but in my tests, 0.45 was working satisfactorily so I decided to leave it as it was.
Great tutorial! It helps me a lot! Thanks!!
I hope things are going great for you! In case you were wondering if anyone missed having an update on Monday, rest assured at least one person did. Thanks again for the great series.
Haha glad to hear that Jordan, don’t worry, I haven’t quit the blog – I just had overtime at work followed by a family vacation 🙂 I’ll try my best to keep things going.
I was pretty sure you hadn’t quite- just wanted you to feel appreciated! 😉 Once a week is a fairly intense schedule for a tutorial series, so I am fairly sure people will forgive you for only keeping it most of the time, haha.
You may want to clarify on the “Jobs” part, that the script to modify is the JobParser, just in case someone thinks that goes into the Job class.
No problem… I updated the heading to say “Job Parser” instead of “Jobs”, thanks for the feedback.
For organization’s sake, what should I do with the Info classes script? For the time being, I placed it in the EventArgs folder, but since I can’t seem to find any reference as to where it should go, I figure I should ask, just to be sure.
Ah, alright. I couldn’t find it in the Google Drive project repository, so that might be something to look into, unless it’s been renamed since, or something. Thanks again.
Having a small difficulty, with displaying the correct damage dealt. I added a damage modifier based on the character type. For example if the unit type is Knight, the hp lost from a hit is reduced 25%. I simply listen for onhpdidchange, then if knight reduce25. If a mage add 25. The damage taken seems to actually subtract correctly, however the damage amount shown on the screen is the predict amount.
Any idea how to update it to show correctly?
Could be a few things. For example, if you wait to modify the damage dealt until after a HP change notification, then that is probably too late. I would try observing a different notification such as a when determining the amount of damage to inflict in the first place. I would expect to see those kinds of modifiers already factored in to the predicted amount. There are a variety of notifications for getting stats and determining attack power based on the attacker and target for these purposes.
Even if you still want to wait and only modify based on damage dealt, then I would next suggest you try observing the “WillChangeNotification” of the HP instead of the “DidChangeNotification”. This is one last chance to modify the values before they are truly applied. Then when you go to display the final value, it should already have been correctly determined.
Also, make sure the label, or whatever it is, which displays the damage dealt is listening to the correct notification. I would want to listen to something which tells me the amount of damage that was applied, not the actual amount of HP lost. For example, say you do a really strong attack that has a damage force of 9999, but the target only had 1 hitpoint. I would care more about seeing the strength of my attack than the mere amount of HP I could reduce.
Was trying base it off of the haste effect mostly.
In the baseabilityeffect when I set the new hit rate info, I think that I need to update here somehow. It seems that in the predict function the getstat for damage is not applying my modification.
So after some back and forth I may be getting further from the goal.
The predict part of an ability however does not take the element into effect.
Since the hit rate floating message is based off of the predict, not the actual amount dealt, the numbers do not match up.
I feel that it should go here in predict somehow, then not register the OnHPWillChange listener. Basically it will just be an added 10% bonus or penalty for various elements.
Is there an example of something like this? I am just not very sure I understand the exceptions system that is in place for calculating the attack damage. Perhaps my logic is missing something somewhere?
Since we are off topic of the base project, I’ve created a thread on my forum to record our discussion and tried to answer you new questions there. I hope it helps!
Ok, so I made a call in the predict function to the element on the unit, which calculates and returns the bonus modification. All things works and run smoothly, but I definitely need to recheck notifications for the damage stacking. Feels like I will definitely need to refactor it later once I understand it better.