godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 93 forks source link

Add "Mimic" and "Mimic3D" node type #7662

Open robotjunkyard opened 1 year ago

robotjunkyard commented 1 year ago

Describe the project you are working on

I am working on a complete Godot-based, rewrite-from-scratch of a 3D turn-based dungeon crawler RPG (aka "blobber" in some circles) that I finished and released last year.

Popular examples of games like this include the original pre-VGA era of Wizardry, Might & Magic, and the dungeon sections of early Ultima games. There are non-turn-based examples, too, such as Eye of the Beholder, Dungeon Master, or Legend of Grimrock, although those examples don't implement the spatial feature I'm about to describe.

Describe the problem or limitation you are having in your project

For context, a Dungeon is a level composed of 20x20 grid of "cells", a cell being a square space with between zero to four walls, a floor, and ceiling, and the player walks through them step-by-step.

For the problem I am having, this is regarding a feature of levels (dungeons) in this specific type of RPG: once you hit the west-most/east-mode/north-most/south-most side of a dungeon, it simply wraps you (or at least your sight) around.

For example, Imagine one long stretch of hallway is created in a given dungeon; the hallway spans straight from west to east, with no obstructions at all: you can hold down the forward movement key and infinitely run forward, always wrapping around back to the other side, indefinitely. In practice, this is subtle so the player doesn't necessarily realize it's happening, which allows dungeons that are constructed within their 20x20 square grid to not [b][i]feel[/i][/b], to the player, like they're strictly confined within square boundaries (to say nothing of other tricks and gameplay shenanigans one can do with this wrap-around effect).

Now, here's where the limitation kicks in: currently, to achieve this in scripts, the 3D representation of a Dungeon (an abstraction implemented in a type of scene I named "Dungeon3D") creates way more than 20x20 "DungeonCell3D" nodes. This is because you can see ahead X number of squares, so it creates (20+X2) by (20+X2) DungeonCell3D nodes in advance. That means initializing way more DungeonCell3D objects than there are actual cells in the dungeon itself -- which introduces an increasing probability of bugs as this codebase becomes increasingly complex.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

A Mimic3D (or for 2D, Mimic) is a proposed Node type which simply does one thing -- mimics exactly what another Node's current-frame geometric+material appearance is, and that of the entire tree of any other sub-nodes within it as well. All a Mimic fundamentally does is a redraw of geometry+material it already drew before. A Mimic3D could perhaps have its own scale/transformation/rotation, but anything more complex than that is beyond the scope of this proposal. It most certainly would NOT be able to mimic anything non-cosmetic of another Node (such as physics/collision).

In the case of this project, in the main dungeon scene, I could simplify my solution by creating eight Mimic3D objects in Godot's 3D editor, point all of them to the Dungeon3D that contains the 20x20 DungeonCell3D nodes already initialized within it, and place those Mimic3D objects to neighbor the nine corners/edges of the ACTUAL Dungeon3D.

Any visual change made to the Dungeon3D or any element within it, is reflected in its mimics -- with zero, or at least extremely little, scripting required.

Distance culling, frustum culling, and occlusion culling would apply to Mimics like any other 3D object.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Imagine the scene has a node of a dancing 3D hamster. It's called Hamster. Create Mimic3D node, add it to scene. In the inspector, click on the Mimic3D node, then for its Source property, select the [Hamster] node Move that Mimic3D somewhere else in the scene When Hamster dances via an animation or whatever, the Mimic3D looks like a perfectly synchronized dancing hamster nearby But fundamentally there's still only one actual Hamster object in the scene

This is a simplistic example... imagine if Hamster itself is not a single Node of a closed mesh, but an amalgamation of multiple nodes for separate mesh instances (like PlayStation 1 games tended to do with 3D characters, each limb being its own island of geometry), which are arranged in a tree whose top node is called Hamster. Well, Mimic3D would still perfectly mimic Hamster [i]and everything grouped under it[/i] too.

Care would need to be taken such that the engine would forbid circular references between two node trees which have a Mimic3D in them which points to anything in the other tree.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Yes, this scripting "workaround" is what I'm currently doing now, but calling it a "few lines" is wishful thinking!!

Iterating through way more DungeonCell3D nodes than the 20x20 actual cells in a dungeon, is something that is prone to mistakes. Especially once I begin implementing the addition of columns soon (which will be yet another separate grid of 3D objects which are placed in the 3D dungeon scene, each depending on whether or not any wall touches a given point between the edges of four actual cells in the Dungeon data structure), then the current strategy of faking the wraparound effect, as it is being implemented now via scripting, is going to get increasingly complex.

Maybe another option is to use non-euclidean shader+camera tricks, like those seen in games like Portal or Superliminal, although my personal experience/know-how with that sort of thing is pretty much zero and would thus add a burdensome amount of "trial-and-error on one feature" time to development. I could always find a freelancer to help with that, I guess.

In my original version of this game, this was all achieved through very old, pre-3.0 OpenGL -- aka "immediate mode", so wrap-around was just a trivial consequence of using simple modulo operations in the rendering loop. But that is not the "modern" way of doing game graphics anymore; certainly not in Godot (or any other similar engine).

Is there a reason why this should be core and not an add-on in the asset library?

I do not know by which principles it is decided whether or not a feature is ultimately added to the Core engine, so I'm not qualified to answer this objectively.

I will say there are probably many other uses for this than my own individual use case that was described. I can imagine cool "kaleidoscope of repeating 3D objects" effects, or even fractal-like effects (if Mimic3D can mimic other Mimic3D objects, each scaling down, etc) which would be easy to do with this proposed feature with significantly less scripting needed than would currently be required.

Zireael07 commented 1 year ago

Why do you need a separate node type for this? Just duplicate the nodes EDIT on the fly and free when no longer needed.

As for wraparound graphics, that's probably either worth a separate proposal or covered by alternate camera projections.

Also: I think immediate mode style graphics is still around in Godot, you just need to dig into renderer classes instead of using nodes

Mickeon commented 1 year ago

The proposed Node serves a fairly niche purpose and wouldn't benefit a majority of users of the engine.

Godot has a decently strict "no bloat" policy, where if it can be done with addons, it really should. Admittedly, it may be looser now, because extending core classes with new functionality across the entire project is a bit of a hassle and sometimes less performant (e.g. adding signals that emit when a built-in value changes). The relatively new GDExtension API mitigates that to an extent, though.

I suppose this proposal can at least highlight how many people need this Node, and point them to the right direction (say, somebody makes such an addon available in the comments).

robotjunkyard commented 1 year ago

In hindsight, for my specific use case, this kind of node may still be computationally wasteful in other ways (even with frustum/etc. culling, something still iterates over all those extra tens of thousands of dungeon polygons, even ones not drawn).

That said, a Mimic node may still offer some performance benefit for other use cases requiring redundant geometry in which running the same script(s) or component helper nodes N number of times may be excessive in terms of performance.

Perhaps a contrived example, but one which is way more likely for a developer to encounter than my dancing hamsters example: a scene with 100 torches scattered throughout, in which the flame is animated from particles. One torch would be all that's needed to do particle simulation, then the other 99 are Mimics which simply re-draw the existing resulting geometry/visuals in their own respective locations. This would be much more performant, compared to the alternative of having 99 more "Torch" nodes running their own separate particle computations per frame.

Not a game engine, but I recall Blender has a feature not unlike this concept, I think it was called "Linked Duplicate". Only the original object needs its various Modifiers/effects/subdivisions/etc processed, while the Linked Duplicate instances save both CPU usage, RAM usage (and definitely simplifying the scene graph) by simply parroting the result of that object they link to.

Stwend commented 1 year ago

If I'm not wrong, Godot's MultiMeshInstance would fill this role: https://docs.godotengine.org/en/stable/classes/class_multimeshinstance3d.html

Besides, for particle effects you'd usually want the exact opposite - each torch should have its own flame loop instead of displaying the exact same sprite or particle cluster as the others.

stephannv commented 1 year ago

I think this is a great idea for an addon