Component question

Get technical support about the C++ source code and about Lua scripts for maps, entities, GUIs, the console, materials, etc. Also covered are the Cafu libraries and APIs, as well as compiling, linking, and the build system.
Post Reply
MatejZajacik
Posts: 10
Joined: 2013-04-11, 17:12

Component question

Post by MatejZajacik » 2013-04-18, 16:46

Hi, Carsten.

I'm curious about the way you plan to implement certain game mechanics via components. For instance, a pickable item, let's say a key. I imagine the game object (GO) will have components:
  • Model - 3D model representing the key.
  • Collision - component defining the collision for the GO. Perhaps this will broadcast "onTouch" event?
  • Pickable - basic component implementing functions regarding what happens when the GO is touched. Or a component derived from this basic component that defines what exactly happens - in our case, just add a key to player's inventory.
  • Light - casting light to make the item more striking.
  • Wobble - component making the GO wobble in a sine manner (or whatever). Heretic and Hexen have items like this - looks nice. :-)
So this would be the skeleton for all pickable items, and every item would only have a different pickable-derived component? Is this anything close to what you have in mind?

I'm also thinking about various ways you can do broadcasting events. One that comes first is that when a component wants to broadcast a message, it tells the game object (GO) and then GO tells other components that listen for the particular event. So on init, components that need to listen for certain events do register themselves as listeners in GO. Say, a collision component calls an "onTouch" event, coupled with a (Lua) table filled with data such as who the toucher is (most likely the player), point of touch, etc., and then GO looks through its list of listeners and send the data table to all who listen for "onTouch". The listener simply checks the table and behaves based on it.

Another topic is the Transform component. In Unity, EVERY GO has the Transform comp by default. It implements position, rotation, scale and all the necessary functions to modify these values. To me, it seems like this could be directly implemented in GO class itself, since every single GO instance has the Transform comp anyway.

What's your take on all these points?
User avatar
Carsten
Site Admin
Posts: 2145
Joined: 2004-08-19, 13:46
Location: Germany
Contact:

Re: Component question

Post by Carsten » 2013-04-19, 00:49

Hi Matej,
MatejZajacik wrote:For instance, a pickable item, let's say a key. I imagine the game object (GO) will have components:
[...]
So this would be the skeleton for all pickable items, and every item would only have a different pickable-derived component? Is this anything close to what you have in mind?
Yes, absolutely!

Considering that we have a working component system for GUI windows already, and that many of the core features will be re-used in the entity component system, I think that it is safe to say that your example very well outlines how the entity component system will work.
I'm also thinking about various ways you can do broadcasting events. One that comes first is that when a component wants to broadcast a message, it tells the game object (GO) and then GO tells other components that listen for the particular event. So on init, components that need to listen for certain events do register themselves as listeners in GO. Say, a collision component calls an "onTouch" event, coupled with a (Lua) table filled with data such as who the toucher is (most likely the player), point of touch, etc., and then GO looks through its list of listeners and send the data table to all who listen for "onTouch". The listener simply checks the table and behaves based on it.
Your approach sounds sound, but I'm not sure yet if we'll need such a (moderately complex) event handling system after all.

Currently, in the (admittedly quite simple) GUI system, if an event occurs, the code where the event originated simply calls a scripting callback function. It is then up to the scripting code to have supplied an OnXyzEvent() function beforehand, that at runtime acts as the event handler and is appropriately implemented to handle the event.

In your example with the pickable item, the Collision component might detect that it has been touched by the player entity, and call its OnTouch(entity, direction) script callback with the related data (touching entity, direction, etc.)

Of course, this leaves open the question how we can teach the "pickable item" prefab a default implementation for OnTouch(entity, direction), e.g. one that runs "if entity is a human player, then add item ... to his inventory".

This is also in the GUI system one of the few not-yet-complete features (this, and prefabs; I can't think of anything else). In essence, we'd need a "prefab-local" or "GO-local" script. This in turn could possibly be achieved by a component of type "Script" or "Behavior". It would be up to this script to provide all the callback implementations of all its components that its GO might need / be interested in.

I have already started to look into these issues (the Lua modules system might come in very handy here), but honestly have not yet thought through all the details. But this is certainly among the next things to tackle. :up:
Another topic is the Transform component. In Unity, EVERY GO has the Transform comp by default. It implements position, rotation, scale and all the necessary functions to modify these values. To me, it seems like this could be directly implemented in GO class itself, since every single GO instance has the Transform comp anyway.
That was exactly my thinking too. I even had started to implement it like this, but then I realized that components are very well prepared for binding their variables and methods to scripts, to show them in the graphical editor and handle changes to their values, to generate reference docs from them, etc. -- and all of this (semi-)automatically!
If name, position, rotation, scale etc. where directly implemented in the GO, each place that naturally handles abstract components already would have to be special-cased for the variables in the GO.
This would have turned code that is short, concise and beautiful into a much less attractive show, as well as making extensibility and maintainability a lot more difficult. And so I changed my mind and, for GUI Windows, added a component "Basics" (which holds the name of the window, and the "is shown?" flag) and another component "Transform" (Position, Size, etc.).
Doing it like this went exactly in the spirit of components, and thus I'm now thinking that having an explicit Transform component also for GO's is the Right Thing to do. ;-)

Btw., I'll soon be ready to upload the new reference documentation for the GUI system. In it's components you'll be able to see much of what we've discussed so far. :up:
Best regards,
Carsten
MatejZajacik
Posts: 10
Joined: 2013-04-11, 17:12

Re: Component question

Post by MatejZajacik » 2013-04-19, 10:27

Your approach sounds sound, but I'm not sure yet if we'll need such a (moderately complex) event handling system after all.

Currently, in the (admittedly quite simple) GUI system, if an event occurs, the code where the event originated simply calls a scripting callback function. It is then up to the scripting code to have supplied an OnXyzEvent() function beforehand, that at runtime acts as the event handler and is appropriately implemented to handle the event.

In your example with the pickable item, the Collision component might detect that it has been touched by the player entity, and call its OnTouch(entity, direction) script callback with the related data (touching entity, direction, etc.)

Of course, this leaves open the question how we can teach the "pickable item" prefab a default implementation for OnTouch(entity, direction), e.g. one that runs "if entity is a human player, then add item ... to his inventory". ... In essence, we'd need a "prefab-local" or "GO-local" script. This in turn could possibly be achieved by a component of type "Script" or "Behavior". It would be up to this script to provide all the callback implementations of all its components that its GO might need / be interested in.
This might work well if we end up with a small number of callbacks. So when a component wants to broadcast a message, it will call the function in the behaviour component (if found), and that script will handle all the necessary stuff, which... hmm... in essence, will be simply relaying the message to whatever other components that need to listen? What I like about the system I spoke about is that it's easily pluggable in that you just add a component to a GO and that component can have any number of whatever callbacks and will behave accordingly without the need to fiddle some intermediate script.
That was exactly my thinking too. I even had started to implement it like this, but then I realized that components are very well prepared for binding their variables and methods to scripts, to show them in the graphical editor and handle changes to their values, to generate reference docs from them, etc. -- and all of this (semi-)automatically!
If name, position, rotation, scale etc. where directly implemented in the GO, each place that naturally handles abstract components already would have to be special-cased for the variables in the GO.
This would have turned code that is short, concise and beautiful into a much less attractive show, as well as making extensibility and maintainability a lot more difficult. And so I changed my mind and, for GUI Windows, added a component "Basics" (which holds the name of the window, and the "is shown?" flag) and another component "Transform" (Position, Size, etc.).
Doing it like this went exactly in the spirit of components, and thus I'm now thinking that having an explicit Transform component also for GO's is the Right Thing to do.
Good reasoning there. I do agree.

All in all, I'm thrilled to hear and see more. :) Best thing to do is most likely to wait for the documentation and see what's up. :)
MatejZajacik
Posts: 10
Joined: 2013-04-11, 17:12

Re: Component question

Post by MatejZajacik » 2013-04-19, 10:37

BTW, you don't even need to do all the registering listeners stuff. It may work like: component wants to broadcast, so it tells GO and GO tells all of its components. If a component has that function implemented, it will be called, if not, nothing happens. My reasoning is that I believe events happen very rarely (consider the case of pickable items), so it very well may be as simple as that. In cases of events that happen very regularly (like every game frame), special handling is needed. My idea was something like this:

Comp1

Code: Select all

-- Perhaps called from C++ collision code.
function onCollision(data)
  self.go:broadcast("onTouch", data)
end
GO

Code: Select all

function broadcast(name, data)
  for k, v in pairs (components)
    if v.onEvent and type(v.onEvent) == "function" then
      v:onEvent(name, data)
    end
  end
end
Comp2

Code: Select all

function onEvent(name, data)
  if name == "onTouch" then
    -- Handle whatever's needed...
  end
end
User avatar
Carsten
Site Admin
Posts: 2145
Joined: 2004-08-19, 13:46
Location: Germany
Contact:

Re: Component question

Post by Carsten » 2013-04-19, 21:58

Ok, I see that you mean. And of course, if the current approach that is implemented in the GUI system turns out as too simplistic or limited, I'm more than happy to drop it in favor of a more universal and flexible approach. In fact, I very much like your proposal, and am very open to it!

But still, let me try to summarize and explore a bit more:
In your example with the pickable item, the suggested flow is:
  • The Collision component detected a collision.
  • Now have the Collision component (maybe with the help of the GO) inform every other component about this collision event (either really every component in the GO, so that components that aren't interested in this particular event can ignore it; or only those components that beforehand have expressed a special interest in this event type, e.g. by having registered themselves at the GO appropriately).
  • Consequently, the "Pickable" component receives the event (its event handler function is called). As the main job of the "Pickable" component is to add some item to the GO's inventory (possibly another component), its event handler implementation can be dedicated to this task.
(Please step in if I reproduced this wrongly.)

What gives me a slight pause is the question how the "Pickable" component would be implemented. Of course, we could implement it like any other component in C++. But alternatively, it seems like it should be possible to implement such a mundane task in script as well.

Now let me take a step back and change perspective: What if we had shipped the Engine without having mappers provided with a pre-implemented "Pickable" component, but with a generic "Script" (aka "Behaviour") component?

The mapper would create the GO with the same components as before, but instead of the "Pickable" he would add a "Script" component. The "Script" component would either house a complete script (a possibly long long string), or (more likely) just a filename to the actual .lua script file that should be used.

Thus, the mapper would write a small Lua script (named e.g. pickable.lua) and point the "Script" component to it.

Now comes the crux:
That Lua script would provide an OnTouch() callback for the "Collision" component (the script has access to everything in its GO), implementing the "add item to GO's inventory" functionality.
And it would check beforehand if there was an OnTouch() callback for the "Collision" component already. And if there was, make sure that it was kept in a local variable before its own handler was put in its place, and have it called as part of our own, new handler.

In this manner, if several ("Script") components where dependent on the "Collision" components OnTouch event, a chain of event handlers would be created.

-----

Back to the perspective of the mapper: Unless the resulting GO is saved as a prefab, the downside of this approach is certainly that he has to complete two steps (1. Add "Script" component to GO, 2. Set its path variable to "pickable.lua") rather than only one (1. Add "Pickable" component to GO).
On the other hand, the pickable.lua script can be re-used arbitrarily often, and the entire GO could be saved as a prefab.
Another aspect is that we saved a hard-coded C++ implementation of a "Pickable" component, moving it into a "behaviour" script instead. (which could be one among many other behavior scripts).

Well, so far my thoughts. :cheesy:
As said above, I dare not draw any conclusion at this point, or dare say which approach is better. I'm very happy to try and explore both approaches, and take whatever turns out to work best!

:up:
Best regards,
Carsten
MatejZajacik
Posts: 10
Joined: 2013-04-11, 17:12

Re: Component question

Post by MatejZajacik » 2013-04-19, 23:51

Carsten wrote:But still, let me try to summarize and explore a bit more:
In your example with the pickable item, the suggested flow is:
  • The Collision component detected a collision.
  • Now have the Collision component (maybe with the help of the GO) inform every other component about this collision event (either really every component in the GO, so that components that aren't interested in this particular event can ignore it; or only those components that beforehand have expressed a special interest in this event type, e.g. by having registered themselves at the GO appropriately).
  • Consequently, the "Pickable" component receives the event (its event handler function is called). As the main job of the "Pickable" component is to add some item to the GO's inventory (possibly another component), its event handler implementation can be dedicated to this task.
(Please step in if I reproduced this wrongly.)
Actually, your understanding of my proposal is flawless. Yes, the Pickable component would handle inventory (which, as you correctly stated, would be just another component (present in the toucher / player)) and removal of the GO (the pickable item) from play.
What gives me a slight pause is the question how the "Pickable" component would be implemented. Of course, we could implement it like any other component in C++. But alternatively, it seems like it should be possible to implement such a mundane task in script as well.
I had this understanding that we would implement almost all components in Lua only. Understandably, some components (like Collision) would have to be partly or fully coded in C++ for speed requirements. But as for the Pickable component, from my naive point of view, I see no reason why you would not code it up in Lua alone. It will get called very rarely (most likely just once during its whole existence per game object). Now what I would do is to have the basic Pickable component implement functions like canBePicked() (for checking whether a medikit should be picked up based on player's health - if player is 100% healthy, return false, etc.) and doPickUp() etc., and then derive and create components for each specific item. Or, in case of a simple game with just a dozen of pickable items, maybe just create one bigger component to handle all the items.
Now let me take a step back and change perspective: What if we had shipped the Engine without having mappers provided with a pre-implemented "Pickable" component, but with a generic "Script" (aka "Behaviour") component?
I believe the engine should be shipped with a few components that very basically implement standard game mechanics you can think of. It would be up to the mapper / scripter / designer to build their own components on top of them, or just rewrite them to their liking. I understand you cannot make a set of components ready to go with any type of game, but implement a few that people can easily learn from and hopefully directly derive from.
Back to the perspective of the mapper: Unless the resulting GO is saved as a prefab, the downside of this approach is certainly that he has to complete two steps (1. Add "Script" component to GO, 2. Set its path variable to "pickable.lua") rather than only one (1. Add "Pickable" component to GO).
In Unity, working with "prefabs" is amongst the very basics you have to learn to even start thinking of creating anything playable. So I would safely assume people will always work with prefabs in Cafu, too. What I want to say is that you don't have to worry what happens if people do not use prefabs. Having said that, I also see both proposals working and each having its own perks.
I'm very happy to try and explore both approaches, and take whatever turns out to work best!
Great to hear that. That's always the best approach.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest