Save/loading in unity3d - unity3d

Now i realize this question gets asked a lot and i assure you i have done a lot of searching on the subject, have tried some scripts and tried the serializer from unity's asset store. but i just can't find what i need.
the next level only unlocks after beating the previous one so it needs to remember which levels have been beaten/unlocked and when you load you can pick any of the unlocked levels also the highest score on each level needs to be remember and only be replaced if a new score is higher, lower scores don't need to be saved.
what i kept finding on google was how to save all objects and stuff that happened in the level but i don't need in-level saving, my levels are really short and ment to be played in 1 go.
EDIT:
this is the script where i'd add 1 to levelIndex after the player beats the level
and my goal is after the player presses load in the startmenu, a new menu pops up and the player will be able to choose from all the unlocked levels and after choosing, the level will load.
(what i currently have regarding this issue is on the // lines)
#pragma strict
var normalParticle : ParticleSystem;
var teleportParticle : ParticleSystem;
var teleportSound : AudioClip;
var ignoreListenerPause: boolean;
// var levelIndex : int;
function Start() {
teleportParticle.Stop();
}
function OnTriggerEnter(other : Collider)
{
if(other.tag == "Player"){
teleportParticle.Play();
normalParticle.Stop();
for (var o : GameObject in GameObject.FindGameObjectsWithTag("Virus"))
{
var comp : Component = o.GetComponent(ChaseScript);
if (comp != null)
{
comp.active = false;
}
}
yield WaitForSeconds (0.5);
audio.ignoreListenerPause = true;
audio.PlayOneShot(teleportSound);
yield WaitForSeconds (3.9);
//levelIndex++;
//PlayerPrefs.SetInt("Last_Level", levelIndex);
Application.LoadLevel(Application.loadedLevel+1);
}
}
function OnTriggerExit(other : Collider)
{
teleportParticle.Stop();
normalParticle.Play();
}
i found a tutorial earlier explaining how to achieve what i wanted but it's not effective for a game with a lot of levels, he uses a script per level completed and uses if's for every single level and my game would have around 50 so i would prefer a better/cleaner way here is the video: http://forum.unity3d.com/threads/unity-level-unlocking-system-using-playerprefs.146955/. u can see his scripts at 10:10 and 14:14.
thanks in advance :)

If the data is simple enough, like just one or two variables when skip to step 3.
Create class to hold your data about progression
Serialize the class with json to get a string
Store the data in PlayerPrefs
Save
PlayerPrefs.SetInt("Last_Level", levelIndex);
PlayerPrefs.Save();
Load
int levelIndex = PlayerPrefs.GetInt("Last_Level", 0);

Related

Unity3D New Input System: Is it really so hard to stop UI clickthroughs (or figure out if cursor is over a UI object)?

Even the official documentation has borderline insane recommendations to solve what is probably one of the most common UI/3D interaction issues:
If I click while the cursor is over a UI button, both the button (via the graphics raycaster) and the 3D world (via the physics raycaster) will receive the event.
The official manual:
https://docs.unity3d.com/Packages/com.unity.inputsystem#1.2/manual/UISupport.html#handling-ambiguities-for-pointer-type-input essentially says "how about you design your game so you don't need 3D and UI at the same time?".
I cannot believe this is not a solved problem. But everything I've tried failed. EventSystem.current.currentSelectedGameObject is sticky, not hover. PointerData is protected and thus not accessible (and one guy offered a workaround via deriving your own class from Standalone Input Module to get around that, but that workaround apparently doesn't work anymore). The old IsPointerOverGameObject throws a warning if you query it in the callback and is always true if you query it in Update().
That's all just mental. Please someone tell me there's a simple, obvious solution to this common, trivial problem that I'm just missing. The graphics raycaster certainly stores somewhere if it's over a UI element, right? Please?
I've looked into this a fair bit and in the end, the easiest solution seems to be to do what the manual says and put it in the Update function.
bool pointerOverUI = false;
void Update()
{
pointerOverUI = EventSystem.current.IsPointerOverGameObject();
}
Your frustration is well founded: there are NO examples of making UI work with NewInput which I've found. I can share a more robust version of the Raycaster workaround, from Youtube:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
/* Danndx 2021 (youtube.com/danndx)
From video: youtu.be/7h1cnGggY2M
thanks - delete me! :) */
public class SCR_UiInteraction : MonoBehaviour
{
public GameObject ui_canvas;
GraphicRaycaster ui_raycaster;
PointerEventData click_data;
List<RaycastResult> click_results;
void Start()
{
ui_raycaster = ui_canvas.GetComponent<GraphicRaycaster>();
click_data = new PointerEventData(EventSystem.current);
click_results = new List<RaycastResult>();
}
void Update()
{
// use isPressed if you wish to ray cast every frame:
//if(Mouse.current.leftButton.isPressed)
// use wasReleasedThisFrame if you wish to ray cast just once per click:
if(Mouse.current.leftButton.wasReleasedThisFrame)
{
GetUiElementsClicked();
}
}
void GetUiElementsClicked()
{
/** Get all the UI elements clicked, using the current mouse position and raycasting. **/
click_data.position = Mouse.current.position.ReadValue();
click_results.Clear();
ui_raycaster.Raycast(click_data, click_results);
foreach(RaycastResult result in click_results)
{
GameObject ui_element = result.gameObject;
Debug.Log(ui_element.name);
}
}
}
So, just drop into my "Menusscript.cs"?
But as a pattern, this is terrible for separating UI concerns. I'm currently rewiring EVERY separately-concerned PointerEventData click I had already working, and my question is, Why? I can't even find how it's supposed to work: to your point there IS no official guide at all around clicking UI, and it does NOT just drop-on-top.
Anyway, I haven't found anything yet which makes new input work easily on UI, and definitely not found how I'm going to sensibly separate Menuclicks from Activityclicks while keeping game & ui assemblies separate.
Good luck to us all.
Unity documentation for this issue with regard to Unity.InputSystem can be found at https://docs.unity3d.com/Packages/com.unity.inputsystem#1.3/manual/UISupport.html#handling-ambiguities-for-pointer-type-input.
IsPointerOverGameObject() can always return true if the extent of your canvas covers the camera's entire field of view.
For clarity, here is the solution which I found worked best (accumulated from several other posts across the web).
Attach this script to your UI Canvas object:
public class CanvasHitDetector : MonoBehaviour {
private GraphicRaycaster _graphicRaycaster;
private void Start()
{
// This instance is needed to compare between UI interactions and
// game interactions with the mouse.
_graphicRaycaster = GetComponent<GraphicRaycaster>();
}
public bool IsPointerOverUI()
{
// Obtain the current mouse position.
var mousePosition = Mouse.current.position.ReadValue();
// Create a pointer event data structure with the current mouse position.
var pointerEventData = new PointerEventData(EventSystem.current);
pointerEventData.position = mousePosition;
// Use the GraphicRaycaster instance to determine how many UI items
// the pointer event hits. If this value is greater-than zero, skip
// further processing.
var results = new List<RaycastResult>();
_graphicRaycaster.Raycast(pointerEventData, results);
return results.Count > 0;
}
}
In class containing the method which is handling the mouse clicks, obtain the reference to the Canvas UI either using GameObject.Find() or a public exposed variable, and call IsPointerOverUI() to filter clicks when over UI.
Reply to #Milad Qasemi's answer
From the docs you have attached in your answer, I have tried the following to check if the user clicked on a UI element or not.
// gets called in the Update method
if(Input.GetMouseButton(0) {
int layerMask = 1 << 5;
// raycast in the UI layer
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity, layerMask);
// if the ray hit any UI element, return
// don't handle player movement
if (hit.collider) { return; }
Debug.Log("Touched not on UI");
playerController.HandlePlayerMovement(x);
}
The raycast doesn't seem to detect collisions on UI elements. Below is a picture of the Graphics Raycaster component of the Canvas:
Reply to #Lowelltech
Your solution worked for me except that instead of Mouse I used Touchscreen
// Obtain the current touch position.
var pointerPosition = Touchscreen.current.position.ReadValue();
An InputSytem is a way to receive new inputs provided by Unity. You can't use existing scripts there, and you'll run into problems like the original questioner. Answers with code like "if(Input.GetMouseButton(0)" are invalid because they use the old system.

Why is it SO Hard to Just Mute a Sound in Unity?

I have spent all day looking for a solution to this problem, and I simply can't find one. Using JavaScript in Unity 3D, I have a script where I want to play a sound when the player's velocity on the X axis reaches a certain point, and if it's not at that point, then the sound will be muted. And I believe I have all the structure right, it's just the line of code that says to mute the audio that won't work. I've tried all kinds of different combinations, and I get an error for each one.
The script looks like this:
#pragma strict
var playing = false;
var audioSource = GetComponent.<AudioSource>();
function Update () {
if (transform.GetComponent.<Rigidbody>().velocity.x <= 2.5 &&
transform.GetComponent.<Rigidbody>().velocity.x >= -2.5)
{
Mute();
} else {
Unmute();
}
}
function Mute () {
audioSource.mute = true;
}
function Unmute () {
audioSource.mute = false;
Sound();
}
function Sound () {
if (transform.GetComponent.<Rigidbody>().velocity.x >= 2.5 && playing ==
false)
{
playing = true;
GetComponent.<AudioSource>().Play();
yield WaitForSeconds(2);
playing = false;
}
if (transform.GetComponent.<Rigidbody>().velocity.x <= -2.5 &&
playing == false)
{
playing = true;
GetComponent.<AudioSource>().Play();
yield WaitForSeconds(2);
playing = false;
}
}
I've gotten all kinds of different errors, but the one I seem to be getting the most says "UnityException: GetComponentFastPath is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'motioncheck' on game object 'Ball'." I'm not sure what this means, since I'm still kinda a nub at JavaScript.
I feel like it shouldn't be this hard to just mute a sound. I'm going to assume that the answer to this is really simple and that I'm just really dumb. That's what usually seems to happen, lol.
In the mean time, I'm going to continue my rampage across the internet in search for answers to this problem.
Your mute code is fine.
"UnityException: GetComponentFastPath is not allowed to be called from
a MonoBehaviour constructor (or instance field initializer), call it
in Awake or Start instead. Called from MonoBehaviour 'motioncheck' on
game object 'Ball'." I'm not sure what this means, since I'm still
kinda a nub at JavaScript.
See this:
var audioSource = GetComponent.<AudioSource>();
That's a Unity API and you have to call their functions from inside a function. The Awake or Start function is appropriate for initializing component variables.
var audioSource : AudioSource;
function Start()
{
audioSource = GetComponent.<AudioSource>();
}
Note that Unityscript/Javascript is now discontinued. They no longer update the doc on this and you cannot create new scripts from the Editor anymore. It still works as for now but the compiler will be removed soon. Please learn and start using C# before its support is totally removed.

How to save the amount of lives when the player dies and then uses one life?

Hi I'm struggling with this issue.I searched a lot before asking here.
So i have programmed an arcade game which when it dies it pops up a menu with a continue option using one life out of five.Like Subway Surfers with the keys.
The issue is that when i use one heart i want to save it.But everytime i restart the game it keeps saying the initial number which is five.Or without using a life,it by itself,subtracts by one without even clicking the "Save me" button.
Here is my code:
public Text heart;
public Text heart2;
public int counter;
void Start () {
heart.text = "" + counter;
heart2.text = "" +counter;
}
// Update is called once per frame
void Update () {
}
public void SaveMe()
{
heart.text = counter.ToString();
heart2.text = counter.ToString();
int heartScore = PlayerPrefs.GetInt("Life2", 0);
heartScore--;
if(heartScore<0)
{
heartScore = counter;
}
PlayerPrefs.SetInt("Life2", heartScore);
heart.text = PlayerPrefs.GetInt("Life2", 0).ToString();
heart2.text = PlayerPrefs.GetInt("Life2", 0).ToString();
}
}
Then i call this method in the button script.
You should need to save life counter in a file or PlayerPrefs in unity so that loading a scene doesn't effect the counters.
Stores and accesses player preferences between game sessions.
When Unity loads a new scene (if not in an additive way) it will destroy everything that belongs to it before loading the new one. If you don't want a GameObject to be destroyed automatically, read the instructions for DontDestroyOnLoad
Also consider generally to separate your game model (in this occasion the variable counter) to a different Component and use scriptable objects for managing it/them. It will make your life easier in the long run.

Save and Load the state of gameobjects in a scene in Unity

So what I am trying to accomplish is when I load a scene I want to have certain objects to be active or inactive depending on their the information saved in my files.
Example being :
In my game, when I load one of my scenes there is a wall, then I push a bookcase to a certain point and triggers the wall to be set to inactive. I exit this scene and come back to the scene and the wall is active again. What I want is when the wall is set inactive, it stays inactive even when I leave the scene and and won't appear when I come back to the scene later.
The trouble I am having is how to do this. At the moment what I have is when the player clicks the "New Game" button I generate a new Game_Info and select the Default() :
[Serializable]
public class Game_Info {
public bool WoodWallEnabled;
public void Default(){
WoodWallEnabled = true;
}
}
My WoodWall script :
public class Get_WoodWall_Info : MonoBehaviour {
void Awake () {
gameObject.SetActive (Helper_Manager.instance.gameInfo.WoodWallEnabled);
}
}
I have a feeling I am not doing this the right way and that there is a better way to handle this, especially since doing like this would need a unique script for each gameobject that I would like to manipulate through my saved file (Get_WoodWall_Info, Get_WoodWall_Info1, etc...) but even if I created enourmous amounts of scripts to do this, I can't find a way to save the state of the gameobjects.
How would you handle and pass around this kind of data?
If you are saving only WoodWallEnabled variable then simple use PlayerPref as mentioned above. Now, if you are saving the whole scene info such as object position or every variable in Game_Info class, you have to convert the class to Json with JsonUtility.ToJson, then save it using PlayerPref. Then to load it, you use PlayerPref to load it then Serialize it back to your Game_Info class with JsonUtility.FromJson.
A full working examples that includes arrays:
Serialize and Deserialize Json and Json Array in Unity
I think PlayerPref is a good choice in this situation.
You can do like this:
Call PlayerPrefs.SetInt("wall enabled", active ? -1 : 0); to save the state of your wood wall with active is the state of the wall.
And in Get_WoodWall_Info, instead of using the value in the Help Manager, use
PlayerPrefs.GetInt("wall enabled") != 0
Values in PlayerPrefs will be saved through game sessions, so you may want to set the wall enabled key to 0 when you start a new game.
EDIT
#JoeyL Why don't you create one script for all your game objects, like this:
public class StateHandler : MonoBehaviour {
void Awake () {
string keyName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + gameObject.name;
if (PlayerPrefs.GetInt(keyName) == 0)
gameObject.SetActive(false);
}
public void SetActiveState (bool state) {
string keyName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + gameObject.name;
PlayerPrefs.SetInt(keyName, state ? -1 : 0);
gameObject.SetActive(state);
}
}
This will use your current scene name and your attached game object name as the key, so you just need to make sure all gameobjects using this script have distinct names.

High Scores logic in Unity using PlayerPrefsX

I've created a fairly basic 2d scrolling game in Unity, and one of the game modes is survival. When you die, this screen will load and allow you to input your high score, which will be saved into a high scores table once the button is pressed.
However, while I can replace the score it is higher than, I cannot for the life of me think of the logic that would instead put the score that was replaced into the position below it, then that score would drop by one, then the next score etc.
The stage I'm at is below, with the code that simply replaces all of the high scores lower than the current store deleted, because it isn't what needs to be done. I currently have five generic scores saved to the playerprefsX int array so i know all of the below code is working, its just the dropping all the scores down one and deleting the final score that is proving problematic for me
public class high_score_input : Game_Over {
private string name = "highscores";
private int score;
public int[] highscores = new int[5];
// Use this for initialization
void Start () {
GameObject levelcontroller = GameObject.Find ("levelcontroller");
survival_mode survival = levelcontroller.GetComponent<survival_mode> ();
score = survival.setScore();
highscores = PlayerPrefsX.GetIntArray(name);}
void OnGUI()
{
base.OnGUI ();
if (GUI.Button (new Rect (Screen.width/10, 250, 120, 30), "Save High Score"))
{
for(int i = 0;i<highscores.Length;i++)
{
if(score>highscores[i])
{
}
}
PlayerPrefsX.SetIntArray(name, highscores);
}
}
Given that it's a small array and this isn't performance-critical code, I'd go for something that's simple and avoids errors:
Make sure that your score array is sorted.
Does the new score beat the score in the last slot? If so, replace it.
Make sure that your score array is sorted.
Given helper functions such as Array.Sort, this should be pretty quick.