Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1364655 Posts in 63843 Topics- by 55727 Members - Latest Member: GuttyKreum

August 22, 2019, 12:04:55 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsJobsCollaborationsMonocle Engine: Pseudocode Sketches
Pages: [1] 2 3 4
Print
Author Topic: Monocle Engine: Pseudocode Sketches  (Read 26761 times)
Alec
Level 10
*****



View Profile WWW
« on: March 05, 2009, 10:47:17 PM »

The purpose of this thread is to share some of my ideas for organizing the Monocle engine with other programmers to get feedback. A lot of it will be quick pseudocode to test out how accessible/easy to use the structures are.

If you're not a C++/engine programmer, don't get too excited. This is early stuff that is liable to change a fair bit.

Here is some brainstorming for some basic GameObject/Component structure. (based on how Unity is set up)

The idea is that you have GameObjects which contain Components. A GameObject can have any number of Components and Components can access each other. GameObjects have certain Components by default. (for example a Transform component)

The developer can derive new Components from the Component class. In the example below is the beginnings of a platformerControls component that simply moves the player to the left when the left action is active.

Input and Time would be static classes used for accessing those systems easily.

Monocle would be a static class for handling creation of GameObjects and storing/loading resources.

Code:
class Component
{
public:
virtual void Update();
virtual void FixedUpdate();
};

class GameObject
{
public:
Component* AddComponent(Component &component);
Component* RemoveComponent(Component &component);
Component* GetComponentByName(String componentName);
private:
Components components;
};

//static
class Time
{
public:
float GetDeltaTime();
};

// static
class Monocle
{
public:
GameObject *CreateGameObject();
};

// PlatformerControls Component
//float moveSpeed = 100.0;

PlatformerControls::PlatformerControls() : Component()
{
}

void PlatformerControls::Update()
{
if (Input::GetAction(ACTION_MOVE_LEFT))
{
transform->position.x -= Time::GetDeltaTime() * moveSpeed;
}
}

// example of creating a player sprite
GameObject *player = Monocle::CreateGameObject();
player->AddComponent(PlatformerControls);
Sprite *sprite = player->AddComponent(Sprite);
sprite->SetMaterial(Monocle::GetMaterial("Sandwich"));

Yeah, I'm thinking of the higher level stuff first. Smiley I want to have an end-goal to work towards.
« Last Edit: March 06, 2009, 12:13:25 PM by Alec » Logged

nihilocrat
Level 10
*****


Full of stars.


View Profile WWW
« Reply #1 on: March 06, 2009, 12:02:50 PM »

That model looks pretty cool and extensible, reminds me of the Object/Behavior system used in Construct. It also looks like Monocle is a singleton or singleton-like, and behaves a lot like OGRE3D's singleton/factory pattern.

I have been indoctrinated into using the prototype pattern, after reading this: http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

So if you follow this pattern, it should be relatively easy to create a new GameObject by inheriting all of the Components of a particular GameObject instance.

Honestly the only thing I feel really strongly about is that everything should be really modular, and it looks like you are off to a great start in that area. I envision it as a superb system if people can easily plug in new Components and/or GameObjects and can easily share the code. Need a platformer character? Search the forums, bam, someone made one already and posted the code. Paste it into your project, bam, it Just Works.

VVV Message passing sounds great! Seperation of concerns is key. You might then need to put some thought into what sort of protocols people will create, and maybe make a thin layer which helps them make a non-convoluted protocol or re-use a "standard" one. This is what ZZT-OOP does... there are certain 'keyword' messages (like "touch", "shoot", etc.) which register to game events.
« Last Edit: March 06, 2009, 01:01:14 PM by nihilocrat » Logged

Alec
Level 10
*****



View Profile WWW
« Reply #2 on: March 06, 2009, 12:25:44 PM »

Yeah! I'm thinking of having an official Monocle Component database, where new components can be submitted and approved by a committee. (to verify how much they apply to our code standards, etc)

So you could either search the boards or hit up the database to find useful components.

I'm thinking there will be a couple of ways for a component to access another component. If you want direct access, you can include the header file for the component and cast a pointer. If not, you can use a message function that will send some kind of generic data. (possibly a string)

That way we can have it so that components don't necessarily have to know about each other to interact, but if you have to get direct access, you still can.
Logged

Snakey
Level 2
**


View Profile WWW
« Reply #3 on: March 06, 2009, 02:02:24 PM »

The last time I employed the component system, I did a little more segregation to help keep things fast. Instead of a one all and be all component system, I split up the base component classes into system specific components instead, such as a PhysicsComponent, RenderComponent, AudioComponent and so forth. However, I can see how this might be a problem if you need it to be ultra flexible, so much so, that you may encourage entire systems to be developed as well.

This was mainly for performance reasons more than anything else I found, since some of my game objects then were containing thirty to forty components in them and trying to iterate over the entire list of them started to get very slow particularly when I just needed to find components that I needed to render, play audio and so forth.

The engine design is looking good so far however.
« Last Edit: March 06, 2009, 02:06:07 PM by Snakey » Logged

I like turtles.
Alec
Level 10
*****



View Profile WWW
« Reply #4 on: March 07, 2009, 10:36:27 AM »

I think components will have to be designed to be functional enough that no more than 8 on an object will be needed.

e.g. Most game objects should only need one audio source component and one renderer component. More complicated game objects could be set up with child game objects.
Logged

Snakey
Level 2
**


View Profile WWW
« Reply #5 on: March 07, 2009, 01:19:44 PM »

Quote
Most game objects should only need one audio source component and one renderer component. More complicated game objects could be set up with child game objects.
Hmm, that could be quite limiting and frustrating.

For example, a rocket game object could require more than a single render component and audio component. You may need a sprite rendering component for the missle itself, another sprite rendering component for the flare behind the rocket (its seperated so that it can animate independently from the missle sprite), a particle rendering system for the smoke trails. An ambient audio component would be required that plays back a general thrust sound, an another audio component that plays occasional sounds (so that it doesn't sound too boring). That's already five components for a rocket. For complex things, this obviously can increase even more!

I suppose the ultimate trade off, for me anyways, was to not allow other people create external systems for my own engine. People couldn't create a new rendering system ... but they could create new render components. The majority of core systems I'd create, but if somebody needed a new system to handle .. let's say some weird network access storage system; they would have to modify my engine quite a bit to do that.
Logged

I like turtles.
Alec
Level 10
*****



View Profile WWW
« Reply #6 on: March 07, 2009, 02:21:42 PM »

I think you're reading in limitations that aren't there.

Folks could totally write their own rendering component if they wanted to. Or use as many as they wanted to. But they shouldn't have to.

If we have a good enough sprite (or mesh in 3D) component we can handle most of that stuff. Also, component game object groups will be batched so it won't be a major hassle to create new / more complicated hierarchies.

But that's generally how you want to do stuff. You have a rocket mesh, you can attach a particle emitter to it, etc. Its better to do it that way than writing a custom renderer. (even though its important to allow for custom renderers for special case things)

The other important consideration to note is that the component system wouldn't be forced on anyone. You could write a LogicRocket component that just loads sound handles direct from the Audio system and plays them that way. That might be how some people prefer to do it. (I know I would in some circumstances)

[Note: by custom renderer, i mean a component that could push whatever geometry / state-changes it wanted to the video card through monocle's cross-platform rendering pipeline]

The components are more there for pieces of code that are highly reusable.
« Last Edit: March 07, 2009, 02:30:00 PM by Alec » Logged

muku
Level 10
*****



View Profile WWW
« Reply #7 on: March 07, 2009, 02:31:03 PM »

This was mainly for performance reasons more than anything else I found, since some of my game objects then were containing thirty to forty components in them and trying to iterate over the entire list of them started to get very slow particularly when I just needed to find components that I needed to render, play audio and so forth.

How about having, say, a separate RenderSubsystem or RenderManager or whatever you want to call it, which every component that needs rendering registers itself with when it's created? The RenderManager would then have a list of components which need rendering which you could iterate over simply and efficiently. The same for other subsystems like audio, physics etc.

One could even abstract this a bit by having something like component "families" which individual components could declare themselves to be members of. This would provide a second grouping mechanism which is orthogonal to the GameObject->Component relation, if that makes sense. But maybe that addresses a nonexistant problem.
Logged

The Cosyne Synthesis Engine - realtime music synthesis for games
Eclipse
Level 10
*****


0xDEADC0DE


View Profile WWW
« Reply #8 on: March 07, 2009, 02:31:25 PM »

You wrote about a "Transform component" (that could simply be a Matrix) and about 3d meshes and sprites like components too..
So like that almost everything will derivate from this "Component" class, right?

I'm currently using a more traditional structure on my engine (read, it's written as almost every 3d engine is, with a scene graph, a vertex class, a vertex declaration object, vertex and index buffer classes, a mesh class, a sprite class, and so on) but i'm interested to undestand how you think about it because working for years on 3d engines got me quite... "fixed" to that style of doing


edit: oh, can i post a sort of uml of what i'd like for the monocle? Tongue
Logged

<Powergloved_Andy> I once fapped to Dora the Explorer
Problem Machine
Level 8
***

It's Not a Disaster


View Profile WWW
« Reply #9 on: March 07, 2009, 02:35:00 PM »

Can components have components?

One thing that I really like and I think might be worthy of consideration is Flash's display list model. This creates a very intuitive way to understand what will be rendered and what won't (if it's not a subnode of the stage it doesn't get rendered). It allows for grouped operations on logically contained subimages (so it would be easy to quickly access all particles in a particular system), and everything is implicitly forward kinematic. Combined with a tween class, it's an extremely powerful tool for programmatic animation. Just something to think about.
Also, a couple of the indielib features are non-obvious but worth considering. These two in particular jumped out at me:
Quote
Advanced collision system. Create an xml file defining your collision areas per sprite using circles, rectangles or triangles. You can define as many collision groups as you want, each one with a different name. Join this file to an entity object and check collision between the collision groups of other entities. Don't worry about translating, rotating or scaling your entities, the collision groups will always fit perfectly your entity. You can also have sprite animations in which each frame has different collision groups
Quote
Render images of any size (not only power of two). You can load images as big as 10000x10000 pixels or more, and IndieLib will cut them internally in smaller blocks. Furthermore, IndieLib will automatically discard all the blocks out of the screen, also if your sprites are transformed and the camera making a zoom or rotated, only what you see is what is going to be rendered.
I would also like to see some kind of sprite-masking system, where one sprite can be used as the alpha mask of another. It may or may not be an obvious feature, I just wanted it to be one we were thinking about from the beginning.

This is so exciting   Hand Shake LeftGrin:handshakeR:
Logged

Alec
Level 10
*****



View Profile WWW
« Reply #10 on: March 07, 2009, 02:43:59 PM »

Naw, no registering. Actually, that might be cool but I need to think more about it.

You can write a render method in a component, i.e.

very simple example (start of a quad renderer)

Code:
void SpriteComponent::Render ()
{
   GL::Begin(GL::Vertex);
   GL::Vertex3(0, 0, 1);
// etc...
   GL::End();
}

Most likely we would be doing vertex buffers instead.

(GL would be Monocle's Graphics Library)

A transform "component" would contain rotation, position, etc. It might not inherit from Component. (depends if there's some benefit to that)

Components would not have components. But GameObjects could have child GameObjects with other components attached.
« Last Edit: March 07, 2009, 02:48:14 PM by Alec » Logged

cpets
Level 0
**


View Profile WWW
« Reply #11 on: March 07, 2009, 02:54:00 PM »

I recently spent some time reading through Intel's Smoke game engine demo, and I think there are a lot of great ideas there.

Smoke is a tech demo for writing games to take advantage of multiple cores, but even if you ignore this they do a lot of things right. Specifically, components from different systems have very limited means of communicating with one another. This enforced encapsulation keeps game code from becoming the tangled mess of mixed concerns that seems to typical of game code, especially game code written for big game frameworks.

In messing around lately, I've gone a step farther in implementing their design with no shared data between systems, and I've been really happy with how it's shaped up. I know that architectural perfection is antithetical to finishing a working project, but I'd love to see these ideas have some impact in your effort, even if not absolutely. I'd be happy to contribute to the code or discussion.
Logged
Alec
Level 10
*****



View Profile WWW
« Reply #12 on: March 07, 2009, 04:02:38 PM »

Reasonable encapsulation is important, but I've worked on a couple projects where the lead programmer wanted TOTAL encapsulation and it made it impossible to make a good game. (if the game design changed at all, whole structures would have to be rewritten)

I want to keep it simple and straightforward to access other parts of the engine easily. That means that yes, people can totally write messy code. But its not up this engine to force people to write good code, its purpose is to be used however the developer wants to. (within reason and with some suggested guidelines)

That means allowing for messy hacks if it helps make a prototype fun. That said, the way I'm picturing things working it should be easy to access the areas of the engine that you need to without writing messy code or creating damaging dependencies.
Logged

Snakey
Level 2
**


View Profile WWW
« Reply #13 on: March 07, 2009, 05:31:15 PM »

Quote
How about having, say, a separate RenderSubsystem or RenderManager or whatever you want to call it, which every component that needs rendering registers itself with when it's created? The RenderManager would then have a list of components which need rendering which you could iterate over simply and efficiently. The same for other subsystems like audio, physics etc.
Hmm, I did a registering thing, although I had not considered this design pattern yet. Although, I did it the way I did because I often did occlusion first of game objects, and then did occlusion of individual components second. But that could be a good method to perhaps try!

Quote
One could even abstract this a bit by having something like component "families" which individual components could declare themselves to be members of. This would provide a second grouping mechanism which is orthogonal to the GameObject->Component relation, if that makes sense. But maybe that addresses a nonexistant problem.
Hmm, that is interesting. Perhaps I need to do some more research into it. Thanks.
Logged

I like turtles.
Saint
Level 3
***



View Profile WWW
« Reply #14 on: March 09, 2009, 05:54:18 AM »

Did anyone else read Bjarne Rene's piece on Component-based object management? It was  printed in Game Programming Gems 5, not sure if there was ever a follow-up article but it is a great piece.

Anyway, he uses interfaces to work around some of the performance problems of just stacking general "components" in objects in a real-time application, essentially saying that while each object can have any number of components they can only have one component take care of certain tasks  such as rendering or physics (though they can also neglect the task - background objects might not need script control, for example).
Logged
Will Vale
Level 4
****



View Profile WWW
« Reply #15 on: March 09, 2009, 02:20:43 PM »

Hi Alec,

Monocle is definitely an idea waiting to happen - I'm looking forward to see how it comes out, and would be happy to offer assistance where possible (my contracts are keeping me pretty busy at the moment). I have a fair bit of PC and console development experience which might be helpful. To wit:

Given you're intending (in the long term) to support platforms other than Mac + PC, you'll have to deal with some weaknesses in CPU and cache. I'm a bit wary of the virtual-heavy approach to components you're outlining - it'll work, but it might not scale too well.

Something I'm trying at home, which isn't commercially proven but looks promising, is to group components together (as per Muku's suggestion) and hoist the virtual functions out to the group level. This allows you to remain flexible but avoids the performance penalty of the virtual calls. It also makes it very easy to iterate components in an icache and dcache friendly manner.

So in my stuff (where a component is called a facet, and a system owns all facets of one type):

Code:

/**
 * A system manages all the facets of a particular type in a world.
 */
struct ISystem : public Noncopyable
{
public:

/**
* \name Creation, destruction.
* \{
*/

/// Default constructor.
inline ISystem( const rtti::IType& type ) : m_type(type), m_world(NULL) {}

/// Empty virtual destructor.
virtual ~ISystem() {}

//}

/**
* \name System API for concrete implementation.
* \{
*/

/// Facet factory. Create a new facet of this system's type.
virtual IFacet *CreateFacet( const data::PropertyTree::Group& group ) = 0;

/// Facet factory. Destroy the given facet.
virtual void DestroyFacet( const IFacet *facet ) = 0;

/// Update the system, called once per frame.
virtual void Tick( const Frame& frame ) = 0;

//}

/// Access the facet type
inline const rtti::IType& FacetType() const
{
return m_type;
}

/// Access the containing world
inline World& GetWorld()
{
SIL_ASSERT(m_world); return *m_world;
}

/// Access the containing world (const version)
inline const World& GetWorld() const
{
SIL_ASSERT(m_world); return *m_world;
}

private:

/// World needs to set backpointer
friend class World;

/// Facet type for this system
const rtti::IType& m_type;

/// World backpointer
World *m_world;

}; // ISystem

/**
 * Basic concrete system, use this if your facet doesn't need to do anything special.
 */
template <typename F>
class System : public ISystem
{
public:

/**
* \name Creation, destruction.
* \{
*/

System( Nat count ) :
ISystem(rtti::Type<F>())
{
SIL_ASSERT( rtti::Type<F>().IsA( rtti::Type<IFacet>() ) );
SIL_VERIFY( m_pool.Create( count ) );
}

//}

/// overrides
IFacet *CreateFacet( /* creation parms */ )
{
return m_pool.Allocate();
}

void DestroyFacet( const IFacet *facet )
{
m_pool.Deallocate( rtti::StaticCast<F>(facet) );
}

void Tick( const Frame& frame )
{
SIL_FOR_EACH( Pool<F>, m_pool, f )
{
// Tick facets, skipping any from entities which were born this frame, or are frozen.
const Entity &e = f->GetEntity();
if ( !e.IsFrozen() && !e.IsNewborn() )
{
f->Tick( frame );
}
}
}

private:

/// Facet pool
Pool<F> m_pool;

}; // System

This is just an example 'default' system for uninteresting components. The one that does my box2d integration is a bit more complicated but supports the same interface. When you create the engine (or a world) you give it a list of systems with their maximum sizes.

The other thing you might want to do is template your GameObject's component lookup - this makes for a nice interface (no casting needed) and unlocks a lot of behind-the-scenes optimisation opportunities if you need them later:

Code:
/// Access an existing facet of this entity
template <typename F>
inline F* Facet() { return rtti::StaticCast<F>( Facet( rtti::Type<F>() ) ); }

/// Access an existing facet of this entity (const version)
template <typename F>
inline const F* Facet() const { return const_cast<Entity*>(this)->Facet<F>(); }

This one calls a generic lookup function with an RTTI object, you could call your string lookup at that point. The nice thing is you can write a specialisation for components you need all the time and store them in members directly.

Best of luck with the project,

Cheers,

Will

Logged
Snakey
Level 2
**


View Profile WWW
« Reply #16 on: March 09, 2009, 08:26:25 PM »

I would personally avoid RTTI like the plague.
Logged

I like turtles.
Alec
Level 10
*****



View Profile WWW
« Reply #17 on: March 09, 2009, 10:00:01 PM »

I would personally avoid RTTI like the plague.

I haven't had any problems.
Logged

Will Vale
Level 4
****



View Profile WWW
« Reply #18 on: March 09, 2009, 10:47:54 PM »

I would personally avoid RTTI like the plague.

The use of RTTI isn't critical to my example. You could just as easily declare an enumerated component type or have a static string name in component classes:

Code:
/// Access an existing facet of this entity
template <typename F>
inline F* Facet()
{
    return static_cast<F>( Facet( F::StaticClassName() ) );
}

Note that both this and the original version go on top of a method which finds a requested facet by class name/type ID/whatever - the equivalent to Alec's GetComponentByName.

That out of the way, can you expand on what don't you like about C++-with-RTTI? I'm such a happy RTTI camper (based on good experiences over several projects) I don't see much of a downside. My only real caveat is that once you have a reflection system it's hard to do without; you need to be quite disciplined about which problems you apply it to, and which you don't really need to.

Cheers,

Will
Logged
Snakey
Level 2
**


View Profile WWW
« Reply #19 on: March 10, 2009, 11:06:24 AM »

Lately I've been experimenting with base interface designs with generalized messages inbetween. The main benefit with this is, is that I can add as many components as I like without ever having to update any RTTI like functions or data subsets, nor do I have to maintain any existing code which uses those components.
« Last Edit: March 10, 2009, 11:14:12 AM by Snakey » Logged

I like turtles.
Pages: [1] 2 3 4
Print
Jump to:  

Theme orange-lt created by panic