• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Tispe

single responsibility principle in practice

14 posts in this topic

Hi, I would like to know how to practice the single responsibility principle.

 

My first thought is that I should seperate Data and the functions that manipulate them, so that each manipulation requires its own class. But that seems counter intuitive since in OOP, a class has all the methods to manipulate its own members.

 

Lets take an "Entity class".

members:

-Position

-Health

-Ammo

-Skeleton

-Mesh

 

methods:

-Move()

-Shoot()

-TakeDamage()

 

Now, we suddenly have a class which does alot of things, but the single responsibility principle says that the class should do only one thing.

 

How then do we practice the single responsibility principle in this example?

 

My guess is that all the methods are taken out and the members are made public. We have an array of Entities that has to go through a Move class, a Shoot class, a TakeDamage class and so on. But that seems wrong, it just makes the code more complex.

 

What is appropiate to have in the Entity class with respect to the single responsibility principle? Should helper functions or other class methods manipulate the Entity members?

2

Share this post


Link to post
Share on other sites

My first thought is that I should seperate Data and the functions that manipulate them, so that each manipulation requires its own class. But that seems counter intuitive since in OOP, a class has all the methods to manipulate its own members.

But the low level hardware dictates that you should process data in batches. Typically I value the opinion of hardware more than sticking to 'good' OOP principles.

For example, having an 'EntityMovement' class to handle the movement of all entities might be a good idea (since it gives you a nice way of testing for collisions between all entities, controlling group motion, etc).

0

Share this post


Link to post
Share on other sites

How then do we practice the single responsibility principle in this example?

 

One option would be to use an entity/component system. Instead of having "health", "position" as member, and some methods like "shoot", etc... you'd have a generic entity class without any real data members. You'd then have a "Health-Component", and a "Position-Component", that you can attach to this entity-class. Those components would only contain data - float health, vector3 position, you get the idea. Then you'd have systems, that would iterate over all entities based on the components they have, and perform the actions - a "Move" system can manage the change in position, for example. (these are all just primitive examples - you wouldn't want to have a pure "Health" component or a "Move" system in any larger project, really).

 

Have a look at https://github.com/alecthomas/entityx, if you eigther use C++11 its a good starting point, otherwise you'd at least get an idea about the implementation. That way, you have the responsibilities optimally distributed amongs multiple modules/classes.

Edited by Juliean
0

Share this post


Link to post
Share on other sites

Tispe, on 07 Jun 2013 - 20:44, said:
Lets take an "Entity class".
members:
-Position
-Health /* Wrong */
-Ammo /* Wrong */
-Skeleton /* Wrong */
-Mesh /* Wrong */

methods:
-Move() /* Somewhat less wrong, indirectly */
-Shoot() /* Wrong */
-TakeDamage() /* Wrong */

Why would a building have health, be able to shoot, or take damage?
Why would a skybox have a skeleton?

You won’t understand what the single-responsibility principal means unless you understand what it means to have a single responsibility.

A CEntity is the lowest-level thing that gets into your game world. Every single object inherits from it. It has 1 job and 1 job only: Provide things with a parent/child relationship.

Next level up you have a CActor, which provides things with world/local orientations.

I didn’t mark your suggestion that an entity have a position wrong because an entity and actor are often combined into 1 and the line between them is ambiguous.


So the only thing every single object in your game world has in common is that they can be parented under each other and they have a local position, which gets translated into a world position whenever changed.
How could you give a health bar to every single object in your game? Does the ground have health? Do buildings explode if you punch them enough?
At this level, we haven’t even gotten to models yet.

CModelInstance -> CActor -> CEntity

Now we have an actual mesh, which can represent some physical objects in the scene.

CDrawableModelInstance -> CModelInstance -> CActor -> CEntity

Now we have physical objects that can be drawn. Remember, not every object is visible. Invisible walls obstruct your path just as well as any other objects do.


CPlayer -> CDestructable -> CDrawableModelInstance -> CModelInstance -> CActor -> CEntity
CGenerator -> CDestructable -> CDrawableModelInstance -> CModelInstance -> CActor -> CEntity
CGiantMoth -> CDestructable -> CDrawableModelInstance -> CModelInstance -> CActor -> CEntity

Now we have something that can take damage. And it took 6 classes to get there.
This is Single-Responsibility Principal. It means a class should do only when it needs to do. It may do more than one thing, of course, as long as it makes sense for it to do that. A CModelInstance can be animated and thus have a skeleton. Because we all know that things may move even if we can’t see them. It makes sense for animation to be part of CModelInstance rather than CDrawableModelInstance. And this is a balance between “is a” and “has a”.

This is Single-Responsibility Principal. Things may have more than one duty, but those duties are logically a part of the object, and may even be delegated to a member of that object. For example a CModelInstance introduces the concept of animation to an actor, but a CSkeleton is the class that actually performs everything related to animations.
Likewise, an actor does not handle the responsibility of moving just because it introduces the concept of a position into the mix. The position, scale, and rotation of anything in the world is handled by the COrientation class. A CActor may introduce local positions to objects, but managing those positions is not its responsibility—that falls to COrientation.

This is Single-Responsibility Principal.


L. Spiro Edited by L. Spiro
1

Share this post


Link to post
Share on other sites

I didn't know inheritance and SRP were so close. Can't you have SRP without inheritance?

 

Sorry for the bad naming, but when I think of an Entity I think of a living creature like a PC or NPC, which has a mesh, skeleton etc.

 

It seemed so obvious that SRP ment that you made classes that contained data and then you made classes that manipulate that data, and then a render class that draw that data. Why a PC and a Sound clip needs the same base class is not clear to me.

1

Share this post


Link to post
Share on other sites

Sorry for the bad naming, but when I think of an Entity I think of a living creature like a PC or NPC, which has a mesh, skeleton etc.

 

It seemed so obvious that SRP ment that you made classes that contained data and then you made classes that manipulate that data, and then a render class that draw that data. Why a PC and a Sound clip needs the same base class is not clear to me.

 

Entity usually refers to any interactive element within the game world, or something along those lines. PC/NPC could be classified as 'Actors'. The is-a relationship would be, "Player is-a Actor is-a Entity", so Actor could inherit from Entity and Player could inherit from Actor. Inheritance is its own bucket of worms, and should be studied separately and comprehensively in order to save you a lot of grief later on.

 

SRP does not mean that you have classes that are all data and classes that are all action. That's really not a great concept to bind yourself to either, as it sounds like a recipe for inappropriate intimacy and eventually a god class. Additionally, SRP does not apply only to classes. It also applies to functions, modules, and really any segregable part of a program.

 

Make simple parts, then use them to build other simple parts.

 

The real goal is that someone can look at one of your parts and easily understand what it does just by reading through it, without needing to look up how its component parts work, or understand how it fits into the larger system.

1

Share this post


Link to post
Share on other sites

Just wondering, Hodgman, with this design at what point are the references to the Model and RigidBody components removed from the PhysicsWorld and VisualScene objects?  If the Player struct calls a remove method in its destructor, then the Player would need to store a reference/pointer to the PhysicsWorld and VisualScene, which seems a bit inelegant IMHO.  Or is storing these references actually the right way to do it?

0

Share this post


Link to post
Share on other sites

methods:

-Move()

-Shoot()

-TakeDamage()

 

Theory is surely a nice thing.. but if implementing this SRP means that a class can't have 3 methods then I'd rather stay away from it :P

0

Share this post


Link to post
Share on other sites

Yes, IMHO, the above is an abuse of inheritance. It's using "is a" (inheritance) where "has a" (composition) would suffice. Standard OO doctorine is to default to composition and only use inheritance where it's actually required -- i.e. where you actually need to polymorphically operate on some abstract type through an interface -- so if ModelInstance isn't implementing an interface that it's inherited from Entity, then it shouldn't inherit from it.

This.
1000x this.

Inheritance chains longer than 2 or 3 deep is likely a symptom that you've now got a horribly ridge relationship graph between all your classes and, as Hodgman goes on to say, some which are 90% duplicates of the others in order to get around a 'small problem'.

Small, focused, simple classes are not only easier to understand and compose but also have the side benefit of likely being a more friendly size for CPU caches as you won't have a cascade of data items playing a game of Take Up Cache Space during various updates etc.
2

Share this post


Link to post
Share on other sites

Just wondering, Hodgman, with this design at what point are the references to the Model and RigidBody components removed from the PhysicsWorld and VisualScene objects?  If the Player struct calls a remove method in its destructor, then the Player would need to store a reference/pointer to the PhysicsWorld and VisualScene, which seems a bit inelegant IMHO.  Or is storing these references actually the right way to do it?

*write tl;dr answer*

*come to ok solution*

 

Make all the 'services' (PhysicsWorld, VisualScene) contain the corresponding entities, which are simply dummy containers of other components or raw data if 'primitive'.

 

The services themselves have methods to modify the components through references, HOWEVER, the entities are accessed by IDs (so you can move them in memory without invalidating everything). You MIGHT want to overload the methods with versions that take in IDs, fetch the component, and call the reference taking version.

 

Then, you should add an iterator for each service. The iterator stores the entity ID we are edition, along with its reference, so we dont have to pass the entity ID/reference to the service for each operation

 

//not sure what to call this as it doesnt need to iterate, although it very well could.

auto iter=PhysicsWorld.getEntity(PhysicsEntityID) //Creates iter, stores ref. to actual physicsentity, stores the ID, stores ref to PhysicsWorld or something

iter.setVelocity(blah) //edits the actual physicsentity directly if possible, or tells physicsworld/whatever else is available to do it (like if it needs a service only physicsworld knows about to access a component of physicsentity

iter.addForce(bleh)

PhysicsWorld.destroyy(PhysicsEntityID)

PhysicsWorld.runDemCollisionDetections()

 

you could possibly do something like

 

PhysicsWorld.setVelocity(PhysicsEntityID,velocity)

 

too i guess.

 

This needs some improvements though, but i thought this might work.

0

Share this post


Link to post
Share on other sites

Theory is surely a nice thing.. but if implementing this SRP means that a class can't have 3 methods then I'd rather stay away from it tongue.png

 

Thats not what SRP say - it means that a class should have only methods that aid into solving a particular problem. From what I know SRP should also be applied to methods themself, so you could actually end up with a lot of small methods in one class, as long as they are necessary to solve a particular problem (parsing an xml file, ...). Moving, shooting and taking damage are independant problems, thus they should be seperated into smaller classes following SRP.

Edited by Juliean
1

Share this post


Link to post
Share on other sites

Hodgman, on 08 Jun 2013 - 14:25, said:
Yes, IMHO, the above is an abuse of inheritance. It's using "is a" (inheritance) where "has a" (composition) would suffice. Standard OO doctorine is to default to composition and only use inheritance where it's actually required -- i.e. where you actually need to polymorphically operate on some abstract type through an interface -- so if ModelInstance isn't implementing an interface that it's inherited from Entity, then it shouldn't inherit from it.

It does, although I did not go into so much detail.
CEntity provides the basic parent-child relationship and I mentioned that CActor and CEntity are often combined into one, though I chose to separate them for cleanliness (again, basically—not going into too much detail). I’d fault no one for combining them (as I have suggested multiple times on this forum and in my book). Making an efficient parent-child relationship with dirty flags that need to be cascaded down to children when local or world translations/scales/rotations change is fairly complicated (when unnecessary updates are to be completely eliminated), further complicated by the “WillChange”/“DidChange” mentioned below, so I made a personal choice to keep them separate.

But what I did not mention is that CActor provides “WorldMatrixWillChange()”, “WorldMatrixDidChange()”, “LocalMatrixWillChange()”, and “LocalMatrixDidChange()” virtual functions that upper classes do actually override, including CModelInstance.

Why? Because CActor/CEntity know nothing about what a bounding box or bounding sphere is. A CModelInstance does, and it needs to be updated when the world matrix changes. That is to say, “CModelInstance implements an interface provided by CActor”. This is proper use of “is a/has a”.
Likewise, a CDrawableModelInstance is a CModelInstance, with the ability to render itself, and yes it does implement an interface provided by CModelInstance, but again I will not go into so much detail.


The rest about CDestructable and CPlayer was off the top of my head to provide an example of what might happen on the game side of things. Everyone should put thought into his or her design, and that example may not be suitable for everyone, but it was just an example.

I also mentioned the balance between “is a/has a”, citing COrientation as an example.

It is definitely debatable as to how CPlayer and CDestructable could be organized, although such a debate would be pointless as there could never actually be a winner. I would be perfectly fine with composition in that case despite my example using inheritance, and admittedly it makes more sense that a player “has a” drawable component and a destructable component.
But to say that inheritance chains deeper than 2 or 3 is a symptom of rigid design and an attempt to get around a small problem is an extreme over-generalization.


L. Spiro

Edited by L. Spiro
-1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0