Draw all items from std::map

Started by
10 comments, last by L. Spiro 8 years, 8 months ago

I have many items in a std::map and i need to draw them all using SFML.

So far i have the following in a class called ItemManager which stores all items:


std::map<std::string, Item> items_;

"Item" is defined here:


struct Item {
	const char* name;
	ItemType type;
	sf::Sprite itemSprite;
	int x, y, sizeX, sizeY;
	int posX, posY;
	int offsetX, offsetY;
};

Now i need to send the std::map that contains all items to the main class called DGame and draw them there but i dont know how to send them there as i dont have much experience with std::map.

Advertisement

Try this:

// note that pair is a std::pair where the first element is the key and the second the value
for (auto& pair : items_) {
     do_the_thing(pair.second);
}

This ^^^

And unless their ordering within the map is important, you probably want std::unordered_map (by default when using maps, use unordered_map unless you need to preserve insertion order).

If you mean that you want to pass the map to the render function (and you dont have to manipulate it there) then just pass it as const reference.

Well, std::map has begin() and end() like all other standard containers, so that is -- luckily -- not a big challenge. You can just iterate over it as usual:


for(auto it = items_.begin(); it != items_.end; ++it)
    do_render(*it);

or even easier:


for(auto& it : items_)
    do_render(it);

(both assume a present-day C++ version, if you use C++98 or C++03, you will have to painfully write out the iterator type, like std::map<std::string, Item>::iterator)

On a different note, you do not have a guarantee whatsoever that objects in a map are laid out in a cache-friendly manner in RAM, so while you can iterate a map for rendering, it is quite possible that it's not the most optimal thing to do.

Thanks guys, it works now but i realized that if i have multiple player characters and one character is made up of multiple items, then i dont have a way to manage all the characters and move them individually on the screen. What should i do?

Something like this:


Item::Draw(sf::RenderTarget &renderTarget, sf::Vector2f characterPos)
{
     //Position the item before drawing.
     sf::Vector2f posToDrawItem = (characterPos + itemOffset);
     sprite.setPosition(posToDrawItem);
     
     //Draw the item.
     renderTarget.draw(sprite);
}

class Character
{
    public:
    
    void Draw(sf::RenderTarget &renderTarget)
    {
         //Draw the character first.
         renderTarget.draw(characterAppearance);
         
         //Draw all the equipped items over him.
         drawEquippedStuff(renderTarget, characterAppearance.getPosition());
    }
    
     private:
     void drawEquippedStuff(sf::RenderTarget &renderTarget, sf::Vector2f characterPos)
     {
          //For every equipped item.
          for(ItemID itemID : equippedItems)
          {
               //Look up the real item using the ID.
               const Item &item = AllTheItems[ItemID];

               //Draw that item.
               item.Draw(characterPos);
          }
     }

     private:
     sf::Sprite characterAppearance;
     std::vector<ItemID> equippedItems;
};

I managed to make a working character class but couldn't figure out how to store the items for each character.

Here is the class:


#ifndef _H_CHARACTER
#define _H_CHARACTER

#include "stdafx.h"
#include "ItemManager.h"

class Character
{
public:
	Character(std::string bodyName, int x, int y);

	void Draw(sf::RenderWindow* rw);
	void SetPosition(int x, int y);

private:
	void DrawItems(sf::RenderWindow* rw);

	ItemManager itm_mgr;
	Item body;

	int posX;
	int posY;
};

#endif

-


#include "Character.h"

Character::Character(std::string bodyName, int x, int y)
{
    body = itm_mgr.LoadItem(ITYPE_BODY, bodyName);
    posX = x;
    posY = y;
}

void Character::Draw(sf::RenderWindow* rw)
{
    body.itemSprite.setPosition(posX, posY);
    rw->draw(body.itemSprite);
}

void Character::DrawItems(sf::RenderWindow* rw)
{

}

void Character::SetPosition(int x, int y){posX = x; posY = y;}

I tried using std::map for the items but that dint work.

If you want a single object to be shared between multiple instances of a class, you need to make it a reference or a pointer.

Right now you are accidentally giving each "Character" their own ItemManager. This means each separate ItemManager doesn't share the same items.

Further, each character doesn't even need its own items, they need to share the same items between each other.

It should look more like this:


struct Point
{
    int x = 0;
    int y = 0;
};

struct ItemDetails
{
    ItemType type;
    std::string imagePath;
    sf::IntRect subrect; //The subrect of the image to use for this sprite.
    Point offset; //Offset from the character origin.
};

struct Item
{
    ItemDetails details;
    sf::Sprite sprite;
    
    void Draw(sf::RenderTarget &renderTarget, Point characterPosition)
    {
        Point finalPosition = (characterPosition + details.offset);
        sprite.setPosition(float(finalPosition.x), float(finalPosition.y));
        renderTarget.draw();
    }
};

typedef unsigned int ItemID;
const ItemID InvalidItemID = ItemID(-1);

//=============================================================================

class ItemRetriever
{
public:
    
    //Called somewhere during game startup, to fill 'this->items' with all the items.
    void LoadAllItemDetails();
    
    //Retrieves an item. It's returned as a const-reference.
    //It's a reference because: We don't want to actually COPY it, only SHARE it.
    //It's const because: We don't want to MODIFY it, only READ it.
    const Item &GetItem(ItemID itemID) const
    {
        return items.at(itemID);
    }
    
private:
    std::unordered_map<ItemID, Item> items;
};

//=============================================================================

class Character
{
public:
	Character(std::string bodyName, Point position, const ItemRetriever &itemRetriever)
        : position(position), itemRetriever(itemRetriever)
    {
        //...
    }

    void SetPosition(Point position);
    
    //We use a reference, not a pointer, because references indicate that the parameter is NOT optional.
    //Because it's not optional (we can't pass nullptr or 0 or NULL), we make the code safer and less likely to crash.
    //Also, we pass a 'RenderTarget' not 'RenderWindow'. 'RenderWindow' inherits from 'RenderTarget', so this is fine,
    //but it also makes the code more flexible for if in the future you want to implement more special graphical effects.
	void Draw(sf::RenderTarget &renderTarget) const;

private:
    void drawItems(sf::RenderTarget &renderTarget) const
    {
        //Draw the body (if we have one equipped):
        if(bodyID != InvalidItemID)
        {
            const Item &body = itemRetriever.GetItem(bodyID);
            body.Draw(renderTarget, position);
        }
        
        //Draw the hat (if we have one equipped):
        if(hatID != InvalidItemID)
        {
            itemRetriever.GetItem(hatID).Draw(renderTarget, position);
        }
        
        //Draw the shoe (if we have one equipped):
        if(shoeID != InvalidItemID)
        {
            itemRetriever.GetItem(shoeID).Draw(renderTarget, position);
        }
    }

private:
    //Another const-reference, because we want to READ-ONLY SHARE this instance.
    const ItemRetriever &itemRetriever;
    
    ItemID bodyID = InvalidItemID; //If the ID is invalid, that means 'no item'.
    ItemID hatID  = InvalidItemID;
    ItemID shoesID = InvalidItemID;
    
    sf::Sprite characterAppearance;
	Point position;
};

How can i make only one ItemManager object and then make it global so every class can access it?

I tried using extern in the stdafx.h but that dint end well..

This topic is closed to new replies.

Advertisement