Tactics RPG Ability Area of Effect

To fully select targets for an ability we need a few more steps. We began with specifying a Range. In this lesson we will implent a variety of Area Of Effect components which are applied based off the selection from the range. In addition, each ability may have multiple “effects” – each of which may specify a unique set of valid targets. I will provide a couple of implementations for these as well, so that we can have a completed targeting selection loop in place.

Area Of Effect

Some abilities have a range with a single target, like an archer. Some abilities have a range with a subrange target, like the blast radius of a magic spell. This difference in targeting area is what I will refer to as the Area of Effect. All abilities will need to have this type of component, just like they needed a Range component. By mixing and matching the two components on your GameObjects you can have a nice variety of ways to select the target(s) for your abilities.

Let’s begin by creating the abstract base class. Create a new script called AbilityArea and place it in Scripts/View Model Component/Ability/Area Of Effect.

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

public abstract class AbilityArea : MonoBehaviour
{
	public abstract List<Tile> GetTilesInArea (Board board, Point pos);
}

Much like the Range component, this script returns a list of tiles in its area. These are tiles which need to be highlighted and shown to the player as candidate locations for effect application. Note that in addition to the Board reference, we must also pass a Point parameter to the method. This is used to indicate a selected location within a range from which to determine what tiles to grab.

Unit Ability Area

Our first concrete subclass is called UnitAbilityArea and it simply returns whatever Tile exists at the indicated position. This could be used for implementing the attack ability of an archer.

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

public class UnitAbilityArea : AbilityArea 
{
	public override List<Tile> GetTilesInArea (Board board, Point pos)
	{
		List<Tile> retValue = new List<Tile>();
		Tile tile = board.GetTile(pos);
		if (tile != null)
			retValue.Add(tile);
		return retValue;
	}
}

Specify Ability Area

When you want to target an area of tiles around the cursor’s position, you will use our next subclass, SpecifyAbilityArea. For example, this would be used to implement a black mage’s fire spell which can also hit tiles adjacent to the targeted location.

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

public class SpecifyAbilityArea : AbilityArea 
{
	public int horizontal;
	public int vertical;
	Tile tile;

	public override List<Tile> GetTilesInArea (Board board, Point pos)
	{
		tile = board.GetTile(pos);
		return board.Search(tile, ExpandSearch);
	}

	bool ExpandSearch (Tile from, Tile to)
	{
		return (from.distance + 1) <= horizontal && Mathf.Abs(to.height - tile.height) <= vertical;
	}
}

Full Ability Area

Our next concrete subclass is called FullAbilityArea, and as the name implies, every tile that is highlighted by an ability’s range is also a potential target for this area of effect. This could be used for a dragoon’s fire breath attack (again I am referencing Final Fantasy Tactics Advance here).

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

public class FullAbilityArea : AbilityArea 
{
	public override List<Tile> GetTilesInArea (Board board, Point pos)
	{
		AbilityRange ar = GetComponent<AbilityRange>();
		return ar.GetTilesInRange(board);
	}
}

Effect Target

As I briefly mentioned in the introduction, an ability can have more than one effect. For example, a Cure spell which normally restores hit points to units might have a secondary effect of damaging the undead. So in this example, the first effect of the ability would only target living units, and the second effect would only target undead units. Even if the ability only has a single effect, it still may require special targeting. The ability to determine what is and is not a valid target will be another component.

Create an abstract base class called AbilityEffectTarget and place it in Scripts/View Model Component/Ability/Effect Target. Use the following implementation:

using UnityEngine;
using System.Collections;

public abstract class AbilityEffectTarget : MonoBehaviour 
{
	public abstract bool IsTarget (Tile tile);
}

Couldn’t be much easier right? The only thing this component does is to determine whether or not the effect applies to whatever may or may not be located at the specified board tile.

Default Ability Effect Target

Let’s create our first concrete subclass called DefaultAbilityEffectTarget. Most ability effects will probably use this – it simply requires that there be something on the tile, and that the something which is there has hit points. Note that it doesn’t necessarily have to be a normal unit – you may or may not wish to include that requirement.

using UnityEngine;
using System.Collections;

public class DefaultAbilityEffectTarget : AbilityEffectTarget 
{
	public override bool IsTarget (Tile tile)
	{
		if (tile == null || tile.content == null)
			return false;

		Stats s = tile.content.GetComponent<Stats>();
		return s != null && s[StatTypes.HP] > 0;
	}
}

KO’d Ability Effect Target

Just for a quick bit of variety I decided to create one more concrete targeter. Add another script called KOdAbilityEffectTarget. This time we are looking for an entity with no hit points. For example, a resurrection skill might require this target type.

using UnityEngine;
using System.Collections;

public class KOdAbilityEffectTarget : AbilityEffectTarget 
{
	public override bool IsTarget (Tile tile)
	{
		if (tile == null || tile.content == null)
			return false;

		Stats s = tile.content.GetComponent<Stats>();
		return s != null && s[StatTypes.HP] <= 0;
	}
}

Turn

Now that we will truly be selecting the targets of an ability, we will store them in the Turn object, so that they are available between multiple battle states. Add a field for a List of Tile called targets:

public List<Tile> targets;

Battle States

We have a few more components to play with, so let’s plug them into the game and see how they work. As is normally the case, we will need to both modify and add new States. I will add a few extra states to help complete the loop of actually using an ability and ending a turn by choosing a facing direction.

Confirm Ability Target State

Once a user has selected a direction for their range, or a location within their range (depending upon the range type of the ability), we will enter a new state called ConfirmAbilityTargetState. This state will highlight a (potentially) new set of tiles which shows the area that can be effected by the ability using the current cursor position or facing angle of the active unit.

When the state enters, we will look thru the effect targeting components attached to the ability and determine what, if any, valid targets fall within the selected area. If we have a target, then we will show it in the secondary stat panel. If we have more than one target, you can use the movement input to cycle through which of the targets is displayed in the stat panel.

Note that in the future, when we determine things like the predicted damage of an ability, hit chance, etc. we will use this state to show this information in the UI to help the player make more informed decisions.

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

public class ConfirmAbilityTargetState : BattleState
{
	List<Tile> tiles;
	AbilityArea aa;
	int index = 0;

	public override void Enter ()
	{
		base.Enter ();
		aa = turn.ability.GetComponent<AbilityArea>();
		tiles = aa.GetTilesInArea(board, pos);
		board.SelectTiles(tiles);
		FindTargets();
		RefreshPrimaryStatPanel(turn.actor.tile.pos);
		SetTarget(0);
	}

	public override void Exit ()
	{
		base.Exit ();
		board.DeSelectTiles(tiles);
		statPanelController.HidePrimary();
		statPanelController.HideSecondary();
	}

	protected override void OnMove (object sender, InfoEventArgs<Point> e)
	{
		if (e.info.y > 0 || e.info.x > 0)
			SetTarget(index + 1);
		else
			SetTarget(index - 1);
	}

	protected override void OnFire (object sender, InfoEventArgs<int> e)
	{
		if (e.info == 0)
		{
			if (turn.targets.Count > 0)
			{
				owner.ChangeState<PerformAbilityState>();
			}
		}
		else
			owner.ChangeState<AbilityTargetState>();
	}

	void FindTargets ()
	{
		turn.targets = new List<Tile>();
		AbilityEffectTarget[] targeters = turn.ability.GetComponentsInChildren<AbilityEffectTarget>();
		for (int i = 0; i < tiles.Count; ++i)
			if (IsTarget(tiles[i], targeters))
				turn.targets.Add(tiles[i]);
	}
	
	bool IsTarget (Tile tile, AbilityEffectTarget[] list)
	{
		for (int i = 0; i < list.Length; ++i)
			if (list[i].IsTarget(tile))
				return true;
		
		return false;
	}

	void SetTarget (int target)
	{
		index = target;
		if (index < 0)
			index = turn.targets.Count - 1;
		if (index >= turn.targets.Count)
			index = 0;
		if (turn.targets.Count > 0)
			RefreshSecondaryStatPanel(turn.targets[index].pos);
	}
}

Ability Target State

In order to reach our new state, we need to modify what happens when you use the confirm input during the AbilityTargetState script. Instead of pretending like we just completed an attack, we will first verify that our selection is valid, and if so, enter the ConfirmAbilityTargetState which we just created.

protected override void OnFire (object sender, InfoEventArgs<int> e)
{
	if (e.info == 0)
	{
		if (ar.directionOriented || tiles.Contains(board.GetTile(pos)))
			owner.ChangeState<ConfirmAbilityTargetState>();
	}
	else
	{
		owner.ChangeState<CategorySelectionState>();
	}
}

Perform Ability State

After having selected an ability and a target to apply the ability to, it is time to actually take action. There are a ton of ways this could be implemented, though Mechanim would probably be used since we are focusing on Unity. Ultimately we need some way to have events tied to animation so that we can do something like swing a sword, and then at a specific point in the animation, play a sound and apply the effect of the ability – which in that case would be to reduce the target’s hit points.

This state is sort of a placeholder – I left comments showing potential places for logic to appear. I also added a TemporaryAttackExample method. As the name hopefully implies, this is placeholder code. In a more complete project, I would not directly do the work of an Ability’s Effect in this state. Instead, there would be another class per effect, very much like we did with the Feature component of an item. The real implementation would probably loop through the effects and targets and attempt to apply the effect on each target.

When the animation and application of the ability are complete, we continue onto the next relevant state.

using UnityEngine;
using System.Collections;

public class PerformAbilityState : BattleState 
{
	public override void Enter ()
	{
		base.Enter ();
		turn.hasUnitActed = true;
		if (turn.hasUnitMoved)
			turn.lockMove = true;
		StartCoroutine(Animate());
	}
	
	IEnumerator Animate ()
	{
		// TODO play animations, etc
		yield return null;
		// TODO apply ability effect, etc
		TemporaryAttackExample();
		
		if (turn.hasUnitMoved)
			owner.ChangeState<EndFacingState>();
		else
			owner.ChangeState<CommandSelectionState>();
	}
	
	void TemporaryAttackExample ()
	{
		for (int i = 0; i < turn.targets.Count; ++i)
		{
			GameObject obj = turn.targets[i].content;
			Stats stats = obj != null ? obj.GetComponentInChildren<Stats>() : null;
			if (stats != null)
			{
				stats[StatTypes.HP] -= 50;
				if (stats[StatTypes.HP] <= 0)
					Debug.Log("KO'd Uni!", obj);
			}
		}
	}
}

End Facing State

This state is used to wrap up the end of a turn, instead of simply choosing “Wait” from the ability menu. It allows you a chance to decide which direction you want a unit to face before giving control to the next unit.

In the future we will add some UI here of arrows over the active units head which indicate what you are supposed to be doing.

using UnityEngine;
using System.Collections;

public class EndFacingState : BattleState 
{
	Directions startDir;

	public override void Enter ()
	{
		base.Enter ();
		startDir = turn.actor.dir;
		SelectTile(turn.actor.tile.pos);
	}
	
	protected override void OnMove (object sender, InfoEventArgs<Point> e)
	{
		turn.actor.dir = e.info.GetDirection();
		turn.actor.Match();
	}
	
	protected override void OnFire (object sender, InfoEventArgs<int> e)
	{
		switch (e.info)
		{
		case 0:
			owner.ChangeState<SelectUnitState>();
			break;
		case 1:
			turn.actor.dir = startDir;
			turn.actor.Match();
			owner.ChangeState<CommandSelectionState>();
			break;
		}
	}
}

Command Selection State

If a player chooses Wait from the Ability Menu, let’s go to the EndFacingState instead of immediately ending the turn. Not only does it allow them to face a different direction, but it provides an opportunity for them to “cancel” back out just in case they had chosen to wait by accident.

protected override void Confirm ()
{
	switch (abilityMenuPanelController.selection)
	{
	case 0: // Move
		owner.ChangeState<MoveTargetState>();
		break;
	case 1: // Action
		owner.ChangeState<CategorySelectionState>();
		break;
	case 2: // Wait
		owner.ChangeState<EndFacingState>();
		break;
	}
}

Demo

Expand the Hero prefab’s hierarchy in the project pane and select the Attack object from before. Experiment by adding different combinations and settings for the different ranges, areas, and effect targets (one of each). Then play the scene, and notice that after attacking another unit(s) their hit points will be reduced. Note that you can even change components while the game is playing, so it would be easy to give each of the Units on the board a different configuration.

Summary

In this lesson we wrapped up the selection process for an ability. A user can see how far an ability will reach (range), what area the ability will affect (area of effect), and who within that area will be targeted (effect target). I demonstrated how keeping each of these as separate components makes it easy to have a large number of configurations and adds great diversity to your game.

We also added a few extra battle states to help the process feel more complete. We will need several more UI pieces, and will need to actually create the abilities and their effects themselves, so stay tuned – I plan to get there eventually!

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.

34 thoughts on “Tactics RPG Ability Area of Effect

  1. I’m still loving the series. I had a question about architecture, and this is probably something you are going to get to in a future post (and did cover a bit in your Items post). Say you wanted to be able to equip various abilities at run time (maybe giving 3 random ninja attacks to a ninja from a pool of 6). Each of those abilities would need an AbilityRange, an AbilityEffectTarget, and an AbillityArea component (plus whatever other components we end up adding, like a graphics component and a damage component). Would it be good to store all that information in a spreadsheet (using a string to represent which version of AbilityRange, etc, we wanted on that ability) and then create a factory class that would parse the spreadsheet in the editor to make prefabs that could be called by name? And then store them in something like ResourcesAbilities and use Resources.Load to access them when we need them? Or would it be better to have a AbilityHolder GameObject that holds all the prefab abilities we created and returns them based on the name we give it?

    Thanks for this- I am really learning a ton.

    1. Great question Jordan. As you mentioned, I would probably want to find a clever way to store the data in a spreadsheet and create prefabs from that as we did with Jobs. I would pick “Resources.Load” over a single AbilityHolder which had a reference to all of the prefabs. This is because when you grab a reference to a project asset it actually loads the entire asset into memory. So if you have 100’s of skills each with custom special fx then all of the models, animations, particles, textures, etc would all also be loaded. Even if you had enough memory to spare, it would still increase loading time.

  2. Hello i am wondering about how i can do weapons range weapons ? and how do the AI knows whats its a range weapon because this system its oriented to bare abilities.

      1. As the Demo section suggested, you can use combinations of ranges, areas and effects to get pretty much anything you want. The general idea is that it doesn’t matter “what” is the cause of an attack. You can “say” it was a melee or ranged weapon, or a magic spell, etc. especially if you also play an appropriate animation alongside it.

        The lessons on A.I. show working solutions, so just keep going a bit further and it should hopefully make sense.

        1. But i would like make something like FFT where if in battle your weapon gets destroyed you lose the ability to shoot arrows so i think FFT 1st checks what weapons has the character in order to decide how he would act. Or the weapon adds that ability so his range its increased?

          1. I would probably design it so that a character always has a “weapon” but that could be a “fist” as the “weapon” if no other weapon was equipped or the previously equipped weapon had been destroyed in battle, etc. The combination of components on the weapon would determine what it could do and how far it could reach. Does that help?

          2. I dint think about making the fist a weapon , so this weapon cant be destroyed because its default, so when you equip another weapon new abilities are added like range(in case bows) or elemental stats.

  3. Great stuff. I love the content. Keep it up!

    Also, I recommend everyone to switch SelectUnitState to EndFacingState in Cancel function, on the CommandSelectionState for same reasons if the player chooses Wait from menu, player won’t end the turn immediately.

  4. Hi, I’m having quite a bit of trouble getting this section to work, if I have an AbilityRange script attached to Attack I get a null reference error in the script ConfirmAbilityTargetState for the line that assigns tiles = aa.GetBoardTilesInArea(board, pos)
    I checked aa in the debug and it seems that aa = turn.ability.GetComponent() is not actually assigning anything to aa.
    If I attach an AbilityArea or AbilityEffect script to Attack I get a different null reference error in the script CategorySelectionState for the line that assigns turn.ability = turn.actor.GetComponentInChildren().gameobject. To be sure I have checked this against the scripts found in the AbilityArea commit, I currently have them in place of my own, and I have not deviated from your tutorials thus far. I haven’t checked all of my AbilityRange scripts against yours yet, as before this section everything was working fine. I’ve hit a wall and can’t think why this is occurring, any advice is appreciated.

    1. I think this message system is excluding anything between the greater than and less then signs but I do indeed have aa looking for the AbilityArea component and turn.ability looking for the AbilityRange component.

    2. The final version of Attack has all of the following at the root level: Ability, Physical Ability Power, Line Ability Range, and Full Ability Area
      And these at the Damage child object: A Type Hit Rate, Default Ability Effect Target, and Damage Ability Effect

      I’m not sure, but it sounds like you might be under the impression that you can work with an ability’s range, area, and effect separately, but that is not the case. They all work together. Range is like the potential reach of an ability, area is what portion of the range is in focus, and effect is what happens to the area in focus.

      My guess is that your object hierarchy and or configuration of the attack prefab is wrong, based on the fact that the GetComponent calls are returning null. The only way for the script to be the problem (and not be found by the GetComponent call) is if you forgot to make sure that the concrete subclasses are inheriting from the base class.

      1. You are correct, I have been attaching only one range, area or effect, I didn’t realise that I needed all three, I was thinking of this system kind of like dragon age origins cone abilities where you set the direction of the cone and then you attack everything inclusive of the area, therefore not needing to confirm anything else, my bad. Thank you very much, also I see that I posted this at like 4am, I’m in New Zealand so it was mid day for me haha.

  5. Huh, I appear to be able to do everything except reduce the health of a unit.

    Is it normal for the Tile Selection Indicator to be centered around the unit attacking, or is it supposed to be able to move over the unit that’s being attacked?

    1. There is a video at the end of A.I. Part 2 that shows gameplay you can compare against. While attacking, the TSI is centered on the attacker, but when casting a spell to a different tile, the TSI will be centered on the target tile.

      For the health issue, are you getting any errors when playing? Otherwise it will be hard for me to help. You may need to set breakpoints and step through the logic as the game plays to figure out where the problem is. If you’ve been copying and pasting code, then the problem is most likely another object configuration step that was missed somewhere.

  6. Hello! This is the last I’ll bother you over this, I just wanted some advice.

    So, I still can’t damage my units, but I had the idea for fixing it. I want to have a simple bit of code and have a thing where I can get a unit’s health when they’re selected, and then I’d have a constant button dedicated to a regular attack (Maybe “Z” or something), and let that handle that.

    I wouldn’t need any hit rate for physical attacks, as all of them will be 100% accurate no matter what, but I do want to know how I can make that simple script affected work with the turn order, so after the person attacks, their turn will be over.

  7. Hi! I’ve been following the tutorial for the last couple of days and it’s fantastic, great work! I have a question about combining Cone Range and Line Range with an Area of Effect that isn’t Full Ability Area. In the code of the mentioned Ranges, the position of the cursor is always the position of the Hero because you are only rotating him to place the range, but when you change from AbilityTargetState to ComfirmAbility TargetState the Areas of Effect are placed onto the Hero because the cursor wasn’t moved. How could be changed to select the Units in the range instead of the Hero?

    Thanks for the help

    1. I’m not sure I follow your question exactly, but I think what I have here already does what you are asking for. If you watch the video at the end of A.I. Part 2, http://theliquidfire.com/2015/12/21/tactics-rpg-a-i-part-2/, you should see “Air Blast” (cone range attack) and “Earth Render” (line range attack). Watch how with both attacks the blue highlighted area (which is where damage will be applied) does not include the attacker. Perhaps it merely appeared to include the attacker because the tile selection indicator is still focused on the user of the attack. If that is the case, it wouldn’t be a large effort to simply hide the T.S.I. during that phase of the attack flow.

      1. For what I’ve seen, combining a Line Range attack with a Specify Ability Area puts the Specify Ability Area over the Hero because it rotates to use the attack instead of selection a tile to use it over. My question is if there is a way to put the area over the tiles of the Line Range attack or a target that is in the area.

        Thanks for the help!

        1. If I understand correctly, what you are looking for is:
          1. rotate the hero to determine facing direction which applies to the Line and Cone Ranges
          2. after confirming the facing direction, allow the T.S.I. to move independently of the hero to select a new target within the highlighted range

          Is that correct? If so, it isn’t a supported flow in this series, but should be “easy” enough to create on your own since we already have a working example to copy from. Targeted abilities already let you move the T.S.I. around, so you would just need to update the state / state machine to allow the input to work as you expect when you expect.

          Alternatively, I think it would be much easier to make the range of the ability already include the tiles from each facing direction and then you could treat it very similar to the way we construct other ranged abilities like the casting of magic spells.

          1. Yes, that was exactly what I was asking. I will try to implement the functionality with your suggestions after I finish the tutorial.

            Thank you very much for all the help!

  8. Hello! I still feel grateful for this great tutorial.

    I’m trying to make an ‘AbilityParser.cs’ works similar as ‘JobParser.cs’, with which I can parse my custom CSV file to make ‘Ability Prefab’.

    The CSV file is as following :

    ******************************************
    Name,Range_Type,Range_Horizontal,Range_Vertical,AOE_Type,AOE_Horizontal,AOE_Vertical,AOE_Target
    Fireball,Constant,6,1,Specify,3,1,Default
    BattleCry,Self,0,0,Specify,1,0,Default
    Resurrection,Infinite,0,0,Unit,0,0,Kod
    BreathOfFire,Cone,3,1,Full,0,0,Default
    PiercingShot,Line,0,1,Full,0,0,Default
    MagicMissile,Line,0,0,Unit,0,0,Default
    ******************************************

    I wanted to declare every aspect of abilities(AbilityRange component,AreaOfEffect, Effect Target, and all relevant parameters like Horizontal/Vertical) at this single CSV file.

    Then I had to make ParseAbilities method as below, to match the TYPE STRING at CSV to actual Component :

    ******************************************
    static void ParseAbilities(Dictionary line)
    {
    GameObject obj = GetOrCreate(line[“Name”].ToString());

    string rangeType = line[“Range_Type”].ToString();

    switch (rangeType)
    {
    case (“Constant”):
    obj.AddComponent();
    break;
    case (“Self”):
    obj.AddComponent();
    break;
    case (“Infinite”):
    obj.AddComponent();
    break;
    case (“Cone”):
    obj.AddComponent();
    break;
    case (“Line”):
    obj.AddComponent();
    break;
    }

    var ar = obj.GetComponent();
    if(ar != null)
    {
    ar.horizontal = (int)line[“Range_Horizontal”] == 0 ? ar.horizontal : (int)line[“Range_Horizontal”];
    ar.vertical = (int)line[“Range_Vertical”] == 0 ? ar.vertical : (int)line[“Range_Vertical”];
    }
    }
    ******************************************

    this code works anyway, but doesn’t look elegant enough :p

    Could you please tell me which will be ‘better’ way to make this work?

    1. Good questions. If you want to go deeper on this feel free to post on my forums and it will be a little easier to follow. Two quick suggestions:
      1. You can use reflection to convert from a string to a class instead of needing an ever expanding switch statement for all the various kinds of components. See the post Ability Effects lesson under the header “Inflict Ability Effect” for an example.
      2. You can use polymorphism to allow each component to decide how to load itself when given the data in the csv. As long as they all conform to an interface or shared base class, this should be trivial. You dont care which component is added, you just save a reference to it (as the base data type version), and call that load method.

      1. Thank you for the reply! It’s super kind of you to still care my reply on this old post 🙂

        I’m still studying ’17. Status Effect’, expected to finish tonight! I’m gonna try both suggestion you’ve given after finishing ’19. Ability Effect’ 🙂

        If I have further question, I’ll dive into the forum.

  9. Hi! I don’t know if you keep track of new comments for this post, but I’d like to congratulate you for this amazing series. It’s been around a month since I started this tutorial and you introduced me a lot of incredibly versatile algorithms. I’m not sure if I’m just imagining things, but the steps for this tutorial are getting shorter and easier to understand and I think that it is all because of the solid architectures you approached on earlier steps, this is simply amazing!

  10. Hello,

    In the Demo section of this post, this is where I get lost. “Expand the Hero prefab’s hierarchy in the project pane and select the Attack object from before.”

    I may have skipped some tests in previous posts, going through them all with searching the keyword “attack”, I still was unable to isolate what you mean by the ‘Attack Object from before’.

    Incredible Tut here, and much for me to dig into further to increase my understanding.

    Thank you very much for this.

    -Matt

    1. In the “Demo” section of the previous lesson, “Ability Range”, you are instructed to create the “Attack” child game object on the “Hero” prefab. Hope that helps!

  11. Thank you, I am tearing through your code with lots of head scratching but I love putting the pieces together.

    This is a very lovely project to share with everyone.

    Best,
    Matt

Leave a Reply to mcmustang51 Cancel reply

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