12
\$\begingroup\$

I'm working on 2d topdown game in SFML 2, and need to find an elegant way in which everything will work and fit together.

Allow me to explain. I have a number of classes that inherit from an abstract base that provides a draw method and an update method to all the classes.

In the game loop, I call update and then draw on each class, I imagine this is a pretty common approach. I have classes for tiles, collisions, the player and a resource manager that contains all the tiles/images/textures. Due to the way input works in SFML I decided to have each class handle input (if required) in its update call.

Up until now I have been passing in dependencies as needed, for example, in the player class when a movement key is pressed, I call a method on the collision class to check if the position the player wants to move to will be a collision, and only move the player if there is no collision.

This works fine for the most part, but I believe it can be done better, I'm just not sure how.

I now have more complex things I need to implement, eg: a player is able to walk up to an object on the ground, press a key to pick it up/loot it and it will then show up in inventory. This means that a few things need to happen:

  • Check if the player is in range of a lootable item on keypress, else do not proceed.
  • Find the item.
  • Update the sprite texture on the item from its default texture to a "looted" texture.
  • Update the collision for the item: it might have changed shape or been removed completely.
  • Inventory needs to be updated with the added item.

How do I make everything communicate? With my current system I will end up with my classes going out of scope, and method calls to each other all over the place. I could tie up all the classes in one big manager and give each one a reference to the parent manager class, but this seems only slightly better.

Any help/advice would be greatly appreciated! If anything is unclear, I'm happy to expand on things.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ You might want to consider composition here, rather than inheritance. Have a look around for composition examples and it might give you some ideas. The pimpl idiom might help with sorting things out too. \$\endgroup\$ Nov 7, 2012 at 0:37
  • 5
    \$\begingroup\$ One of the canon articles on composition: Evolve your Hierarchy \$\endgroup\$ Nov 7, 2012 at 4:12
  • \$\begingroup\$ Seems too localised. Try the Code Review SE? \$\endgroup\$ Dec 10, 2012 at 18:15

1 Answer 1

5
\$\begingroup\$

Not sure if composition will solve all the problems. Maybe can partially help. But if what you want is to decouple classes I would look into more events driven logic. This way e.g. you'll have OnLoot function which needs to have player position and info about loots available to find the closest. Then function sends event to the looted item. Looted item in its event process cycle handles this event so item only needs to know how to update itself. OnLoot function can also update player inventory or item itself may send updateInventory/*OnLootSucess* event and player/inventory will handle it in its own process events cycle.

Pros: you've decoupled some of your classes

Cons: added events classes, maybe unneeded code overhead.

Here is one of the possible ways of how it may look:

case LOOT_KEY:
   OnLoot(PLayer->getPos(), &inventoryItems);
....

// note onLoot do not needs to know anything about InvItem class (forward decl in enough)
int onLoot(vec3 pos, InvItems& pitems)
{
    InvItem* pitem = findInRange(pos, pitems, LOOT_RANGE);
    if(pitem)
     EventManager::Instance->post( Event::makeLootEvent(pitem));
}
....

// knows only about EventManager
InvItem::processEvents()
{
    while(!events.empty())
    {
        Event* pev = events.pop();
        ...
        case LOOT_EVENT:
            // in case you broadcasted it, but better is to sort all posted/sent events and add them only if they addressed to particular item 
            if(pev->item == this && handleLoot((LootEvent)pev))
            {
                EventManager::Instance->post(Event::makeLootSuccessEvent(this));
            }
    }
}

int handleLoot(LootEvent* plootev)
{
    InvItem* pi = plootev->item;
    if(pi->canLoot())
    {
        updateTexture(pi->icon, LOOTED_ICON_RES);
        return true;
    }
    return false; 
}


...
// knows only LootSuccessEvent and player
Inventory::processEvents()
{
    while(!events.empty())
    {
        Event* pev = events.pop();
        ...
        case LOOT_SUCCESS_EVENT:
             player->GetInventory()->add( ((LootSuccessEvent*)pev)->item );
        ...
}

This is just one of the possible ways. Probably you don't need so many events. And I am sure you can do better knowing your data, this is only one of many ways.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .