Test Unity method that uses local private SerializedField - unity3d

I would like to check the content of clockText.text after a couple of seconds.
public class Clock : MonoBehaviour
{
[SerializeField]
TextMeshProUGUI clockText;
TextMeshProUGUI ClockText { get { return clockText; } }
float remainingSeconds = 0f;
public void StartCountDown(int seconds)
{
remainingSeconds = seconds;
}
private void Update()
{
remainingSeconds -= Time.deltaTime;
ClockText.text = Math.Ceiling(remainingSeconds).ToString();
}
}
With this test:
public class TestClock
{
[UnityTest]
public IEnumerator ClockShowCorrectTime()
{
var clock = new GameObject().AddComponent<Components.Shot.Clock>();
clock.StartCountDown(2);
yield return new WaitForSeconds(1);
Assert.AreEqual("00:01", clock.ClockText.text);
}
}
but of course clock.ClockText is null. And I would like to keep clockText as private, so I can't set clockText from the Test.
Should it be public because we can change it on Editor?

Creating a new instance of Clock will not have its clockText member variable set because it's only settable in an inspector window, or when deserializing.
So your test will only work if you make clockText a public variable and set it yourself.
Another workaround is removing your dependency on a text field altogether and make the time value just a private string variable (with a public getter) so your clock can also run without a display field.

Related

Calling protected variable from another class

I need to call a protected variable from a public class into an if statement in a private method of another public class
I am programing a video game in unity and I need to use a bool variable (that shows if the character is out of stamina) in an if statement to determine whether or not the character can run
This is what my code looks like excluding everything unrelated to the problem
Public class CharacterStats : MonoBehaviour
{
[SerialzeField] protected bool Tired;
}
Public class PlayerMovement : MonoBehaviour
{
Private void HandleRunning()
{
If (Input.GetKeyDown(KeyCode.LeftShift) && X != True)
{
Speed = RunSpeed;
}
}
}
X is where I want the Tired variable to be.
Use a public readonly property like e.g.
public class CharacterStats : MonoBehaviour
{
// Keep you serialized field protected
[SerialzeField] protected bool tired;
// Have a public read-only accessor property
public bool Tired => tired;
}
and then e.g.
public class PlayerMovement : MonoBehaviour
{
// Somehow you will need to get a reference to the CharacterStats instance
// e.g. via the Inspector
[SerializeField] private CharacterStats stats;
[SerializeField] private float RunSpeed;
private float Speed;
private void HandleRunning()
{
if (Input.GetKeyDown(KeyCode.LeftShift) && !stats.IsTired)
{
Speed = RunSpeed;
}
}
}
Alternatively (and my apologies to #aybe who had answered this) you can actually directly serialize a property using explicitely
[field: SerializeField] public bool Tired { get; protected set; }
this is a property which can be accessed by everyone but only this and inherited classes (and due to the serialization now the Inspector) have the permission to set the value.
In general: Fix your casing! In c# all keywords are lower key!

Correct way to serialized in custome editor?

this is test script
public class Test : MonoBehaviour
{
public int something;
}
1.if i directly set the value without use serializedProperty
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
private Test test;
private void OnEnable()
{
test = target as Test;
}
public override void OnInspectorGUI()
{
test.something = EditorGUILayout.IntField("Something", test.something);
if (GUI.changed)
EditorUtility.SetDirty(this);
}
}
value is saved successfully in editor
result
but if i restart unity,the value will be reset,this means the value is not serialized to disk?
2.if use serializedProperty
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
var something = serializedObject.FindProperty("something");
something.intValue = EditorGUILayout.IntField("Something", something.intValue);
serializedObject.ApplyModifiedProperties();
}
}
everything is right
my question
why first way can save value in editor but not serilized to disk? is my script something wrong?
what's the true difference bewtween them?
second way is the correct way to serilized?
You should use SerializedProperty class for editing properties on objects in a completely generic way that automatically handles undo and styling UI for Prefabs.
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
SerializedProperty something;
void OnEnable()
{
/* Fetch the objects from the GameObject script to display in the inspector */
something = serializedObject.FindProperty("something");
}
public override void OnInspectorGUI()
{
/* Update the serializedProperty - always do this in the beginning of OnInspectorGUI. */
serializedObject.Update();
/* Display your field in the inspector */
EditorGUILayout.PropertyField(something);
/* Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI. */
serializedObject.ApplyModifiedProperties();
}
}

Serialisation not working when inheriting from monobehaviour

Im trying to do a property drawer for a class that i need to be updated as well as editable from the editor.
If i make the class a monobehaviour the serialisation stops working, but if i remove the monobehaviour inheritance it wont update with the game loop.
Is there any way to have both? I would need the object to be able to instantiate with default (empty) values if a monobehaviour script has non instantiated reference.
[Serializable]
public class MySmallTestProp : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
private string name;
[SerializeField]
private string _name;
[SerializeField]
private float _someFloat;
public float someFloat;
public MySmallTestProp()
{ }
public void OnBeforeSerialize()
{
_name = name;
}
public void OnAfterDeserialize()
{
name = _name;
}
}
[CustomPropertyDrawer(typeof(MySmallTestProp))]
public class MySmallTestPropPropertyDrawer : PropertyDrawer
{
float rowHeight;
int rowSpacing = 5;
int index;
Rect currentPosition;
public override float GetPropertyHeight(SerializedProperty prop, GUIContent label)
{
rowHeight = base.GetPropertyHeight(prop, label);
var rows = 2;
if (Application.isPlaying)
{
rows++;
}
else
{
rows++;
}
return rowHeight * rows;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
index = 1;
currentPosition = position;
EditorGUI.BeginProperty(position, label, property);
var nameProp = property.FindPropertyRelative("_name");
EditorGUI.PropertyField(NextPosition(), nameProp, new GUIContent("Name"));
EditorGUI.EndProperty();
}
Rect NextPosition()
{
currentPosition.height = rowHeight;
currentPosition.y = rowSpacing + (rowHeight + rowSpacing) * index++;
return currentPosition;
}
}
So if I understand you correctly what you want to achieve is having a class which is
Serializable
has some default field values
Receives an Update call every frame
Actually I don't think you need any custom property drawer for this.
First two points are as simple as having e.g.
[Serializable]
public class Example
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only via the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
}
Now to the last and tricky part - the update calls.
The easiest way is to have a dedicated MonoBehaviour like e.g.
public class UpdateDispatcher : MonoBehaviour
{
// Storing the instance for Singleton Pattern
private static UpdateDispatcher _instance;
// Register to this event to receive one call each Update
public static event Action OnUpdate;
// This method is automatically called by Unity when the application is started
// or you enter play mode in the editor
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
// _instsnce is already assigned and alive?
if(_instance) return;
// Otherwise search for one in the scene
_instance = FindObjectOfType<UpdateDispatcher>();
// Found one?
if(_instance) return;
// Otherwise create it now
_instance = new GameObject(nameof(UpdateDispatcher)).AddComponent<UpdateDispatcher>();
}
private void Awake ()
{
// Does another instance already exist?
if(_instance && _instance != this)
{
// Destroy this one
Destroy (gameObject);
return;
}
// Otherwise assign this as the instance and make sure it isn't destroyed when the scene chsnges
_instance = this;
DontDestroyOnLoad (gameObject);
}
private void Update ()
{
// Call the event every frame if something is registered
OnUpdate?.Invoke ();
}
}
And then you can use ISerislizationCallbackReceiver but not for actually doing the serialization (it is already done automatically for the fields) but rather for registration to the update callback like e.g.
[Serializable]
public class Example, ISerializationCallbackReceiver
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only vis the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
// Nothing to do here, only needed for the interface
public void OnBeforeSerialize() { }
public void OnAfterDeserialize()
{
// Register to the Update event
// It is save to unregister before registering even if we haven't been registered before
// this makes sure we are registered only exactly once
UpdateDispatcher.OnUpdate -= Update;
UpdateDispatcher.OnUpdate += Update;
}
private void Update ()
{
someFloat += Time.deltaTime;
}
}
This answer should be more like a comment, but due to the extension I've decided to post it here.
The objective of a PropertyDrawer is to display properties differently on the editor.
To achieve that you need 2 things:
1.One class that inherits from PropertyAttribute, this will be the reference used in your future scripts.
2.Another class that inherits from PropertyDrawer, here you can type HOW to display the attribute.
One implementation example of a property drawer that shows an attribute without leting the user to edit it from editor:
using UnityEditor;
using UnityEngine;
public class DisplayWithoutEdit : PropertyAttribute
{
}
[CustomPropertyDrawer(typeof(DisplayWithoutEdit))]
public class DisplayWithoutEditDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
Then you can use it on another script doing something like:
[DisplayWithoutEdit] public float randomNumber = 0f;

Instantiating random or chosen prefabs with sub-container thru Factory or Pool

I have an array of prefabs and I want to be able to Instantiate randomly picked prefabs thru Zenject Factory and perform their bindings in their sub-containers.
What I want to do is the same as in this code sample from Zenject documentation, but for randomly selected prefabs.
https://github.com/modesttree/Zenject/blob/master/Documentation/SubContainers.md#using-game-object-contexts-no-monobehaviours
using UnityEngine;
using Zenject;
public class GameInstaller : MonoInstaller
{
[SerializeField]
GameObject ShipPrefab;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameRunner>().AsSingle();
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<ShipInstaller>(ShipPrefab);
}
}
I was able to partially make it work with
[SerializeField] private GameObject[] ships;
...
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewGameObjectMethod(SpawnShip);
...
private void SpawnShip(DiContainer container, float speed)
{
container.Bind<ShipFacade>().AsSingle();
container.Bind<Transform>().FromComponentOnRoot();
var shipPrefab = ships[Random.Range(0, ships.Length)];
var ship = container.InstantiatePrefab(shipPrefab);
container.Bind<ShipHealthHandler>().FromNewComponentOn(ship).WhenInjectedInto<ShipFacade>();
container.BindInstance(speed).WhenInjectedInto<ShipInputHandler>();
}
But it's awkward and in this case I guess I'm not using an advantage of sub-container. And also prefabs spawns in an empty GameObject.
What I want to achieve is to be able to use ShipInstaller for sub-container binding.
You're right, there wasn't really a very elegant way to choose the sub-container prefab dynamically.
I took some time to make this better today with this commit. If you use the latest version of Extenject then you can now do things like this:
public class QuxInstaller : Installer {
float _speed;
public QuxInstaller(float speed) {
_speed = speed;
}
public override void InstallBindings() {
Container.BindInstance(_speed);
Container.Bind<QuxFacade>().AsSingle();
}
}
public class CubeInstaller : MonoInstaller
{
public List<GameObject> QuxPrefabs;
public override void InstallBindings()
{
Container.BindFactory<float, QuxFacade, QuxFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<QuxInstaller>(ChooseQuxPrefab);
}
UnityEngine.Object ChooseQuxPrefab(InjectContext _) {
return QuxPrefabs[Random.Range(0, QuxPrefabs.Count)];
}
}

How to control one of multiple instances of a prefab?

My "game" read a file XML, identify some elements and instance him at runtime. This instances is of a prefab.
So, I have a loop with a variable "ins" that create the instances:
ins = (GameObject)Instantiate (this.MyPrefab, position, Quaternion.identity);
I would like, for example, to click on a instance and change its color and not on all instances.
The problem in your code is thatUpdate() calls Click() in every frame. So, whenever you press the mouse button if( Input.GetMouseButtonDown(0) ) becomes true for every prefab and they all process the click event, irrespective of whether they were clicked on or not.
The solution would be to add a Collider component to your prefab and implement OnMouseDown() in ButtonDiagram class to detect mouse clicks on the object. Something like :
public class ButtonDiagram : MonoBehaviour
{
// rest of your code
void OnMouseDown()
{
Debug.Log("Click!");
}
}
Unity's documentation:
http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseDown.html
You could do an array of gameobjects.
Do a foreach loop and add each instance to the your array. Then you could do ins[0].DOSOMETHING or, simpler, have the prefabs you're instantiating have a script on it then accepts mouse clicks or other input and that will affect only the gameobject/prefab that the user interacts with.
using UnityEngine;
using System.Collections;
public class Program : MonoBehaviour
{
private Diagram diagram { get; set; }
public string arquivoXMI;
public GameObject ButtonDiagramGameObject;
private const int SPACEX = 2;
private GameObject ins;
public ArrayList instances{ get; private set; }
// Use this for initialization
void Start ()
{
this.diagram = new Diagram (arquivoXMI);
ButtonForEachSequenceDiagram ();
}
//BUTTON FOR EACH SEQUENCE DIAGRAM
private void ButtonForEachSequenceDiagram()
{
instances = new ArrayList ();
if (this.diagram.SequenceDiagrams.Count > 0) {
float increment = (this.ButtonDiagramGameObject.transform.localScale.x / 2) + SPACEX;
float position = 0;
foreach( Sequence s in this.diagram.SequenceDiagrams )
{
float posBDx = position;
float posBDy = this.ButtonDiagramGameObject.transform.position.y;
float posBDz = this.ButtonDiagramGameObject.transform.position.z;
Vector3 posButtonDiagram = new Vector3 (posBDx, posBDy, posBDz);
ins = (GameObject)Instantiate (this.ButtonDiagramGameObject, posButtonDiagram, Quaternion.identity) ;
ins.GetComponentInChildren<ButtonDiagram> ().NameDiagram ( s.Name );
instances.Add(ins);
position += increment;
}
}
}
// Update is called once per frame
void Update ()
{
foreach( GameObject i in instances ){
i.GetComponentInChildren<ButtonDiagram>().Click();
}
}
}
using UnityEngine;
using System.Collections;
public class ButtonDiagram : MonoBehaviour {
public TextMesh Name;
public GameObject MyCube;
private string nameDiagram;
private float random;
// Use this for initialization
void Start () {
// random = Random.Range(-10.0f, 10.0f);
// NameDiagram = random.ToString();
Name.text = nameDiagram;
}
public void NameDiagram( string nome ){
this.nameDiagram = nome;
}
public void Click(){
if( Input.GetMouseButtonDown(0) )
{
Debug.Log("Click!");
}
}
// Update is called once per frame
void Update () {
}
}