This feature is something you’ll have to plan for early on in your project, and you’ll need to take control of a lot of processes that the Unity Editor kind of manages for you behind the scenes. I encountered a similar need recently when working on something akin to a city building game. Naturally, users needed to be able to place objects in the scene, and also manipulate terrain topography, textures, and details. To approach this, I wrote several manager classes to help handle all that complexity. The basic principles should transfer to your level editor scenario, so bear with me here:
The first thing to bring up is whether your level editor will be contained in the same executable as the game, or built as a separate executable. This may amount to preference, and if so I’d suggest keeping everything in one project if you can. So users will probably get an option to visit the level editor, which would be a separate scene that you create, presumably using the same art assets as any other scene, just different methods for user interaction.
Sounds like the first thing you need to take control of is runtime terrain manipulation. Almost anything about a terrain can be changed via code at runtime, so you’ll be writing code that translates user input into changes in the terrain data in some meaningful way. Nothing super complex, really, once you understand how terrains work in Unity. You can duplicate most of the Unity Editor’s terrain editing functions yourself with your own code. This will allow users to change terrains at runtime.
Next you’d need a way to save and load terrains at runtime. This is best accomplished by serializing every aspect of terrain data that a user can alter. Basically your Save Level method should examine the terrain object (which the user just edited at runtime), then write all user-manipulated values to an .xml file. Your Load Level method will first instantiate an un-edited prefab of the terrain (or a brand new terrain object, whichever’s easier), then read that .xml file, changing terrain features as it goes along. Again, all very do-able.
The only difference with user-placed objects is making sure you can serialize their properties. I forget exactly what issues I encountered when doing this myself, but if you make sure your custom object classes have 100% serializable attributes, it’ll be easy as pie. So your Save method will look at user-placed objects one by one, doing the same task as before. Let’s say it’s a loot chest the user can place in their level, so it’d save the position, rotation, as well as any custom properties (hasBeenOpened, isLocked, etc). The Load method should then instantiate each object by examining the saved properties. (Oh, the saved one is locked? Okay, newChest.Lock(); )
It takes quite a bit of set-up and care, but overall the process should be very straightforward once you know what you’re getting yourself into. 