How to cast a certain spell on a unit instead of having the ability's code auto execute when clicked

Hello all,
I am using an RTS Engine asset to make a simple RTS. I just manged to make an ability/spell for my units yesterday. This ability is supposed to instantly kill a unit. My problem is I need to be able to write code that allows me to select a unit with the cursor RIGHT after I click on the ability and then have that ability be used on the clicked unit. This is similar to buffs used in WC3 or WOW. Where you would click on the Buff you wanted to cast, then click on the unit you wanted to cast it on.

What is the best approach to doing this? Should I make a new script and figure it out that way, or is it possible to do this within the same script?

EDIT: I found a solution. I wrote a new script and attached it to the unit casting the spell. It handles most of the spell logic. The only thing I did in the original script was assign a boolean. If this bool is true, then run the Update () of the new script. If it is false, then do not. A method in the new script requires a Unit type to be used as a parameter, this unit is selected via raycast code in the Update ().

For my (limited) gaming experience, that’s a bit backwards. :slight_smile: I’m used to selecting a target first, then using a skill. Nevertheless, if you prefer it that way, it’s your game :slight_smile:
Can you only ever have 1 skill first before the target?
What if you already have a target selected and choose a skill?

What might possibly work for you could be having a ‘current spell’ (or spells) in a variable (or list), and on selecting a target, you can execute them (if non-null/not an empty list)…

It’s generally best to separate functions in your code. If you put it on that script, then it only works with that spell. It might be all right to start the code on that script because it’s convenient or whatever.

Generally speaking, you’ll probably want to have it be two different stages/functions to the same script (it’s easy to make it two scripts later, if some reason comes up that it needs to be). Stage one is where you click a spell using the UI, that signals the central controller and says “a spell has been selected”, and the controller loads that spell into a temp variable like a bullet into a gun. It would then switch over to “select target mode” (probably using some form of state machine) and the UI, listening for that state change, would adjust to allow selecting a target instead of selecting spells. When one is clicked, this sends a signal back to the central controller saying “this is the target”, and now that the central controller has both a spell AND a target, it triggers a function to execute the attack.

Does that make sense / answer the question?

You would need to select the unit creating the spell and then the unit you apply the spell on. Right?

I did a similar solution to what you stated, but without any lists (there is only one spell and one spelled upon unit). I will post solution worked for me in my OP. Thank you for all your help :slight_smile:

I ended up writing 2 new scripts, one of which was scrapped. It all worked out, solution in OP via edit.

Thank you :slight_smile:

Your answer is the best one thus far in this thread. I am not sure if I completely understood what you meant to convey, but it helped me solve the problem regardless! Haha.
Thank you for your help! :smile:

Here is the code, what do you think? (I have a different question if you are able to help?) Why is UIMgr = GameMgr.UIMgr; giving me a null reference error, even though the exact same code (copynpaste) was used in a different script with no errors?

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

public class SlaughterSpell : MonoBehaviour {

    //Scripts:
    public UIManager UIMgr;
    [HideInInspector]
    public GameManager GameMgr;

    bool hasSpellBeenCast;
    Unit selectedUnit;
    public LayerMask lm;

    // Use this for initialization
    void Awake () {
     
        GameMgr = GameManager.Instance;
        //SelectionMgr = GameMgr.SelectionMgr;
        //UIMgr = GameMgr.UIMgr; //why is this null?

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

        hasSpellBeenCast = UIMgr.slaughterToggle;
        //Debug.Log ("hasSpellBeenCast is" + hasSpellBeenCast);

        if (hasSpellBeenCast == true) {

            RaycastHit hit = new RaycastHit ();
            Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
            //Debug.Log ("Slaughter Script is running 2");

            if (Physics.Raycast (ray, out hit, 100.0f, lm)) {
                //Debug.Log ("1");
                if (hit.transform.gameObject.GetComponent<HerdBehavior> () && (Input.GetMouseButtonDown (0))) { //if you click on a herd unit
                    //Debug.Log ("2");
                    Slaughter (hit.transform.gameObject.GetComponent<Unit> ()); //call slaughterspell
                }
            }
        }
    }

    public void Slaughter (Unit herd) {

        herd.GetComponent<HerdBehavior> ().Slaughter();
        UIMgr.slaughterToggle = false; //this resets the bool so that we make sure we only slaughter the first herd unit we select, and so we can use the spell again when we select it
    }
}
//WRITE CODE TO CANCEL SPELL IF RIGHT CLICK
//WRITE CODE TO ALLOW MULTIPLE HERD UNIT SELECTION

My guess is that GameManager.Instance is returning null, and since that’s null you can’t try to pull a UIMgr from it. There’s no way to tell why that’s the case here though- you’ll have to go to the GameManager script, or the script it derives from if you have some sort of SingletonMonoBehaviour base class or something, and find “Instance”. If Instance is a property, you’ll have to read how it works and why it might be returning a null in your scene. Debug.Log at every step through the logical path the code follows, and you’ll find your answer eventually.

Oh, this may or may not be the cause of your problem here, but the general rule of thumb is to do anything involving self-initialization in Awake that does NOT involve accessing other MonoBehaviours, and self-initialize anything that DOES involve accessing other MonoBehaviours in Start. This is because the order that the Awake functions are called in, from one MonoBehaviour script to the next, is not guaranteed by default- an Awake for one script may run before OR after Awake from a different script at times. That means that, when you access GameManager here from Awake, GameManager might not have run its own Awake function yet, and any initialization done there won’t have been performed. Start functions are guaranteed to run after ALL other Awake functions, so it’s far safer to access GameManager.Instance there.

Does that make sense? Just a rule of thumb though- I don’t know if it’s related to your problem.

1 Like

That made a lot of sense and it also helped solve the problem! Thank you!
The whole game engine is based off of using the Awake () function with code from other scripts (managers) with the Game Manager acting as some sort of central hub. It is a singleton thing though, as I just googled “how to code a game manager” and turns out that is what the Game Manager is doing. I managed to fix the problem with the help of the original developer; I went to Edit → Project Settings → Script Executions Order and added my new script in the list (made sure it is after Game Manager).

For the record, at the risk of beating a dead horse since the problem is apparently solved, this is exactly the answer I was trying to avoid giving you. If using Script Execution Order for your own scripts is made necessary by the RTS Engine, then it almost definitely has a flawed design and that needs to be looked into by the developer. You know how to get around the issue now, but for your own projects in the future, please do not use the Script Execution Order this freely or you’ll end up micro-managing your project to death. Simply do the initialization that does not require accessing other scripts in Awake, and initialization that requires accessing other scripts in Start- that’s a far better mindset and can avoid the great great majority of problems that changing the Script Execution Order functions as a band-aid for.

Agreed. That’s good advice. :slight_smile: Only go for the execution order of scripts if you really have a good reason (and need) :slight_smile:

No dead horse around here! I am still learning, and these tips help tremendously.

You see, this is my first game, and instead of going through multiple courses on the subject (because I am too lazy to dedicate all that time and redundant work for those slivers of important information) I decided to learn by doing. In this case, I am learning how to code a game by modifying a game engine and using it to make my own code. This entails learning coding conventions by mimicking code in this engine I purchased. The engine developer is also generous enough to help me. From all of this, I assumed Script Execution Order was a commonly used feature. But now, because of you, I know better!

^ This is gold. Thank you for your help!