Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » OO design. Should an object control its own actions?

This thread is locked; no one can reply to it. rss feed Print
OO design. Should an object control its own actions?
Karadoc ~~
Member #2,749
September 2002
avatar

I was never formally taught object orientated programming, and so I always face this question with doubt in my mind...

Let me try to illustrate the question with an example.

Suppose I have a class called House which is meant to represent a house in my virtual world. The House class might have some stuff like this:

class House
{
  char house_map[house_width][house_height];
  std::vector<Door> house_doors;
  std::vector<Object> items_on_the_floor;
  std::vector<Person> people_inside_the_house;
  ...
};

My question is about what each class should be responsible for. In particular, if a Person in the house wants to do something, who should control the action? Should it be like house.MovePerson(person_number, action); or person.Move(action)?

It seems to me that the person's actions should be taken care of by the Person class, but many actions depend on the person's surroundings, which the Person class does not have direct access to.

I've experimented with different approaches and I've found that if I let the House handle the Person's actions (metaphorically speaking) then I end up with a bloated House class and a thin Person class. But if I let the Person handle its own actions then I find I end up needing to provide the Person class with a pointer to the House that it resides in, so that it can do stuff like house_pointer->OpenDoor();

So the question sticks in my mind. Should each Person have a pointer to the house, or should the house just control what happens to the person? Or am I making a more fundamental mistake in this kind of design?

-----------

Tobias Dammers
Member #2,604
August 2002
avatar

The Person initiates the move by telling the World that it intends to move from point A to point B. The World queries the Person and the House for relevant properties, calculates whether or not the movement can take place, and sends a message to the Person and the House to inform them about the change.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

jmasterx
Member #11,410
October 2009

I don't think this is abstract enough.

I would have a middle man which would abstract the logic from the objects. Essentially what you want is Person and House classes not to want to know anything about each other. So the player would move forward, the engine might take care of collision so the game then looks for overlapping objects and can send a collision resolver an event, it would then resolve overlapping objects, unless you have a physics engine or something. In terms of actions, each entity can send out an action, you can then send these actions to mediators that can do logic: If the Person sends an Action withing Radius of Door, send player through door.

This is how I'd do it. Not saying its right, but it works for me.

Sirocco
Member #88
April 2000
avatar

I don't use OO much (or really, at all) in my games, but what I try to do is model things after Real Life™ when it isn't impractical. So, taking your example, each object would be responsible for its own actions, and making its own decisions. If people are in a house, the house doesn't need to know or do anything. It just "is". If it suffers too much damage and collapses, an event is generated that applies damage to all nearby objects (house.ohshitblargimdead(stuffgoeshere)). If this happens to cause the people inside to perish, all the better :)

-->
Graphic file formats used to fascinate me, but now I find them rather satanic.

Tobias Dammers
Member #2,604
August 2002
avatar

Sirocco said:

So, taking your example, each object would be responsible for its own actions, and making its own decisions.

But in RealLife, if you decide to walk through a solid wall, the laws of physics won't let you. What happens is you initiate a movement, and world produces the consequences.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

AMCerasoli
Member #11,955
May 2010
avatar

When you use OOP, and you want to do that kind of approach is when and engine comes in, well if isn't OOP engines also are there but with OOP looks better.

So basically I think your objects should send packages of info to the engine, depending of your game, that info may include, position rotation things like that, and then the engine sends back that info back modified to the object.

I haven't done anything of this, but I use to think about it.

suppose a class that is inside each object of your game:

#SelectExpand
1class DATA{ 2 3 public 4 short kind_of_data; 5 int posX; 6 int posY; 7 bool explode; 8 int fly; 9 bool die; 10 bool butt_UP; 11 bool butt_LE; 12 bool butt_RI; 13 bool butt_DO; 14}data;

So you use a function, let's say logic() to retrieve that data from all objects.

so:

#SelectExpand
1 2DATA data_r; 3 4data_r = house.logic(); 5 6//the you could check what kind of object it's 7 8if(data_r.kind_of_data == HOUSE){ 9//blablabla 10} 11else if(data_r.kind_of_data == CHARACTER){ 12//blablabla 13}

Then you send the data package back to the object modified, so basically, you move the character outside the object, you have no function to move your character inside your object, because you don't know if you are walking on water or in the ground so your engine should check this and return a package with all the data modified... Well actually you could move your character inside your object and just send the position to the engine I thin it's the same... The animations and things like that are done inside the object. Well, I don't know if this could work I haven't think too much as you can see, but I guess with OOP you need to check everything outside the object with an engine.

The engine would be the world and you can't introduce anything of the world inside your object because then your object should retrieve the position, etc. of aaaalll objects and this is more expensive than sending the data and check it with an engine.

And I think this kind of approach bring you closer to Network programming I think.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Karadoc ~~
Member #2,749
September 2002
avatar

The Person initiates the move by telling the World that it intends to move from point A to point B.

How does it tell the World that? With a function call using a World pointer? Or by just having an internal property that the World is meant to poll for?

jmasterx said:

Essentially what you want is Person and House classes not to want to know anything about each other. So the player would move forward, the engine might take care of collision so the game then looks for overlapping objects and can send a collision resolver an event,

This is roughly what I've been doing up until now. The Person[1] has a Move() function which just goes ahead and changes their position coordinates based on whatever is in their command queue, and then later on the class representing the surrounding area performs collision detection and bumps the Person back to where they were if required.

But now I want to implement a command that does different things depending on where the Person is standing.. so it simply cannot handle the command itself without access to information about the surrounding area. So I figure my options are to either give the Person access to their surroundings, or take away their Move command and give that responsibility to the soundings area.

Sirocco said:

So, taking your example, each object would be responsible for its own actions, and making its own decisions. If people are in a house, the house doesn't need to know or do anything.

So I suppose the object would have access to the map and such so that it would know how to take care of its own actions?

References

  1. actually, in my code the equivalent class is called Character; and there is no House... but that's beside the point

-----------

james_lohr
Member #1,947
February 2002

You need to define what interaction the Person (or perhaps any moving object) needs with the House (or perhaps any navigable area), and then encapsulate this into an interface and pass this interface to the person object.

For example, you might write an interface called Navigable with methods:

//returns whether a move is possible in the given 
//direction from the specified location
bool ClearToMove(Location location, Direction direction); 


//Identifies what objects surround the given location
//(e.g. doors etc which implement the Interactive interface)
Interactive[] IdentifySurroundings(Location location);

You then have your House class implement this interface, and cast your house object to a Navigable before passing it to the person. Your person then uses the methods exposed in this interface to navigate the house without actually knowing anything about the house (or even that it is a house). You can also see that the logic is then split reasonably between the two class: the House class has to implement an interface, defining what a house is like to be navigated. Similarly, the Person class must define how it generally navigates anything that is Navigable.

Can you see how this also saves you a lot of work in future? - If you then add a Hotel class, instead of having to define how a person navigates a Hotel, you simply have the Hotel class implement the Navigable interface. Suddenly your Person class can navigate a Hotel too.

At this point you spot that the House and Hotel classes implement Navigable in nearly identical ways. You generalise this by creating a Building base class which implements the Navigable interface, and have both House and Hotel extend this.

You then decide to add an Animal, which you soon realise uses very similar logic to move around Navigables as Person. So, you extract the methods into an interface called Moveable and implement it in a base class called, say, Creature which both Animal and Person extend.

Earlier I stated that you "pass this interface to the person". Actually, it doesn't matter that much what you do: as long as you have generalised the interactions between these classes, then it should be easy to refactor as you wish.

For example, you've now got a general way to move any Creature around any Navigable, and you want to actually control this. So, you might create a PlayerMovementController which defines the logic for moving the player around his present location. Perhaps it could have the constructor:

PlayerMovementController(Moveable creature, Navigable area)

You now realise that your creature can only be controlled in a single House/Hotel (area) at one time. However (and this is the cool bit!), you realise that you can create a new class: World which also implements Navigable, which contains all areas (such as houses hotels etc), and which contains the logic for gluing these contained Navigable objects together.

You can now pass world (cast as a Navigable) into your PlayerMovementController, and suddenly your player can move around the world seamlessly. What's more, the code is in all the right places: how to generally navigate a House/Hotel is in the Building base class. How to generally move a Person/Animal around a Navigable area is in the Creature base class. How to glue all navigable areas together into one seamless Navigable is in the World class.

This isn't necessarily the design I would choose, I've just followed through one scenario to illustrate how powerful it is to generalise interfaces between objects.

Sirocco
Member #88
April 2000
avatar

So I suppose the object would have access to the map and such so that it would know how to take care of its own actions?

Given that particular scenario, it would have to, unless you wanted to pass things off to an intermediary and let it manage activities.

-->
Graphic file formats used to fascinate me, but now I find them rather satanic.

Thomas Fjellstrom
Member #476
June 2000
avatar

It makes sense to me that a person class should be able to interact with the objects around them, so I would go with Person::CurrentHouse->OpenDoor().

Which door?

I'd treat doors as entities, fetch the closest entity, and when the player hits the action button, it does something like near_entity->doAction(player), if the entity is a door, it'd open, or not depending if its locked, or what have you. If it is locked, the door could check the player entity you pass in to see if it has a key. etc.

That's just off the top of my head though. But that't in general what I'd try first.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Tobias Dammers
Member #2,604
August 2002
avatar

How does it tell the World that? With a function call using a World pointer?

Why, yes.

void Person::takeNearestExit(World& world) {
    Door* door = world.getNearestDoor(*this);
    if (door) {
        world.openDoor(*door, *this);
        world.movePersonThroughDoor(*this, *door);
    }
}

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Go to: