Seperating data from monobehaviours, how do you then create levels?

Typically i like my data to be centralised in some kinda manager rather than each object having its own data. That way GameObjects are just visually representations of the data and the manager controls the link between the two, such that if i delete some data in the manager, it will destroy the associated game objects etc.

The problem i find in unity is this approach seems rather difficult when wanting to design levels in editor. Because data has to be added to a manager and then instantiates relevant game objects, but the managers only really work and make sense at run time not in editor.

How does everyone seperate their data from game objects in a clean way like this and still find an easy way to design levels? It feels like an uphill battle to do that in Unity, and whilst DOTS seems like a solution, the whole API for that is just a headache and not easy to get into so i don’t particularly have much interest in DOTS for time being.

1 Like

I’ve had two ideas in mind that I’ll need to explore for my current project:

1: Have a baking process. Basically you hit a button, and a bunch of editor scripts trawl through the scene and pile the data into your manager.

2: More granular editor tooling. Ergo make some editor tools you use to design your levels to interoperate with your backing data.

Either way it is going to require editor tooling of some kind, but ever project does anyway.

1 Like

Game Objects are data containers, so separating data from GameObjects seems a bit odd!

Generally if you want to be more data oriented, but still live in a GameObject paradigm so you don’t have to live with the abstraction olympics that is ECS, you use your MonoBehaviours as just data containers (ie. just serialized fields, no Update calls), which would register themselves (or be collected) into some sort of central data lookup, and then you have systems that act on that data, which you can spin up either using some kind of auto-instantiating singleton, or the PlayerLoop, or something like that.

Are you aware of what you’re implying here? :wink:

Data does not belong into a ā€œmanagerā€. A manager ā€œmanagesā€ (whatever that means, it’s an extremely vague term) but data ā€œbelongsā€ or in other words, data demands a collection type. A database even.

One great way to design and store data in Unity is to put your data container class (ie a struct with various fields but minimal to no logic) in a ScriptableObject asset. This is edit-time data. Every time the game launches, that data will have the values you set in the editor.

Then, to make that data persist, you’d simply load/save that SO struct at runtime perhaps as json file, so there’s no need to serialize the SO itself but rather the SO will start life using the design-time values and at any time you can request it to load any persisted runtime values.

If you need a different set of ā€œdatasā€ for each scene (levels), then you could have an editor script that creates this SO asset for you when you start creating a new level. This would also associated the SO with the level, usually by assigning it to a MonoBehaviour in the scene and yes, that then could be your ā€œmanagerā€ type.

But don’t call it DataManager … this would rank among the most nondescript names I can think of. :wink:
I also don’t name my classes SpaghettiScript or GameBehaviour or hey, why not have a SingletonManager since there’s so many of them. I’m a stickler for good names (actually: meaningfully defined purpose such that the name of the type derives easily from that) so please excuse my rant. :stuck_out_tongue_winking_eye:

Since you remain very vague in your description or the purpose of this manager class, I don’t see why this would be a problem. There’s a clear deliniation: either you need something to work in the editor or at runtime. Ideally these should still be separate types albeit the editor script depending on and interacting with the runtime script.

If this distinction isn’t made clear, for instance by throwing public runtime and editor methods into the same class, you are going to set yourself up for big headaches no matter what the purpose of the class may be. If this trickles outwards, say your DataManager has some editor-only methods that other code must either enclose in #if UNITY_EDITOR or the enclosing happens within DataManager but there’s no clear indication that this method won’t do anything at runtime, this spells trouble.

Not sure if this is what you meant with ā€œmakes sense only at runtimeā€. I’m actually seeing the opposite most of the time.

It depends on what you mean by ā€œdesign levelsā€. This could be a tile-based level on a small grid, or a streaming open world terrain. And a gazillion other ways of designing levels.

But for the most part it boils down to defining what data you need, and what part of the data will be const meaning it can only be designed in the editor and never changes at runtime. The other types of data are either values that can change at runtime but need to be reset on occassion (ie start of game/level), and those that reflect progression (ie experience points, score) and should be persisted between runs/levels.

Coming back at this I wonder if perhaps you have a double-edged sword. In one way, you’re changing data which changes the level. But you also want to design the level, which means creating new entries or perhaps also altering data.

Is that the kind of problem space you’re in?

It would be best if the system primarily worked in one or the other direction. Ie you can generate a level from your data, and you can continue to edit the level through the data. Say for simplicity you have an array in a SO which represents the tiles on the grid and (ignoring how awkward it would be to edit) then this would generate your tiles in the scene.

But when you also edit these tiles by manually placing objects onto them, you’ve created yourself a problem. Actually it’s the same problem as having a code generator, and then manually making edits to generated scripts.

I think you need to be more specific as to what kind of levels you are designing and the specific ways this is failing on you. I’m sure we can give some good practical advice, but as it stands the problem space is far too vague.

^ I’ve definitely used this approach, ranging from the trivial to the complex, such as tools that let me emplace level geometry and parts of a level via a PNG file (special-colored pixels) and then bake it into a GameObject collection that I can further edit. I like this, it’s great for gamejams, but it is also a step-destructive process, mostly, but it can be made to be back-and-forth lossless with some work. An example of my PNG-to-GameObject process can be found here:

Colors are specified as a particular RGB (see the B2GConfig class) and considered as a collection to find the best match, precluding errors from slight color changes / tints.

^ This seems to be the one way that scales the best, but it also saddles you with the task of maintaining whatever editor you make AND your game… twice the work! But like I said, with proper design, it scales almost infinitely. And again with proper design, you share a lot of common functionality and your editor becomes simultaneously a testbed for level design / layout.

But it lets you ā€œlive closer to the dataā€ so that you can edit the data representation (perhaps JSON?) directly, reading / writing it while simultaneously using the same code your game would use to visualize it, turning it into GameObjects, etc. to be seen, either in the editor or in the game itself, even do gross validations on it, like verifying that every level has a player start and player exit and a boundary.

And you can keep layering validation on top of that, and when your game grows far enough, make a robot program to play all of your levels through, letting you verify that they are all passable / playable still.

2 Likes
  1. Implement a Load method that converts data into game objects
  2. Implement a Save method that converts game objects into data.
  3. Add editor tooling that make it possible to load chunks of your data in Edit Mode, and then save those back into your data container.

So basically rebuild Unity’s prefab system.

When you go out of your way to avoid using all the great tools that Unity offers, you generally have to reinvent the wheel and build (probably much worse) replacements for everything from scratch. You would make life for yourself much easier if you embraced the Unity way of doing things instead :slightly_smiling_face:

Prefabs and ScriptableObjects. That handles like 90% of anything related to ā€˜Database-like’ stuff I need. The other 10% would be either proprietary files that are loaded on demand or actual databases.

If you want a ā€˜centralized’ place to manage data then ScriptableObjects are the goto. If you want more granular control over individual GameObjects then prefabs. It’ pretty rare that I ever place raw GameObjects into a scene anymore except when testing or for completely trivia one-off things.

1 Like

Another possibility that occurred to me overnight: build the data at the beginning of runtime. This will probably require some editor tooling that establishes a bunch of references to objects in the scene. Then when entering playmode/entering the scene, you can build your backing data.

Also I think people here are overlooking that in certain types of projects you do really need your own separate data layer.

1 Like

That’s a good point. On one MMO project I worked on level data came from the backend, which meant that they could only be loaded at runtime.

In the earlier phases of that project level designers used external software that could export to JSON (Tiled) for creating levels.

Over time we invested a lot of effort into building good in-game editing tools, and later on our level designers worked with basically the same set of tools as our players did for creating UGC.

2 Likes

I tend to think the other way around for systems with alot of data. For example we have a ballistics system. The ballistics is simulated with a struct so many simulations can be alive. Then scene related stuff like supersonic sonic cracks, fly by sub sonic sounds, richochet splashes, etc are done using reusable game objects.