About Monkey 2 › Forums › Monkey 2 Development › Instancing entities with components
This topic contains 8 replies, has 4 voices, and was last updated by
Ethernaut
2 years, 2 months ago.
-
AuthorPosts
-
January 25, 2017 at 4:59 am #6806
Long post!
I’ve been working for a while on a little personal project. I chose to go with an entity/component design (a simpler version of how Unity works).
I want to keep all the design decisions outside the code itself. For example, all classes will be declared in the monkey 2 files, but the entities will be created by reading .json files or similar formats. This may allow using existing editors, instead of creating my own.
So far so good. The problem is when I need to instance multiple entities from the same “prefab”.
If I’m doing it all in code, all I have to do is wrap each entity into a class, like this (pseudocode):Monkey123456789Class Enemy Extends EntityMethod New( x, y )AddComponent( Transform( x, y ) )AddComponent( AIPlayer )AddComponent( MovementController )AddComponent( SpriteRenderer )'Etc…EndEndSo that when I want a new enemy, all I do is “New Enemy( x, y )”. Easy! The thing is that I DON’T want a dedicated Enemy class, all I want is a basic entity + necessary components that give me the behavior I want. That way I can create all sorts of gameplay elements minimally touching the code. Or so I hope.
The Problem:
After I create the first prefab entity, assign the components and try to “instance” it is when I run into issues. When I reference the same component in two or more different entities, it works fine if the component has no persistent state for more than a frame, otherwise one entity may stomp the component’s state for another entity.
How do I COPY an existing object, with all of its fields, stacks, etc? No need to copy private stuff, just the public fields. I believe this is called “deep copy”?
I’m currently trying it by adding “Copy()” methods to most of my classes, but that’s kind of a pain!
I also tried converting the components to Structs, which could solve the issues since structs are actually copied, instead of referenced, but I’m running into crashes (see thread: http://monkey2.monkey-x.com/forums/topic/struct-still-crashes-in-some-cases/ )
I’m afraid someone will say “just use Reflection”, blissfully unaware that I’m a noob who has a lot of trouble dealing with the complexities of Reflection!
But if it’s the only way, I’ll resign myself to it. After all it may be necessary anyway in order to load the .json files…
(I’m secretly hoping someone will have a supercool “Copy<obj>( from:obj )” function that will magically solve my issues…)
Thanks!
January 25, 2017 at 8:53 am #6807casual grep-ping of the source doesn’t seem to show anything that would help.
low level messing with memcpy would be basically useless aside from only ending up with a ptr to the new object (if it worked at all) the GC wouldn’t know about the new “class”
Without seeing how you are creating these “components” its a little hard to guess whats going on.
try using structs but as Mark suggests put the struct in another file (ideally on its own) why having its own “compilation unit” should help is just one of the “delights” of c++
January 25, 2017 at 8:57 am #6808Reflection..
When (and IF) we have a full reflection support, we can write object Serializer / Deserializer that allow us dynamically creation of any objects, like
Monkey1Local obj := Serializer.FromJson<MyObjType>( jsonStr )This time is impossible to do that.
p.s.
From time to time I’m thinking about building unity-like small editor, and only way I invented for instantiate prefab is
- Generate code with needed properties set – by editor.
- Rebuild sources on start – that’s very sad.
Like this
Monkey12345678910111213141516171819202122232425' autogeneratedFunction Instantiate<T>:T( prefab:T ) Where T Extends EntityLocal obj:Object=NullLocal name:=prefab.InstanceType.NameSelect nameCase "house"obj=Inst_House()Case ...Case ...EndReturn Cast<T>( obj )EndPrivateFunction Inst_House:HouseEntity()Local t:=New HouseEntityt.mass=100t.AddComponent( New Collider )' other inits hereReturn tEndJanuary 26, 2017 at 1:41 am #6813Thanks for the replies!
Out of curiosity, what’s missing from the current Reflection system? I’ve only dabbled in it, but it seems like you can access all fields from a class. Couldn’t a serializer be written with the current available features?
try using structs but as Mark suggests put the struct in another file (ideally on its own) why having its own “compilation unit” should help is just one of the “delights” of c++
I tried, still get the same compiler error. Also tried “New AppInstance” to make sure Image was initialized, no luck.
January 26, 2017 at 6:21 am #6816Reflection Limitations
Currently, typeinfo is only generated for non-generic, non-extension, non-extern 100% pure monkey2 globals, fields, function, methods, classes and namespaces. You can still use other types (structs etc) with variants etc, but you wont be able to inspect their members.
So, we can’t inspect a very common Vec2 class bcoz it’s generic.
We can ‘solve’ it by using our own version of vector2 together with operator To:Vec2 to mix internal and native vectors.
January 28, 2017 at 5:31 am #6850Monkey1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798' ---------------------------------------------- '' IMPLEMENTATION' ---------------------------------------------- 'Interface IComponentProperty Name:StringProperty Owner:GameObject()Method OnLoad()Method OnUpdate()Method OnStop()EndClass GameObjectField List<String> Tags;Field List<IComponent> Components;Method FindComponent:IComponent(name:String)' foreach component return if name matchesEndMethod Start()EndMethod Update()EndMethod End()EndEnd' ---------------------------------------------- '' ---------------------------------------------- '' CUSTOM COMPONENTS' ---------------------------------------------- 'Class CharacterController Implements IComponent'...'EndClass EnemyAI Implements IComponent'...'End' ---------------------------------------------- '' ---------------------------------------------- '' EXAMPLE' ---------------------------------------------- '' both game objects have the same classLocal player := New GameObjectLocal enemy := New GameObject' each game object however has different behaviourplayer.Components.AddLast(New CharacterController)enemy.Components.AddLast(New EnemyAI)' ---------------------------------------------- '' OTHER NOTES' Creating an object pool is important because' you might need to fetch them at any time.Class GameObjectPool AbstractGlobal GameObjects:List<GameObject>Function Add(g:GameObject)'...EndEnd' and then at the game object constructorClass GameObject'...'Method New()GameObjectPool.Add(Self)End'...'End' Later on...' You can use global search functions.Function FindGameObjectByTag:GameObject(tag:string)'...EndFunction FindGameObjectsByTag:List<GameObject>(tag:string)'...End' Another alternative is to put them in a registry class' no need for searching the most important objects constantly.Class GameObjectRegistry AbstractGlobal Player:GameObjectGlobal Enemies:List<GameObject>EndCode is from muscle memory, unfortunately is not a perfect design, as design flaws occur they will have to be solved on the spot.
For example, just now I added “Name” + “Owner” on the IComponent.
The reason is that for example acquiring a nitro boost component, will increase the speed of movement component. This means that the “nitro compo” will send message to the “movement compo”.Monkey123456789Class NitroComponent Implements IComponentField Move:MoveComponentMethod OnLoad()Self.Move = Owner.FindComponent("Move")' Perhaps reflection also might be a good idea.EndEndJanuary 28, 2017 at 6:19 am #6853@cocon the question was – how to dynamically create (instantiate) copy of any objects (that may contains any components). You just described the design part – how to make such component-based system.
January 29, 2017 at 7:20 pm #6856its a shame “class” doesn’t have a clone method, that will create a deep copy, that and final methods would make some powerful and interesting things possible…
January 29, 2017 at 10:25 pm #6858I’m temporarily going back to creating the gameplay elements through code, like in the “Class Enemy” example in the original post.
Pros: Doable right now, and super easy to create instances (“New Enemy(x,y)”, etc.).
If I want to share the same instance of a component on all entities, I can simply make it a global in that new class. I tried instancing many entities sharing the same SpriteRenderer component, and it worked very well. Each entity passes its own state to the sprite renderer, which will draw different animation frames according to the current entity’s state.
Cons: Not very flexible from a game design perspective, can’t create new gameplay elements through a visual editor if those elements’ classes don’t already exist in code. Requires re-compiling every time a new class is added.
This attempt made me really appreciate all the under-the-hood effort that goes towards making game design easier in large engines like Unity, but at the same time I feel like I’m learning a lot.
I’ll try again in the future, for now I just want to move forward with what I have. Deep copying and Serialization would be great items to be included in Monkey2’s road map, with practical game development of complex projects in mind.
Cheers!
-
AuthorPosts
You must be logged in to reply to this topic.