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. Iâm used to selecting a target first, then using a skill. Nevertheless, if you prefer it that way, itâs your game
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.
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
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!
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.
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.
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!