I'm trying to create a shop where you can buy a different player sprite from in-game currency. (The shop is a separate scene from the level) I was told that using scriptableobject is the way to go, so I made the following:
[CreateAssetMenu(fileName = "New Sprite", menuName = "Player Sprites")]
public class PlayerSprites : ScriptableObject
{
public string spriteName;
public int cost;
public Sprite sprite;
}
And I just added to the player script
public SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
I'm not really sure where to go from here... how to render the sprite on to the player from a different scene when the sprite button is pressed... Any help is greatly appreciated thank you!
Though your question is quite hazy and I don't really see what you tried so far:
As you have it right now you will need one ScriptableObject for each Sprite item ... I don't think that's what you want. You should rather use one ScriptableObject storing the information of all Sprite items.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Assets/New Store", menuName = "Sprites-Store")]
public class SpriteStoreContainer : ScriptableObject
{
public List<StoreSpriteItem> SpriteItems = new List<StoreSpriteItem>();
// you can/should also implement methods here as in any usual component!
}
Also make sure your fileName starts with Assets/
And a separate class for the Items which uses [System.Serializable] so you can display it in the Inspector.
using UnityEngine;
[System.Serializable]
public class StoreSpriteItem
{
public string spriteName;
public int cost;
public Sprite sprite;
public bool IsAvailable;
// also here you could/should implement some methods e.g.
public void BuyItem()
{
IsAvailable = true;
}
}
And back in Unity:
Now you first have to Instantiate the ScriptableObject asset:
Go to the Project View (Assets) -> right mouse click -> in the menu click on Create -> click on Sprites-Store
This should create a new asset called New Store(.asset) under Assets
Now in the Inspector of this created asset you fill in the information you need. You should see our List SpriteItems with Size = 0.
To create elements just increase the Size value and hit enter(Carefull: Unity doesn't ask if you change this value => take care you don't delete items by reducing this value accidently later)
Now you can adjust all information for those SpriteItems
Later wherever you need access to the information of this Asset you can just use the reference as any other Component => you can assign it e.g. via the Inspector using a public field e.g.
using UnityEngine;
public class ScriptThatUsesStore : MonoBehaviour
{
public SpriteStoreContainer Store;
public void DoSomthingWithStore()
{
// example: just get the first List element
Sprite someSprite = Store.SpriteItems[0].sprite;
}
}
and than access the data in it. Though I strongly recommend you rather implement some methods in the ScriptableObject like e.g. BuyItem, GetSprite, etc.
Related
I am trying to make a wave spawn system in a serialized list in the inspector, but I can't figure out how to make it work. I can't seem to reference variables in other classes within the same script, and I need that data to make it work.
Right now it looks something like this, with classes nested within each other:
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class WaveSystemScript : MonoBehaviour
{
[SerializeField] public WaveBubbleScriptableObject waveBubbleSO; //This contains the list of valid bubbles, but I can't reference it from the other classes.
[SerializeField] public List<GameObject> validBubbles = new List<GameObject>(); //So does this
[SerializeField] List<Waves> waveList = new List<Waves>();
}
[System.Serializable]
class Bubble
{
[SerializeField] GameObject bubbleSelection; //select the bubble to spawn repeatedly
[SerializeField] float spawnInterval;
}
[System.Serializable]
class Waves
{
[SerializeField] List<Bubble> waveBubbles = new List<Bubble>(); //each wave may have multiple bubbles, with different variable each
float waveLength;
}
`
The main issue is setting the bubbleSelection gameobject in the bubble class. Anything in the monobehaviour class can't be referenced in the other classes for a reason I'm not aware of. I tried lists, arrays and scriptableobjects but couldn't figure out how. I could reference the prefab directly using an empty GameObject variable, but I would like it to work such that all the valid spawnable objects are already present in an array/list or dropdown for easy selection.
Well, both fields within Bubbles as well as within Wave have the default member accessibility private and therefore are not visible for any other type.
(See access modifiers)
You either simply want to make those public (in which case the [SerializeField] becomes redundant as it is only needed to serialize fields that are not public)
// [System.Serializable] <- this is also redundant as MonoBehaviour or anything
// derived from UnityEngine.Object already is serialized anyway
public class WaveSystemScript : MonoBehaviour
{
public WaveBubbleScriptableObject waveBubbleSO;
public List<GameObject> validBubbles = new List<GameObject>();
public List<Waves> waveList = new List<Waves>();
}
[System.Serializable]
class Bubble
{
public GameObject bubbleSelection;
public float spawnInterval;
}
[System.Serializable]
class Waves
{
public List<Bubble> waveBubbles = new List<Bubble>();
public float waveLength;
}
or for encapsulation, depending on your use case, you can keep them as private so they are only editable via the Inspector but via code only provide them as public readonly properties like e.g.
[System.Serializable]
public class Waves
{
[SerializeField] private List<Bubble> _waveBubbles = new List<Bubble>();
[SerilaizeField] private float _waveLength;
public IReadOnlyList<Bubble> waveBubbles => _waveBubbles;
public float waveLength => _waveLength;
}
I'm restructuring my code and created an namespace with the intent to put all the scriptable object classes inside it. (I'm attaching the code and screenshot below for the sake of organization.)
When I go back to Unity, it seems fine. It also displays the option to create in the menu.
But when I check the object, it seems to have no script attached to it.
Some people have shown that Unity is capable of having more than one like in this post from Unity's Forum.
The guy even lists the same behavior as mine in the Case #4. But I can't understand why my code isn't behaving like his Case #2. I'm thinking that's caused by the Attribute, but I don't know how to display the option to create the asset.. Can someone help me?
edit: I did try using a Editor class as the post but got the same result.
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Size Preset", menuName = "BBMM/Size Preset")]
public class SO_UI_SizePreset : ScriptableObject
{
public string presetName;
public Vector2 realSize;
public UnitType unit;
}
[CreateAssetMenu(fileName = "New Object", menuName = "BBMM/Object Container")]
public class SO_UI_ObjectList_Item : ScriptableObject
{
public string objectName;
public GameObject prefab;
}
}
Here's the asset created using the code above:
First of all make sure each class is in a separate file with matching name!
SO_UI_SizePreset.cs
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Size Preset", menuName = "BBMM/Size Preset")]
public class SO_UI_SizePreset : ScriptableObject
{
public string presetName;
public Vector2 realSize;
public UnitType unit;
}
}
SO_UI_ObjectList_Item.cs
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Object", menuName = "BBMM/Object Container")]
public class SO_UI_ObjectList_Item : ScriptableObject
{
public string objectName;
public GameObject prefab;
}
}
Then if you created the SO asset first and then changed the namespace Unity might loose the connection (depending on how exactly you made that change).
-> you can either recreate them or go to the Inspector top-right corner menu, enable Debug mode and drag&drop the correct script into the Script field.
I'm making a saving system for my mobile game. I have a script for economy that tracks the amount of in-game currency and i'd like to reference a non MonoBehaviour script that will hold the data to save
public class Money : MonoBehaviour
{
public int Gold;
public int Platinum;
public int Tokens;
public DataHolder data;
private void Update()
{
data.Platinum = Platinum;
data.Tokens = Tokens;
data.Gold = Gold;
}
}
public class DataHolder
{
public int[] dragonLevel;
public bool[] dragonMasterLevel;
public int Gold;
public int Platinum;
public int Tokens;
}
Will unity automatically get the reference and will it properly transfer this data?
You are allowed to use your own classes / structs in Unity. Unlike Unity components like ScriptableObject and MonoBehaviour, you need to create them and make sure they get deleted.
ScriptableObject
public class DataHolder : ScriptableObject {
...
}
ScriptableObjects are Unity's solution for exactly your use case. You can then create assets that are instances of DataHolder.
In this solution the data gets serialized with the GameObject so you can have default values in Unity Editor Inspector.
Keep using DataHolder as is
private void Update () {
if (data == null) data = new DataHolder();
...
}
In both these solutions the runtime values will not persist between sessions. To save values and restore them there are different options. Here are a few in order of simplicity:
PlayerPrefs:
Add WriteToPrefs() and LoadFromPrefs() methods to DataHolder.
FileSystem
online databases like Google Firebase
Adding to the other answer, you could just create an object of your DataHolder class in one of your monobehaviours, and call DontDestroyOnLoad on the gameObject. you will also need to use the code below in Awake on your DontDestroyOnLoad() object to make sure only one instance of the MonoBehaviour is available, so you dont create duplicates when you navigate between scenes
public DataHolder dataHolder;
public static MyComponent myComponent;
private void Awake()
{
DontDestroyOnLoad(gameObject);
if (myComponent == null) myComponent = this;
else Destroy(gameObject);
dataHolder = new DataHolder();
}
now you can write to this dataHolder object and keep the GameObject alive in case you want to add any more data to it. It is good practice to have a persistant GameObject in the scene to save in-game data, or just about anything that needs to be preserved between scenes.
Alternatively, you can also make your DataHolder class static and it's members static. although i would advise against this, especially if there are multiple players/characters in your game that use the same component. But this will actually help you reference it from any script, without creating objects, or maintaining a persistent GameObject in the scene.
I'm working on a 2D Pixel Platformer RPG, I need to develop a save and load mechanism in it. There are several scenes in the game (and will have many more), the question is, how do I save the scene number the player is currently in, so that when he quits and reloads the game, he's in the same scene. How can I implement it in C# Unity. (please be clear as I'm somewhat a beginner).
Ok, so there are a few things you need to do in order to achive this:
First, in the first scene in your build - create an Empty GameObject, name it "SceneManager".
Then, create a new tag "SceneManager" and add it to the "SceneManager" GameObject
Finally, add the "SceneManager" script to the "SceneManager" GameObject:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneManager : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
}
public void SaveScene()
{
int activeScene = SceneManager.GetActiveScene().buildIndex;
PlayerPrefs.SetInt("ActiveScene", activeScene);
}
public void LoadScene()
{
int activeScene = PlayerPrefs.GetInt("ActiveScene");
SceneManager.LoadScene(activeScene);
}
}
Then, you can load/save scenes by using this script:
using UnityEngine;
public class UsageScript: MonoBehaviour {
private SceneManager SceneManager;
void Awake ()
{
sceneManager = GameObject.FindGameObjectWithTag("SceneManager").GetComponent<SceneManager>();
}
void UsageManager()
{
sceneManager.SaveScene();
sceneManager.LoadScene();
}
}
I am doing a Unity project.
I have two scenes in my project.
In the first scene. I set three buttons texted "1 min", "2 mins" and "3 mins".
And I want to have the corresponding game duration in my second scene (game scene).
I have countdown function in my script.
now I am using singleton pattern following the steps of this tutorial. https://www.youtube.com/watch?v=CPKAgyp8cno
Problem is when I run the game, three texts of time start simultaneously.
And my buttons in Scene one could control load scenes but not choose time.
The following are the codes. In the PersistentManager Script which attached to the PersistentManager game object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PersistentManagerScript : MonoBehaviour
{public static PersistentManagerScript Instance { get; private set; }
public int Value;
// Start is called before the first frame update
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
In the SceneManager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneManagerScript : MonoBehaviour
{
public GameObject CountDownIn1;
public GameObject CountDownIn2;
public GameObject CountDownIn3;
public Text ValueText;
public Text ValueText1;
public Text ValueText2;
private void Start()
{
switch (this.gameObject.name)
{
case "Button1min":
SetGameTimeEqualOne();
break;
case "Button2mins":
SetGameTimeEqualTwo();
break;
case "Button3mins":
SetGameTimeEqualThree();
break;
}
}
public void SetGameTimeEqualOne()
{
CountDownIn1.SetActive(true);
ValueText.text = PersistentManagerScript.Instance.Value.ToString();
Debug.Log("COUNTDOWN1 active");
//DontDestroyOnLoad(this.CountDownIn1);
}
public void SetGameTimeEqualTwo()
{
CountDownIn2.SetActive(true);
Debug.Log("COUNTDOWN2 active");
ValueText1.text = PersistentManagerScript.Instance.Value.ToString();
DontDestroyOnLoad(this.gameObject);
}
public void SetGameTimeEqualThree()
{
CountDownIn3.SetActive(true);
Debug.Log("COUNTDOWN3 active");
ValueText2.text = PersistentManagerScript.Instance.Value.ToString();
DontDestroyOnLoad(this.gameObject);
}
Any suggestions will be appreciated.
There are multiple ways to pass data between scenes. The most common recommendation is to use a Singleton pattern that is consumed by other scripts in each scene. However, this can be overly complicated.
Interestingly enough, Unity's newer ScriptableObjects can be used for a similar strategy. Since ScriptableObjects are persisted at a higher scope than scenes, you can use them to hold data between scenes without having to utilize statics and singletons. Unity even markets them as more than just data containers! I prefer to pass data between objects and scenes by creating a common scriptable object, and referencing that asset in each script that needs to share data (regardless of scene).
Here's an example using your requirement:
GameData.cs
[CreateAssetMenu(fileName = "GameData", menuName = "Game Data")]
public class GameData: ScriptableObject
{
float gameDuration; //This is where you will store and read your selected game duration
}
GameSelection.cs This script exists on Scene 1
public class GameSelection : Monobehaviour
{
public GameData gameData;
public void SetDuration(float time) //Call this method from each button with a different parameter
{
gameData.gameDuration = time;
}
}
Countdown.cs This script exists in Scene 2
public class Countdown: Monobehaviour
{
public GameData gameData;
float countdownTimer;
public void Start()
{
countdownTimer = gameData.gameDuration;
}
//... rest of your countdown script
}
Now you can create a GameData asset in your project folder and drag it into the inspector in Scene1, and the inspector in Scene2. If Scene1 changes the value of the timer, Scene2 will know about it. Additionally, you can set a default value for gameDuration on the GameData asset and run/test Scene2 without having to run Scene1 first!