Instantiate ScriptableObject With Editor Set Values - unity3d

In Unity am creating an item system using the base class:
[CreateAssetMenu]
class ItemBase : ScriptableObject(){
public string ItemName;
public Sprite ItemSprite;
void Spawn() {
//Spawn Item Using The Sprite
}
}
and then I am using the CreateAssetMenu to right click in my project folder for each item I want to add to the game and then assign their values in the editor.
When i drag my object from the editor onto a game object, everything works fine.
The issue im having is that I cannot figure out how to actually instantiate these at runtime from a script.
When I try to reference it like a class, the class is not found, when I add a script that mirrors the created item, the properties set by the editor are null upon instantiation.
Ideally what id like to do is something like:
Right click and create new ItemBase called FishingRod from editor
Assign name and sprite in editor
\\Instantiate From Script
FishingRod rod = ScriptableObject.CreateInstance(typeof(FishingRod)); // Properties should be the properties set by editor
FishingRod.Spawn(); // Inserts Item Into Game
But when I instantiate the class, its properties are null;
rod.ItemSprite //is null
rod.ItemName //is null

From your use case description it sounds like you shouldn't need to create instances of the ScriptableObject on runtime. You rather want to reference and use the ones you already created.
So what you should do is
(As you already did) create a new ItemBase instance via the Inspector's Asset create menu (Create -> ItemBase)
(As you already did) fill this "container" with your data (name and sprite)
Now in the file that want's to use this asset reference it via a field in the Inspector:
// In the Inspector go to according GameObject
// and drag the ScriptableObject you created into this slot
[SerilaizeField] private ItemBase fishingRod;
And then you simply use the values and methods of this instance via
fishingRod.Spawn();
So if you want to switch between different items you would e.g. have a kind of controller script like
[SerilaizeField] private ItemBase fishingRod;
[SerilaizeField] private ItemBase hammer;
[SerilaizeField] private ItemBase umbrella;
Then you could switch between them like e.g.
private ItemBase _activeItem;
public void SelectFishingRod()
{
_activeItem = fishingRod;
}
public void SelectHammer()
{
_activeItem = hammer;
}
public void SelectUmbrella()
{
_activeItem = umbrella;
}
and then e.g. do
public void UseActiveTool()
{
_activeItem.Spawn();
}

Related

Public variables created in Rider do not show up in Unity

I just started using Unity (which came with VSC), but I had better experience using JetBarin products such as IntelliJ IDEA, so I went and switched to Rider. However, I am now unable to connect public variables (int, float, GameObject) to my Unity projects.
I tried updating Rider and changing some setting, but came none the wiser.
UPDATE: There have been (obvious) request for my code to see the exact issue, so I hope this helps clear up the issue a little bit:
Code written in VSC
The resulting public variables showing up in Unity
Similar code written using Rider
No interactive variables showing up in Unity
Unity serializes only fields (not properties with get or set) of MonoBehaviours. All public fields are serialized unless have [System.NonSerialized] attribute.
DO NOT get confused with the [HideInInspector] attribute, it won't be visible in the inspector (if you don't have a custom inspector) but WILL BE serialized.
class Foo
{
// Bar won't be shown in the inspector or serialized.
[System.NonSerialized]
public int Bar = 5;
}
To serialize a non-public field use [SerializeField] attribute for primitive types (such as int, float, bool).
public class SomePerson : MonoBehaviour
{
// This field gets serialized because it is public.
public string name = "John";
// This field does not get serialized because it is private.
private int age = 40;
// This field gets serialized even though it is private
// because it has the SerializeField attribute applied.
[SerializeField]
private bool isMale = true;
}
If you wanna serialize own class or struct, use [System.Serializable] attribute.
[System.Serializable]
public struct PlayerStats
{
public int level;
public int health;
}

Extenject - NullReferenceException when second time inject

I'm new at Zenject(Extenject).
My dev environment: Win10, Unity2020, Extenject 9.2.0
Here is my question:
In installer bind the class
Container.Bind<AccountInfo>().AsCached();
Inject it at classA
private AccountInfo accountInfo;
[Inject]
private void Init(GameSetup _gameSetup, AccountInfo _accountInfo)
{
this.gameSetup = _gameSetup;
this.accountInfo = _accountInfo;
}
accountInfo.address = "xxx'; // works fine
Then inject AccountInfo to classB
private AccountInfo accountInfo;
[Inject]
private void Init(AccountInfo _accountInfo)
{
this.accountInfo = _accountInfo;
}
accountInfo.address = "xxx'; //NullReferenceException: Object reference not set to an instance of an object
Why accountInfo changed to null? AsCached() dosen't work? Or something worng else?
Help please~~ Thank you!
Here is my code:
Installer
"ClassA" inject GameSetup, and create instance, works fine
"ClassB" inject GameSetup, Error: null object
"ClassB" Creator, I'm trying use container.Instantiate() to create it
---update---
gameSetup still Null Object
There are two cases, when injection will not work properly in your code.
The code, that uses injected object is executed before Init. For example if this code is placed in the construcor.
You create your GameObject/Component in runtime whithout using IInstantiator. While you use Znject you always should use IInstantiator to create objects. To do it you should inject IInstantiator to the object, that creates another objects. IItstantiator is always binded in the container by default, so you don't have to bind it manually. For example:
public class Creator : MonoBehaviour {
[SerializeField]
private GameObject _somePrefab;
private IInstantiator _instantiator;
[Inject]
public void Initialize(IInstantiator instantiator) {
_instantiator = instantiator;
}
private void Start() {
// example of creating components
var gameObj = new GameObject(); // new empty gameobjects can be created without IInstantiator
_instantiator.InstantiateComponent<YourComponentClass>(gameObj);
// example of instantiating prefab
var prefabInstance = _instantiator.InstantiatePrefab(_somePrefab);
}
}
Not an expert but I think that passing IInstantiator or the container around is not a good practice. If you need to create injected instances at runtime, then you need a Factory.
From the documentation
1.- Best practice with DI is to only reference the container in the composition root "layer"
Note that factories are part of this layer and the container can be referenced there (which is necessary to create objects at runtime).
2.- "When instantiating objects directly, you can either use DiContainer or you can use IInstantiator, which DiContainer inherits from. However, note that injecting the DiContainer is usually a sign of bad practice, since there is almost always a better way to design your code such that you don't need to reference DiContainer directly".
3.- "Once again, best practice with dependency injection is to only reference the DiContainer in the "composition root layer""

Argument exception when I try to set a variable with a custom set in unity

I tried to use the Generate() function only if a variable has changed without having to check it every frame. I used the following tutorial to achieve this. but for some reason, whenever i try to set the variable, I get this error:
ArgumentException: GetComponent requires that the requested component 'List`1' derives from MonoBehaviour or Component or is an interface.
the script:
public GameObject CEMM;
private int ListLength;
public static int ListLengthProperty
{
get
{
return JLSV.instance.ListLength;
}
set
{
JLSV.instance.ListLength = value;
JLSV.instance.Generate();
}
}
private void Awake()
{
instance = this;
}
I tried to set the value like this: JLScrollView.ListLengthProperty = JLScrollView.instance.CEMM.GetComponent<List<JLClass>>().Count;
The generic type parameter that you use when calling GetComponent must be a class that derives from Component (or an interface type). List is a plain old class object, which is why you are getting the exception from this:
GetComponent<List<JLClass>>()
I'm not really sure what value you are trying to assign to the property. If you are trying to get the number of components of a certain type on the GameObject you can use GetComponents.
JLScrollView.ListLengthProperty = JLScrollView.instance.GetComponents<JLClass>().Length;

Why i need to declare a variable that have the same name of class and script?

public class CanvasManager : MonoBehaviour
{
public static CanvasManager Instance; // = lobby
[SerializeField]
private LobbyFunction _lobbyFunction;
public LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
...
below is one of the reference
private void Start()
{
GameObject lobbyCanvasGO = CanvasManager.Instance.LobbyFunction.gameObject;
...
I am confused that is it necessary to have the same name of canvasmanager that it is declared , and why there is no error when I sayCanvasManager.Instance.LobbyFunction ,it made me confused since LobbyFunction is belonged to CanvasManager, not Instance.
Finally , sometimes ,
private LobbyFunction _lobbyFunction;
private LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
Sometimes,
private LobbyFunction _lobbyFunction;
public LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
Thanks for your patience reading this, and your help would be greatly appreciated, thanks!
Your class is named CanvasManager, but you cannot statically access it right away.
You created a static member variable in CanvasManager, which holds a reference to a CanvasManager. This is called the singleton pattern.
You can only access static members without a class instance. But in the case of singletons, you create a single instance of the class (usually assigned in Start() or in getInstance() (lazy) after checking if it exists) which you can then access statically through "Instance".
Now, Instance is a static variable holding a reference to a single instance of CanvasManager. So, you can then access non-static members and functions of CanvasManager, if you access "Instance".
Think about it like this:
CanvasManager local_instance = new CanvasManager();
local_instance.non_static_member = value; // this works
CanvasManager.static_member = value; // this works
CanvasManager.non_static_member = value; // won't work.
And now one step further, you access the instance via CanvasManager.Instance.*
CanvasManager.Instance.non_static_member = value; // works!
Explanation of static vs non-static:
normal variables:
Variables needs memory. So usually you create 5 instances of CanvasManager and each instance can have different values. Because each instance reserves memory for each Variable. But if you want to change one, you need to explicitly talk to that instance. You could manage them in a List or by having multiple variables in Code like manager1, manager2...
Think of it as books, where each copy can be modified (write notes into it)
static variables
If you create a static variable, the memory is reserved once for the Class. You can then directly get/set this static variable from anywhere in Code without the need of a Reference to an instance.
Think of it as an online blog, where changes are applied for everyone, being accessible from everywhere. The text exists once in the blog database.
Singletons:
If you only want a single CanvasManager and not 5, you could attach it to any GameObject and access it. But every other script needs a reference, like public CanvasManager my_manager which you need to assign in inspector. As an alterantive, you could use
GameObject.Find("CanvasManagerObject").getComponent<CanvasManager>()
in each script... If only there was a better way to access this CanvasManager from everywhere...
The singleton pattern allows you to get a reference to a single, nonstatic instance of the CanvasManager, while it doesn't even need a GameObject it can attach to.
Naming
You are talking about "it has to have the same name" - this is not true. You can name the instance whatever you like. CanvasManager.MyCustomlyNamedInstance would work too. But the MyCustomlyNamedInstance must be a static variable in the CanvasManager class, or any other class. You could have a GameManager that manages your instances, so GameManager.MyCanvasManagerInstance would work too.

Serialization of a list of custom objects in unity

While trying to make a script for building assets, I ran into an issue with unity's serialization. I have a class in which I store some arbitrary information, which is then stored in an array in a MonoBehaviour on a prefab. I cannot for the life of me get the array to save however, as when I make the object into a prefab it loses the list's values. I have tried using [System.Serializable] and ScriptableObject, but both seem to pose their own new issues.
For instance, using ScriptableObject would mean having to save the data objects as assets, which would become way too much since these objects can get to hundreds in number.
Am I making a mistake in my understanding of unity's serialization? Is there a way to get this working without the ScriptableObject approach of saving every ArbitraryInfo object in an asset?
Data object:
[System.Serializable]
public class ArbitraryInfo{
public int intValue;
public Vector3 vectorValue;
}
OR
public class ArbitraryInfo : ScriptableObject {
public int intValue;
public Vector3 vectorValue;
void OnEnable() {
hideflags = HideFlags.HideAndDontSave;
}
}
Behaviour:
public class MyBuilder : MonoBehaviour {
public ArbitraryInfo[] infoArray;
}
Editor:
[CustomEditor(typeof(MyBuilder))]
public class MyBuilderEditor : Editor {
private SerializedProperty infoArrayProperty;
void OnLoad() {
infoArrayProperty = serializedObject.FindProperty("infoArray");
}
void OnInspectorGUI() {
serializedObject.Update();
for (var i = 0; i < infoArrayProperty.arraySize; i++) {
if (i > 0) EditorGUILayout.Space();
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
EditorGUILayout.LabelField("Info " + i, EditorStyles.boldLabel);
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
}
serializedObject.ApplyModifiedProperties();
}
}
EDIT 1, Thank you derHugo
I changed my code to incorporate the changes. Now there are errors for ArbitraryInfo not being a supported pptr value.
Secondly, ArbitraryInfo no longer being a ScriptableObject poses the question of how to initialize it. An empty object can be added to infoArrayProperty through infoArrayProperty.arraySize++, but this new empty object seems to be null in my case. This might be due to the pptr issue mentioned above.
EDIT 2
The issue I was having was caused by another piece of code where I tried to check if infoArrayProperty.objectReferenceValue == null. I changed this to another check that did the same thing and everything worked!
No, no ScriptableObject needed.
But note that GetArrayElementAtIndex(i) returns a SerializedProperty. You can not simply parse it to your target class.
so instead of
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
and
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
you have to get the info's SerializedPropertys by using FindPropertyRelative:
var info = infoArrayProperty.GetArrayElementAtIndex(i);
var intValue = info.FindPropertyRelative("intValue");
var vectorValue = info.FindPropertyRelative("vectorValue");
than you can/should use PropertyFields
EditorGUILayout.PropertyField(intValue);
EditorGUILayout.PropertyField(vectorValue);
allways try to avoid using direct setters and use those SerializedProperties instead! This provides you with Undo/Redo functionality and marking the changed Behaviour/Scene as unsaved automatically. Otherwise you would have to tak care of that manually (... don't ^^).