Reputation: 143
I'm new to ScriptableObjects, and I have kind of a noob question. I've read that scriptable objects are used to store data, and can be used to even store a single variable. So here's what I did: I created a script like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/MainCamera",
order = 1)]
public class MainCamera : ScriptableObject
{
public Camera Camera;
}
I then created a scriptable object from it in my Assets folder, like described here: https://docs.unity3d.com/Manual/class-ScriptableObject.html
And now I want to assign the Main camera to that Camera variable in the inspector. However, the selection menu only shows "none", but no camera.
How do I assign the camera to the camera variable in my scriptable object?
Upvotes: 4
Views: 1719
Reputation: 90649
You can not directly attach Scene references to ScriptableObject
s or actually any assets.
But you can go the other way round: Give the Camera the ScriptableObject
reference and make it tell it's own reference to that ScriptableObject
:
// This attribute makes this classes messages be executed also in editmode
// (= also of not in playmode)
[ExecuteInEditModo]
// Assure there is a Camera component
[RequireComponent(typeof(Camera))]
public class CameraSetter : MonoBehaviour
{
[SerializeField] private MainCamera mainCameraAsset;
// Called on initialize
// With [ExecuteInEditModo] also called on recompile
private void Awake ()
{
mainCameraAsset.Camera = GetComponent<Camera>();
}
}
And reference your MainCamera
instance in mainCameraAsset
.
Is there a reason you don't use Camera.main instead of the ScriptableObject
?
Map for different Scenes
In case you want to archive something like a "manager asset" storing a different Camera
reference for each scene as requested in the comments (I hope I understood you correctly) I would change your MainCamera
to something like
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/MainCamera",
order = 1)]
public class MainCamera : ScriptableObject
{
public List<SceneCameraPair> SceneCameraPairs = new List<SceneCameraPair>();
public Dictionary<string, Camera> sceneToCamera = new Dictionary<string, Camera>();
public void AddPair(SceneCameraPair pair)
{
if(SceneCameraPairs.Contains(pair)) return;
SceneCameraPairs.Add(pair);
sceneToCamera[pair.scene.path] = pair.camera;
}
public void ResetPairs()
{
SceneCameraPairs.Clear();
sceneToCamera.Clear();
}
}
[System.Serializable]
public class SceneCameraPair
{
public Scene scene;
public Camera camera;
}
and in the setter use SceneManager.GetActiveScene
// This attribute makes this classes messages be executed also in editmode
// (= also of not in playmode)
[ExecuteInEditModo]
// Assure there is a Camera component
[RequireComponent(typeof(Camera))]
public class CameraSetter : MonoBehaviour
{
[SerializeField] private MainCamera mainCameraAsset;
// Called on initialize
// With [ExecuteInEditModo] also called on recompile
private void Awake ()
{
mainCameraAsset.AddPair(SceneManager.GetActiveScene, GetComponent<Camera>();
}
}
Than later in a scene you can either use the list with FirstOrDefault (which doesn't throw an exception but return null
if item not found) and the Scene.path (because the scene name might be the same and you can't compare scene
directly since it's instance is not the same as the referenced one) like e.g.
var camera = mainCameraReference.SceneCameraPairs.FirstOrDefault(pair => pair.scene.path == ScaneManager.GetActiveScene().path);
or the dictionary like
var camera = mainCameraReference.sceneToCamera[ScaneManager.GetActiveScene().path];
Various Types
To be able to store various references of different types (assuming only one per type) you could do something like e.g.
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/Data", order = 1)]
public class References : ScriptableObject
{
public Camera mainCamera;
public CharacterController controller;
public Transform transform;
// fix for the generic methods
// a bit dirty maybe but should work
public void Set(Component component)
{
if(component.GetType() == typeof(Camera))
{
mainCamera = (Camera) component;
}
else if(component.GetType() == typeof(CharacterController))
{
controller = (CharacterController) component;
}
else if(component.GetType() == typeof(Transform))
{
transform = (Transform) component;
}
}
public void Set(Camera camera)
{
mainCamera = camera;
}
public void Set(CharacterController characterController )
{
controller = characterController ;
}
public void Set(Transform characterTransform)
{
transform = characterTransform;
}
// or simply all at once
public void Set(Camera camera, CharacterController characterController, Transform characterTransform)
{
mainCamera = camera;
controller = characterController;
transform = characterTransform;
}
// etc
}
Than you could have one base setter class like
public abstract class SetterBase<T> : MonoBehaviour where T : Component
{
// unfortunately you can not serialize generics in
// the inspector so for now we stick with only one single ScriptableObject
public References references;
privtae void Awake()
{
SetReference<T>();
}
private void SetReference<T>() where T : Component
{
var component = GetComponent<T>();
references.Set(component);
}
}
Now you can inherit implementations for every type you need / that is present in References
like
public CameraSetter : SetterBase<Camera>
{
// doesn't have to do anything else ... but could
}
and
public TransformSetter : SetterBase<Transform>
{
// doesn't have to do anything else ... but could
}
etc
Or alternatively
(and that's why I added one setter for everything) you could let it be handled all by one single manager
public class ReferenceSetter : MonoBehaviour
{
public References references;
// Reference those in the inspector as usual
public Camera camera;
public Transform transform;
public CharacterController controller;
private void Awake()
{
references.Set(camera, controller, transform);
}
}
Upvotes: 2