#Undo

1 messages · Page 1 of 1 (latest)

stiff badger
#

What are you doing/ trying to do?

rare patio
#

Basically my problem is this: I'm dragging a wall around using handles, and generating blocks to fill out its length. They get created and destroyed during the drag. I want to be able to undo this operation afterwards, but I get a warning about dangling references if I both add and remove the same prefab instance during the same drag operation.

#

At the moment every object that is created gets recorded via Undo, and I'm using Undo.DestroyImmediate to clear them

#

I believe the issue is arising when the same object is created and destroyed in a single input operation.

#

But I'm not sure.

stiff badger
#

Can you show the code for that section?

rare patio
#
using UnityEngine;
using UnityEditor;
using Sirenix.OdinInspector.Editor;
using System.Collections.Generic;

namespace EffortStar.Editor {
  [CustomEditor(typeof(WallCollider)), CanEditMultipleObjects]
  public class WallColliderEditor : OdinEditor {
    public void OnSceneGUI() {
      var wall = target as WallCollider;
      if (Selection.Contains(wall.gameObject)) {
        var start = (Vector3) wall.Start;
        var end = (Vector3) wall.End;
        var thickness = wall.Thickness;

        using var check = new EditorGUI.ChangeCheckScope();
        Handles.TransformHandle(ref start, Quaternion.identity, ref thickness);
        Handles.TransformHandle(ref end, Quaternion.identity, ref thickness);

        if (check.changed) {
          RecordUndo(wall);
          wall.Initialize(start, end, thickness, generateMesh: true);
        }
      }
    }


    static List<Object> _objects = new();
    void RecordUndo(WallCollider wall) {
      _objects.Clear();
      wall.GetUndoObjects(_objects);
      Undo.RecordObjects(_objects.ToArray(), "Modify wall");
    }
  }
}
#

Remove the unnecessary part

#

And then within Initialize

      transform.DestroyAllChildrenImmediate();
      // redacted...
      for (var i = 0; i < blockCount; i++) {
        var xOffset = (_blockPrefab.Size.x / 2 - _blockPrefab.Origin.x) * blockScale.x;
        var position = new Vector3(
          x: 0,
          y: i * blockLength + xOffset,
          z: 0
        );
        var block = GameObjectUtility.InstantiateChild(
          _blockPrefab,
          transform,
          position
        );
#if UNITY_EDITOR
        if (!Application.isPlaying) {
          UnityEditor.Undo.RegisterCreatedObjectUndo(block, "Created block");
        }
#endif
        block.transform.localRotation = blockRotation;
        block.transform.localScale = blockScale;

        if (cladding != null) {
          block.SetLeftCladding(cladding.Sample());
          block.SetRightCladding(cladding.Sample());
        }
      }
#
    public static void DestroyAllChildrenImmediate(this Transform transform) {
      for (var i = transform.childCount - 1; i >= 0; i--) {
#if UNITY_EDITOR
        if (!Application.isPlaying) {
          UnityEditor.Undo.DestroyObjectImmediate(transform.GetChild(i).gameObject);
          continue;
        }
#else
        Object.DestroyImmediate(transform.GetChild(i).gameObject);
#endif
      }
    }
#

Sorry it's complicated

#

But basically this codepath runs every time a handle moves.

#

Works perfectly but for the undoing

#

It would be easy to fix if I could somehow find out when the drag stops, but I don't know how

#

Or if I could work out when the undo was applied, that would also be fine

stiff badger
#

Right, I get the gist I think. So here is what you should be doing I think. When you start a move/edit, cache the Undo.CurrentUndoGroup(), record changes to the wall prefabs using Undo.RecordObject(wallSegment.transform, "wall changed), when creating a new wall segment, use Undo.RegisterCreatedObjectUndo(newWallSegment, "wall created);, and when getting rid of a wall segment you use Undo.DestoryObjectImmediate(wallSegment);.
Once the change is finished, you do Undo.CollapseUndoGroup(cachedGroup);

#

Don't be creating and destorying every object in the wall each frame, that is going to be terrible for performance and balloon the memory. Just the delta.

rare patio
#

Once the change is finished, you do Undo.CollapseUndoGroup(cachedGroup);
If I was able to get a change finished event then I could solve this in any number of ways.

#

Although my understanding is that the undo collapses itself when the input event ends?

#

Ahhh... I can use the current group ID as a cache breaker maybe

#

The problem is that this code runs every time the mouse moves (or doesn't) during the drag operation

#

wrt performance -- it seems fine.

#

It's just editor code.

stiff badger
stiff badger
rare patio
#

No, it's both editor and runtime code

#

But it only generates once in the game

#

There will be no free dragging of walls in the game

#

And it can be changed later

#

I've just made it so that the walls are destroyed when you undo and there's a button on the inspector to regen them

#

Not worth the automation