How can I create a custom inspector guilayout.toggle? - c#

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
Control control = (Control)target;
if (GUILayout.Toggle(control.isControl, "Control"))
{
control.ToControl();
}
}
}
And
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl = false;
// Start is called before the first frame update
void Start()
{
}
public void ToControl()
{
if(isControl == false)
{
}
else
{
Destroy(rigidbody);
}
}
}
What I want to do is a guilayout.toggle or a button and to be able to destroy and to add a Rigidbody to the gameobject the Control script will be on.
How do I create back add the Rigidbody to the gameobject ?
And how do I use the isControl flag ? The idea is to use the guilayout.toggle in the editor script.
I want to destroy or add a new rigidbody while the game is running ! But using a guilayout.toggle or button in the inspector.

Actually you wouldn't need an inspector script for that at all. Simply add a repeadetly check for the bool like e.g. in LateUpdate and make the component [ExecuteInEditoMode] like
using UnityEngine;
[ExecuteInEditoMode]
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
// repeatedly check the bool
private void LateUpdate()
{
ToControl();
}
public void ToControl()
{
if (!isControl && rigidbody)
{
// in editmode use DestroyImmediate
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
// adjust settings of rigidbody
}
}
}
This way LateUpdate is called both, in playmode and in editmode, and will simply react to the isControl value.
Ofcourse there is an overhead for calling this LateUpdate all the time so if you want to avoid it you can call it only from the editor. However, since you are using base.OnInspectorGUI(); you don't really need an additional Toggle since you already have the one of the default inspector.
So could simply do
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
public void ToControl()
{
if (!isControl && rigidbody)
{
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
}
}
}
and in the editor script simply do
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will already notice that this might affect how Undo/Redo works - in this case it would e.g. reset the isControl value but not remove along the RigidBody component leading to errors (see more below)
Or since you asked it you can add the ToggleField (currently you will have it twice since one also ships with base.OnInspectorGUI();)
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
control.isControl = EditorGUILayout.Toggle("additional isControl", control.isControl);
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will notice that this solution changing the value using the additional isControl lacks the possibility of using Undo/Redo completely and it will NOT mark your scene as "dirty" so Unity might not save those changes!
So if you really want to have your custom toggle field in an inspector script I would actually recommend strongly to use proper SerializedPropertys instead of directly making changes to the target (sometimes it can't be avoided like with the adding of the component though):
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private SerializedProperty _isControl;
private SerializedProperty rigidbody;
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
// link serialized property
_isControl = serializedObject.FindProperty("isControl");
rigidbody = serializedObject.FindProperty("rigidbody");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
// load current values into the serialized copy
serializedObject.Update();
if (!_isControl.boolValue && rigidbody.objectReferenceValue)
{
DestroyImmediate(rigidbody.objectReferenceValue);
rigidbody.objectReferenceValue = null;
}
else if (_isControl.boolValue && !rigidbody.objectReferenceValue)
{
var rb = control.gameObject.AddComponent<Rigidbody>();
rigidbody.objectReferenceValue = rb;
}
// write back changed serialized values to the actual values
serializedObject.ApplyModifiedProperties();
}
}
This looks more complicated and actually you have duplicate code but it gives you full Undo/Redo support and marks your objects and scenes dirty so Unity saves the changes.

Related

OnTriggerEnter2D doesn't work after switching scenes

I have a game that is similar to "Flappy Bird" and I have main menu where I can start game and change skin of a pigeon. My skin collection is implemented with scroll rect and in the center there is a trigger which starts an animation of scaling a pigeon, it works fine until I click "start" and the scene changes to game and when I return to my main menu and click "skins" this trigger doesn't work anymore.
Script what is attached to all scroll rect elements to detect collisions with trigger:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResizeFieldScript : MonoBehaviour
{
private Animator _anim;
private void Start()
{
_anim = GetComponent<Animator>();
}
public void OnTriggerEnter2D(Collider2D collider)
{
Debug.Log("Trigger is working");
if(collider.tag == "ResizeField")
{
Debug.Log("Condition is working");
_anim.SetBool("isInTrigger", true);
}
}
public void OnTriggerExit2D(Collider2D collider)
{
_anim.SetBool("isInTrigger", false);
}
}
Script what is attached to an empty object to change scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
public class UIController : MonoBehaviour
{
[SerializeField] private List<string> sceneNameList;
private string sceneToFind;
private int index = 0;
public void SceneChanger()
{
sceneToFind = EventSystem.current.currentSelectedGameObject.name;
foreach(string str in sceneNameList)
{
if(str == sceneToFind)
{
SceneManager.LoadScene(index);
index = 0;
break;
}
index++;
}
}
public void Exit()
{
Application.Quit();
}
public void BackMenu()
{
SceneManager.LoadScene(4);
}
}
OnTriggerEnter2D doesn't work after switching scenes. We could use OnTriggerEnter2D to jump scenes.
code show as below:
private void Update() {
// If E is pressed
if (Input. GetKeyDown(KeyCode. E)) {
// scene switching
SceneManager.LoadScene(4);
}
}
private void OnTriggerEnter2D(Collider collision) {
if (collision. tag == "ResizeField") {
// The UI prompts the user to press E to jump
EnterDialog.SetActive(true);
Debug.Log("Condition is working");
// _anim.SetBool("isInTrigger", true);
}
}
Hope it helps you.

W, A,S, D movement in Unity, don't know how to use new funtions

Hello i have a problem with animation controller script used to play animations depending on which key is pressed
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimationController : MonoBehaviour
{
function UpdateAnimations()
{
if (Input.GetKeyDown(KeyCode.W))
{
animation.CrossFade("goup");
}
else if (Input.GetKeyDown(KeyCode.A))
{
animation.CrossFade("goleft");
}
else if (Input.GetKeyDown(KeyCode.D))
{
animation.CrossFade("goright");
}
else if (Input.GetKeyDown(KeyCode.S))
{
animation.CrossFade("godown");
}
}
void Start()
{
}
// Update is called once per frame
void Update()
{
UpdateAnimations();
}
It says that "Component.animation" is too old, and i should use GetComponent but i don't know how to
First of all you most probably mean void instead of function as your code is in c# not unityscript (which is also long deprecated by now)
And then Yes, how old is that code you got there? The direct accesses to things like Component.animation, Component.renderer, Component.camera were deprecated years ago ^^
As the error already tells you rather use e.g. Component.GetComponent like e.g.
public class AnimationController : MonoBehaviour
{
// Reference this via the Inspector in Unity
// via drag and drop. Then you don't need GetComponent at all
[SerializeField] private Animation _animation;
private void Awake()
{
// or as fallback get it on runtime
if(!_animation) _animation = GetCompoenent<Animation>();
}
// Update is called once per frame
private void Update()
{
UpdateAnimations();
}
private void UpdateAnimations()
{
if (Input.GetKeyDown(KeyCode.W))
{
_animation.CrossFade("goup");
}
else if (Input.GetKeyDown(KeyCode.A))
{
_animation.CrossFade("goleft");
}
else if (Input.GetKeyDown(KeyCode.D))
{
_animation.CrossFade("goright");
}
else if (Input.GetKeyDown(KeyCode.S))
{
_animation.CrossFade("godown");
}
}
}
How have you defined "animation" in your code?
you could try adding animation reference in the top of the code:
private Animation animation;
and in your Start() or Awake() method, add:
// will add reference to the animation to be the Animation component
// in this GameObject
animation = GetComponent<Animation>();

Unity 3d pass bool variable between two objects

how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html

enable onCollitionEnter by a variable

I have 2 script and i am try to make one trigger for the first script to enable other script trigger, i have try to investigate but i still stuck on my code.
my first code is
using UnityEngine;
using System.Collections;
public class endpoint10 : MonoBehaviour {
public static int IsColliderEnabled;
endpoint10.IsColliderEnabled = 0;
void OnTriggerEnter(Collider other)
{
if (IsColliderEnabled = 1) {
//do stuff here
// The switch statement checks what tag the other gameobject is, and reacts accordingly.
switch (other.gameObject.tag) {
case "end":
Debug.Log (other.gameObject.tag);
PlayerPrefs.SetInt ("moneyPref", scoreManager.money);
PlayerPrefs.SetInt ("scorePref", scoreManager.score);
ScoreSystem.level += 1;
PlayerPrefs.SetInt ("levelPref", ScoreSystem.level);
Debug.Log ("values stored");
Application.LoadLevel ("level_11");
break;
}
}
// Finally, this line destroys the gameObject the player collided with.
//Destroy(other.gameObject);
}
}
and my second code is
using UnityEngine;
using System.Collections;
public class trigguercubex : MonoBehaviour {
public GameObject[] objects;
void OnTriggerEnter(Collider other)
{
endpoint10.IsColliderEnabled = 1;
Debug.Log (other.gameObject.tag);
}
// Finally, this line destroys the gameObject the player collided with.
//Destroy(other.gameObject);
}
Do you have a game manager script?
You could use setters and getters
using UnityEngine;
using System.Collections;
public class GameManager : MonoBehaviour {
public static GameManager instance = null;
private bool triggerredOccurred = false;
public bool IsTriggerredOccurred {
get { return triggerredOccurred;}
}
public void TriggerredOccurred() {
triggerredOccurred = true;
}
void Awake(){
if (instance == null) { //check if an instance of Game Manager is created
instance = this; //if not create one
} else if (instance != this) {
Destroy(gameObject); //if already exists destroy the new one trying to be created
}
DontDestroyOnLoad(gameObject); //Unity function allows a game object to persist between scenes
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
In your endpoint class, when the collision was detected
GameManager.instance.TriggerOccurred ();
In your trigguercubex class
if (GameManager.instance.IsTriggerOccurred) {
do some stuff ();
}
I attach the GameManager script to my game's Main Camera

animation.Play() doesn't work

I'm pretty sure that
animation.Play("DoorOpen");
Would play the animation "DoorOpen", but when i'm trying to put it in my code, it just giving me an error message:
The Animation attached to this GameObject (null if there is none attached).
using UnityEngine;
using System.Collections;
public class DoorPhysics : MonoBehaviour {
int Open = 0;
// Update is called once per frame
void Update() {
if (Open == 0) {
if (Input.GetKeyDown("e")) {
animation.Play("DoorOpen");
}
}
}
}
You need to show location of gameobjects in unity, they do not know eachother, you have to always use :
GameObject.GetComponent<T>()
GetComponentInParent<T>()
GetComponentInChildren<T>()
best practice is to get object references at Start()
also you should attach IMPORTANT!!! Animation component to the object this script it attached to
public class DoorPhysics : MonoBehaviour {
public Animation animation;
int Open = 0;
void Start()
{
animation=GameObject.GetComponent<Animation>(); //if your have derived type change Animation to good class DoorAnimation for example
}
void Update()
{
if (Open == 0) {
if (Input.GetKeyDown("e")) {
this.animation.Play("DoorOpen");
}
}
}
}
if that code wont work, you will need show me your GameObject hierarchy
And if your just start your trip with it learn MonoBehaviour call order
and life cycles of events

Categories