I am struggling to replace the sprite I selected to another sprite.
Here is what I've got so far:
void Object::replaceSprite(const string & resourceName)
{
cocos2d::SpriteFrameCache * spriteFrameCache = cocos2d::SpriteFrameCache::getInstance();
cocos2d::SpriteFrame * spriteFrame = spriteFrameCache->getSpriteFrameByName(resourceName);
//mSprite->setTexture(spriteFrame->getTexture());
//mSprite->setDisplayFrame(spriteFrame);
mSprite->setSpriteFrame(resourceName);
}
As you can see, I tried different approach but none of them worked.
Also, I would like to ask if I do have to add the sprite again once I replace the frame onto the scene? What I am thinking right now is to create a new sprite every time I asked to replace it with a new one. But I do not know if there is more elegant and efficient way to do this.
Thank you!
setSpriteFrame accept a string or SpriteFrame* as argument
in your code
mSprite->setSpriteFrame(resourceName);
the argument is a string, you have no need to get frame from frameCache, but you must make sure that the frame exists in frameCache.
you can make breakpoint in the function, and check if spriteFrame is nullptr after
cocos2d::SpriteFrame * spriteFrame = spriteFrameCache->getSpriteFrameByName(resourceName);
Related
I try to use ClippingNode for my Cocos2d project, but due to some unknown reason it doesn`t work the proper way neither on Iphone, nor Android. Here is the code being used. The stencil is Label with string "7". Are there any mistakes or is it simply a Cocos2d problem?
auto colors = Sprite::create("colors.png");
colors->setContentSize(Size(nodeSize.width * 1.25, nodeSize.height * 1.25));
colors->setPosition(recordNumLbl->getPosition());
colors->setName("recordNum");
auto cropNode = ClippingNode::create(recordNumLbl);
cropNode->setGlobalZOrder(11);
cropNode->setName("cropNode");
cropNode->addChild(colors);
this->addChild(cropNode);
You can see the result I get on the first image, and what I try to get on the second one. Any help is highly appreciated!
https://i.stack.imgur.com/fZ9LX.png
https://i.stack.imgur.com/xH1hp.png
The global Z value needs to be exactly the same for any children of a clipping node. So, for the example you posted, you would need to set this:
colors->setGlobalZOrder(11);
Also, make sure the stencil you use (recordNumLbl?) is also set to a global Z of 11.
In my scene I have a vector with multiple custom sprites. When I tap on one of them, I want an action to be fired on another element on the scene, can be another sprite in the vector, or another node. I have been researching the best way to do this, but I'm not quite sure how to implement it. The options are:
Add a touch listener to the scene, and verify if it was tapped inside the bounds of the sprite with rect. containsPoint(point). And after that, I have to get the sprite that was tapped to do the action I want. For me, it doesn't seems very clean to do it this way. And if two sprites are overlaped, I have to verify if the sprite is behind or in the front in order to retrieve the desired sprite. I followed this example: Touch Event example
Add a touch listener in the subclass of the sprite (my custom sprite). And add onTouchBegan and onTouchEnded inside it. But this way, I don't know how to modify an attribute of another sprite, or another element in the scene (Is it possible to use Delegates like Objective-C does?). I followed this example: Subclass Sprite Example
My main problem is that I don't understand very well how to make a node interact with another node in the scene. I have seen a lot of tutorials, but in all of them, when you interact with a node, it only changes its attributes, not other nodes' attributes.
Thanks in advance.
I shall propose "EventCustom" way :)
You can add in your touchBegan / touchEnded methods (wherever you put them... you got the point...) additional code for passing an EventCusto to the _eventDispatcher and get it announced to the world ;)
EventCustom *e = new EventCustom("MyAwesomeEvent");
e->setUserData(ptrMyFantasticData); //This function takes a void pointer. cheers :)
_eventDispatcher->dispatchEvent(e);
You may subclass the EventCustom class but that is hardly necessary. You can always hang an object to it with setUserData().
Now the objects which need to react to the event can listen to it by
_myCustomListener = EventListenerCustom::create(
"MyAwesomeEvent",
CC_CALLBACK_1(
ListeningClass::onMyAwesomeEvent,
this
)
);
_eventDispatcher->addEventListenerWithXXXXXPriority(_myCustomListener, XXX);
//ScreenGraphPriority / FixedPriority depends on situation. Either should work.
It's always a good practice to remove your listeners when you go off, so somewhere, perhaps in onExit(), where you removed touch listeners remove this listener too, as
_eventDispatcher->removeEventListener(_myCustomListener);
Going a bit off the track, a clarification:
CC_CALLBACK_X are a bit tricky names. The X indicates the no. of args the target function will get. Here, event dispatcher will pass 1 arg i.e. ptr to object of EventCustom you handed it, so we use CC_CALLBACK_1. The next arg - here "this" - is the object on which the method will be invoked.
In short, we may say that this callback is going to result into a function call this->onMyAwesomeEvent(e);
For CC_CALLBACK_2 onwards, we can specify additional args, 3rd arg onwards.
Coming back to the issue at hand, ListeningClass::onMyAwesomeEvent will look something like
void ListeningClass::onMyAwesomeEvent(EventCustom *e)
{
MyFantasticData *d = (MyFantasticData *) e->getUserData();
CCLOG("[ListeningClass::onMyAwesomeEvent] %d", d->getMyPreciousInt());
}
Hope it helps :)
Set your elements tags or names with setTag and setName. Then if element x is touched, get them with getChildByTag or getChildByName and do what you need to do.
With the second option you list above.
To make a node interact with another node in the scene you can add touch callback function to your custom sprite object like that:
https://github.com/Longpc/RTS/tree/master/Classes/base/dialogBase
and in main scene you can define function to handle this callback. So you can do every thing to unit in you scene
I'm trying to make a custom sprite, which could receive touch and handle the function as callback.
Okay, first step, receive the touch, easy, we can search it online everywhere.
The one I couldn't do is, I want to make it receive SOMETHING in the class the sprite is created, a function that will be called when the sprite is touched.
I was searching on internet and I think (not really sure) that SEL_Callfunc can do what I want, but I couldn't understand how this one work, so can you guys give me an example please?
For example, my custom class is BSprite, so when I create new object in HelloWorld.cpp, it should be
BSprite* sprite = BSprite::create("HelloWorld.png",HelloWorld::TouchCallback);
Thanks for reading :)
sprite->addTouchEventListener(CC_CALLBACK_0(HelloWorld::onTouchSprite, this));
void HelloWorld::onTouchSprite() {
}
Note: onTouchSprite method should not have any parameters
I am trying to show slowly one char from string ,like novel game.
To do it,at first I wrote the code as follows using CCLabelBMFont to extract one char from string.
string str = "I like an apple";
CCLabelBMFont *label = CCLabelBMFont::create(str.c_str(), "font.fnt");
CCSprite *spr = (CCSprite*)label1->getChildByTag(0);
spr->setPosition(ccp(100, 100));
this->addChild(spr);
I want to show spr that are extracted from CCLabelBMFont in GameScene(this)
But I am getting the error as follows
CCAssert( child->m_pParent == NULL, "child already added. It can't be added again");
Why did such the error appear ? and How should I do?
Look at the method you are using and the error you get. You use getChildByTag(...) to get your sprite. So what you get is a child node of your CCLabelBMFont, meaning it has a parent. Your error says that this sprite cannot be added again as a child, because it already has a parent.
I can't think of a straigthforward and sure way to achieve what you want, but here are some suggestions you may try :
Adding each letter as a seperate label - to be honest, this is really dumb as it unnecessarily multiplies your code and would use too much memory.
Making the children of your label invisible, and them make them visible one by one. When you create the label, iterate through its children and call setVisible(false) on them. Then schedule a call in which you will call setVisible(true) on subsequent children.
Possibly creating subclass of LabelBMFont and/or creating a custom action may be the most flexible way, but propably not the easiest.
Let me know if anything is not clear!
I have already asked something similar but I can't figure out properly how to debug this. That's the question.
I added some Exceptions handler (catches all Objective-C) exceptions and that's the result of what I see:
The problem is with the setTexture method and it fails at the assertion verifying whether the texture name that needs to be displayed is the same as the one in the current sprite batch node.
This happens when trying to replace one scene with another but doesn't happen all the times. It has to do with the new scene as I tried to "isolate" the problem by calling the replace from a different part of the game and it still does give trouble.
In the game scene I have a couple of sprite sheets and sprite batch nodes but as I don't manage to isolate a sprite sheet id I cannot understand which sprite frame is giving me the problem, as well as I don't understand why this happens only sometimes.
I would like to:
understand which sprite frame name gives me the AssertionFailure
understand to which sprite sheet it belongs
This should help me to understand if it is a naming problem or if this has to do with something else.
Hoping not to be too lame with this question..
EDIT: I tried the answer but I am not able to read the 'fileName' information, here is what the debugger says "Summary unavailable":
That's how I create the filename property:
/** TMP: Bug solving filename */
#property (copy) NSString *fileName;
-(id) initWithTexture:(CCTexture2D*)texture rectInPixels:(CGRect)rect rotated:(BOOL)rotated offset:(CGPoint)offset originalSize:(CGSize)originalSize
{
if( (self=[super init]) )
{
self.fileName = [NSString stringWithFormat:#"GLUINT texture name: %i", texture.name];
self.texture = texture;
rectInPixels_ = rect;
rect_ = CC_RECT_PIXELS_TO_POINTS( rect );
offsetInPixels_ = offset;
offset_ = CC_POINT_PIXELS_TO_POINTS( offsetInPixels_ );
originalSizeInPixels_ = originalSize;
originalSize_ = CC_SIZE_PIXELS_TO_POINTS( originalSizeInPixels_ );
rotated_ = rotated;
}
return self;
}
-(id) initWithTextureFilename:(NSString *)filename rectInPixels:(CGRect)rect rotated:(BOOL)rotated offset:(CGPoint)offset originalSize:(CGSize)originalSize
{
if( (self=[super init]) )
{
self.fileName = fileName; //TMP
texture_ = nil;
textureFilename_ = [filename copy];
rectInPixels_ = rect;
rect_ = CC_RECT_PIXELS_TO_POINTS( rect );
offsetInPixels_ = offset;
offset_ = CC_POINT_PIXELS_TO_POINTS( offsetInPixels_ );
originalSizeInPixels_ = originalSize;
originalSize_ = CC_SIZE_PIXELS_TO_POINTS( originalSizeInPixels_ );
rotated_ = rotated;
}
return self;
}
In such cases logging is your friend. Every time you create a CCAnimate action (or CCAnimation) you should log something like this:
// during 'create sprite frames from sprite frame names' loop
NSLog(#"adding sprite frame name for CCAnimate: %#", spriteFrameName);
// after CCAnimate was created
NSLog(#"creating CCAnimate %# with sprite frames: %#, animate, arrayOfSpriteFrames);
You will probably want to add more detail, like which sprite frame names were added to that CCAnimate. You may also have to add additional logging if you cache CCAnimations and reuse them later (log each CCAnimation when reused).
Now when you receive that error you should select the [CCSprite setDisplayFrame:] method in the call stack. The debugger will then show you the value for the CCSpriteFrame it wants to set. Look for the pointer value, it will read something like 0x254fb22e.
Search for that value in your log, this will bring you back to one of the "creating CCAnimate.." logs. From the log lines above you can see the sprite frame names it contains. Since you also logged the 'arrayOfSpriteFrames' you can get their pointer value, compare it with the pointer value of the CCSpriteFrame that caused the assertion.
When you have a match, and it's the fourth item in the sprite frame array, just look up the name of the fourth sprite frame name added to the CCAnimate.
There may be a quicker way to do this depending on which information is available in the debugger (and how well versed you are in debugging), but this is one approach that will definitely lead you to the offending sprite frame name in relatively short time.
Note that the pointer values are not unique - it's possible that a different CCAnimate was created with the same pointer value. Especially if there's a high frequence of CCAnimate playing and stopping it can happen that another CCAnimate is allocated at the exact same memory location of a previous one. So be wary if the results don't seem to match up. A quick way to find out is to test on different devices or Simulator vs. Device, as pointer values and allocation strategies vary.
You don't need to log which sprite frame name belongs to which texture atlas. Simply open the plist of each atlas and search for the sprite frame name. The plist is an XML file.
Tip: common cause for this problem can be having the same sprite frame name in two different texture atlases - cocos2d may use either texture when you request a sprite frame with a duplicate name.
Another Tip: if the logging seems daunting, I'd simply do the following:
open source code for CCSpriteFrame class
add a NSString* property 'filename' with 'copy' attribute
every time you create a CCSpriteFrame object, assign the filename to the CCSpriteFrame
Now whenever you see a CCSpriteFrame in the debugger, it'll show you the associated filename in the debugger view.
ok , looks like you have been there before ... Comment the first NSAssert, uncomment the if block. Then put a break point on the newly uncommented NSAssert. Execution will pause BEFORE the exception, and you should be to inspect the iVars of each class instance in the call stack (at least i can with AppCode, hope xCode allows that). Maybe you will get enough of a hint to figure out which texture/animation/batchnode is giving you a hard time.
Also ... fyi. Whenever i start a project with cocos2d (any version) , i apply some 'standard' fixes to it so that my life will be easier for debugging. A standard 'patch' is to add a name to CCNode, which i set to some meaningful value : always. Also i override the descpription method to return MY name. Assigning meaningful names has helped me many times, especially when walking down (or up) the node hierarchy to figure out a bug. I also nuke many of the NSAsserts and whenever possible (especially in ctors), return nil and log an error message. As per Stefen's suggestion, if a cocos2d ctor returns me a nil, i spew out an error log message. I can then break on that log statement and drill-down the issue.