简体   繁体   中英

Attach camera to scriptable object in unity

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?

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 eg

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 eg

[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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM