Public Group

Is it ok for a component to expect a gameobject to have certain properties?

This topic is 885 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

My "Shooter" component allows a gameobject to fire a weapon. But it expects that gameobject to have an inventory and an equipped item. So this means that only my "Player" and "Enemy" derived classes can actually use it without an error/exception. Is this bad design? Should a component work with as generic a gameobject as possible? Or can some components be created specifically for certain objects?

class Shooter:
__slots__ = ['gameobject']

def __init__(self, gameobject):
self.gameobject = gameobject

#NOT FULLY IMPLEMENTED YET
def shoot():
if self.gameobject.equipped_item.ammo_type is None: #For melee weapons
return gameobjects.Bullet(self.gameobject.rect.topleft, self.gameobject.rect.size, 'bullet', self.gameobject.direction, self.gameobject.equipped_item.damage, 1)

As you can see, the Shooter component expects the gameobject to have an "equipped_item" and a "direction" (Both exclusive to the "Player" and "Enemy" class currently).

Edited by mrpeed

Share on other sites

Well, this is a very subjective question.

IMO:

1) if it only makes sense for player and enemy objects to have a shooter component, why should tightly linking be such a bad thing? If there is a gameobject that should be able to shoot, it is certainly either friendly or foe, thus either a player character or an enemy. Even if you want to introduce neutral armed objects, you might reuse the enemy gameobject anyway and just tweak the AI so it can be/is non-aggressive.

2) You could also make sure only these two gameobjects can ever use it, or test if the gameobjects getting assigned this component has inventory and items. With asserts or any other kind of tests. How you handle the case where someone wrongly assigns the component to the wrong gameobject is for you to decide, if you want to gracefully deactivate the component to prevent errors, or throw a descriptive error.

3) I think you are overthinking things. Its good to reusable write code that works in as many use cases as possible... there are tradeoffs to that though, this type of code is generally taking longer to write and test (or being more prone to errors), is harder to read (not always), all the while you most probably will not re-use it just as much as you would think.

Generally, write code for your specific use-case, if you don't see an obvious reason why you would want to reuse it in the future. Make sure the code is as fast, stable and easy to read for that one usecase as possible.

If you later on find the need to adapt this code for another use case, either do exactly that, or make it work in a way that could scale to even more re-use in the future. It is going to be additional work doing that later, yes. Its still less work than trying to overengineer every damn object and component you ever write so you can re-use everything.

Overengineering is the root of all evil... that or premature optimization. Most probably both.

Share on other sites

If you make two classes that must talk to each other, it is inevitable that they agree on something. They can agree on certain data to be present, certain functions they can use, or some combination of it.

You can add layers, meta-layers, meta-meta-layers,etc, use reflection, or I don't know what, but if they don't share some concepts, they can't understand each other.

Given that, usually the best option is to take the most direct route, as it reduces complexity as much as possible. That means only add new layers if you need them. I even go as far as not adding before I need them, as in, having a concrete case where it's useful, but ymmv here.

Share on other sites

Why don't you make inventory a component too? Are you hard-coding functionality in some class hierarchy together with component-based system? Shooter component shouldn't care about "what" entity is, only if it has inventory.

If you make it a component, you can query it's presence by another component, which makes more sense than checking for certain type of entity. If you add more entities that can have inventory and equip items you will check all your code for these exceptions? Wouldn't it be easier to just add an Inventory component to such entity and be done?

If you insist on your actual design, if it's Python, just catch exception if the object doesn't provide necessary members...

Edited by noizex

Share on other sites

I think it's fine to have a component that needs to be linked to another to do its work.   So you could either have a separate component with the inventory items and have the shooter component query the object for it, or just initialize the shooter to point to an existing inventory component when it's created.  You should really avoid putting anything in the object that is only used for a few (or a single) components.

There are other examples of dependent components, like for example a mesh and other dependent components like mesh animators or renderers.  Those only do anything if they're pointed at the mesh component with the actual data.

Share on other sites

To me, that's oversimplifying the issue. In this language, sure, this exploits duck typing - but the concept exists in any language, such as if I implemented it in Unity like so:

var ei = gameobject.GetComponent<EquippedItem>();
if (ei.ammo_type == null)
return gameobjects.Bullet... etc etc

And then the question is back to - is it okay, inside Shooter, to assume that the current gameobject has a equipped item component? What does the component do if ei is null? Should the system attempt to guarantee it never happens at compile/build time? Raise an exception at run-time? Do nothing, but print a debug message?

I don't think there's a single correct answer there. Being able to mix and match components can be handy, but if it literally makes no sense to have a Shooter component without a EquippedItem component, then some sort of constraint or check is sensible. But that means one component can end up doing a variety of things (or nothing at all) based on the presence or absence of other components, which isn't very predictable or maintainable. Alternatively, you might just decide that EquippedItem should handle the Shooter responsibilities... perhaps you need a ShootableEquippedItem instead... but now you're back into writing a lot of special case code which isn't very modular.

Personally this is why I think component-based systems are a bit of a fad - they change problems but don't really fix them. By all means prefer composition where possible, but not every game logic problem is suited to it.

Share on other sites

Personally this is why I think component-based systems are a bit of a fad - they change problems but don't really fix them. By all means prefer composition where possible, but not every game logic problem is suited to it.

I share the same sentiment but different reasoning.

It's generally true that composition is preferable to inheritance.

Many OOP programmers, upon having this epiphany, make the declaration that "OO is bad" (even though this is a key teaching of OO, so these people haven't actually learned enough OO in the first place to be able to disown it) and then commit the over-engineering sin of building a "composition framework" to assist them in using their new silver bullet... Which ends up being as inflexible and ill-conceived as their original inheritance anti-designs.

You can use components and composition and game objects, without there ever being a class called "component" or a class called "game object" because those kinds of frameworks are unnecessarily reinventing existing language features as less-capable library features, and/or implementing design idioms (which are ways that you structure your code - meta code) as a library of actual code. Most "component frameworks" are, frankly, bullshit.

Share on other sites

As this is For Beginners...

Yes, it is reasonable for you to expect that your code implements specific features.  You can set your own expectations as long as you follow them.

In larger projects the team determines what those expectations are, and team members are expected to ensure their code meets the expectations. Sometimes those expectations are enforced by code, where the code won't compile.  Other times they are enforced by tools that scan the code. Other times they are enforced by code reviews and humans, with the expected occasional human error.

The policies the other posts talk about are good ideas to think about, and each policy comes with its own set of pros and cons. Exactly what you choose and why you choose it may vary from project to project and from team to team.

In this example of your own code, where you write that you know the only things it will hit have both an "equipped_item" and a "direction" property, then because it is your code you can make that expectation.

As a general rule you should test for existence of the object before using the object.  In this case it might be a simple guard like:  if(gameObject != null && gameObject.equipedItem != null && ...) {...}  That type of guard in front, the tests against null, will verify that it actually exists before you start using it.

On the other hand, you might want code that crashes or breaks visibly when an object is null.  If it is something that is always expected you will want debug builds to throw up all kinds of warning messages so programmers will fix the bug.

Share on other sites
Alternatively, you might just decide that EquippedItem should handle the Shooter responsibilities... perhaps you need a ShootableEquippedItem instead... but now you're back into writing a lot of special case code which isn't very modular.   Personally this is why I think component-based systems are a bit of a fad - they change problems but don't really fix them. By all means prefer composition where possible, but not every game logic problem is suited to it.

What would be alternatives to component and inheritance based systems? I still want to follow SOLID principles and general good design. I found when I used inheritance in the past I violated a lot of that. What else could I look into?

Edited by mrpeed

1. 1
2. 2
Rutin
17
3. 3
4. 4
5. 5

• 13
• 26
• 10
• 11
• 9
• Forum Statistics

• Total Topics
633735
• Total Posts
3013596
×