#Referencing another script
1 messages Β· Page 1 of 1 (latest)
hello
Post the other script as well, but in a link - https://gdl.space
Alright, I believe I see the issue.
in the script Enemy
spawner = GetComponent<EnemySpawner>();
GetComponent used this way, targets the game object of the Enemy (game object of the script)
But you need to get the EnemySpawner of another object
To fix this, I just need to ask one thing - is SpawnEnemies() working as intended?
I do not often see do-while loops
yes, it works fine, stops spawning enemies when 10 of them spawn
because i am a beginner i thought of using a do while loop because it seemed the easiest, what should I have gone with instead?
if that loop started when there were 10 enemies spawned, it would spawn another, before checking the while condition
basically, do-while always runs once
while doesn't do that, it checks the condition first
Anyway, since you have a spawner, you can actually get the component of the Enemy, and add the reference to the spawner.
should i do this instead?
IEnumerator SpawnEnemies()
{
while (currentEnemyCount < 10)
{
yield return new WaitForSeconds(3);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
currentEnemyCount++;
enemiesSpawnedCount++;
}
}
}
That looks good. Make sure it behaves as intended.
when i try to start the game my unity freezes completely now, i have to turn it off with task manager π€
um, okay, regret the changes, and see if that solves it.
it runs now yes
Not sure how that change messed things up really.
yield return new WaitForSeconds(3);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Enemy enemy = Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
enemy.spawner = this;
currentEnemyCount++;
enemiesSpawnedCount++;
What
Maybe I'm confusing things, but which Unity version are you running?
Ah right
uhh my editor version is 2021.3.11f1
yield return new WaitForSeconds(3);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Enemy enemy = (Enemy)Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
enemy.spawner = this;
currentEnemyCount++;
enemiesSpawnedCount++;
try that
but enemy.spawner is private, so in this case make a public method to set it instead
I'm going to make an example myself, brb
hmm
I'm having a discussion with some friends π
Seems the summer wiped some stuff.
But another solution is to make your SpawnManager have a static instance, which the other scripts can freely access.
It is called singleton.
@rough grove
PS: You can use the solution I set up here, if you
// Change this
public GameObject enemyPrefab;
// To that
public Enemy enemyPrefab;
did the changes, unity still freezes
no errors now, but it still freezes
Post your current code again.
using UnityEngine;
using System.Collections;
public class EnemySpawner : MonoBehaviour
{
public Enemy enemyPrefab;
public int enemiesSpawnedCount = 0;
public int currentEnemyCount = 0;
public int enemiesKilled = 0;
// Get coordinates
public float minX;
public float maxX;
public float minY;
public float maxY;
void Start()
{
StartCoroutine(SpawnEnemies());
}
void Update()
{
if (enemiesSpawnedCount == 10)
{
StopCoroutine(SpawnEnemies());
}
}
IEnumerator SpawnEnemies()
{
while (currentEnemyCount < 10);
{
yield return new WaitForSeconds(3);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
}
}
}
This happened because you changed the do-while to a while again?
wait so do i use a do while instead of a while/
That's what your original code did.
I suggested the while loop instead, but once you changed it, the game froze.
So I asked you to revert it, because it was an unnecessary change, and I'm not immediately sure what caused that to freeze.
ah okay my bad i thought the new code you sent will maybe fix the issue, anyways, with the updated code i get these 2 errors now, the first one is when an enemy spawns and the 2nd one is when i kill it, also only one enemy spawns now
using UnityEngine;
using System.Collections;
public class EnemySpawner : MonoBehaviour
{
public Enemy enemyPrefab;
public int enemiesSpawnedCount = 0;
public int currentEnemyCount = 0;
public int enemiesKilled = 0;
// Get coordinates
public float minX;
public float maxX;
public float minY;
public float maxY;
void Start()
{
StartCoroutine(SpawnEnemies());
}
void Update()
{
if (enemiesSpawnedCount == 10)
{
StopCoroutine(SpawnEnemies());
}
}
IEnumerator SpawnEnemies()
{
do
{
yield return new WaitForSeconds(3);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
}
while (currentEnemyCount < 10) ;
}
}
double-click the first error, and tell me which line in which script it points to.
line 39 in EnemySpawner.cs
Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
Are you certain you shared the most up-to-date code?
yes, this is the code i have in my enemyspawner.cs
still the same two errors
Hm. You know what. I'm going to look into this approach later today. I think it has just been too long since I actually did it, but right now I need to leave the chat and take care of a friend in need.
But there is a solution, and that one will work - Singletons.
You basically make the EnemyManager have a static variable, that contains itself.
Statics are accessible to all scripts in the same namespace, so there's no need to use GetComponent.
What I was trying to achieve with the approach, was for every Enemy to have a reference to the EnemyManager. And the solution with Instantiate seemed fastest, but a singleton is more the right approach.
Sorry for wasting your time with this rust in my mind.
I'll come back in a few hours to help fix it if you haven't already.
yo dont worry i appreciate that you tried to help me ill try to use singletons and ill let you know if i fix the issue, take care!
Hey, thanks π
Still a while from available. Did you figure it out?
nope, i tried to use the singleton but i couldnt figure out where to use it and stuff, got way too confused, i just launched unity again and ill try fixing it
by the way, this i had to revert this code because it would only spawn one enemy, but i can replace it again in the future if your code is needed ```cs
// Change this
public GameObject enemyPrefab;
// To that
public Enemy enemyPrefab;
Alright. I'll see if I can squeeze in a singleton explanation before going afk again.
Singleton is just a fancy word for a script with a static instance
you could also make a regular static C# class,
but using a singleton MonoBehaviour lets you load/unload that behavior, saving resources.
Plus, it ties in better with game logic
When the class is static, it means you can type ClassName.Method() or ClassName.Property from any other script.
ClassName.instance.Method() etc in the instance of singletons, due to the static instance variable that connects it.
Good luck. I'll be back.
this is where i tried to learn about singletons but i coudlnt figure out if i have to make a seperate script for it or do i write the code in enemyspawner.cs
Just replace Singleton with EnemySpawner
the instance type is the script name
like this?
That produces an error, which makes it obviously wrong.
Singleton is a pattern, not a thing.
It is simply the same script you're working on, but with a static ScriptName instance; field
You still need to add the rest from that article, so that only one of that singleton ever exists.
Read everything. It's very well explained.
ive reread it multiple times but i still dont really get it, is this more like it? although i get one error
Remove the "Singleton" class declaration.
Everything inside it goes inside the EnemySpawner class.
I'm free for a bit now. Have you figured it out?
nah not at all man im struggling really hard
Alright. Post your updated code again. Both scripts. https://gdl.space
@rough grove
If you're using Visual Studio Community, this will fix code indentation mess: CTRL + K + D
yeah that fixed it, do you want me to resend the code?
Alright. This looks good. Just a few missteps to go over.
This one is two mistakes:
First off, the line would not work that early in Awake()
(Also, it doesn't need to get its own enemiesSpawnedCount)
And the int enemiesSpawnedCount inside Awake() is actually a local variabe, and only exists within its parent Method, which is Awake().
public int enemiesSpawnedCount;
int enemiesSpawnedCount = EnemySpawner.Instance.currentEnemyCount;
The logic here is recursive.
This is the EnemySpawner script.
It has been given a static instance, so that other scripts may access it.
So line 37 shouldn't be there. It doesn't make sense. Agreed?
The reason the Null Reference Exception happens, is because you run this on line 37, but instance isn't being defined until line 46. So at line 37, instance doesn't exist yet, it is null.
ohhhhhhh i see
Either way, line 37 doesn't need to exist.
You've already defined the instance variable, and the singleton is referenced from your Enemy script.
It kinda looks good to go.
it works!!
Yeah I'm pretty sure you solved this an hour ago, and line 37 has just been holding you back.
Good stuff!
now it does -1 to "currentEnemyCount" when i kill an enemy which is exactly what i wanted
Now, I'm curious if we can fix that do-while loop, if you're up for it
Is it spawning multiple enemies?
let me see
hmm okay so it spawns 10 enemies, i killed one of them and it didnt spawn another (but it should due to while (currentEnemyCount < 10);)
oh wait
Indeed, but the do makes the code run once regardless, and then it starts checking the while-condition
void Update()
{
if (enemiesSpawnedCount == 10)
{
StopCoroutine(SpawnEnemies());
}
}
``` i have this which is stopping the coroutine right?
Actually, I believe you can only stop a coroutine by string
maybe I'm remembering old doctrine
confirm if it works
hmm wait one sec
If it doesn't work, it should be fixed by rewriting the start and stop
StartCoroutine(nameof(SpawnEnemies));
StopCoroutine(nameof(SpawnEnemies));
The video I'm recalling is actually from last month, so I'm pretty sure your original solution will Not stop the coroutine.
yeah it doesnt stop it
Alright, then the above rewrite should fix it
nameof(SpawnEnemies) == "SpawnEnemies"
where do i write this?
Nowhere. I just told you in programming-speak what it does
ahh ok
== is equal to
yep now it stops the coroutine
can i use SetActive on the couroutine to pause it when 10 enemies spawn, and resume it when the player kills 10 enemies?
You don't need to stop the coroutine to achieve that effect.
The while condition will ensure that it doesn't spawn while the condition is false.
ahh ok
For optimization, it is a good idea, but not critical right now.
That's the idea, but the last time you did this, you said Unity froze.
So BEFORE you do it
Save the Project
It's really important to save before making experimental loops π
One infinite loop and all unsaved changes are lost.
yeah i save them dont worry, if the code is right > https://gdl.space/tihobijive.cs then unity doesnt freeze anymore, i can now start the game and play it
Looks good. Try it out.
yeah the game works fine, it stops spawning the enemies after 10 of them spawn
now i need to do the 10 kills to resume the loop thing?
ah, you're actually using enemiesSpawnedCount for that one
ah wait
I misread
so you want to kill all enemies, and then they respawn one by one?
Then it makes sense to stop it.
ah yes, remove the ;
it isn't there when it's a normal while-loop
oh yeah mb dunno why i wrote it
I think it is required in a do-while
aka do-loop
sec
void Update()
{
if (enemiesSpawnedCount == 10)
{
StopCoroutine(nameof(SpawnEnemies));
}
if (enemiesKilled == 10)
{
StartCoroutine(nameof(SpawnEnemies));
}
}
Two things:
You probably want to track the total kills, but we also need to count the kills between each stop/start.
Create private int enemiesKilledTotal;
i actually had this exact code written a few minutes ago but i deleted it because i thought it was going to be bad using another if method for some reason but i guess not lol
We'll get to that :))
can i ask why its private? is it because we're only accessing it in the same script?
i dont really understand public/private fully yet
yes, I see no reason for it to be public, especially with what I have in mind
public means it is accessible from other scripts
ok got everything written
ahh okay
also
in the inspector
[SerializeField] private and public both "serialize" the field so that Unity can work with it (drag and drop in the inspector)
but you can also have
[HideInInspector] public
So anyway
you make the KillsTotal variable
and now we're going to create one or two public methods, so that we can automate some of the logic, and not worry about it outside the script itself
public void EnemyKilled()
{
currentEnemyCount--;
enemiesKilled++
enemiesKilledTotal++;
}
place that inside EnemySpawner
and in the Enemy script, line 23:
EnemySpawner.Instance.EnemyKilled();
okay i see what we're doing
Last thing:
so i will have to use EnemySpawner.Instance.whatever to access anything right?
Yes
got it
on the line above StopCoroutine
enemieskilled -= 10;
wait is this code even doing anything actually? i can delete it and i get no errors
A static is globally accessible, hence there can be only one (of each static class).
Singletons have a static instance, but fundementally derive from MonoBehaviour.
So if you were to spawn in yet another clone of the EnemySpawner, things would get messy fast, but that code keeps it clean, and makes sure instance is only set if it is null beforehand, and if it exists already then the object realizes it is a doppelganger and destroys itself.
Let's say you start the game and load the first scene.
It has EnemySpawner.
Then you kill 10 enemies 3 times, and load a new scene.
The new scene also has EnemySpawner.
If that new spawner sets the EnemySpawner.instance to be itself,
then it will start off with blank values, and all the data from the first scene is separated, because the new spawner overrode itself as the static.
hmm okay
The old data is still there, but you'd have to make the first spawner set itself to instance again
Normally, GameObjects are destroyed between scenes, but
on singletons there is usually also DontDestroyOnLoad()
which you have not added here
also now im not tracking the "currentEnemyCount", can i just add it like this?
public void TakeDamage(int damageAmount)
{
currentHealth -= 15;
print(currentHealth);
if (currentHealth <= 0)
{
Destroy(gameObject);
EnemySpawner.Instance.EnemyKilled();
currentEnemyCount++; <--
}
}
ohh okay
Actually, I noticed some changes in the code I've been reading. I need your EnemySpawner script again to be sure.
I think you might be confusing the logic
the line above it triggers EnemyKilled()
which does currentEnemyCount--
currentEnemyCount++ should be in the while loop
oh yes i meant to add it in the while loop shit
just like i had it before
so to keep track of currentEnemyCount and enemiesSpawnedCount i can just do ++; on them both below?
yield return new WaitForSeconds(1);
Vector2 randomPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
Instantiate(enemyPrefab, randomPosition, Quaternion.identity);
Yes
And inside Update() in the first if-statement
// Instead of
if (enemiesSpawnedCount == 10)
// Do this
if (currentEnemyCount == 10)
I think that should take care of the technical logic.
If you're up for more, I have two or even three specific goals in mind.
- Improve script structure
- Events instead of polling
- Display stats in UI
yes everything works perfectly
the enemiesKilled gets reset but I think thats fine for now, I was maybe thinking of keeping track of it if i were to make upgrades in the future you know, because it resets to 0 when i get 10 kills
Alright. BTW I just realized you don't need an event, because it is already running on the OnCollision event and Coroutine.
I want to suggest a final change
Create private void EnemySpawned()
Move the number++ logic into it (from the coroutine SpawnEnemies)
And then finally, move the two if-statements from Update()
into the bottom of EnemyKilled and EnemySpawned respectively
Then you're not polling every frame in Update
enemiesKilled is supposed to be reset, so it keeps stop/starting the Coroutine
that's why we made enemiesKilledTotal, to keep track of the total sum
Anyway, you've done great.
Let me know if you have any questions about my last suggestion.
oh right i forgot about that
one second making the changes right now
Yeah, just one thing we both missed
== 10 but in the coroutine you do < 15
make an int variable, and use it instead of hardcoding the numbers
maxEnemies has a nice ring to it
do i do while (currentEnemyCount < maxEnemies)? then I can set the maxEnemies in the inspector tab
yep i was gonna ask if i should do that but i wasnt sure if that was the case
so if (enemiesKilled == maxEnemies) and if (currentEnemyCount == maxEnemies)? what about the enemiesKilled -= 10;
That one, too.
and
now you don't need the ints to be public
And you can remove Update() entirely
public GameObject enemyPrefab;
private int enemiesSpawnedCount;
private int currentEnemyCount;
private int enemiesKilled;
private int enemiesKilledTotal;
public int maxEnemies;
so like this?
yep did it
Double Yep
now the script stops the couroutine when the "currentEnemyCount" hits 10, but if i kill the enemies while theyre spawning, I can technically never stop the couroutine, maybe it would be smarter to somehow make it so it ends the couroutine every 10 enemiesKilledTotal?
or is that a stupid idea
no that's possible, I'm just not sure it's practical for statistics
Say you wanted to increase by 5 enemies per cycle
Then that formula breaks
Sec
post your script again
add private int enemiesToSpawn;
yes done
it's over 2am, just give me a few mins π
all good dont worry its 3 am here im almost dying myself lmao
Deep sleep after this lol
same lol
would this script that we're doing be viable if my game were to have a wave system? wave 1 = x enemies, wave 2 = x enemies and so on, probably just set values, no need to multiply them by anything as if you die you lose everything (roguelike game)
Yes, but you could also multiply them or make a simple formula.
ill finish this enemiesToSpawn feature and ill go to sleep probably, i can barely keep my eyes open lol
if that doesn't work, we'll fix it tomorrow
I renamed the int btw
inverse logic
works perfectly!
it should π€£
i genuinely want to thank you for your help today youve spent literally hours helping me and we actually got so far, i wont say i understood everything we did completely but i definitely learnt something!!
i get this "error" once in a while not sure why but it doesnt seem to harm my project
You're welcome π At this point I have hours to spend. It takes time to absorb everything. I hope this experience improved your understanding. Sleeping does wonders for learning.
yeah I definitely understood way more than when I opened unity today
Hm. Some error and warnings are due to bugs. This could be one. But it's only a warning, so shouldn't be troublesome for simple projects.
Restarting Unity can help.
yeah i think it stopped after restarting
okay ill go to bed wont hold you for any longer once again i appreciate the help a lot, for tomorrow ill try to do some ui stuff, track the kills and stuff, in my mind it doesnt seem hard but we'll see how it goes :D ill probably close this thread and message you or @ you if I have any questions tomorrow?
Keep the thread open and message me (in this thread) if you want help specifically with UI Text. It's a much shorter affair.
Good night dude o/
alright ill keep it open then, goodnight!
using TMPro;
//...
[SerializeField] private TMP_Text ui;
//...
// in Update or run on any change
ui.text = $"text {variable} text {anotherVariable}";
hey goodmorning, should i make a different script for the text ui stuff?
Not necessary.
Just add the lines where appropriate.
Remember to drag and drop the UI object into the ui field.
@rough grove Oh, and use a TMPro text object.
I don't recall if the Unity default UI is still in 2021, but it's beyond bad, so always use TMPro
TextMeshPro*
i only see the text in my "Scene", not in my "Game" scene, is this something to do with the layers?
are you sure it's not the other way around?
Where was the Canvas when you first created the object?
UI objects need to have a parent Canvas.
Cameras render canvases.
Canvases contain objects.
Where in Scene View could you see the Canvas when it was first spawned?
is it like 100 times larger than the camera view itself?
Screenshot the component settings of the Canvas
Hm. Weird.
ok i fixed it
yo, i have to go for like 3-4 hours (ill be able to respond but not code cause ill be out of home for now), ill message you whenever im back if you dont mind!
That's alright. Just keep it in the thread. Once that topic is finished, we'll close it. You'll get faster help from asking the room.
Last thing I can offer to help you create, is an FPS counter.
im available now
sure!
Fell asleep early today / last night
Well, it's Saturday and you've done good work, so here's the simple FPS Counter I helped a guy make some time ago.
using UnityEngine;
using TMPro;
public class FPSCounter : MonoBehaviour
{
private TMP_Text uiText;
private int frameCount;
private float timeCount;
private float framesPerSecond;
private float pollingRate = 1f; // Update interval in seconds
void Awake()
{
uiText = GetComponent<TMP_Text>();
}
void Update()
{
frameCount++;
timeCount += Time.deltaTime;
if (timeCount >= pollingRate)
{
framesPerSecond = frameCount / timeCount;
uiText.text = framesPerSecond.ToString("F2");
frameCount = 0;
timeCount = 0;
}
}
}
In the simplest form, FPS can be calculated by 1/Time.deltaTime
But doing that every frame would be overly exhausting to watch.
That is why one usually adds pollingRate, to only calculate FPS at specific intervals
In this solution, the FPS counter keeps statistics for all frames and time, between each time of polling, to get a true average.
The F2 in ToString will limit the output to 2 decimals.
F0 is no decimals.
Since it has a private TMP_Text variable, without serialization,
and it uses GetComponent in Awake, that means the script needs to be attached to that UI game object.
if you are ever getting a reference from a different game object than the script is attached to,
then you have to get that component (whichever way) in Start() which runs after all other objects in the scene have awoken.
@rough grove Did you try it out?
i did but i couldnt get it to show up in my game scene, i think i just didnt set it up properly