Well, this should be much simpler than it is! Or I'm blinded by my own ignorance!
What I want is some means of getting the string name of the last playable level that was played.
To clarify:
All playable levels are given sequential names - "Level_01" thru "Level_100".
All non-playable scenes are given names such as "Start Menu" and "LevelCompleted" these scenes contain buttons like "Main Menu" and "Replay Last Level".
I am NOT using the UI system and do not wish to do so at all. Rather, I have set up objects with colliders and am using my game mechanics to act as "buttons". For example, when the player shoots the button called "Replay Last Level" the OnCollisionEnter2D should load the last level played.
Whenever a level is completed or lost by the player they are taken to "LevelCompleted" and shown some game statistics (scores, times, etc.). They are given three choices: "Main Menu", "Replay Last Level" and "Continue".
What I need is simply a string variable that always contains the name of the previously loaded PLAYABLE level (i.e. those with names like "Level_01" and not like "Start Menu". )
Then I will use that string to enter into my GameManager script in the appropriate place. The last level played will load when "Replay Last Level" is shot by the player. When the player shoots "Continue" I want to play the next playable level.
So pretty simple concept: I just lost "Level_02". I am now in "LevelCompleted". I shoot "Replay Last Level". I enter "Level_02" again. If, instead, I shoot "Continue" it takes me to the next playable level, in this case, "Level_03" if that level has been unlocked. If it has not been unlocked then the "Continue" button will take me back to "level_02".
BTW, I have a GameManager Script on an otherwise empty game object. It is a singleton that persists to every scene. Within this script are two classes - one is the main public class that is the singleton and does a whole bunch of stuff with data in larger arrays - the other is simply a serializable "public Class GameData". It contains only variables that are written to a file and then reload as needed during the game.
So as is often the case the answer was way simpler than I thought!
In my Gamemanager Script, I set up a public static string variable "currentLevel".
Then on Awake() I did this:
Scene scene = SceneManager.GetActiveScene();
if (scene.name != "LevelCompleted")
{
currentLevel = scene.name;
}
I realized that my "LevelCompleted" scene would never be opened by another "non-playable" scene like "Start Menu" and that it would only ever be opened by a playable level like "Level_xx". By using the conditional, it assures that when that scene "LevelCompleted" is opened, the variable is not updated by the singleton GameManager such that the variable will always contain the last played playable level.
In the scene "LevelCompleted", in the button "Replay Last Level", I have:
private string lastLevelPlayed;
void OnCollisionEnter2D(Collision2D collision)
{
lastLevelPlayed = GameManager.currentLevel;
SceneManager.GetSceneByName(lastLevelPlayed);
}
Related
I have a simple project. A button adds points one at a time to a counter. I then change the scene via another button and return to the original scene. The point counter has gone back to zero. How do i prevent this?
Many ways to achieve this. But for starters you can save the number in Player Prefs.
"PlayerPrefs` is a class that stores Player preferences between game sessions. It can store string, float and integer values into the user’s platform registry"
So forexample this is your button callback function:
public void ButtonCallback()
{
number++;
PlayerPrefs.SetInt("MyNumber", number);
}
To get the number next time you are in the scene, you can get the number using:
int number = PlayerPrefs.GetInt("MyNumber");
You will probably need to check if number exists in the first place when you launch the scene first time using:
PlayerPrefs.HasKey("MyNumber");
Read up more on this on:
https://docs.unity3d.com/ScriptReference/PlayerPrefs.html
I am trying to load an animation from an fbx file and have it play on a GameObject:
TestObject.AddComponent<Animation>();
animation_handler = TestObject.GetComponent<Animation>();
walking_anim = Resources.Load("fbx_anims/walking_anim_test", typeof(AnimationClip)) as AnimationClip;
if(walking_anim == null)
{
Debug.Log("walking anim not found");
}
walking_anim.legacy = true;
animation_handler.AddClip(walking_anim, "walking");
animation_handler.wrapMode = WrapMode.Loop;
In the game loop, I tried using this:
if (Input.GetKeyDown(KeyCode.W))
{
if (!(animation_handler.IsPlaying("walking")))
{
animation_handler.clip = walking_anim;
animation_handler.Play("walking");
}
}
It doesnt give any errors, yet it doesn't work either. Anything I'm missing?
EDIT: For clarification: The model stays in the default T-Pose, after pressing 'W'. After inserting Debug.Logs at different points, I can confirm that the Play function is getting called only once, after which IsPlaying always returns true. Yet the "playing" animation causes no visual changes in the model (yes, the bone names are the same).
You don't want to use the Animation component, it is an old legacy component that has been replaced by the much improved Animator component. There are a lot of good posts on the Internet on how to use it - no need to repeat it here. The important steps are:
Add the Animator (not Animation) component to the model.
Create an "Animator Controller" in your project and add the clips (like the "walking_anim"). Here you can have a lot of different clips and tell Unity how to interpolate between them by using different parameters.
Add the "Animator Controller" to your "Animator" component.
Add an "avatar" of your model (usually created when the model is imported).
By code alter the parameters of the Animator Controller to tell it which animation clips to play.
It may look like a lot of steps, but it is not so hard and you will quickly have a walking, running, jumping creature on your screen. Good luck!
Working in Godot 3.2, I have a scene, Player.tscn. Near the top of Player.tscn, I have "class_name Player"
Now, when instantiating the Player, I have, as far as I see it, two options:
player = Player.new()
or
player = load("res://Player.tscn").instance() as Player
Now, the first version seems best to me...but it clearly isn't. If I use .new(), it claims that it has no children, and any method calls that attempt to get to its children (.get_texture() on a Sprite, e.g.), produces things like "Attempt to call function 'get_texture' in base 'null instance' on a null instance", because apparently Player has a no children.
Of course, doing it the second way, everything works fine. But why? Why can't I just use .new() if I've registered it as a class using class_name?
I'm new to Godot myself so I may be wrong, but I think it's because the .new() keyword is a language feature for loading single classes/nodes,
whereas .instance() is more of an engine feature that takes a packaged scene which is a combination of many classes/nodes, and restores their relative position in the tree, attaches scripts and resources, etc
So if your player is a tscn scene, you'd do something like:
var player = load("res://etc/Player.tscn").instance()
get_tree().root.add_child(player)
player.global_transform = etc ...
which would load the root node of Player.tscn and all of its children (rigidbodies, collidershapes etc) into the scene tree, set their relative positions, connect the scripts and what have you
Or if your Player is a class or a GDScript, you could do
var player = Player.new()
get_tree().root.add_child(player)
player.global_transform = etc ...
Which would add the single-node root player object only into the scene tree, but not any children
Once it's in the scene tree and _ready()'s triggered though, you can instantiate and attach the child component nodes (.new() > parent.add_child() > set transform) from the script itself
I'm using the A* pathfinding algorithm for my 2D game (from my understanding, Unity Nav Meshes don't work in 2D). I would like to be able to pre-calculate navigation grids for all of my scenes, and save them in resource files that can be loaded whenever the player enters a new scene. Rather than having to remember to click "calculate" for every scene -- and remember to recalculate all of my scenes if I make a change to my navigation grids -- I want to be able to programatically have the Unity Editor iterate though each scene and calculate the grids.
Is there a way to create a command in the Unity editor that will iteratively open each scene in the editor and run a method on a MonoBehaviour that's in the scene? Alternatively, is there another way to accomplish what I'm trying to do?
Yes you can!
In editmode you can't use SceneManager but have to use the EditorSceneManager.
First of all you need the scenes you want to iterate.
Could be e.g. a public static field with a list of SceneAsset in the Inspector where you simply reference the scenes
public static List<SceneAsset> Scenes = new List<SceneAsset>();
or you could get them by script e.g. for only use the scenes added to the build settings using EditorBuildSettings.scenes
List<EditorBuildSettingsScene> Scenes = EditorBuildSettings.scenes;
For both you can get a list of the scene paths e.g. using LinQ Select (this is basically a kind of shortcut for a foreach loop) and AssetDatabase.GetAssetPath like
List<string> scenePaths = Scenes.Select(scene => AssetDatabase.GetAssetPath(scene)).ToList();
for the EditorBuildSettingsScene from EditorBuildSettings.scenes you can also simply use
List<string> scenePaths = Scenes.Select(scene => scene.path).ToList();
Now you can iterate over them all and do your stuff by using EditorSceneManager.OpenScene, EditorSceneManager.SaveScene and EditorSceneManager.CloseScene (and if you need it AssetDatabase.SaveAssets)
foreach(string scenePath in scenePaths)
{
// Open a scene e.g. in single mode
var currentScene = EditorSceneManager.OpenScene(scenePath);
/* Do your calculation for currentScene */
// Don't know if it makes changes to your scenes .. if not you can probably skip this
EditorSceneManager.SaveScene(currentScene);
// Finally Close and remove the scene
EditorSceneManager.CloseScene(currentScene, true);
}
// you might be doing changes to an asset you want to save when done
AssetDatabase.SaveAssets();
Before starting you should probably ask to save the current open scene(s) using EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo
if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
// Saved => the foreach loop here
}
else
{
// aborted => do nothing
}
Than in order to finally start that method the simplest would be to add a [MenuItem]
public static class Calculation
{
[MenuItem("YourMenu/RunCalculation")]
public static void RunCalculation()
{
// all the before mentioned snippets here
// depending how exactly you want to do it
}
}
This will add a new menu YourMenu with one entry RunCalculation to the top menubar of the Unity Editor.
Note:
Since this uses a lot of types (EditorSceneManager etc) that only exist in the UnityEditor namespace you should either place the whole script in an Editor folder (so it is ignored in the final build) or use pre-processors like
#if UNITY_EDITOR
// ... code here
#endif
so in the build the code is also ignored.
Note that I'm assuming so far you also did your calculation in editmode. The thing is if this calculation relies somewhere aon any Start or Awake method you have to call it manually from that editorscript before running the calculation.
I've seen similar questions all over the internet, but none seem to apply to my situation.
I have a very simple project with two scenes: MainMenuScene and OtherScene. OtherScene is nothing but a blank canvas, and MainMenuScene just has a button which is meant to be used to navigate to OtherScene. The Main Camera has the following script attached to it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LaunchScene : MonoBehaviour {
public void LaunchSceneOnButtonClick(int index)
{
SceneManager.LoadScene(index);
}
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
}
and in the OnClick() of the button, this script is called. I have made sure to add both levels to the Build Settings.
If I call the version of the LanchSceneOnButtonClick which takes an integer argument (and specify the appropriate value in the OnClick() method) then the desired scene loads.
However, if I call the version which takes a string argument and use the exact name of the scene I want to load (and I've quadruple checked that it is indeed the exact string, that the string name has no spaces or hidden characters, and have copy pasted the scene name into the OnClick() argument field) the scene does not load and instead I get the error message
Scene "OtherScene" couldn't be loaded because it has not been added to
the build settings or the AssetBundle has not been loaded.
Since using the scene index works, we know it was added to the Build Settings properly, so I reason that there is some issue with the scene name. But I've copied it exactly. I want to avoid using the full path of the scene in case I later decide to move scenes around to different folders, so what are my options here?
Thanks in advance.
EDIT:
Adding screenshots.
Scene in folder structure:
Scenes in build settings:
OnClick() input:
Keep in mind I've also tried the input to OnClick() with and without quotation marks.
The only possible problems are these:
The string passed is different from the scene name; bear in mind, the string used in LoadScene is not case sensitive, i.e. you can use OtherScene, OTHERscene, etc.
The scene in the build settings is not active (i.e. with the checkmark on the left disabled).
The scene isn't present at all in the build settings.
The full path of the scene is irrelevant btw, you can have OtherScene inside any dir/subdir in the Assets, and you'll need only the name of the scene. You need the full path of the scene only if you have more than scene with the same name in different paths (and anyway you shouldn't, it's such an obvious terrible practice).
EDIT: You say that you tried even without " (as you should do, when entering string fields in the inspector you don't need them), what happens if you modify the method body to:
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene("OtherScene");
}
Try this and report back what happens please.
I had also the problem that
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
did not work for me in the first place. What did I do? I'm saving the names of my scenes in a List (read from a file) and from that List I choose the scene I need.
But somehow the reading progress seems to give me some unwanted characters so the scene loading does not work as intended. When I added .Trim() it started to work.
For a better understanding, here's an example:
string s = sceneList[1].Trim();
this.loadScene(s);
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
So make sure that you get rid of unwanted characters.