Save current Scrollrect position Unity3D - c#

I am new with unity and C#, I have question about how I save current scrollrect position.
Example : I am scrolling the view , and move to another scene and then back to previous scene but the scroll shows the previous position before I moved the scene, not resetting the scroll to default.

Unfortunately, what you want to make is not available ready-made, you have to make it yourself
first use Recyclable-Scroll-Rect
When scrolling to the bottom of the scroll, you have to save the id you sent to DemoCall via PlayerPrefs, then when you go to another scene and back again to the selected scene, call the scroll info from the point it left off, which is the id you saved
EDIT
After adding the Recyclable-Scroll-Rect, you can use this code
using System.Collections.Generic;
using UnityEngine;
using PolyAndCode.UI;
using System.Collections;
public struct ContactTsnif
{
public string id;
}
public class Objx
{
public string id;
}
public class RecyclTsnif : MonoBehaviour, IRecyclableScrollRectDataSource
{
[SerializeField]
RecyclableScrollRect _recycHat;
public GameObject RecyScrHat;
[SerializeField]
public int _dataLenHat;
public int beginning;
private List<ContactTsnif> _contactList = new List<ContactTsnif>();
public List<string> id = new List<string>();
void Start()
{
beginning = PlayerPrefebs.GetInt("Start", 5)// start with 5
GetHat();
}
public void GetHat()
{
_dataLenHat = 0;
_recycHat.DataSource = this;
InitDataHat();
RecyScrHat.GetComponent<RecyclableScrollRect>().Initialize();
}
public void InitDataHat()
{
if (_contactList != null) _contactList.Clear();
for (int i = beginning; i < _dataLenHat;)
{
ContactTsnif obj = new ContactTsnif();
obj.id = id[i];
i++;
_contactList.Add(obj);
}
}
#region DATA-SOURCE
public int GetItemCount()
{
return _contactList.Count;
}
public void SetCell(ICell cell, int index)
{
var item1 = cell as DemoTsnif;
item1.ConfigureCellSor(_contactList[index], index);
}
#endregion
}
Demo
using UnityEngine;
using System;
using System.Collections;
public class DemoTsnif : MonoBehaviour, ICell
{
private ContactTsnif _ContactInfo;
private int _cellIndex;
public int id;
public void GetData()
{
}
public void ConfigureCellSor(ContactTsnif contactInfo,int cellIndex)
{
_cellIndex = cellIndex;
_ContactInfo = contactInfo;
id = contactInfo.id;
GetData();
}
}

Do you tried read / write normalizedPosition?
You basically need to do two things:
You need to attach this script to the GameObject which contains the ScrollRect in order to persist the position:
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems; // Required when using event data
using UnityEngine.UI;
public class DragNotify : MonoBehaviour, IEndDragHandler // required interface when using the OnEndDrag method.
{
//Do this when the user stops dragging this UI Element.
public void OnEndDrag(PointerEventData data)
{
PlayerPrefs.SetFloat("scrollX", this.GetComponent<ScrollRect>().normalizedPosition.x);
}
}
You also need to apply the normalizedPosition once you initialized the ScrollRect and after you applied the desired content:
this.transform.Find("Scroll View").GetComponent<ScrollRect>().normalizedPosition = new Vector2(PlayerPrefs.GetFloat("scrollX"), 0F);

Related

Can I create array of Scenes unity?

I want to create public Scene[] levels; and manually assign my levels to the array, then loop through it and generate level selection buttons, but it won't show up in the inspector.
Is there any workaround?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class True : MonoBehaviour
{
// Start is called before the first frame update
public static int money;
[SerializeField]
public SceneManager[] scenes;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void nextscencefirst()
{
SceneManager.LoadScene("Level2");
money++;
}
}
SceneManager is a built-in static class to control scenes during runtime. We can go to any scene by calling LoadScene method from that class. Also SceneManager cannot be seen in Inspector since it cannot be serialized class.
A reminder: Your scenes should be listed on build settings for these to work.
There are three ways to do what you want to do:
Method 1. Create a string list that holds names of scenes
public List<string> sceneNameList = new List<string>;
Method 2. Create a list of indices of scenes that added in build settings.
public List<int> sceneBuildIndexList = new List<int>
If the names of scenes are long or somehow difficult or you want to use sceneBuildIndex value it would be good to create a class and reach scenes from it
[System.Serializable]
public class SceneData
{
public int sceneBuildIndex;
public string sceneKey;
//public void LoadScene() //Maybe
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].sceneKey == key)
{
return sceneDataList[i];
}
}
return null;
}
Method 3: In Unity most classes are created from UnityEngine.Object, so it may let you assign scene from the Inspector.
[System.Serializable]
public class SceneData
{
public UnityEngine.Object scene;
public string key;
public void LoadScene()
{
if (scene == null)
{
Debug.LogError("Scene is not assigned");
return;
}
string pathToScene = AssetDatabase.GetAssetPath(scene);
SceneManager.LoadScene(pathToScene); //If the scene is not set in BuildSettings it will throw an error.
}
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].key == key)
{
return sceneDataList[i];
}
}
return null;
}
private void Awake()
{
SceneData data = GetSceneData("Level1");
if (data != null)
{
data.LoadScene();
}
}

unity Scriptable object not working in bulid

hi I am new to unity and C# and I am making my first game. In it I have a Scriptable object named "sss".It contains values like coins,speed,jump power. It works fine in the editor but after I build it it wont work.what I mean by it wont work is after the player dies in the game depending on how long they live they will get a certain amount of coins when they go to the store they will see those coins displayed as a UI and they can spend them on things like speed and jump boost. this works in the editor but not in a build. Dose anybody know why this is? here is the code I have in my Scriptable object
using UnityEngine;
[CreateAssetMenu(fileName = "data",menuName = "sss",order = 1)]
public class sss : ScriptableObject
{
public float coins = 0f;
public float speed = 10f;
public float jump = -9.81f;
public bool set = true;
public int face = 1;
}
here is the code I use to display the coins float
coinstext.text = sss.coins.ToString();
and here is the whole store-manager script used to buy stuff
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Security;
using UnityEngine;
using UnityEngine.UI;
public class storemanager : MonoBehaviour
{
public sss sss;
public Text coinstext;
public Slider sliders;
public Slider sliderj;
public int maxcap = 30;
public int maxcapj = 500;
public void Start()
{
coinstext.text = sss.coins.ToString();
}
public void speedbuy()
{
if (sss.coins >= 5 && sss.speed < maxcap)
{
sss.speed += 1;
sss.buyspeed = true;
sss.coins -= 5;
sliders.value += 1;
coinstext.text = sss.coins.ToString();
}
}
public void jumpbuy()
{
if (sss.coins >= 7 && sss.jump < maxcapj)
{
sss.jump += 10;
sss.buyjump = true;
sss.coins -= 7;
sliderj.value += 10;
coinstext.text = sss.coins.ToString();
}
}
}
While changes in ScriptableObject within the UnityEditor are persistent, they are not persistent in a build!
When you use the Editor, you can save data to ScriptableObjects while editing and at run time because ScriptableObjects use the Editor namespace and Editor scripting. In a deployed build, however, you can’t use ScriptableObjects to save data, but you can use the saved data from the ScriptableObject Assets that you set up during development.
So after a build the data in the asset is kind of baked and you can change it at runtime but when restarting the app you will always re-load the data of the build.
You would need to store your data instead in a local file stored on the device itself e.g. using Json like
using UnityEngine;
using System.IO;
...
public class sss : ScriptableObject
{
public float coins = 0f;
public float speed = 10f;
public float jump = -9.81f;
public bool set = true;
public int face = 1;
private const string FILENAME = "sss.dat";
public void SaveToFile()
{
var filePath = Path.Combine(Application.persistentDataPath, FILENAME);
if(!File.Exists(filePath))
{
File.Create(filePath);
}
var json = JsonUtility.ToJson(this);
File.WriteAllText(filePath, json);
}
public void LoadDataFromFile()
{
var filePath = Path.Combine(Application.persistentDataPath, FILENAME);
if(!File.Exists(filePath))
{
Debug.LogWarning($"File \"{filePath}\" not found!", this);
return;
}
var json = File.ReadAllText(filePath);
JsonUtility.FromJsonOverwrite(json, this);
}
}
And then in your manager script call the load and save methods e.g.
public class storemanager : MonoBehaviour
{
public sss sss;
public Text coinstext;
public Slider sliders;
public Slider sliderj;
public int maxcap = 30;
public int maxcapj = 500;
public void Start()
{
sss.LoadFromFile();
coinstext.text = sss.coins.ToString();
}
// Called when the application is going to quit
private void OnApplicationQuit()
{
sss.SaveToFile();
}
Note: Typed on smartphone but I hope the idea gets clear

Dictionary not read when using GetComponent<>() in Unity

I have three scripts inside GameObjects strucured as following:
GameObjectA
└ Script1
GameObjectB
└ Script2
└ Script3
Script3 has a Dictionary I need to recall in Script2:
public void SomeMethod()
{
Dictionary<string, int> someDictionary;
someDictionary = gameObject.GetComponent<Script3>().myDictionary;
//other stuff
}
If I call the method in Script2 in its Start method, it gets called and normally, and the Dictionary is read without problems.
However, during code execution, I'll sometimes need to call Script2 from Script1:
Public GameObject GameObjectB;
GameObjectB.GetComponent<Script2>().SomeMethod();
and, if I do so, when stepping through code during execution I realize the Dictionary SomeDictionary is empty.
Is this normal? I can give more information or the real snippets if needed, I find this behavior weird and I don't know if it acts regularly.
Edit: real codes below.
Script1, PauseMenu:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PauseMenu : MonoBehaviour
{
public GameObject tracksMenuContent;
public void TracksList()
{
tracksMenuContent.GetComponent<PopulateGrid>().Populate();
}
}
Script2, PopulateGrid:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PopulateGrid : MonoBehaviour
{
[SerializeField] private TileDescr _prefab;
public static PopulateGrid instance;
private void Awake()
{
Populate();
}
public void Populate()
{
Dictionary<string, TileSupply> tilesList;
tilesList = gameObject.GetComponent<TileDB>().tiles30;
TileDescr newObj; // Create GameObject instance
foreach (KeyValuePair<string, TileSupply> tile in tilesList)
{
Sprite tileSprite = Resources.Load<Sprite>("Tiles/tile" + tile.Key);
string tileText = "[" + tile.Key + "] " + tile.Value.available + "/" + tile.Value.total;
newObj = Instantiate(_prefab, transform);
newObj.Initialize(tileSprite, tileText);
}
}
}
Script3, TileDB:
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TileSupply
{
public int available;
public int total;
}
public class TileDB : MonoBehaviour
{
public Dictionary<string, TileSupply> tiles30 = new Dictionary<string, TileSupply>();
public void Awake()
{
tiles30.Add("1", new TileSupply() { available = 1, total = 1 });
//many more elements, all pretty similar
}
}
a possible solution is to use static:
public class TileDB : MonoBehaviour
{
public static Dictionary<string, TileSupply> tiles30 = new Dictionary<string, TileSupply>();
public void Awake()
{
tiles30.Add("1", new TileSupply() { available = 1, total = 1 });
}
}
after to use the tiles30 in other script, just call the dictionary like that
TileDB.tiles30

Problem with changing the color of a game player object

I am making a small platform game and in that, I made a scene where you can change the colour of your player. I made 3 buttons that change the colour when you click on them. I also made two code files, but they are not working. I see no errors in the console either.
P.S the colour changing code and buttons are on a different scene than the game object.
This is the code for the buttons that change the colour:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColourManager : MonoBehaviour
{
public static int colour;
public void DefaultBlue()
{
colour = 0;
}
public void Green()
{
colour = 1;
}
public void Red()
{
colour = 2;
}
}
This is the code on the game object itself:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColourTarget : MonoBehaviour
{
void Start()
{
rend = GetComponent<Renderer>();
rend.sharedMaterial = materials[0];
rend.enabled = true;
}
private Renderer rend;
public int lcolour = ColourManager.colour;
public Material[] materials;
private void Update()
{
if (lcolour == 0)
{
rend.sharedMaterial = materials[0];
}
if (lcolour == 1)
{
rend.sharedMaterial = materials[1];
}
if (lcolour == 2)
{
rend.sharedMaterial = materials[2];
}
}
}
Note that changing sharedMaterial might not be what you want to do here.
If you want to modify the material of a renderer use material instead.
so rather use material. Especially once e.g. a color was changed and you are dealing with instanced materials, afterwards changing the shared material has no effect at all.
Then do not do this in Update! It is very redundant and inefficient setting this every frame!
Finally note that this
public int lcolour = ColourManager.colour;
is only assigned ONCE the moment this object is initialized and then never changed anymore ... int is a VALUE type, not a reference!
I would rather use and event and make your target listen to any changes.
So your code might look like
public class ColourManager : MonoBehaviour
{
public static int colour;
// we will invoke this event everytime the color index is changed
// and directly pass the according new index in
public static event Action<int> OnColourIndexChanged;
public void DefaultBlue()
{
colour = 0;
// The ? is a null check and only
// calls Invoke if there is at least one listener to this event
OnColourIndexChanged?.Invoke(colour);
}
public void Green()
{
colour = 1;
OnColourIndexChanged?.Invoke(colour);
}
public void Red()
{
colour = 2;
OnColourIndexChanged?.Invoke(colour);
}
}
and then
public class ColourTarget : MonoBehaviour
{
[SerializeField] private Renderer _renderer;
public Material[] materials;
private void Awake()
{
if(!_renderer) _renderer = GetComponent<Renderer>();
_renderer.enabled = true;
// Add a callback to the event
// Removing it first is save also if it wasn't added so far
// This just makes sure it is always added only exactly once
ColourManager.OnColourIndexChanged -= UpdateMaterial;
ColourManager.OnColourIndexChanged += UpdateMaterial;
// do the first update now with the current state
UpdateMaterial(ColourManager.colour);
}
// Now this is called only when the value is changed in the manager
// script and once at the beginning with the initial state
private void UpdateMaterial(int index)
{
// check for validity
if(index < 0 || index >= materials.Length) return;
_renderer.material = materials[index];
}
private void OnDestroy()
{
// Always make sure to clean up listeners once not needed anymore
// otherwise you get NullReferencExceptions
ColourManager.OnColourIndexChanged -= UpdateMaterial;
}
}

C# Unity Queue NullReferenceException

I am trying to create a simple dialogue system for my game in Unity. I've set up a special trigger for a Dialogue to start and the code is passing right variable but somehow it gets stuck at clearing the queue and throws a NullReferenceException.
I've seen through debugger that all variables and triggers work perfectly fine till cs:27 inside DialogueManager.cs. I've also checked the inspector to make sure everything is correctly assigned.
Dialogue class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Dialogue
{
public string name;
[TextArea(3,10)]
public string[] sentences;
}
DialogueTrigger class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPC_DialogTrigger : MonoBehaviour
{
// Player
public Transform player;
// GameMaager to close
public GameObject Gameplay;
// Camera and Canvas to turn on
public GameObject DialogueManager;
// NPC Details
public GameObject InteractionNPCNameTextField;
public Transform interactionTransform;
public float radius = 3f;
private bool isBusy = false;
// DialogueStart
public GameObject DialogueStart;
void Start()
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
void Update()
{
float distance = Vector3.Distance(player.position, interactionTransform.position);
if (distance <= radius)
{
if (isBusy == false)
{
InteractionNPCNameTextField.gameObject.SetActive(true);
if (Input.GetKeyDown(KeyCode.E))
{
Dialogue();
Debug.Log("Started Dialogue procedure.");
}
}
}
else if (distance > radius)
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
}
public void Dialogue()
{
Gameplay.SetActive(false);
DialogueManager.SetActive(true);
DialogueStart.GetComponent<DialogueStart>().TriggerDialogue();
Debug.Log("Triggered Dialogue.");
}
void OnDrawGizmosSelected()
{
if (interactionTransform == null)
{
interactionTransform = transform;
}
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(interactionTransform.position, radius);
}
}
DialogueStart class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueStart : MonoBehaviour
{
public Dialogue dialogue;
public void TriggerDialogue()
{
FindObjectOfType<DialogueManager>().StartDialogue(dialogue);
Debug.Log("Dialogue sent to dialogue manager.");
}
}
DialogueManager class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class DialogueManager : MonoBehaviour
{
public Text nameText;
public Text DialogueText;
private Queue<string> sentences;
public GameObject DialogueManagerUI;
void Start()
{
if (sentences == null)
{
sentences = new Queue<string>();
}
}
public void StartDialogue (Dialogue dialogue)
{
Debug.Log("Received dialogues: " + dialogue);
nameText.text = dialogue.name;
Debug.Log("Start Dialogue: " + sentences.Count);
sentences.Clear();
Debug.Log("Sentences Cleared: " + sentences);
foreach (string sentence in dialogue.sentences)
{
sentences.Enqueue(sentence);
}
DisplayNextSentence();
}
public void DisplayNextSentence()
{
if(sentences.Count == 0)
{
EndDialogue();
return;
}
string sentence = sentences.Dequeue();
DialogueText.text = sentence;
}
void EndDialogue()
{
Debug.Log("End of conversation.");
}
private void Update()
{
if (DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else if (!DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
}
}
Visual Studio doesn't give me any errors.
Unity error code -> Screenshot
Debugger right before issue -> Screenshot2
Seems to me like the Queue is never assigned to sentences and it remains empty.
If it is so - why?
It sounds like DialogueManager.StartDialogue probably via DialogueStart.TriggerDialogue is called from somewhere in either Awake or at least before your Start was executed.
Especially
DialogueManager.SetActive(true);
lets assume the DialogueManager object is inactive at first anyway. So maybe your stuff is called before it is set to active.
This might also be due to the Start where you set
InteractionNPCNameTextField.gameObject.SetActive(false);
so any component on this GameObject might not have its Start method getting called. Maybe you referenced the wrong GameObject here?
Debugging would help to figure out in which order your methods get called.
In general my thumb rule for initializing is
Do everything that depends only on yourself in Awake
Do everything that depends on other components being setup already in Start
This way you can (almost) always be sure that stuff is already initialized when you need it.
However, these are only guesses but
The simple solution here:
You could already solve this by simply initializing the sentences right away using
private readonly Queue<string> sentences = new Queue<string>();
now it is definitely initialized even before Start or Awake get called!

Categories

Resources