I am making a game with C++ and SFML, and I am having a problem during rendering various items with array. When I try to draw an array of sprites, the debugging is alright, and there are no warning and errors, but my game program runs only 20 seconds, then it stops running, and says, 'Unhandled exception at 0x7C50CF2E(sfml-graphics-d-2.dll), ECOVID-19 SFML.exe): 0xC0000005: Access violation reading location 0xCCCCCCD0.' I did everything I can, but I don't know why this exception occurs.
Here are my codes that I suspect the causation of the error.
Thank you for reading in spite of my poor English.
#include <SFML/Graphics.hpp>
...
using namespace std;
using namespace sf;
...
int main () {
...
//item Sprites
Texture bombTex;
bombTex.loadFromFile("images/bomb.png");
Sprite bomb;
...
Texture bomb2Tex;
bomb2Tex.loadFromFile("images/bomb_2.png");
Sprite bomb_2;
...
Texture cakeTex;
cakeTex.loadFromFile("images/cake.png");
Sprite cake;
...
Texture coffeeTex;
coffeeTex.loadFromFile("images/coffee.png");
Sprite coffee;
...
Texture chickenTex;
chickenTex.loadFromFile("images/chicken.png");
Sprite chicken;
...
Texture pizzaTex;
pizzaTex.loadFromFile("images/pizza.png");
Sprite pizza;
//item array (I made an item array to display & render various items in the game screen.)
Sprite item[10]; //Although I change the array size to 4 or 5, the same exception occurs.
item[0] = bomb;
item[1] = coffee;
item[2] = bomb_2;
item[3] = chicken;
item[4] = pizza;
std::vector<Sprite> items;
items.push_back(Sprite(item[4]));
...
while (window.isOpen())
{ ...
...
for (size_t i = 0; i < items.size(); i++)
{
if (humanArr[index].getGlobalBounds().intersects(item[i].getGlobalBounds()))
//humanArr[index] is a player Sprite.
{
...
items.erase(items.begin() + i);
}
}
...
window.clear();
...
for (size_t i = 0; i < items.size(); i++)
{
window.draw(item[i]); // <- the exception error occurs here.
}
...
window.display();
}
return 0;
}
What may be happening is that when you copy the Sprite item[10]; to the std::vector<Sprite> items; the Sprite class is making a shallow copy. It means that if the Sprite class is allocating any memory with new operator and storing it in a member pointer, then the shallow copy will only copy the address to which the pointer is pointing. When you do call items.erase(items.begin() + i); the destructor of the Sprite will be called, and in the destructor it may be calling delete in that pointer to some resourse.
When you call window.draw(item[i]); the library will try to use that resource and will find an invalid address.
What I suggest is that you don't use the Sprite item[10]; and only the std::vector<Sprite> items; , like this:
std::vector<Sprite> items;
items.push_back(bomb);
items.push_back(coffee);
...
window.draw(items[i]);
You don't need to use an intermediate array, just the std::vector<Sprite>;
Related
I am making a game with C++ and SFML, and I have a serious problem with it.
What I want to make is, when my player(a.k.a. human character) collides with one item, then only that item should be erased. For example, when player collides with 'chicken' item, then only 'chicken' item should be erased, not other items.
However, when I run my program, and when player collides with 'chicken' item, then other items are all erased. And I don't know why. I really need your help. In spite of my poor English, thanks for reading!
And here's my code :
#include <SFML/Graphics.hpp>
...
using namespace std;
using namespace sf;
...
int main () {
...
//item Sprites
Texture bombTex;
bombTex.loadFromFile("images/bomb.png");
Sprite bomb;
...
Texture bomb2Tex;
bomb2Tex.loadFromFile("images/bomb_2.png");
Sprite bomb_2;
...
Texture cakeTex;
cakeTex.loadFromFile("images/cake.png");
Sprite cake;
...
Texture coffeeTex;
coffeeTex.loadFromFile("images/coffee.png");
Sprite coffee;
...
Texture chickenTex;
chickenTex.loadFromFile("images/chicken.png");
Sprite chicken;
...
Texture pizzaTex;
pizzaTex.loadFromFile("images/pizza.png");
Sprite pizza;
//item array (I made an item array to display & render various items in the game screen.)
Sprite item[10];
item[0] = bomb;
item[1] = coffee;
item[2] = bomb_2;
item[3] = chicken;
item[4] = pizza;
std::vector<Sprite> items;
items.push_back(Sprite(item[4]));
...
while (window.isOpen())
{ ...
...
for (size_t i = 0; i < items.size(); i++)
{
if (humanArr[index].getGlobalBounds().intersects(item[i].getGlobalBounds()))
//humanArr[index] is a player Sprite.
{
...
items.erase(items.begin());
}
}
...
window.clear();
...
for (size_t i = 0; i < items.size(); i++)
{
window.draw(item[i]);
}
...
window.display();
}
return 0;
}
for (size_t i = 0; i < items.size(); i++)
{
// first problem: you are accessing item[i] instead of items[i]
if (humanArr[index].getGlobalBounds().intersects(item[i].getGlobalBounds()))
{
...
items.erase(items.begin()); // <---------- second problem is here
}
}
You are not erasing the item you are iterating over, you are instead erasing the first item. You are also accessing the wrong array. I recommend changing the name of item to avoid this in the future. Here is how you solve this:
// Pull human bounds out of the loop so that we dont' access them each
// iteration.
const humanBounds = humanArr[index];
for (auto iter = items.begin(); iter != items.end();)
{
if (humanBounds.intersects(iter->getGlobalBounds()))
{
iter = items.erase(iter);
}
else
{
++iter;
}
}
See std::vector::erase for more information.
I'm making a game, and I have created a class to store the map. I have a function to create a single sprite on which is drawn all the visible map. I've used a RenderTexture and then I create and return a sprite created with it. However, the sprite is completely white.
Here is the code for the map generation and the sprite draw
void Map::procedural_generate()
{
cout << "Starting the map generation" << endl;
int generation_max = m_size.x * m_size.y,
current_generation = 0;
vector<Decor> decor_vector;
m_decor_vector.clear();
const vector<Decor> const_vector
{
Decor(GRASS)
};
for (int i = 0; i < m_size.x; i++)
{
decor_vector.clear();
for (int j = 0; j < m_size.y; j++)
{
decor_vector.push_back(const_vector[GRASS]);
decor_vector[j].set_position(Vector2f(i * 32, j * 32));
current_generation++;
cout << "Generation : " << current_generation << '/' << generation_max << '\r';
}
m_decor_vector.push_back(decor_vector);
decor_vector.clear();
}
cout << "Map generation has ended" << endl;
}
Sprite Map::sprite()
{
RenderTexture map;
if (!map.create(WINDOW_WIDTH, WINDOW_HEIGTH))
cout << "Map : Unable to create the RenderTexture" << endl;
map.clear(Color::Green);
for (int i = 0; i < m_size.x; i++)
for (int j = 0; j < m_size.y; j++)
map.draw(m_decor_vector[i][j].sprite());
map.display();
Sprite sprite(map.getTexture());
return sprite;
}
The problem seems to come from the map.draw part, as, if I use map.clear(Color::Red) before the double loop, the sprite stays white, but if I use sprite.setColor(Color::Red), it works. The fact is that the decor sprites are not covering the whole texture (the texture is 1920x1080, and the sprites 320x320), so I can't understand what's happening.
This is not the decor sprite loading, if I use map.draw(Decor(GRASS)) the sprite is displayed correctly.
I've tried to use pointer for const_vector and for the returned sprite, whithout success.
Popular mistake when using SFML.
Sprite Map::sprite()
{
RenderTexture map;
// ...
Sprite sprite(map.getTexture());
return sprite;
}
map is local. When function ends, map is destroyed. Sprite takes texture of map as shallow copy, it is only pointer to texture, according to official tutorial/documentation:
The white square problem
You successfully loaded a texture, constructed a sprite correctly, and... all you see on your screen now
is a white square. What happened?
This is a common mistake. When you set the texture of a sprite, all it
does internally is store a pointer to the texture instance. Therefore,
if the texture is destroyed or moves elsewhere in memory, the sprite
ends up with an invalid texture pointer.
So, sprite returned by copy stores dangling pointer. It is just undefined behaviour.
Related (my) answer, posted 1 day ago
Solution: you have to wrap sprites with textures in some kind of deep-copy-able class.
You cannot rely on shallow defaulted generated copy operations.
Such a class could look like:
class TexturedSprite {
public:
sf::Sprite sprite;
sf::Texture texture;
TexturedSprite() {
init(); // load texture
}
void init() {
// load texture
texture.loadFromFile("texture1.png");
sprite.setTexture(texture);
sprite.setPosition(0,0);
}
TexturedSprite(const TexturedSprite& theOther) {
texture = theOther.texture; // deep copy
sprite.setTexture(texture);
}
TexturedSprite& operator=(const TexturedSprite& theOther) {
if (this == &theOther)
return *this;
texture = theOther.texture; // deep copy
sprite.setTexture(texture);
return *this;
}
};
then the code:
TexturedSprite fooMain;
{
TexturedSprite foo; // local
fooMain = foo;
} // foo is destroyed, but = did deep copy of texture
is safe.
I try to have a map where the key is an enum, and the value an array of SDL_Rect (which contains) all the coords for the animation.
std::map<Animation, SDL_Rect*> animations;
Animation currAnim;
I dont want to define a fixed number of animation like a solution I found here. How can I do ? Because
SDL_Rect right[3];
right[0].x = 264;
right[0].y = 0;
right[0].w = 36;
right[0].h = 64;
...
animations.insert(std::pair<Animation, SDL_Rect*>(Animation::MoveRight, right));
doesn't work.
I tried this but know I have another problem in my draw, where I must find the element of the vector with an int (frame) :
void Player::Draw(SDL_Renderer * renderer, int frame)
{
std::vector<SDL_Rect>::iterator it = std::find(animator->GetAnimations()[animator->GetCurrAnim()].begin(),
animator->GetAnimations()[animator->GetCurrAnim()].end(), frame);
SDL_RenderCopy(renderer, lTexture->GetTexture(), &*it, &rect);
}
I tried this but there is an error which said
'==' There is no operator found which accept type "SDL_Rect"
I would guess that the array you're using goes out of scope, thus the pointer you stored in the map is no longer valid.
Since you didn't post all of your code, one suggestion to you is to not store pointers as the data in the map. Instead, use std::vector.
#include <map>
#include <vector>
typedef std::map<Animation, std::vector<SDL_Rect>> AnimationMap;
//...
AnimationMap animations;
//...
std::vector<SDL_Rect> right(3);
right[0].x = 264;
right[0].y = 0;
right[0].w = 36;
right[0].h = 64;
//...
animations.insert(std::make_pair(Animation::MoveRight, right));
//...
If the vector right goes out of scope, the map is still ok, as a copy of the vector is placed in the data portion of the map.
I try to create a tiled base map by using Qt GUI:
I have two classes: one to set my tile images that inherits from QGraphicPixmapItem, one to load my map from a text file that is a 2D array composed of the numbers 1, 0 and 2, and add it to a scene:
But my program quits unexpectedly and I do not know where is the break:
my tile class:
class mapTile: public QGraphicsPixmapItem
{
public:
enum Tile {DOOR, GRASS, FIRE};
mapTile(): QGraphicsPixmapItem(),currentTile(GRASS){
syncBitmap();
}
Tile getTile() const {return currentTile;}
void setTile(Tile newTile){
if(currentTile!= newTile){
currentTile=newTile;
syncBitmap();
}
}
private:
void syncBitmap() {//Set my image to my tile
switch(currentTile) {
case DOOR:
image->setPixmap(QPixmap(":/mpa/castledoors.png"));
case GRASS:
image->setPixmap(QPixmap(":/map/grass3_blur.jpg"));
case FIRE:
image->setPixmap(QPixmap(":/map/feu1/png"));
}
}
Tile currentTile;
QGraphicsPixmapItem *image;
};
my class map:
Map.h:
class Map
{
public:
static const int TILE_SIZE=20; //value of the tile in pixels!!!->20X20=a tile
void paintMap(QGraphicsScene *scene);
Map();
private:
static const int WIDTH= 13;// width of the grid cell
static const int HEIGHT= 9;//height of the grid cell
//my grid cell map !!!!
int array[WIDTH][HEIGHT];
mapTile *tile; //my tile
};
And Map.cpp
/*Create a default Map*/
Map::Map()
{
QFile myfile("path to my file");
if (!myfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::information(0, "error", myfile.errorString());
}
QTextStream in(&myfile);
QString line ;
QStringList fields;
int i=0;
int j=0;
while (!in.atEnd()) {
//Fill my array with my list--> convert Qtring into Int
for (i=0; i<HEIGHT; i++ ) {
line = in.readLine();
fields = line.split(" ");
for(j=0;j<WIDTH;j++)
{
//cout<<fields[j].toInt();
array[i][j] = fields[j].toInt();
}
}
}
}
//Paint my map with my tile
void Map::paintMap(QGraphicsScene *scene){
int i=0, j=0;
tile= new mapTile();
for (i=0; i<HEIGHT; i++){
for(j=0; j<WIDTH; j++){
switch(array[i][j]){
case 0:
tile->setTile(mapTile::GRASS);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 1:
tile->setTile(mapTile::FIRE);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 2:
tile->setTile(mapTile::DOOR);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
}
i+=TILE_SIZE;//
}
}
}
and finally my mainwindow (I directly give the file.cpp):
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
myMap= new Map;
scene= new QGraphicsScene(this);
myMap->paintMap(scene);
ui->graphicsView->setScene(scene);
}
MainWindow::~MainWindow()
{
delete ui;
}
Sorry guys for the long post but I am stuck!
Any ideas on what I missed?
Well, there are at least five problems in your code:
You can't share the same myTile instance when adding them to the scene. You need to create a new one each time you're adding a tile to the scene. At the moment you're creating only one instance at the start of Map::paintMap.
Your myTile class inherits from QGraphicsPixmapItem, however it also has a QGraphicsPixmapItem *image; member which it leaves uninitialized (so it's a roaming pointer), but then it uses it in image->setPixmap(QPixmap(":/mpa/castledoors.png")); which will crash. Instead, you'll want to just call setPixmap(QPixmap(":/mpa/castledoors.png")) (calling the function of your superclass).
In the above item you may have noticed that you misspelled "map" as "mpa", though this will not be the reason of your crash.
In mapTile::syncPixmap as well as in Map::paintMap, you forgot the break; statements in the switch. Without those, all tiles would appear like fire tiles.
You're adding your tile size to the i and j iterators, as if they were the pixel coordinates, but at the same time you're using them as array indexes. You need to either use separate variables for the pixel coordinates or multiply i and j by the TILE_SIZE when calling setPos instead.
Good luck!
I have finally found solutions to my problems:
I added a scene to my function paintMap for adding each item to my scene and created a QGraphicScene* getScene() that returns the scene then I just call it my mainWindow by just writing
ui->graphicsView->setScene(myMap->getScene())
Moreover in my mapTile class I don't use anymore the function setTile(). I removed it, then changed my QGraphicPixmapItem *image by a QPixmap image and in my function syncbitmap() I did
this->setPixmap(image.scaled(TILE_SIZE,TILE_SIZE));at the end of my switch! Why? Because my tile is already a QGraphicsPixmapItem (inheritance) so I just have to set a pixmap to it! And I put it at the end of the switch because if I set the image before, the image will never change because it will be already set! And finally in the constructor I removed currentTile(GRASS), added a Tile variable in my constructor as an argument and just put currentTile= name of your variable in the constructor and then called the syncBitmap function. So in paintMap() you just have to do:
tile= new mapTile(mapTile::GRASS);
tile->setPos(j*TILE_SIZE,i*TILE_SIZE);
scene->addItem(tile);
in each case! VoilĂ !
I hope that will help a newbie like me, at least to understand the logic behind! I am not sure that is the best way to do but this way works for me! :)
I'm currently starting with cocos2d-x to build games for blackberry/android/iOS.
I have the png and plist for the animations of a character created with texturepacker. I load them with CCSpriteBatchNode and CCSpriteFrameCache then I use a function created by me that loads all frames into an array of frames, then create a CCAnimation object and store the CCAnimate object created with the animation (code is more clear) the thing is that I have a function that detect touches and it is supposed to cycle through all animations, but it always crashes.
here is some code (this goes in the init()):
_batchNode = CCSpriteBatchNode::create("Character/girl.png");
_cache = CCSpriteFrameCache::sharedSpriteFrameCache();
_cache->addSpriteFramesWithFile("Personajes/girl.plist");
_character = CCSprite::createWithSpriteFrameName("girlneutral1.png");
_character->setPosition(ccp(winSize.width * 0.1, winSize.height * 0.5));
_batchNode->addChild(_character, 1);
this->addChild(_batchNode);
createAnimation(0, "girlpush", 8, 0.15f);
createAnimation(1, "girlneutral", 4, 0.3f);
createAnimation(2, "girlcrash", 12, 0.3f);
createAnimation(3, "girljump", 12, 0.2f);
createAnimation(4, "girltrick", 12, 0.3f);
_character->runAction(CCRepeatForever::create( _charanimation[3]));
this->setTouchEnabled(true);
the function that loads the animations (_charanimation[] is just an array of CCAnimate):
void HelloWorld::createAnimation(int a, CCString animation_name, int frames, float delay)
{
CCArray* animframes = CCArray::createWithCapacity(frames);
char str[100] = {0};
for(int i = 1; i <= frames; i++)
{
sprintf(str, "%s%d.png", animation_name.getCString(), i);
CCSpriteFrame* frame = _cache->spriteFrameByName( str );
animframes->addObject(frame);
}
_charanimation[a] = CCAnimate::create(CCAnimation::createWithSpriteFrames(animframes, delay));
//_charanimation[a]->getAnimation()->setLoops(-1);
}
and I get the animation to work (the one I set with runAction()) but if I try to change the animation, for example, when I touch the screen:
void HelloWorld::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
{
action++;
action%=5;
_character->stopAllActions();
_character->runAction( CCRepeatForever::create(_charanimation[action]));
char str[100];
sprintf(str, "Animation: %d", action);
pLabel->setString(str);
}
it crashes... I don't know if I am doing it wrong, if anyone could help, I will appreciate.
If I change the animation in runAction() it shows the animation correctly, but I can't change ingame with touches.
by the way, this is the error I get in console:
cocos2d-x debug info Assert failed: reference count should greater than 0
In function retain -- ..\..\cocoa\CCObject.cpp:92 m_uReference > 0 -- assertion failed
Its because the CCAnimate object you have created is autorelease object and you are not retaining the object. Autorelease objects will be deleted automatically if they are not retained, either explicitly or by some other object.
While adding to array You can do
CCAnimate *animate = CCAnimate::create(CCAnimation::createWithSpriteFrames(animframes, delay));
animate->retain();
_charanimation[a] = animate;
do not forget to release all the objects in the array when everything is over
_charanimation[index]->release();
Note:
Instead of using the simple C or C++ array you can use Cocos2d's CCArray which retains the object once it is added to the array.
For Example: (The memory handling is similar to Objective-C)
_charanimation = new CCArray();
//To add object to array
_charanimation->addObject(object); //This will retain the object
//To get an object
_charanimation->objectAtIndex(index);
_charanimation->lastObject();
_charanimation->randomObject();
//To remove object
_charanimation->removeObjectAtIndex(index); //This will release object
_charanimation->removeObject(object); //This will release object
//Dont forget delete array later
delete (_charanimation);
You can refer this link for more on CCArray