Loading next level Unity c# - c#

Heres my first attempt at loading the next level upon reaching "Goal"
public int CurrentLevel = 0;
public static int maxlevel = 3;
public void CompleteLevel()
{
if (CurrentLevel < maxlevel)
{
CurrentLevel += 1;
SceneManager.LoadScene(CurrentLevel);
}
else
print("you win");
}
This is in another class...
void OnTriggerEnter(Collider other)
{
if (other.transform.tag == "DeathObject")
Die();
if (other.transform.tag == "Goal")
Manager.CompleteLevel();
}
It ended up loading the first level, went to the second, then it would repeat the second map when i reached goal. Another thing it would do is skip to the last level from the first or second level.. not sure whats going on there. Heres the second thing i tried:
public void LevelPassTest()
{
if (Application.loadedLevel > 4)
Application.LoadLevel(Application.loadedLevel + 1);
else
print("win");
}
This is in another class....
void OnTriggerEnter(Collider other)
{
if (other.transform.tag == "DeathObject")
Die();
if (other.transform.tag == "Goal")
Manager.LevelPassTest();
}
I'm not sure why, but sometimes it doesn't load the next level, it worked for a while then stopped, after I reset unity it works again, so I'm not sure if it's my code or unity... I get a warning message that says "Warning CS0618 'Application.loadedLevel' is obsolete: 'Use SceneManager to determine what scenes have been loaded'" and "Warning CS0618 'Application.LoadLevel(int)' is obsolete: 'Use SceneManager.LoadScene'" I'm new to coding and completely lost on this. spent 4 hours trying to fix it. What are these warnings? can they be effecting the loading? I was also wanting to be able to save the value of the level so i can use it in game saves, and attach high scores to it. I tried using this in my first attempt, but it didn't work how i was hoping. (CurrentLevel)

Application.LoadLevel and the rest are obsolete as of Unity 5. You have to use SceneManager instead.
So Application.LoadLevel() is now SceneManager.LoadScene().
You must also include using UnityEngine.SceneManagement;
See here: http://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html
All it takes is a quick Google.

Are your scenes correctly added to the Build Settings?
Go to File > Build Settings... and check in "Scenes In Build" whether all your scenes from scene #0 to scene #3 are added the way you need them.
The first example you gave should work, however you have to make sure, that your first script (containing CurrentLevel) is not destroyed on loading the next scene. Do this by calling GameObject.DontDestroyOnLoad(gameObject) in its void Start(). Alternatively you can make CurrentLevel a static member.
This is necessary so the value of CurrentLevel is not lost when loading the next scene. This would explain why you kept loading the same level over and over.
In your second example, in LevelPassTest() you check if Application.loadedLevel > 4, this of course has to be Application.loadedLevel < 4. If you fix this, the second example should work just fine, too.
And yes, you are partially working on obsolete API, but that does not explain why it is not working.

using UnityEngine.SceneManagement;
and then use SceneManager.LoadScene() the same way we used to use Application.LoadLevel(), in unity 5.x.x Application.LoadLevel is obsolete - meaning it will be removed in the future and should not be used, and it is not guaranteed that it will work properly.
I recommend you read Unity's documentation on Scene and SceneManager classes so you can get familiar with the changes unity 5 introduced.

Related

Parameter 'Animator' does not exist

I'm new in unity so I need some help with calling the animations. I'm trying to call an animation on a third collision and I have no errors except this one. I have no idea what to do.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Lose_Collider : MonoBehaviour
{
int count = 0;
public int score = 0;
public Animator boom;
void Start() {
boom = GetComponent<Animator>();
}
private void OnTriggerEnter2D(Collider2D collision){
if(collision.gameObject.name.Contains("UFO")){
Destroy(collision.gameObject);
count++;
score -= 500;
if(count == 1){
Destroy(GameObject.Find("HP 3"));
} else if (count == 2){
Destroy(GameObject.Find("HP 2"));
} else {
Destroy(GameObject.Find("HP 1"));
boom.SetTrigger("ExpAnimator");
//boom.SetTrigger(collision.gameObject.name.Contains("UFO").ToString());
//Destroy(gameObject);
//SceneManager.LoadScene("Main Menu");
}
}
else if(collision.gameObject.name.Contains("Star")) {
score += 100;
Destroy(collision.gameObject);
}
}
}
Firstly, that is not an error, it is a warning. The difference being that with warnings, the game is able to still run. Warnings are helpful tips or reminders that something is not exactly right, but there is built-in error handling to quietly deal with the issue. I would never leave warnings as they exist to let you know that you have most likely missed something in your code, created unintended functionality, or have created a bug. Your particular warning is stating that your Animation Tree does not contain an Animation Parameter called ExpAnimato.
I am going to make an assumption and say you do not have an Animation Paramater named ExpAnimator but simply want to play the animation or a state in it. If you want to play an Animator, simply call boom.Play. If you want to play a specific state inside of the animation on the animator, call boom.Play("YourStateNameHere"). There are other parameters to this function call that can alter how the animator handles the state update.
The other issue I am seeing with your code is you have not assigned the Boom reference. As the field is already exposed and serialized in the inspector, assign it by dragging the gameObject from the scene into the field. Right now you are also probably getting a null reference exception when you start the game about how boom is un-initialized.
If you want to use a parameter to change the state from say the entry state of Idle to your ExpAnimation, you need to add a new parameter to your animation state machine. I could try to explain it, but I find it is easier if you watch someone do it. There are no code examples in this tutorial, just the setup of the actual Animation State Machine which I think is what you are not familiar with.

How to fix unintended scene buildIndex of -1 in Unity

Essentially, after testing a few level prototypes, I've decided to stick with my current game idea so I started creating a GameManager to control the flow of levels, etc. I don't have any additional libraries or asset packages being used outside of the default, but for some reason the buildIndex of all of my scenes is -1, which I learned according to the API, means that they're supposed to be loaded through an AssetBundle. Now I can't load anything with SceneManager and I'm not sure how to move forward. I did temporarily have the 2d-extras-master folder in the project as I assumed I'd be using it, but removed it after realizing I wouldn't need it. Does anyone know how to reset the buildIndices of my scenes to the values in the Build Settings? Any/All help is greatly appreciated!
Edit: I should also mention, that the latest Scene I added (when I still had the 2d-extras-manager) still retained a normal buildIndex of 0
Edit #2: So I've found that I can access the buildIndices of the other scenes amongst themselves. It's only when I try to access the buildIndices from my MainMenu Scene that things don't work
Edit #3: I've found a fix, but it doesn't necessarily answer the question. I found that I can force the LoadScene function to work if I know what the buildIndex is, but if I search for it via a scene name, it would return -1.
ex.
// where 1 is the buildIndex of Scene "Main" in BuildSettings
// works
SceneManager.LoadScene(1);
// doesn't work
int index = SceneManager.GetSceneByName("Main").buildIndex; //returns -1
SceneManager.LoadScene(index);
// also doesn't work (Where current scene is buildIndex 0)
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
SceneManager.GetSceneByBuildIndex or SceneManager.GetSceneByName and functions alike work only on loaded scenes. If scene isn't loaded it will return -1. Reason is, you can load scenes additively, so it's possible to have even 5 loaded scenes.
It's true that the api is lacking in many ways.
In your case you are trying to use it right after SceneManager.LoadScene(1);, int index = SceneManager.GetSceneByName("Main").buildIndex; returns -1 most likely because scene will be loaded on the next frame "officially".
You could try to wait 1 frame in coroutine and then do that. But usually you will have scene index or scene name to load/save it and not care about logic after scene has been loaded. You can create a manager which will store all of the loaded scenes where you could look up their build indices if you need them to decide what scene to load next.
Alternatively you can use Addressables package which handles a lot, it's hard to get into but scene loading works way better there in my opinion.
In the build menu, accessed by clicking in File on Unity Editor you can organize your scenes and add them to the flow like in this image
I'm not sure of it, but I'm supose when you didn't assigned a Scene to the flow, it get sceneIndex = -1. So load a scene, go to BuildSettings, add it to the build flow, do the same to all of them. I think this way you can solve it.
You can use this method to get buildIndex.
https://docs.unity3d.com/ScriptReference/SceneManagement.SceneUtility.GetBuildIndexByScenePath.html
And after checking the validity of the buildIndex, if the buildIndexis not equal to -1, then the scene exists in the build settings.
public static bool SceneExists(string scenePath)
{
return SceneUtility.GetBuildIndexByScenePath(scenePath) != -1;
}

Unity StateMachineBehaviour : Start function called once?

i'm making my first enemy AI in unity. I'm trying to make a finished state machine with the animator controller to do it.
I just discovered the StateMachineBehaviour script which is called when the AI is in a state. It has multiple methods, including the OnStateEnter. It is called everytime the AI enter the state.
My problem is only about optimization, my AI need to get the GameObject "Player" in order to attack it. So i'm getting it in my OnStateEnter method for the moment, which i feel is bad, because i'm getting it every time the animation is called, i would like to get it only once, at the start.
I basicly need a start function but it's not working, i have made research and found nothing. I tried to watch video about people making a finished state machine but they are just getting the same GameObject multiple time ( example here : https://youtu.be/dYi-i83sq5g?t=409 ).
So, is there a way to have a start function or to get an element only once ?
I could make a bool that is called only the first time and that get the GameObject, but again that would be an "useless" if running in my function.
Any suggestions ? Thanks
No, unlike a MonoBehaviour a StateMachineBehaviour has no Start message only OnStateEnter, OnStateExit, OnStateIK, OnStateMove and OnStateUpdate.
There are also Awake and OnEnable but I'm pretty sure they are not used in the StateMachine and might not behave as expected.
You can however either use
OnStateMachineEnter
Called on the first Update frame when making a transition to a StateMachine. This is not called when making a transition into a StateMachine sub-state.
Or use a simple bool flag like
bool alreadyExecuted = false;
OnStateEnter()
{
if(alreadyExecuted) return;
// Do your stuff
alreadyExecuted = true;
}
(Just a guess)
In the Inspector you actually can enable and disable StateMachineBehaviours like components. So it might be able to do this also in script maybe the same way using
enabled = false;
but I didn't find anything about it in the API and since I'm currently on my Smartphone I can't test it.

Unity game manager. Script works only one time

I'm making simple game manager. I have a script, which will be accessible from all scenes in the game. And I need to check values of its variables after loading new scene. But my code runs only once after starting the simulation while an object with this script exists in all scenes. What is wrong? Why doesn't it work after loading a new scene?
In every Unity project you must have A PRELOAD SCENE.
It is quite confusing that Unity does not have a preload scene "built-in".
They will add this concept in the future.
Fort now you have to click to add a preload scene yourself.
This is the SINGLE GREATEST MISUNDERSTANDING for new programmers trying Unity!
Fortunately, it is extremely easy to have a preload scene.
Step 1.
Make a scene named "preload". It must be scene 0 in Build Manager.
Step 2.
In the "preload" scene make an empty GameObject called, say, "__app".
Simply, put DontDestroyOnLoad on '__app'.
Note:
This is the only place in the whole project you use DontDestroyOnLoad.
It's that simple.
In the example: the developers have made a one-line DDOL script.
Put that script on the "__app" object.
You never have to think about DDOL again.
Step 3
Your app will have (many) "general behaviors". So, things like database connectivity, sound effects, scoring, and so on.
You must, and can only, put your general behaviors on "_app".
It's really that simple.
The general behaviors are then - of course - available everywhere in the project, at all times, and in all scenes.
How else could you do it?
In the image example above, notice "Iap" ("in-app purchase") and the others.
All of your "generally-needed behaviors" - sound effects, scoring, and so on - are right there on that object.
Important...
This means that - of course, naturally -
...your general behaviors will have ordinary Inspectors, just like everything else in Unity.
You can use all the usual features of Unity, which you use on every other game object. Inspector variables, drag to connect, settings, and so on.
(Indeed: say you've been hired to work on an existing project. The first thing you will do, is glance at the preload scene. You will see all the "general behaviors" in the preload scene - sound effects, scoring, AI, etc etc. You will instantly see all the settings for those things as Inspector variables ... speech volume, playstore ID, etc etc.)
Here's an example "Sound effects" general behavior:
Looks like there's also a "voice over" general behavior, and a "music" general behavior".
To repeat. Regarding your "general behaviors". (Sound effects, scoring, social, etc etc.) These CAN ONLY GO on a game object in the preload scene.
This is not optional: there's no alternative!
It's that easy.
Sometimes engineers coming from other environments get caught up on this, because it seems like "it can't be that easy".
To repeat, Unity just plain forgot to "build-in" a preload scene. So, you simply click to add your preload scene. Don't forget to add the DDOL.
So, during development:
Always start your game from Preload scene.
It's that simple.
Important: Your app will certainly have "early" scenes. Examples:
"splash screen"
"menu"
Note. Tou CAN NOT use splash or menu as the preload scene. You have to literally have a separate preload scene.
The preload scene will then load your splash or menu or other early scene.
The central issue: "finding" those from other scripts:
So you have a preload scene.
All of your "general behaviors" are simply on the preload scene.
You next have the problem of, quite simply, finding say "SoundEffects".
You have to be able to find them easily, from, any script, on any game object, in any of your scenes.
Fortunately it is dead easy, it is one line of code.
Sound sound = Object.FindObjectOfType<Sound>();
Game game = Object.FindObjectOfType<Game>();
Do that in Awake, for any script that needs it.
It's honestly that simple. That's all there is to it.
Sound sound = Object.FindObjectOfType<Sound>();
Tremendous confusion arises because of the 100s of absolutely wrong code examples seen online.
It really is that easy - honest!
It's bizarre that Unity forgot to add a built-in "preload scene" - somewhere to attach your systems like SoundEffects, GameManager, etc. It's just one of those weird thing about Unity. So, the first thing you do in any Unity project is just click once to make a preload scene.
That's it!
A Detail...
Note that, if you really want to type even less (!) lines of code, it's remarkably easy - you can just use a global for each of these things!
This is explained in detail here , many folks now use something like this, a Grid.cs script ...
using Assets.scripts.network;
using UnityEngine;
static class Grid
{
public static Comms comms;
public static State state;
public static Launch launch;
public static INetworkCommunicator iNetworkCommunicator;
public static Sfx sfx;
static Grid()
{
GameObject g = GameObject.Find("_app");
comms = g.GetComponent<Comms>();
state = g.GetComponent<State>();
launch = g.GetComponent<Launch>();
iNetworkCommunicator = g.GetComponent<INetworkCommunicator>();
sfx = g.GetComponent<Sfx>();
}
}
Then, anywhere in the project you can say
Grid.sfx.Explosions();
It's just that easy, that's the whole thing.
Don't forget that each of those "general systems" is on, and can only be on, the DDOL game object in the preload scene.
DylanB asks: "During development it's quite annoying that you have to click to the preload scene every time before you click "Play". Can this be automated?"
Sure, every team has a different way to do this. Here's a trivial example:
// this should run absolutely first; use script-execution-order to do so.
// (of course, normally never use the script-execution-order feature,
// this is an unusual case, just for development.)
...
public class DevPreload:MonoBehaviour
{
void Awake()
{
GameObject check = GameObject.Find("__app");
if (check==null)
{ UnityEngine.SceneManagement.SceneManager.LoadScene("_preload"); }
}
}
But don't forget: what else can you do? Games have to start from a preload scene. What else can you do, other than click to go to the preload scene, to start the game? One may as well ask "it's annoying launching Unity to run Unity - how to avoid launching Unity?!" Games simply, of course, absolutely have to start from a preload scene - how else could it be? So sure, you have to "click to the preload scene before you click Play" when working in Unity - how else could it be?
#Fattie: Thanks for elaborating all this, it's great! There is a point though that people are trying to get through to you, and I'll just give it a go as well:
We do not want every instantiation of everything in our mobile games to do a "FindObjectOfType" for each and every every "global class"!
Instead you can just have it use an Instantiation of a static / a Singleton right away, without looking for it!
And it's as simple as this:
Write this in what class you want to access from anywhere, where XXXXX is the name of the class, for example "Sound"
public static XXXXX Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; } else { Debug.Log("Warning: multiple " + this + " in scene!"); }
}
Now instead of your example
Sound sound = Object.FindObjectOfType<Sound>();
Just simply use it, without looking, and no extra variables, simply like this, right off from anywhere:
Sound.Instance.someWickedFunction();
Alternately (technically identical), just use one global class, usually called Grid, to "hold" each of those. Howto. So,
Grid.sound.someWickedFunction();
Grid.networking.blah();
Grid.ai.blah();
Here is how you can start whatever scene you like and be sure to reintegrate your _preload scene every time you hit play button in unity editor. There is new attribute available since Unity 2017 RuntimeInitializeOnLoadMethod, more about it here.
Basically you have a simple plane c# class and a static method with RuntimeInitializeOnLoadMethod on it. Now every time you start the game, this method will load the preload scene for you.
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadingSceneIntegration {
#if UNITY_EDITOR
public static int otherScene = -2;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InitLoadingScene()
{
Debug.Log("InitLoadingScene()");
int sceneIndex = SceneManager.GetActiveScene().buildIndex;
if (sceneIndex == 0) return;
Debug.Log("Loading _preload scene");
otherScene = sceneIndex;
//make sure your _preload scene is the first in scene build list
SceneManager.LoadScene(0);
}
#endif
}
Then in your _preload scene you have another script who will load back desired scene (from where you have started):
...
#if UNITY_EDITOR
private void Awake()
{
if (LoadingSceneIntegration.otherScene > 0)
{
Debug.Log("Returning again to the scene: " + LoadingSceneIntegration.otherScene);
SceneManager.LoadScene(LoadingSceneIntegration.otherScene);
}
}
#endif
...
An alternate solution from May 2019 without _preload:
https://low-scope.com/unity-tips-1-dont-use-your-first-scene-for-global-script-initialization/
I've paraphrased from the above blog to a how-to for it below:
Loading a Static Resource Prefab for all Scenes
In Project > Assets create a folder called Resources.
Create a Main Prefab from an empty GameObject and place in the Resources folder.
Create a Main.cs C# script in your Assets > Scripts or wherever.
using UnityEngine;
public class Main : MonoBehaviour
{
// Runs before a scene gets loaded
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void LoadMain()
{
GameObject main = GameObject.Instantiate(Resources.Load("Main")) as GameObject;
GameObject.DontDestroyOnLoad(main);
}
// You can choose to add any "Service" component to the Main prefab.
// Examples are: Input, Saving, Sound, Config, Asset Bundles, Advertisements
}
Add Main.cs to the Main Prefab in your Resources folder.
Note how it uses RuntimeInitializeOnLoadMethod along with Resources.Load("Main") and DontDestroyOnLoad.
Attach any other scripts that need to be global across scenes to this prefab.
Note that if you link to other scene game objects to those scripts you probably want to use something like this in the Start function for those scripts:
if(score == null)
score = FindObjectOfType<Score>();
if(playerDamage == null)
playerDamage = GameObject.Find("Player").GetComponent<HitDamage>();
Or better yet, use an Asset management system like Addressable Assets or the Asset Bundles.
actually as a programmer who comes to unity world I see none of these approaches
standard
the most simplest and standard way: create a prefab, according to unity docs:
Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects
as a reusable Asset. The Prefab Asset acts as a template from which you can create new Prefab instances in the Scene.
Details:
Create a prefab within your Resources folder:
if you don't know how to create a prefab study this unity document
if you don't have resources directory create a folder and name it exactly Resources because it is a unity Special folder name
create a script with contents like below:
using UnityEngine;
public class Globals : MonoBehaviour // change Globals (it should be the same name of your script)
{
// loads before any other scene:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void LoadMain()
{
Debug.Log("i am before everything else");
}
}
assign it to your prefab
and you can make it even better:
use prefab and namespaces together:
in your prefab script:
using UnityEngine;
namespace Globals {
public class UserSettings
{
static string language = "per";
public static string GetLanguage()
{
return language;
}
public static void SetLanguage (string inputLang)
{
language = inputLang;
}
}
}
in your other scripts:
using Globals;
public class ManageInGameScene : MonoBehaviour
{
void Start()
{
string language = UserSettings.GetLanguage();
}
void Update()
{
}
}

Unity !IsDestroying() Error

In Unity I'm currently getting an error where none existed before, or if they did it didn't appear to matter.
Whenever I destroy something and go to restart the level in my game, the game freezes partway into loading and comes up with this error:
!IsDestroying()
UnityEngine.GameObject:AddComponent()
EggScript:OnDestroy() (at Assets/Scripts/GameScripts/EggScript.cs:85)
The function it's talking about:
void OnDestroy()
{
PlayerPrefs.SetInt("Game Paused", 1);
transform.parent.gameObject.transform.parent.gameObject.AddComponent<GameOverScript>();
}
I've removed the PlayerPrefs call to no apparent avail but I need the other call to actually restart the level.
The other error that hasn't appeared to matter is:
batchDelete.reservedObjectCount >= batchDelete.objectCount
I'm not quite sure where I'm creating that because what I'm working on is a bit of a conglomeration of tutorials but I don't think it's relevant to this particular error.
The GameOverScript code is below (though it doesn't sound like it would be the error either):
void Start()
{
GameManagerScript.Instance.pauseGame();
//gameObject.BroadcastMessage("saveScore", null, SendMessageOptions.DontRequireReceiver);
if (PlayerPrefs.GetString("curLevel") == "Tutorial" || PlayerPrefs.GetString("curLevel") == "Tutorial1" || PlayerPrefs.GetString("curLevel") == "Tutorial2")
{
//Do nothing
}
else
{
//We're in an infinite level, so we save the score
gameObject.BroadcastMessage("saveScore");
}
PauseScript.displayMenu();
//gameObject.AddComponent<FrontmostClickCheckerScript>();
//HighScoreManager._instance.SaveHighScore("You", (System.Int32)PlayerPrefs.GetFloat("LatestScore"));
HighScoreManager._instance.SaveHighScore("You", GameManagerScript.Instance.getScore());
}
I actually believe you might be making a few mistakes here:
transform.parent.gameObject.transform.parent.gameObject.AddComponent() should be replaced with a public variable to hold that GameObject, the calling AddComponent on it, also replacing your AddComponent with enabled as was mentioned in the comments would also work.
You're destroying an object on pause. You should post more code to give context here, but you don't want to be using OnDestroy to manage game state the way it appears that you are. You can use messaging, or references, the latter is faster, but the former is fine when you're not using it too often (read: repeatedly in OnUpdate)
I think the likeliest possibility here is the object you're calling AddComponent on might be getting Destroyed while you're trying to add the component. It'd be a very hard thing to debug here because you're climbing the hierarchy manually. Also, try removing the AddComponent altogether, it sounds like you need SetActive instead.

Categories