About Monkey 2 › Forums › Monkey 2 Programming Help › Extending Mojo3D
Tagged: components, mojo3d
This topic contains 26 replies, has 3 voices, and was last updated by
nerobot 1 year, 7 months ago.
-
AuthorPosts
-
September 8, 2017 at 6:15 am #10338
Challenge accepted!
(will take a while, though, I’m really slow)
I don’t think GetComponentBox will be that useful, I put it there just in case I need it for now. Also need to rename it to GetEntityBox…>>I’m interested in how you will override them?
Mostly I won’t! The plan is to leave the Mojo3D Entity subclasses alone, and add as much new functionality as possible through components only. I don’t think I’ll even add virtual classes on EntityBox, all the events will be called on the components. But you’re right, we’ll see, I may have to change a lot as I port my old projects.
September 9, 2017 at 3:56 am #10358Actually, I quite like the ComponentBox idea – it does provide a way to add fields to a class via class extension anyway.
One think I have added to Entity that may be of use here is:
Monkey1234SetDynamicProperty<T>( name:String,value:T )GetDynamicProperty<T>:T( name:String )Very simple, but I think it should be very useful.
I am using this to store the RigidBody for an entity in the entity itself (similar to a component I guess). To date, while you have been able to do ‘rigidBody.Entity’, you haven’t been able to do the opposite, ie: ‘entity.RigidBody’. Since I want to keep physics in an optional module, Entity may not necesarilly know about RigidBody. I didn’t want to go for a full blown Component system here, because I haven’t need it – everything works except for entity.RigidBody and this fixes that.
But the dynamic property stuff is also there for general use and it would be entirely possible to use it for a ComponentBox, eg:
Monkey1234567891011Function GetComponentBox:ComponentBox( entity:Entity )Local cbox:=entity.GetDynamicProperty<ComponentBox>( "cbox" )If Not cboxcbox=New ComponentBoxentity.SetDynamicProperty( "cbox",cbox )EndifReturn cboxEndThis is, of course if you *really* want to use Entity as *the* GameObject type. I’m not sure this is a such good idea, but I think I’m basically just gonna sit this one out and watch what you come up with as I don’t have a lot of experience here.
Another thing with the component virtual methods, with the ComponentBox approach it ‘s kind of a non-issue in a way. For example:
Monkey123456789101112131415161718192021222324252627282930Class ComponentBoxField _components:Stack<Component>EndClass ComponentVirtual OnUpdate() VirtualEndEndClass Entity ExtensionMethod GetComponentBox:ComponentBox()'..uses GetDynamicProperty here...maybe protected/private too?EndMethod GetComponents:Stack<Component>()'...maybe protected/private?EndMethod Update()For local c:=Eachin GetComponents()c.OnUpdate()NextEndEndTherefore, calling entity.Update() will call all virtual Component.OnUpdate methods. You can’t override Entity.Update, but the point here is to not have to extend Entity anyway, but use Component instead, right?
Another thing that may help is the ‘signals’ in Entity, ie:
Monkey12345Field Copied:Void( copy:Entity )Field Destroyed:Void()Field Hidden:Void()Field Shown:Void()These are ‘hooked’ by RigidBody and could be by ComponentBox too, eg:
Monkey12345678910111213141516171819202122Class ComponentBoxField _entity:EntityField _components:=New Stack<Component>Method New( entity:Entity )_entity=entity_entity.Destroyed+=Lambda()For Local c:=Eachin _componentsc.OnDestroyed() 'call to component virtual methodNextEndEndEndAgain though, this assume Entity *is* your GameObject which for some reason makes me hella nervous…but please ignore me as this is well outside my comfort zone.
Very keen to see what you come up with!
September 9, 2017 at 4:17 am #10360DynamicProperty looks good. And I like entity’s signals, it may helps to wrap entity into gameobject if we want to provide public gameObject.Entity property to work directly with entity. But need to add many signals in this case, like Moved, Rotated, Scaled.
September 9, 2017 at 5:24 am #10361I don’t know if I’lll be adding Moved/Rotated/Scaled signals. I think (a bit like Layout) changes to entity matrices are much better dealt with in one ‘hit’ in one phase. It can take game logic several moves/rotates etc to update an entity, and if each step triggers an OnMove (and possibly child OnMove recursively etc) it can add up.
Entity does in fact have a (currently hidden) Seq:Int property that is incremented when entity matrix is modified. This is actually used by physics to update bullet data if necessary just before World.Update(). A game engine could do the same.
I think this is partly why I’m nervous about using Entity as GameObject. There is not real control over what can be done to the entity – it’s kind of like having a public field. Unless I add a signal to *every* setter and potentially modifying method, there is just no sane way to tell what has been done to the entity.
On the other hand, if all operations go through a GameObject or Component wrapper and the underling enties are private fields, the wrapper knows exactly what’s going on, fire off signals, call virtual methods and generally do whatever it needs to do. It can ‘redesign’ the entity API, add new methods, hide unwanted ones etc. Yes, there’s a fair bit of crappy work involved, but the win is 100% control over the underlying entity.
September 9, 2017 at 6:53 am #10363Ok, I’ve hacked up yet another approach to all this. This attempts to duplicate unity’s system in that matrix/hierarchy are stored in a Transform component that is present in all game objects. It implements the transform component using a dummy entity. Any camera, light and model etc components use this dummy entity as a parent for their internal mojo3d entties.
The actualy entities involved are *never* publicly available so there’s no chance of the hierarchy being broken/rearranged behind your back or any other other weirdness happending. Also, I’ve added signals for ALL property setters just for the hell of it.
It doesn’t look too bad to me. There’s a certain amount of work involved adding wrappers for everything, but there aren’t that many entities so IMO it’s doable. Transform will probbly be the trickiest as that’s how you move things around etc. But lots of that stuff is in an extension class that can be copied/pasted too.
The coolest thing about this IMO is that GameObject/Component are 100% ‘clean code’. There are no dependancies on how mojo3d or Entity works (or doesn’t!).
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225Alias Mojo3dCamera:mojo3d.graphics.CameraAlias Mojo3dLight:mojo3d.graphics.LightAlias Mojo3dModel:mojo3d.graphics.ModelClass GameObjectMethod New()New Transform( this )EndMethod GetComponent<T>:T( name:String )Return _components[name]EndInternalProperty Components:StringMap<Component>()Return _componentsEndPrivateField _components:=New StringMap<Component>EndClass ComponentMethod New( gameObject:GameObject,type:String )_gameObject=gameObject_type=type_gameObject.Components[_componentType]=SelfEndProperty GameObject:GameObject()Return _gameObjectEndProperty Type:String()Return _typeEndPrivateField _gameObject:GameObjectField _type:StringEndClass Transform Extends ComponentField ParentChanged:Void()Field MatrixChanged:Void()Field Moved:Void()Method New( gameObject:GameObject )Super.New( gameObject,"transform" )_entity=New Entity_entity.SetDynamicProperty( "component",Self )EndProperty Parent:Transform()Return _parentSetter( parent:Transform )If parent=_parent Return_entity.Parent=_parent ? _parent._entity Else Null_parent=parentParentChanged()EndProperty Matrix:Mat4f()Return _entity.MatrixSetter( matrix:Mat4f )If matrix=_entity.Matrix Return_entity.Matrix=matrixMatrixChanged()EndMethod Move( v:Vec3f )_entity.Move( v )Moved()EndInternalProperty Entity:Entity()Return _entityEndPrivateField _parent:TransformField _entity:EntityEndClass Camera Extends ComponentField FOVChanged:Void()Method New( gameObject:GameObject )Super.New( gameObject,"camera" )_camera=New Mojo3dCamera( gameObject.GetComponent<Transform>().Entity )EndProperty FOV:Float()Return _camera.FOVSetter( fov:Float )If fov=_camera.FOV Return_camera.FOV=fovFOVChanged()EndPrivateField _camera:Mojo3dCameraEndClass Light Extends ComponentField TypeChanged:Void()Method New( gameObject:GameObject )Super.New( gameObject,"light" )_light=New Mojo3dLight( gameObject.GetComponent<Transform>().Entity )EndProperty Type:LightType()Return _light.TypeSetter( type:LightType )If type=_light.Type Return_light.Type=typeTypeChanged()EndPrivateField _light:Mojo3dLightEndClass MeshRenderer Extends ComponentField MeshChanged:Void()Field MaterialChanged:Void()Method New( gameObject:Object )Super.New( gameObject,"meshRenderer" )_model=New Mojo3dLight( gameObject.GetComponent<Transform>().Entity )EndProperty Mesh:Mesh()Return _model.MeshSetter( mesh:Mesh )if mesh=_model.Mesh Return_model.Mesh=meshMeshChanged()EndProperty Material:Material()Return _model.MatrialSetter( material:Material )If material=_model.Material Return_model.Material=materialMaterialChanged()EndPrivateField _model:Mojo3dModelEndSeptember 9, 2017 at 9:46 am #10366Wow, cool!
One thing I dislike here – using component string name. In my code above I achived this by Typeof().Name, because you can take a mistake in string but not in type name.
cam=GetComponent<Camera>()
Afaik, in unity you can’t directly create components, them exists attached to gameobject only. Private / internal constructor is here.
When we call gameObject.AddComponent () we get new instance.
In monkey we can also declare private constructor and call virtual protected OnCreate method.
My undone approach is here: https://bitbucket.org/nerobot/cluequa2d
I think my core code (GameObject, Component, Behaviour) is good enough.
September 9, 2017 at 7:34 pm #10371I like this a lot!
But doesn’t the GameObject approach mean you have to wrap all the functionality in the Entity subclasses? What I like about using Entity “directly” is that the box class makes very little assumptions about the Entity class, and simply provides access to the functionality not present in Mojo3D.
In other words, even if Mojo3D changes a lot, it would still be easy to adapt the component system to work, while eliminating the “why did you change it, Mark?” type of whining!
One thing I dislike here – using component string name
I definitely want to implement your reflection method when my brain recovers from being fried after taking care of a toddler during the day and learning to use Houdini at night…
One question though… doesn’t that mean using reflection allows you only one type of component per Entity? Using string means you can possibly name them differently.
One of the components types I like using a lot is a “Modifier” component. It simply changes another entity’s “Stats” component on collision. For instance, a bullet can have two Modifier components, one that subtracts -10 from a “shield” stat, and one that checks for the shield, and and subtracts X from health depending on how much shield you have.
While you can have a single Modifier component that does more than one thing, it was really fun to keep those components as simple and small as possible, and control game logic by adding more than one when necessary. I don’t know, I’ll think about it.
(if you’re concerned about performance when creating that many components, I made a while ago a single stage 2D side scrolling shooter test using this philosophy, and it had hundreds of entities. The update loop, not including rendering, was still a fraction of a millisecond!)
Cheers!
September 9, 2017 at 8:34 pm #10372One thing I dislike here – using component string name. In my code above I achived this by Typeof().Name, because you can take a mistake in string but not in type name.
I didn’t put much thought into how the actual component system should work, and that ‘s sort of the point – having ‘clean’ GameObject and Component classes means you can do whatever you want, however you want.
But doesn’t the GameObject approach mean you have to wrap all the functionality in the Entity subclasses?
Yes it does, and it’s up to you to decide if that’s an acceptable amount of work. But it *does* give you 100% control over access to entities, so you can fire off callbacks and invoke signals etc whenever you want etc. You can also mess with parameters, or even prevent properties being modified or methods being called at all. It’s a form of Proxy object I guess, and is really the only way I can think of to gain 100% control over an object.
A ‘halfway’ solution to this would be to add Entity:Camera, Entity:Light, Entity:Model properties to components to return underlying entities, so people could access component entities directly. This would save you writing all the ‘wrapper’ properties/methods, but again, you lose a degree of control over things this way, as you have NO IDEA how the entity could potential be modified. But its really up to you how big a deal that is. The above is just an example of IMO a 100% safe/clean approach.
September 9, 2017 at 9:03 pm #10374Thanks, I think I have plenty to digest now. Will keep my progress posted!
September 9, 2017 at 9:35 pm #10376And just to confuse things a bit more, here’s another approach!
This is a bit of a compromise in that it doesn’t store entities in components, but stores a single entity per gameobject (which is effectively its ‘transform’ too) that you can access directly. This means a gameobject can’t have both a camera and a light component – it must have one or the other. You can think of a gameobject’s entity as a sort of special case component.
It doesn’t bother managing a hierarchy – gameobjects are just stored in a linear stack. You can of course parent gameobject entities to each other though, much like Transform above.
Again, the implementation of components here isn’t really important, I’m more trying to think out how mojo3d can be interfaced with.
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141Class GameObjectMethod New( entity:Entity )_entity=entity_entity.SetDynamicProperty( "gameObject",Self )_gameObjects.Add( Self )EndProperty Entity:Entity()Return _entityEnd'for lazy people like me....Property Camera:Camera()Return Cast<Camera>( _entity )EndProperty Light:Light()Return Cast<Light>( _entity )EndProperty Model:Model()Return Cast<Model>( _entity )EndMethod Destroy()For Local component:=Eachin Componentscomponent.OnDestroy()Next_gameObjects.Remove( Self )_entity.Destroy()_components=Null_entity=NullEnd'helpers...Function CreatePivot:GameObject()Return New GameObject( New Entity )EndFunction CreateLight:GameObject()Return New GameObject( New Light )EndFunction CreateCamera:GameObject()Return New GameObject( New Camera )EndFunction CreateModel:GameObject()Return New GameObject( New Model )EndFunction UpdateAll()For Local gameObject:=Eachin _gameObjectsLocal modified:=_seq<>_entity.Seq_seq=_entity.SeqFor Local component:=Eachin gameObject.Componentscomponent.OnUpdate( modified )NextNextEndInternalProperty Components:StringMap<Object>()Return _componentsEndPrivateGlobal _gameObjects:=New Stack<GameObject>Field _components:=New StringMap<Object>Field _entity:EntityField _seq:IntEndClass ComponentMethod New( gameObject:GameObject,type:String )_gameObject=gameObject_type=type_gameObject.Components[_type]=SelfEndProperty GameObject:GameObject()Return _gameObjectEndProperty Type:String()Return _typeEndProtectedMethod OnUpdate( modified:Bool ) VirtualEndMethod OnDestroy() VirtualEndPrivateField _gameObject:GameObjectField _type:StringEndSeptember 10, 2017 at 5:58 am #10381This is a bit of a compromise in that it doesn’t store entities in components, but stores a single entity per gameobject (which is effectively its ‘transform’ too) that you can access directly.
As a variant. But storing (wrapping) entities in components allow us easilly replace mojo3d.entity with any other and our system will still work because we work with components not with raw entities.
This means a gameobject can’t have both a camera and a light component – it must have one or the other.
I checked it – unity3d allow us to place light and camera into single gameObject. So you can do it if you wish (although it’s looks abnormal way).
September 10, 2017 at 6:26 am #10384As for me, component-based approach can be presented as a custom module – internal or 3d party.
-
AuthorPosts
You must be logged in to reply to this topic.