Hi,
I am making a class to preview a noise that I generate. I create a VisualElement and change the backgroundImage with the texture I generate, but I don’t know how to detect when the texture is changed and update the visual.
Here is the code:
[CustomEditor(typeof(NoisePreview))]
public class NoisePreviewEditor : Editor
{
NoisePreview noisePreview;
VisualElement preview;
public override VisualElement CreateInspectorGUI()
{
noisePreview = (NoisePreview)target;
VisualElement root = new VisualElement();
preview = new VisualElement();
preview.style.width = 300;
preview.style.height = 300;
preview.style.alignSelf = Align.Center;
preview.style.marginTop = 10;
preview.style.marginBottom = 10;
root.Add(preview);
InspectorElement.FillDefaultInspector(root, this.serializedObject, this);
noisePreview.OnTextureChanged += (texture) =>
{
preview.style.backgroundImage = new StyleBackground(texture);
};
noisePreview.CreateTexture();
return root;
}
}
#endif
public class NoisePreview : MonoBehaviour
{
[SerializeField] FractalNoise fractalNoise = FractalNoise.Default;
public const int previewImageSize = 256;
public event Action<Texture2D> OnTextureChanged;
void OnValidate()
{
CreateTexture();
}
public Texture2D CreateTexture()
{
CSNoise noise = new CSNoise();
Texture2D texture = noise.GetTexture(previewImageSize, previewImageSize, fractalNoise);
OnTextureChanged?.Invoke(texture);
return texture;
}
}
I know it’s not the right way to do it, but I tried to use SerializedObject or SerializedProperty but I didn’t understand how to use them.
I don’t think you would even need to inform the visual element when the texture is changed. So long as you’re modifying the same texture instance, whenever you apply changes to said texture, that should simply reflect in the visual element itself.
Unfortunately, it doesn’t work. Here is what I tried:
[CustomEditor(typeof(NoisePreview))]
public class NoisePreviewEditor : Editor
{
NoisePreview noisePreview;
VisualElement preview;
public override VisualElement CreateInspectorGUI()
{
noisePreview = (NoisePreview)target;
VisualElement root = new VisualElement();
preview = new VisualElement();
preview.style.width = 300;
preview.style.height = 300;
preview.style.alignSelf = Align.Center;
preview.style.marginTop = 10;
preview.style.marginBottom = 10;
preview.style.backgroundImage = new StyleBackground(noisePreview.Texture);
root.Add(preview);
InspectorElement.FillDefaultInspector(root, this.serializedObject, this);
noisePreview.CreateTexture();
return root;
}
}
public class NoisePreview : MonoBehaviour
{
[SerializeField] FractalNoise fractalNoise = FractalNoise.Default;
public const int previewImageSize = 256;
Texture2D texture;
public Texture2D Texture => texture;
void OnValidate()
{
CreateTexture();
}
public Texture2D CreateTexture()
{
CSNoise noise = new CSNoise();
texture = noise.GetTexture(previewImageSize, previewImageSize, fractalNoise);
return texture;
}
}
Probably because CSNoise.GetTexture
is returning a new texture instance each time.
1 Like
You are right, I will change that.
PS: I hadn’t thought of that. Thanks, it works fine now.
1 Like
Glad its working!
FWIW, and for future readers, here’s an example that applies that idea, alongside tracking the value of a SerializedProperty
:
namespace LBG.Core.Testing
{
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.UIElements;
#endif
public class PerliinNoiseTextureExample : MonoBehaviour
{
#region Inspector Fields
[SerializeField]
private Texture2D _texture;
[SerializeField]
private float _scaleX, _scaleY;
#endregion
#region Properties
public float ScaleX => _scaleX;
public float ScaleY => _scaleY;
#endregion
}
#if UNITY_EDITOR
[CustomEditor(typeof(PerliinNoiseTextureExample))]
public class PerliinNoiseTextureExampleEditor : UnityEditor.Editor
{
private SerializedProperty _textureProperty;
private VisualElement _textureElement;
private void OnEnable()
{
_textureProperty = this.serializedObject.FindProperty("_texture");
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
InspectorElement.FillDefaultInspector(root, serializedObject, this);
_textureElement = new VisualElement()
{
style =
{
height = 512
}
};
_textureElement.TrackPropertyValue(_textureProperty, UpdateBackground);
root.Add(_textureElement);
UpdateBackground(_textureProperty);
var generateButton = new Button(GenerateTexture)
{
text = "Generate Texture"
};
root.Add(generateButton);
var clearButton = new Button(ClearTexture)
{
text = "Clear Texture"
};
root.Add(clearButton);
return root;
}
private void GenerateTexture()
{
var texture = (Texture2D)_textureProperty.objectReferenceValue;
if (texture == null)
{
texture = new Texture2D(512, 512);
_textureProperty.objectReferenceValue = texture;
this.serializedObject.ApplyModifiedProperties();
}
var example = (PerliinNoiseTextureExample)this.target;
float scaleX = example.ScaleX;
float scaleY = example.ScaleY;
Color32[] pixels = new Color32[512 * 512];
for (int x = 0; x < 512; x++)
{
for (int y = 0; y < 512; y++)
{
float p = Mathf.PerlinNoise(x / 512f * scaleX, y / 512f * scaleY);
int i = (y * 512) + x;
byte a = (byte)Mathf.Lerp(0, 255, p);
Color32 c = new(255, 255, 255, a);
pixels[i] = c;
}
}
texture.SetPixels32(pixels);
texture.Apply();
}
private void ClearTexture()
{
var texture = _textureProperty.objectReferenceValue;
Object.DestroyImmediate(texture);
_textureProperty.objectReferenceValue = null;
this.serializedObject.ApplyModifiedProperties();
}
private void UpdateBackground(SerializedProperty property)
{
var texture = (Texture2D)_textureProperty.objectReferenceValue;
if (texture == null)
{
_textureElement.style.backgroundImage = default;
}
else
{
_textureElement.style.backgroundImage = Background.FromTexture2D(texture);
}
}
}
#endif
}
Fun to play with:
(Just noticed my typo, too).
1 Like