Unity editor scripting : how to execute two different actions with the same button? - unity3d

I wrote some code to add a button to my custom editor in Unity. What I'd like is the following. When I click the button once, a component is added, and when I click again the component is removed. In the simple code below, I just try to print "Add" or "Remove" when clicking the button. I noticed that the variable toggleRigidBody takes the value true but then take the value false right after. It doesn't stay "true" even though I never explicitly change it in the code. The "else if" is never fired. I'm not sure why.
using UnityEditor;
using UnityEngine;
using SS3D.Engine.Inventory;
[CustomEditor(typeof(ContainerController))]
public class ContainerControllerEditor : Editor
{
private bool toggleRigidBody = false;
public override void OnInspectorGUI()
{
DrawDefaultInspector();
ContainerController containerController = (ContainerController)target;
Debug.Log(toggleRigidBody);
bool buttonPressed = GUILayout.Button("AddAttachedContainer");
if (buttonPressed && toggleRigidBody == false)
{
Debug.Log("Add");
toggleRigidBody = true;
}
else if (buttonPressed && toggleRigidBody == true)
{
Debug.Log("Remove");
toggleRigidBody = false;
}
}
}
My code only print "Add" when I click the button. What's happening here ?

The main problem here is that the editor instance is created when the object is clicked and the Inspector loaded. And then it is destroyed as soon as the object loses focus and the Inspector not shown for this object anymore.
=> Your flag toggleRigidBody is not persistent!
What you rather want to do is serialize the flag inside your object or even better: Serialize the reference itself.
This way you
Have already access to the reference in your script in case you need it on runtime
Have the reference in the editor for a) checking if it exists and b) being able to remove it directly
So having your class like
public class ContainerController : MonoBehaviour
{
// Store the reference in a field
[SerializeField] private Rigidbody _rigidbody;
...
}
The editor could look like
[CustomEditor(typeof(ContainerController))]
public class ContainerControllerEditor : Editor
{
private SerializedProperty _rigidbody;
ContainerController containerController;
private void OnEnable()
{
_rigidbody = serializedObject.FindProperty("_rigidbody");
containerController = (ContainerController)target;
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// Loads all current serialized values from the target into the serialized properties
serializedObject.Update();
// If the _rigidbody field is not assigned
// try GetComponent as fallback
if(!_rigidbody.objectReferenceValue) _rigidbody.objectReferenceValue = containerController.GetComponent<Rigidbody>();
// simply put everything that belongs to one button click inside one if block
// this is easier to maintain and read
// Of course alternatively you could also simply have two completely different buttons to display
// depending on the value of "_rigidbody.objectReferenceValue"
if(GUILayout.Button(_rigidbody.objectReferenceValue ? "Remove Rigidbody" : "Add Rigidbody")
{
// Is there a Rigidbody?
if(_rigidbody.objectReferenceValue)
{
// Yes -> destroy it
// There are two different destroy methods depending whether you are
// in Play mode or Edit mode
if(Application.isPlaying)
{
Destroy(_rigidbody.objectReferenceValue);
}
else
{
DestroyImmediate(_rigidbody.objectReferenceValue);
}
}
// Otherwise the field is currently not set and no component was found using GetComponent
else
{
// Add the component via the ObjectFactory
// this enabled undo/redo and marks the scene dirty etc
// and assign it to the serialized property
_rigidbody.objectReferenceValue = ObjectFactory.AddComponent<Rigidbody>(target.gameObject);
}
}
// Writes back all modified properties to the target and takes care of Undo/Redo and marking dirty
serializedObject.ApplyModifiedProperties ();
}
}

The editor object is created when it's being displayed, and destroyed when it's not, so for your data to persist you will need to store the values somewhere else. So that's definitely what is happening. The easiest way to have a value of a variable to be persistent between sessions you would use EditorPrefs to save the variable values. Thats the easiest way you can go about it, so you would use this to save the toggleRigidBody value.
https://docs.unity3d.com/ScriptReference/EditorPrefs.SetBool.html

Related

SteamVR: Correct way to get the input device triggered by an action and then get it's corresponding Hand class?

I have an action that is mapped to both my left amd right hand triggers on my VR controllers. I would like to access these instances...
Player.instance.rightHand
Player.instance.leftHand
...depending on which trigger is used but I can't fathom the proper way to do it from the SteamVR API. So far the closest I have gotten is this...
public SteamVR_Action_Boolean CubeNavigation_Position;
private void Update()
{
if (CubeNavigation_Position[SteamVR_Input_Sources.Any].state) {
// this returns an enum which can be converted to string for LeftHand or RightHand
SteamVR_Input_Sources inputSource = CubeNavigation_Position[SteamVR_Input_Sources.Any].activeDevice;
}
}
...am I supposed to do multiple if statements for SteamVR_Input_Sources.LeftHand and SteamVR_Input_Sources.RightHand? That doesn't seem correct.
I just want to get the input device that triggered the action and then access it using Player.instance.
I was also looking for an answer to this. I've for now done what I think is what you mean with the if-statements. It works, but definitely not ideal. You want to directly refer to the hand which triggered the action, right?
With the 'inputHand' variable here I get the transform.position of the hand from which I will raycast and show a visible line. I could have put a separate instance of a raycastScript like this on each hand, of course, but I wanted to make a 'global' script, if that makes sense.
private SteamVR_Input_Sources inputSource = SteamVR_Input_Sources.Any; //which controller
public SteamVR_Action_Boolean raycastTrigger; // action-button
private Hand inputHand;
private void Update()
{
if (raycastTrigger.stateDown && !isRaycasting) // If holding down trigger
{
isRaycasting = true;
inputHand = inputChecker();
}
if (raycastTrigger.stateUp && isRaycasting)
{
isRaycasting = false;
}
}
private Hand inputChecker()
{
if (raycastTrigger.activeDevice == SteamVR_Input_Sources.RightHand)
{
inputHand = Player.instance.rightHand;
}
else if (raycastTrigger.activeDevice == SteamVR_Input_Sources.LeftHand)
{
inputHand = Player.instance.leftHand;
}
return inputHand;
}

Multi-scene launching of build, as is, from Unity, how?

Multiscene editing in Unity, blessed be it, permits the launching (via Editor Play mode) of the current scenes, in their current hierarchical state.
However, building and running the project doesn't recognise the current scene setup in the editor, and starts with whatever is set in the Build Settings.
Is there some way to make builds aware of the current editor state of Multi-scene editing hierarchy, and build and run that setup?
1. Getting the editor scenes into the build settings
First for collecting the settings you can use an editor script using
EditorSceneManager.GetSceneManagerSetup to receive the current setup of scenes in the editor
I assume you want only loaded Scenes so make a list of only scenes with isLoaded = true
EditorBuildSettings.scenes to add those scenes to the build settings
1.a. Update on MenuItem click
I made it an extra button in the menu since you might not want to have it always automatically.
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
public static class UpdateBuildSettigns
{
[MenuItem("Example/UpdateBuildSettings")]
public static void UpdateSettings()
{
// get current editor setup
SceneSetup[] editorScenes = EditorSceneManager.GetSceneManagerSetup();
// filter list e.g. get only scenes with isActive true
var activeEditorScenes = editorScenes.Where(scene => scene.isLoaded);
// set those scenes as the buildsettings
List<EditorBuildSettingsScene> editorBuildSettingsScenes = new List<EditorBuildSettingsScene>();
foreach (var sceneAsset in activeEditorScenes)
{
string scenePath = sceneAsset.path;
// ignore unsaved scenes
if (!string.IsNullOrEmpty(scenePath)) continue;
editorBuildSettingsScenes.Add(new EditorBuildSettingsScene(scenePath, true));
}
// Set the Build Settings window Scene list
EditorBuildSettings.scenes = editorBuildSettingsScenes.ToArray();
}
}
Updating on menu button
1.b. Update automaticly on (un)loading scenes
If you want it happening automatically you could also add the call as callback to EditorSceneManager.sceneOpened and EditorSceneManager.sceneClosed using InitializeOnLoad and a static constructor to get the callbacks added after recompile or opening the UnityEditor like
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
[InitializeOnLoad]
public static class UpdateBuildSettigns
{
// ofcourse you still can also call it via menu item
[MenuItem("Example/UpdateBuildSettings")]
public static void UpdateSettings()
{
//...
}
static UpdateBuildSettigns()
{
// it is always save to remove callbacks even if they are not there
// makes sure they are always only added once
//
// this is a static constructor so actually there should be no
// callbacks yet ... but .. you never know ;)
EditorSceneManager.sceneOpened -= OnSceneLoaded;
EditorSceneManager.sceneClosed -= OnSceneUnloaded;
EditorSceneManager.sceneOpened += OnSceneLoaded;
EditorSceneManager.sceneClosed += OnSceneUnloaded;
}
private static void OnSceneUnloaded(Scene current)
{
UpdateSettings();
}
private static void OnSceneLoaded(Scene current, OpenSceneMode mode)
{
UpdateSettings();
}
}
Using automatic update
1.c. Enable/Disable automatic updates
If you want more control you can also add extra menu entries for enabling and disabling the automatic updates like
// flag to check if auto-updates are currently enabled
private static bool isEnabled;
// disable the "EnableAutoUpdate" button if already enabled
[MenuItem("Example/EnableAutoUpdate", true)]
private static bool CanEnable()
{
return !isEnabled;
}
// disable the "DisableAutoUpdate" button if already disabled
[MenuItem("Example/DisableAutoUpdate", true)]
private static bool CanDisable()
{
return isEnabled;
}
// add callbacks
[MenuItem("Example/EnableAutoUpdate")]
private static void EnableAutoUpdate()
{
// it is always save to remove callbacks even if they are not there
// makes sure they are always only added once
EditorSceneManager.sceneOpened -= OnSceneLoaded;
EditorSceneManager.sceneClosed -= OnSceneUnloaded;
EditorSceneManager.sceneOpened += OnSceneLoaded;
EditorSceneManager.sceneClosed += OnSceneUnloaded;
isEnabled = true;
}
// remove callbacks
[MenuItem("Example/DisableAutoUpdate")]
private static void DisableAutoUpdate()
{
EditorSceneManager.sceneOpened -= OnSceneLoaded;
EditorSceneManager.sceneClosed -= OnSceneUnloaded;
isEnabled = false;
}
Note since this uses the UnityEditor namespace you should either place this script in an Editor folder or use proper pre-processors like
#if UNITY_EDITOR
// above code here
#endif
2. Loading all scenes from the build settings
Than later when running the app in the first scene there should be a script responsible for loading all those scenes. Something like e.g.
// making it a component to make sure it is inside of one scene
public class SceneLoader : MonoBehaviour
{
private void Start()
{
var thisScene = SceneManager.GetActiveScene();
// load all scenes
for(int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
// skip if is current scene since we don't want it twice
if(thisScene.buildIndex == i) continue;
// Skip if scene is already loaded
if(SceneManager.GetSceneByBuildIndex(i).IsValid()) continue;
SceneManager.LoadScene(i, LoadSceneMode.Additive);
// or depending on your usecase
SceneManager.LoadSceneAsync(i, LoadSceneMode.Additive);
}
}
}
refs:
SceneManager.sceneCountInBuildSettings
Scene.buildIndex
SceneManager.GetSceneByBuildIndex
SceneManager.LoadScene
SceneManager.LoadSceneAsync
What I would do is attach some sort of a script to the launching scene in unity that would then trigger the loading of the rest of the required scenes after the game has started. That would require some fiddling to start properly (e.g detect the fact that the scenes are not already loaded before trying to load them).
I might extend the answer with a code snippet to achieve the result if you need it.
For now you could take a look at the docs here:
https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.GetSceneByName.html https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html
The basic idea would be:
Get the necessary scenes using SceneManager.GetSceneByName and
filter out all the scenes that are already loaded.
For the scenes that are not loaded yet, call LoadSceneAsync and
attach some sort of
the coroutine to check the loading progress.
When all of the scenes are loaded, run a callback so that rest of
the game knows that the scenes are loaded and it is good to run the
rest of necessary actions which rely on those scenes being loaded.
If you want to preserve the current hierarchy (a set of scenes that are opened in the editor) when building, then it might be achievable with BuildPipeline:
https://docs.unity3d.com/Manual/BuildPlayerPipeline.html
There is a way to make a build with a programmatically-accessible list of scenes:
// Get filename.
string path = EditorUtility.SaveFolderPanel("Choose Location of Built Game", "", "");
string[] levels = new string[] {"Assets/Scene1.unity", "Assets/Scene2.unity"}; // You'd have to assemble this list yourself.
// Build player.
BuildPipeline.BuildPlayer(levels, path + "/BuiltGame.exe", BuildTarget.StandaloneWindows, BuildOptions.None);
(which you can determine based on the currently loaded scenes when running your build). This wouldn't be a standard (Cmd + b) build though, but pretty close.

UNITY An object reference is required to access non-static member `Outline.OutlineMode'

I'm trying to create an outline when you are near it, but i'm getting all the time the same error.
void Update () {
if (Input.GetKeyDown(KeyCode.E)){
var outline = gameObject.AddComponent<Outline>();
outline.OutlineMode = Outline.Mode.OutlineAll;
outline.OutlineColor = Color.yellow;
outline.OutlineWidth = 5f;
}
}
void OnTriggerStay(Collider other) {
if (Outline.OutlineMode == Outline.Mode.OutlineAll) {
Debug.Log("test");
}
}
If i press E it works, and if i change it to ontriggerstay works too, but im trying that it only applies one time, because im getting some errors if its on. I have to say that im using an asset, called quick outline
Srry for my very bad english and explanation and thank you
add the outline to your object in Awake() then set it to disabled.
then enable it in OnTriggerEnter() and disable it in OnTriggerExit()
this will keep you from making multiple copies, and it will only be active when you are in range of your trigger

How to access gameobject present in another scene [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 4 years ago.
I am creating a simple number guessing game in unity3d.
I want to open a new scene on button click and change the text of a text element present in the loaded scene from the current scene.
I have been able to open new scene on button click but how can i access the text element in other scene so that i can change its text from the current scene.
This is what i have so far but it obviously throws NullReferenceException because i can't access the text element in another scene from current scene.
SceneManager.LoadScene("End Scene");
gameResultText.text = "You Won!!"; //<-------this line throws the exception
gameResultText.color = Color.green;
Better solution I came up with:
Make a script that sets a static string variable. This script must be in your Game scene and will hold the result.
public class ResultTextScript : MonoBehaviour
{
public static string ResultText;
void EndGame(){
if (won){ //if won game
ResultText = "You won!"
}
else //if lost game
{
ResultText = "You lost, try again another time!"
}
//Change scene with SceneManager.LoadScene("");
}
}
Put this script on your result text in the end scene. This script will retrieve the result and display it.
Using UnityEngine.UI;
public class EndGameDisplayResult{
Text text;
OnEnable(){
Text.text = ResultTextScript.ResultText
}
}
This way, it will set the text as soon as the new scene is loaded.
Previous/alternative method:
If you already have the scene open, one option would be to add a script to the "You won!" text which holds a static variable with a reference to itself.
So like this.
public class ResultTextScript : MonoBehaviour
{
public static ResultTextScript Instance;
void Awake(){
if (Instance == null)
Instance = this;
}
}
Then you can call the reference to that GameObject from anywhere in the other scripts, including between scenes.
Like this ResultTextScript.Instance
Note though that you cannot call the reference in the Awake method, as that is where the variable is initialized, you can use it after the Awake methods have been called though.
Basically
Add the ResultTextScript to your Text object in the 'End Scene'
Open the 'End Scene' from the 'Game Scene', for example with your SceneManager approach as you already do.
Ensure that the End Scene has loaded, then say in the script you wish to change the text gameObject go = ResultTextScript.Instance.gameObject
I do not believe there is a way to modify the context or objects of a scene that is not currently open.
public class GameResults {
public static string finalText = "";
}
In your function where you are loading the scene, right before you call load scene you can access that text like so:
GameResults.finalText = "You Win!";
or
GameResults.finalText = "You Lose!";
load your scene, and on your text object give it a script like this:
using UnityEngine;
using UnityEngine.UI;
public class ResultTextScript : MonoBehaviour
{
public Text textResults;
void Start() {
textResults = getComponent<Text>();
if(textResults != null) {
textResults.text = GameResults.finalText;
}
}
}
There are other things you can use as well is, storing the game results in PlayerPrefs and loading the string or int you stored in PlayerPrefs preferences at the start of your end scene. This will help you avoid creating an unnecessary class or static variable.
So Like before you can do:
PlayerPrefs.SetString("GameResults", "You Win!");
or
PlayerPrefs.SetString("GameResults", "You Lose!");
load your scene, and on your text object give it a script like this:
using UnityEngine;
using UnityEngine.UI;
public class ResultTextScript : MonoBehaviour
{
public Text textResults;
void Start() {
textResults = getComponent<Text>();
if(textResults != null) {
textResults.text = PlayerPrefs.GetString("GameResults", "");
}
}
}

Telling another GameObject to do something (Unity2D)?

I have a simple touch/mouseclick script attached to a GameObject as a sort of "Master Script" i.e. the GameObject is invisible and doesn't do anything but hold this Touch script when the game is running.
How do I tell other named GameObjects that are generated at runtime to do things e.g. highlight when touched/clicked from this Master Script?
The script for highlighting seems to be: renderer.material.color= colorRed;
But I'm not sure how to tell the GameObject clicked on to become highlighted from the Master Script.
Please help! (am programming in C#)
Thanks!
Alright so you'll want to use a ray cast if you're not doing it in GUI. Check out Unity Ray Casting and then use
hit.transform.gameObject.renderer.material.color = red;
You can have a switch that is like:
if (hit.transform.gameObject.CompareTag("tag")) {
// turn to red;
} else {
// turn to white;
}
Use the ScreenPointToRay or ScreenPointToWorld depending on what you're doing.
For touch, should look like:
void Update () {
foreach (Touch touch in Input.touches)
{
Ray ray = Camera.main.ScreenPointToRay(touch.position);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 1000.0f))
{
if (hit.collider.gameObject.CompareTag("tag"))
{
hit.collider.gameObject.renderer.material.color = red;
}
}
}
// You can also add in a "go back" feature in the update but this will "go back" then when the touch ends or moves off
// Also not so good to search for Objects in the update function but that's at your discretion.
GameObject[] gObjs = GameObject.FindGameObjectsWithTag("tag");
foreach (GameObject go in gObjs) {
go.renderer.material.color = white;
}
}
To answer your question about pinging the 'manager'
I would do one of two options.
Either:
// Drop the object containing YourManager into the box in the inspector where it says "manage"
public YourManager manage;
// In the update and in the Ray Cast function (right next to your color change line):
manager.yourCall ();
// and
manager.yourString = "cool";
OR
private YourManager manage;
void Awake () {
manager = GameObject.FindObjectWithTag("manager").GetComponent<YourManager> ();
}
// In the update and in the Ray Cast function (right next to your color change line):
// In your manager file have "public bool selected;" at the top so you can access that bool from this file like:
manager.selected = true;
I detail this a little in another one of my answers HERE
For mouse clicks, I would check out the MonoDevelop functions they have in store such as:
// This file would be on the game object in the scene
// When the mouse is hovering the GameObject
void OnMouseEnter () {
selected = true;
renderer.material.color = red;
}
// When the mouse moved out
void OnMouseExit () {
selected = false;
renderer.material.color = white;
}
// Or you can use the same system as above with the:
Input.GetMouseButtonDown(0))
Resolution:
Use a bool in your manager file true is selected, false isn't. Have all the objects you instantiate have a tag, use the ray cast from the master file to the game object. When it his the game object with that tag, swap colors and sap the bool from the master file. Probably better to do it internally from the master file.
(All depends on what you're doing)
If you know what the name of the GameObjects will be at runtime, you can use GameObject.Find("") and store that in a GameObject variable. You can then set the renderer of that GameObject to whatever you like (assuming a renderer is linked to that GameObject).
The most obvious way of doing this would be to use prefabs and layers or tags.
You can add a tag to your prefab (say "Selectable") or move the prefab to some "Selectable" layer and then write your code around this, knowing that all selectable items are on this layer/have this tag.
Another way of doing this (And in my opinion is also a better way) is implementing your custom 'Selectable' component. You would search for this component on a clicked item and then perform the selection, if you have found that component. This way is better because you can add some additional selection logic in this component which would otherwise reside in your selection master script (image the size of your script after you've added a couple of selectables).
You can do it by implementing a SelectableItem script (name is arbitrary) and a couple of it's derivatives:
public class SelectableItem : MonoBehavior {
public virtual void OnSelected() {
renderer.material.color = red;
}
}
public class SpecificSelectable : SelectableItem {
public override void OnSelected() {
//You can do whatever you want in here
renderer.material.color = green;
}
}
//Adding new selectables is easy and doesn't require adding more code to your master script.
public class AnotherSpecificSelectable : SelectableItem {
public override void OnSelected() {
renderer.material.color = yellow;
}
}
And in your master script:
// Somewhere in your master selection script
// These values are arbitrary and this whole mask thing is optional, but would improve your performance when you click around a lot.
var selectablesLayer = 8;
var selectablesMask = 1 << selectablesLayer;
//Use layers and layer masks to only raycast agains selectable objects.
//And avoid unnecessary GetComponent() calls.
if (Physics.Raycast(ray, out hit, 1000.0f, selectablesMask))
{
var selectable = hit.collider.gameObject.GetComponent<SelectableItem>();
if (selectable) {
// This one would turn item red or green or yellow depending on which type of SelectableItem this is (which is controlled by which component this GameObject has)
// This is called polymorphic dispatch and is one of the reasons we love Object Oriented design so much.
selectable.OnSelected();
}
}
So say you have a couple of different selectables and you want them to do different things upon selection. This is the way you would do this. Some of your selectables would have one of the selection components and others would have another one. The logic that performs the selection resides in the Master script while the actions that have to be performed are in those specific scripts that are attached to game objects.
You can go further and add OnUnselect() action in those Selectables:
public class SelectableItem : MonoBehaviour {
public virtual void OnSelected() {
renderer.material.color = red;
}
public virtual void OnUnselected() {
renderer.material.color = white;
}
}
and then even do something like this:
//In your master script:
private SelectableItem currentSelection;
var selectable = hit.collider.gameObject.GetComponent<SelectableItem>();
if (selectable) {
if (currentSelection) currentSelection.OnUnselected();
selectable.OnSelected();
CurrentSelection = selectable;
}
And we've just added deselection logic.
DISCLAIMER: These are just a bunch of snippets. If you just copy and paste those they probably wouldn't work right away.