I am having problems mutating referenced values through using Unity's API for modifying the Editor GUI.
When I go into the editor to change the values in the GUI, it just refreshes and retains the label text/object I provide in the arguments, why isn't it mutating the reference and displaying that instead?
1.
I am referencing a class attached to a particular Game-object ButtonManager script = target as ButtonManager;
I want to change the values of that class script.thisImageCaption = EditorGUILayout.TextArea("Content for the main image slide", script.thisImageCaption); but this does not work
odly though... bools work, when I check the box, the GUI remembers my choice and modifys the referenced value, so why don't the others?
script.hasAudioClip = EditorGUILayout.Toggle("Voice Over?", script.hasAudioClip);
2.
I am also referencing GameObjects and their Individual Components
//following code is a snippet of code, the full context isnt provided, only the context related to mutating referenced values
List<Tuple<int, Text, Image>> imageCloneHolder = new List<Tuple<int, Text, Image>>();
imageCloneHolder.Add(new Tuple<int, Text, Image>(
slide.GetInstanceID(),
slide.transform.GetChild(1).GetComponent<Text>(),//get reference to text
slide.transform.GetChild(0).GetComponent<Image>()//get reference to image
));
item.Item2.text = EditorGUILayout.TextArea("Content for the image cloneslide",
item.Item2.text);//dosen't modify the referenced text
item.Item3.sprite = EditorGUILayout.ObjectField("This second Image slides image",
item.Item3.sprite,
typeof(Sprite),
false) as Sprite;//doesn't modify the referenced image
I don't quite understand what's wrong, I thought when you grabbed references in Unity & C#(with classes,components, etc) they would be pointers to the real object not a copy, but it appears Unity's API for the GUI is having me modify copies? Is this not true for strings referenced in a class? Or for some Unity Components?
Screen Dump
Code: https://imgur.com/a/5fRR56c
Editor: https://imgur.com/a/jeXMGSN
The main issue I guess is that you don't mark stuff as dirty. This is required to save any changes.
In general you should not directly make changes to the target from within an Editor. Rather use SerializedProperty. They handle all the marking stuff dirty (thus saving changes) and Undo/Redo functionality for you.
Also I don't see why you are using a List there if anyway you are adding only one element...
I don't see your full code especially the class and full Editor would be helpful. But for what you provided it should be something like
public class YourEditor : Editor
{
private SerializedProperty thisImageCaption;
private SerializedProperty hasAudioClip;
// wherever you get this from
private XY item;
private void OnEnable()
{
// Link SerializedProperties to the real ones
thisImageCaption = serializedObject.FindProperty("thisImageCaption");
hasAudioClip = serializedObject.FindProperty("hasAudioClip");
}
public override void OnInpectorGUI()
{
// Load the current real values
// into the SerializedProperties
serializedObject.Update();
// Now only change the SerializedProperties
thisImageCaption.stringValue = EditorGUILayout.TextArea("Content for the main image slide", thisImageCaption.stringValue);
// Using PropertyField automatically
// uses the property drawer according
// to the property's type
EditorGUILayout.PropertyField(hasAudioClip);
// now to the tricky part
// for both slider sub-components you will need
// to create a serializedObject from the given components e.g.
var textSerializedObject = new SerializedObject(item.Item2);
var imageSerializedObject = new SerializedObject(item.Item3);
// do the update for both
textSerializedObject.Update();
imageSerializedObject.Update();
// now change what you want to change via serializedProperties e.g.
EditorGUILayout.PropertyField(textSerializedObject.FindProperty("m_text"), new GUIContent("Content for the image cloneslide"));
EditorGUILayout.PropertyField(imageSerializedObject.FindProperty("m_Sprite"), new GUIContent("This second Image slides image"));
// Write changes back to the real components
textSerializedObject.ApplyModifiedProperties();
imageSerializedObject.ApplyModifiedProperties();
serializedObject.ApplyModifiedProperties();
}
}
Note: Typed on smartphone so no warranty but I hope the idea gets clear.
Related
I am attempting to add multiple flags of similar types (arrows) to a live chart using a C# windows forms project. This is to provide a label when a value falls out of a pre-defined specification.
I am currently stuck in how to create new instances of the ArrowAnnotation class so if multiple events happen there will be multiple flags for the people checking the chart. I am able to create one instance and manipulate the position to the latest data point in the series (it shouldn't be a stretch to lock it to a historical point, I just haven't done that yet.)
I have an understanding of creating multiple instances of other classes and keeping track of them with lists/ dictionaries but this one has me stumped (or maybe I don't have as good an understanding as I think?)
I can't share the code I have directly but I think I can write some example code if needed.
edit-
I am looking into using a memberwise clone to copy common attributes of each arrow and add those objects to a dictionary.
Thanks
Okay, I have managed to figure out how to do this for my use case.
When updating the live chart I can call a method if a parameter falls out of specification. In that method I create a new instance of the annotation, and the properties that I want to use as a template. (these values can also be changed with conditional logic if you want slight variation, and can be passed in with the argument) than add that newly made annotation to the annotation group. I am still looking for improvements to the code. I am still wanting a way to assign a name to the arrow, than recall that one and modify it (if I wanted to). And change the pass/fail criteria than apply annotations for the new failure points (but that is going into new territory)
// This is not the full method just the part that counts for this question.
private ArrowAnnotation floatArrow;
private void UpdateChart()
{
GenerateArrows(dateTime, sensorValue);
this.chart1.Annotations.Add(floatArrow);enter code here
}
private void GenerateArrows(DateTime x, double y)
{
floatArrow = new ArrowAnnotation();
floatArrow.Name = Convert.ToString(x);
floatArrow.ToolTip = Convert.ToString(x);
floatArrow.AxisXName = "ChartArea1\\rX";
floatArrow.AxisYName = "ChartArea1\\rY";
floatArrow.X = x.ToOADate();
floatArrow.Y = y;
floatArrow.Height = 5;
floatArrow.Width = 0;
floatArrow.BackColor = Color.Red;
}
Preamble
I am currently making a game where a player can go from third person view, walking around to transition into a vehicle.
I have thought about using a transmitter/receiver type set up, but I think my way of simplifying it isn't correct.
I am using assets from third parties for controllers that use inputs. My plan was to enable/disable the appropriate script and camera from the object I want to control. I've gotten as far as being able to disable the previous controller and enable the next, though I can't go back to enabling since the script obviously doesn't run anymore.
Question/Request
I'd like to be able to reference the pawn's input component script in a different component script on the same gameobject to then be able to enable/disable the aformentioned component, though the issue is that the input controllers have variable names (Different names depending on the third party on each pawn).
Here's how I have it set up:
I have a PlayerTransmitter that handles the basics of turning things on and off. I tried making this where all of the inputs are being handled, but I don't want to have to change the original controller scripts to look at this script. This is on an empty game object and handles the 'state' of the player, (walking or in which vehicle).
on each pawn gameobject (The walking pawn CleanerPawn and the vehicle TractorPawn), I've added a script called InputReceiver. This was originally intended to pass the inputs from the PlayerTransmitter to the actual controller on the object itself.
Right now, the walking pawn has a component called AdvancedWalkerController (You may know the one I'm talking about) that controls the player walking movement and the vehicle has a component called VehicleController which controls how the vehicle moves and handles.
CleanerPawn
TractorPawn
The two images above show that I am using the same InputReceiver component on both pawns. My plan was to pass in each pawn's input controller (temporarily named CleanerController) and then enable/disable that input controller depending on the PlayerTransmitter 'state'.
The InputReceiver currently looks like this:
public bool isEnabled;
public Component CleanerController;
public void TurnOffControls()
{
isEnabled = false;
}
public void TurnOnControls()
{
isEnabled = true;
}
In each pawn's input controller, I added a line to the Update() function:
enabled = gameObject.GetComponent<InputReceiver>().isEnabled;. Two problems with this; 1. I don't want to change any code in the input scripts (I hope this is possible) and 2. Once it's disabled, it won't read an update to enable itself again.
I was hoping I could just use the reference to the component CleanerController to say CleanerController.enabled = true; or false with that reference being just that one component, though I'm missing something here.
My final thought which I am going to try is to allow/disallow the input controls within each input script depending on my isEnabled boolean. Though again, I would have to change the original scripts to accommodate this.
What speaks against
public Behaviour CleanerController;
and then
CleanerController.enabled = false; // or true accordingly
See
Behaviour
Behaviours are Components that can be enabled or disabled.
Behaviour.enabled
Enabled Behaviours are Updated, disabled Behaviours are not.
or in other words: If a Behaviour is set to enabled = false then it's Update (and similar event methods) is not called anymore.
I would recommend you to have a Controller Script. This named "CameraController" for example and has a Method to switch between the Modes. This would also make it possible to have even more Options. For Example you could use an Enum to define which modes exist:
enum CameraMode {
Player,
Car
}
public SwitchMode(CameraMode mode) {
switch(mode) {
case CameraMode.Player:
TurnOnControls();
break;
case CameraMode.Car:
TurnOffControls();
break;
default:
throw new NotSupportedException();
}
}
You could even go further and create an Interface/Abstract which allows all of this and just has an execute method doing different stuff. In the end you just need that controller which allowes to swtich between the modes and handles the key input.
After reading the API info here:
https://docs.unity3d.com/ScriptReference/TrailRenderer-colorGradient.html
I am wondering if I can "tune in" my trail renderer in the normal unity interface, print those complicated code parameters, then use that code in my script to change color on triggers, etc.
To clarify, how do I get the information here presented in code:
[]
I guess I am approaching this from a CSS background. Is there a Unity colorgradient version of this website:
https://www.colorzilla.com/gradient-editor/
Can I make the script print the characteristics of the trail renderer (for the purpose of replicating it elsewhere in my code)?
1
Much appreciate the help!
I'm still not 100% sure if I understood the question but I'll give it a shot.
As I understand you want to have a component on every trigger object where you can define different gradient settings for each.
And I assume by Unity interface you mean the Inspector.
So something like e.g.
public class GradientSetter : MonoBehaviour
{
public GradientColorKey[] colorKeys;
public GradientAlphaKey[] alphaKeys;
}
Put this on the trigger object(s) and adjust the settings via the Inspector. At beginning they should be empty arrays so to add elements just enter the wanted element count in the size property of both arrays.
And wherever you have the Collision implemented on your TrailRenderer object
void OnTriggerEnter(Collider other)
{
var gradientSetter = other.GetComponent<GradientSetter>();
if(!gradientSetter) return;
gradient.SetKeys(gradientSetter.colorKeys, gradientSetter.alphaKeys);
...
}
I'm assuming GradientColorKey and GradientAlphaKey are Serializable. If you implement this but they don't show up in the Inspector let me know, then you'll have to make a wrapper class for them. (I can't test it right now)
Note: Typed on smartphone so no warranty but I hope the idea gets clear
I'm tying to migrate an Epicor V9 system with Progress/ABL code to v10 with C# code. I've got most of it done but I need a way to keep data between a BPMs pre and post processing. The comments in the original ABL code state:
Description : This function stores data from a BPM pre processing action, it does this by using a private-data (storage attribute) on the calling program...
this remains in scope during both the BPM pre and BPM post forward to procedure calls
The Epicor v9 system was set up such that the Quote form calls the BPM pre/post processing in a .p file. The .p file in turned call the code I am trying to migrate in a .i file. It looks to be a simple stack or array of strings.
What would be used in Epicor 10 to persist data between pre/post BPM processing like the .i code did in V9?
You can use CallContext.Properties for this.
In E10.0 the CallContext.Properties was of type Epicor.Utilities.PropertyBag, and items would be accessed as below:
//Add
CallContext.Properties.Add("LineRef", LineRef);
// Get
var LineRef = (string)CallContext.Properties["LineRef"];
// Remove
CallContext.Properties.Remove("LineRef");
E10.1 CallContext.Properties is now of type System.Collections.Concurrent.ConcurentDictionary, which is a .Net built in type and much better documented. However the methods to add and remove entries from it have changes as below:
//Add
bool added = CallContext.Properties.TryAdd("LineRef", LineRef);
// Get
var LineRef = (string)CallContext.Properties["LineRef"]; //Note: Do not use .ToString() this converts instead of unboxing.
// Remove
object dummy;
bool foundAndRemoved = CallContext.Properties.TryRemove("LineRef", out dummy);
To use this your class needs to inherit from ContextBoundBase and implement the only the context bound constructor or you will get 'Ice.ContextBoundBase<Erp.ErpContext>.ContextBoundBase()' is obsolete: 'Use the constructor that takes a data context'
public partial class MyInvokeExternalMethodThing : ContextBoundBase<ErpContext>
{
public MyInvokeExternalMethodThing(ErpContext ctx) : base(ctx)
{
}
In E10.1 you can put any kind of object into this, so if you have an array of strings you don't need to use the old trick of tilde~separated~values.
I don't know about using .i files from E9 but I do know how to persist data between pre and post method directives in E10. Hopefully this helps.
There are a couple of different ways to do this. If when creating the pre-process bpm you chose the "Execute Custom Code" option. You can do it directly in your code using callContextBpmData. Almost all of the field names are similar to that of the user fields that E9 used (i.e. Number01, Chracter01, Date01).
In your code if you are setting text you could simply type:
callContextBpmData.Character01 = "some text";
Alternatively you could set it directly in the bpm designer without any code. In the designer left window pane, scroll all the way to the bottom, you should see something called "Set BPM Data Field". Drag it into the design area. After dragging it into the designer area you should see the option to set a field and its value in the bottom window pane. Select the field, then when you select "value" you are taken to a window similar to baq calculated field designer. You can use static data or use the data in the business object to calculate a value.
I have created a form that is used for both adding and editing a custom object. Which mode the form takes is provided by an enum value passed from the calling code. I also pass in an object of the custom type. All of my controls at data bound to the specific properties of the custom object. When the form is in Add mode, this works great as when the controls are updated with data, the underlying object is as well. However, in Edit mode, I keep two variables of the custom object supplied by the calling code, the original, and a temporary one made through deep copying. The controls are then bound to the temporary copy, this makes it easy to discard the changes if the user clicks the Cancel button. What I want to know is how to persist those changes back to the original object if the user clicks the OK button, since there is now a disconnect because of the deep copying. I am trying to avoid implementing a internal property on the Add/Edit form if I can. Below is an example of my code:
public AddEditCustomerDialog(Customer customer, DialogMode mode)
{
InitializeComponent();
InitializeCustomer(customer, mode);
}
private void InitializeCustomer(Customer customer, DialogMode mode)
{
this.customer = customer;
if (mode == DialogMode.Edit)
{
this.Text = "Edit Customer";
this.tempCustomer = ObjectCopyHelper.DeepCopy(this.customer);
this.customerListBindingSource.DataSource = this.tempCustomer;
this.phoneListBindingSource.DataSource = this.tempCustomer.PhoneList;
}
else
{
this.customerListBindingSource.DataSource = this.customer;
this.phoneListBindingSource.DataSource = this.customer.PhoneList;
}
}
You could always add a function to your object (Customer), either as "Copy(Customer cust)" or "Update(Customer cust)" and consume the changes that way.
The other way would be to have a wrapper class around Customer EditableCustomer, which takes a customer object in its constructor EditableCustomer(Customer root) and uses that to hold the changes. In the final event just call a function like "UpdateRoot()" and populate the changes back to the original customer, failing to call this will be the same as a discard.
You wont be able to use deep copies directly but this will allow you to control this type of situation, and actually allow you to control both edits and undo's dynamically.