Some technical challenges in building an editor

#1

I’ve been building my own thing (it uses rendy, but not amethyst) and in the process have hit a number of problems I think amethyst will also need to deal with. I wanted to mention them here in hopes that it will be helpful in forming consensus around an initial technical design for amethyst’s editor.

A couple videos of what I have so far (again, this is not amethyst)

Code is here and I’m happy for anyone to use it however they see fit either in amethyst or otherwise: https://github.com/aclysma/minimum.

First I’m going to ignore using IPC for editor/game communication, communication between tools, prefabs, etc… Just the minimum-viable-functionality for an editor that interacts directly with the disk is enough to think about on its own!

One key thought that I think needs to be considered: the data needed to create a component is not the same as the data needed to represent the component at runtime. In my opinion, separating these concerns (design-time and run-time representation) is very important. (In fact there are probably other representations, such as a “cooked” asset).

Picking/Selection - Typically, an editor allows selecting one or more than one entity.

  • Some sort of ray cast or other collision detection is required to determine what is under the mouse
  • Usually you can drag a box around multiple entities to select them
  • The physical representation/bounds for an entity is not always clear. Ideally a mesh, you could click on the mesh. For a point-light, if it’s represented by a 2d texture, you could click on that. Determining what shape to use for an arbitrary set of components could be tricky.
    • There should probably be editor-specific rendering for objects like lights, collision, or abstract concepts like trigger volumes or spawn positions
  • Once selection is decided, some representation is required to know what is selected. (In my own system, I have a “selected” component.)
  • I have a trait that can be implemented for a component to define what the clickable shape of that component is. This is working ok for now as a prototype but it would likely need more work to be a polished, completed solution.
  • https://github.com/aclysma/minimum/tree/master/minimum-framework/src/select - I suspect that there are broader ideas here than just selection. For example, editor-specific rendering. Or offering specific editing tools for certain types of entities/components.

Closely related, often you would want ways to interact directly with entities. For example, widgets/gizmos to translate, scale, or rotate.

  • Sometimes this is world space, or local space
  • Often you would want to be able to choose between snapping or not snapping, and the interval for snapping.
  • Being able to move the center/pivot points can be very useful
  • Drawing these widgets/gizmos, and handling interaction with them is not trivial
  • In my case, I made an immediate-mode API kind of like imgui that allows for drawing shapes using world coordinates, but that detects dragging in ui space.
    • editor_shapes.add_circle(position, radius, color, “id_of_shape”)
    • if let Some(drag_in_progress) = editor_shapes.drag_in_progress(“id_of_shape”) { … }
  • I suspect there are reasonable circumstances where someone would want to draw custom editing UI in the editor. (i.e. an editor for a path/spline.)
  • https://github.com/aclysma/minimum/blob/master/minimum-demo/src/resources/editor_draw.rs - Basic prototype for drawing shapes within the editor
  • https://github.com/aclysma/minimum/blob/master/minimum-demo/src/tasks/editor/editor_handle_input.rs - Some code that uses it to draw and handle moving entities. (Only translation is implemented)

Handling property editing is tricky as well.

  • Will need to decide if selecting multiple elements is allowed.
  • If so, what should render and what should behavior be like if some elements are inconsistent (for example, the entities have the same rotation but not the same position.)
  • Applying the changes can be tricky as well. For example, if physics are involved, “fixing” the physics body to match a property edit may not be as simple as changing a number.
  • I handle this by adding a component EditorModifiedComponent, and at the end of the frame, I destroy and recreate all entities that were modified. (Care needs to be taken here to maintain selection if the entity was selected.)
  • I split out imgui-specific macros to something reusable here: https://github.com/aclysma/imgui-inspect
  • I used a similar “registry” pattern to the selection above: https://github.com/aclysma/minimum/tree/master/minimum-framework/src/inspect
  • I think it is beneficial to allow for quick-and-easy handling of simple structs with reasonable defaults, but also it should be possible to heavily customize the how a component gets inspected.
  • My approach only works because I keep the “create” data simple and clone-able - even if the runtime representation for a component isn’t a simple flat object.

Undo/Redo will need to be carefully considered from the start. Since entities/components are fairly data-centric, this might not be too bad. I’ve heard of some designs where you get undo/redo “for free” by using messages to manipulate the world, and keeping the history of the state of all edits. The context for this was that the level/asset states are hosted by some other process and the editor acts as a client. I’m not certain how well this would work though really because undo/redo sometimes includes things that are non-mutating, like changing which entities are selected.

Copy/Cut/Paste/Duplication is also worth considering, but is hopefully not too difficult since there are lots of other reasons why entities/components will need to be serializable/deserializable.

There is also a need for playing, pausing, inspecting the paused game, and resetting. I am handling this by having a component that has an Arc back the the “prototype/definition” of the entity. I handle editing an object as finding the component prototype (the data required to create the component) in the Arc, editing it, and then marking the entity as needing to be destroyed/recreated later in the frame.

I think it would be very beneficial to prototype some of this functionality early on (even if it’s with a non-permanent solution like Imgui) so that any engine-level design can be informed and driven by some of these use cases. For example, this could be a potential proof-of-concept:

  • Launch game
  • Load a scene
  • Select some things
  • Edit them
  • Delete some things
  • Add some things
  • Play/Pause
  • Inspect at Runtime
  • Save
  • Quit
  • Relaunch and load saved data

I hope this is useful. I’m interested to see other people’s take on solving some of these problems!

12 Likes
Editor planning meeting
Editor planning meeting
(Andrea Catania) #2

Godot works in this way, here an example in GDScript:

This method is simple to use and effective and I didn’t have problem with it.

1 Like
(Swarnim Arun) #3

This has a problem of slight ambiguity, as undos is just a set of operations executed in the same top down fashion. (Caused a lot of bugs for me during my work with some major parts of Godot Editor & Visual Scripting and stuff)

So(this is a very contrived example but hopefully you get the point),

undo->add_do(obj, "create", something);
undo->add_undo(obj, "delete", something);
undo->add_do(something, "move", pos);
undo->add_undo(something, "move", org_pos); // crash on runtime atleast in Godot

Few other problems are the need to have to expose things(uses signals to perform actions aka requires exposing the internal state) externally to do an undo/redo if not insecure it’s atleast very cumbersome to expose soo many things,

maintainability of these operations (everything you change some behaviour they can easily break) aka somewhat fragile,

and you can not merge complex operations into one unified operation without creating another operation to handle that extra condition…

I have a couple ideas of how we might be able to do this using something like the Memento/Token Patterns and internally managed state changes (isolated externally for the most part). But most ideas would have to be tailored as per the GUI system we might be using IMHO.