How to refresh sprites (remove and recreate it again) - c++

I am trying to make a day-night background mode in my game and I want to create a control button in a option dialog that when I click on it, all background are change without exiting the dialog. I have just made it run OK by re-open the scene but it also quit the option dialog.
I have an initBackground() method like this
void MenuScene::initMenuBackground() {
setBackgroundMode();
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
//calculate system hour time
time_t now = time(0); // get time now
tm * ltm = localtime(&now);
double hour = ltm->tm_hour;
int curHourTime = (int) hour;
CCLOG("MenuScene hour is: %dh", curHourTime);
CCAnimation* caveAnim1 = CCAnimation::createWithSpriteFrames (senspark::Utils::createFramesArray("cave-%d.png", 3), 0.2f);
CCAnimation* caveAnim2 = CCAnimation::createWithSpriteFrames(senspark::Utils::createFramesArray("cave-%d.png", 3, 0, true), 0.2f);
cloudSpr->runAction(CCRepeatForever::create(CCSequence::create(CCMoveTo::create(30, ccp(winSize.width+100, cloudSpr->getPositionY())),
CCMoveTo::create(0, ccp(-100, cloudSpr->getPositionY())),
NULL)));
cloudNightSpr->runAction(CCRepeatForever::create(CCSequence::create(CCMoveTo::create(30, ccp(winSize.width+100, cloudSpr->getPositionY())),
CCMoveTo::create(0, ccp(-100, cloudSpr->getPositionY())),
NULL)));
//night
if ( ((curHourTime < 6 || curHourTime > 18) && (_isAuto==true)) || _isNight==true) {
caveNightSpr->runAction(CCRepeatForever::create(CCSequence::create(CCAnimate::create(caveAnim1),
CCAnimate::create(caveAnim2),
CCDelayTime::create(0.2f),
NULL)));
cloudSpr->setVisible(false);
startGoldSpr->setVisible(false);
backgroundSpr->setVisible(false);
backgroundSkySpr->setVisible(false);
backgroundNightSpr->setScaleX(CCDirector::sharedDirector()->getWinSize().width/designSize.width);
backgroundSkyNightSpr->setScaleX(CCDirector::sharedDirector()->getWinSize().width/designSize.width);
}
//daytime
if ( ((curHourTime > 6 && curHourTime < 18) && (_isAuto==true)) || _isDay==true) {
caveSpr->runAction(CCRepeatForever::create(CCSequence::create(CCAnimate::create(caveAnim1),
CCAnimate::create(caveAnim2),
CCDelayTime::create(0.2f),
NULL)));
cloudNightSpr->setVisible(false);
startGoldNightSpr->setVisible(false);
backgroundNightSpr->setVisible(false);
backgroundSkyNightSpr->setVisible(false);
backgroundSpr->setScaleX(CCDirector::sharedDirector()->getWinSize().width/designSize.width);
backgroundSkySpr->setScaleX(CCDirector::sharedDirector()->getWinSize().width/designSize.width);
}
and I don't know how to refresh these Sprite (remove and then recall them again).
Sorry for my bad English. Any help would be appreciated.

From what i understand from your question, you need to remove sprite from the scene and add it again.
CCNode/Sprite has this method removeFromParentAndCleanup(bool cleanup)
Also you can remove all child from a CCNode/Sprite with
removeAllChildrenWithCleanup(bool cleanup).
Node/Sprite can be assigned special setTag(), which can used later to remove special Sprite with removeChildWithTag(). People generally have enums to identify key elements.
I hope that answers your question.
Refrences:
http://www.cocos2d-x.org/reference/native-cpp/V2.2.3/d9/d1f/group__base__nodes.html#ga5889948f4c40933e93aaf70cb8846192

Finally figure out my issue, it's very simple that I can change the sprite image by using mySprite->setTexture(CCTextureCache::sharedTextureCache()->addImage("newImage.png"));
no more need to remove and then add it again.

Related

Updating image UI with unity

I was trying to make a program that updated the amount of hearts the player has every turn (full, half and empty hearts). When I was doing this I instantiated a gameobject of that prefab as a variable and then assigned it to my UI panel in unity. However (I'm not sure but I think that) the variables used in the update just before are still being referenced after being destroyed in the next loop giving me the error:
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Here is the update loop:
void Update()
{
if (HP <= 0)
{
anim.SetBool("Death", true);
Destroy(GameObject.Find("Hearts"));
Destroy(GameObject.Find("Inventory"));
text.alignment = TextAnchor.LowerCenter;
text.text = "\n YOU DIED";
text.fontSize = 150;
text.color = new Color(255, 0, 0);
} else {
foreach (Transform child in GameObject.Find("Hearts").transform)
{
Destroy(child.gameObject);
}
for (var i = 0; i<(int)HP; i++)
{
GameObject Heart = Instantiate(heart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
Heart.transform.SetParent(GameObject.Find("Hearts").transform);
}
if (HP - (float)((int)HP) == 0.5F) {
GameObject HalfHeart = Instantiate(halfheart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
HalfHeart.transform.SetParent(GameObject.Find("Hearts").transform);
}
for (var i =0; i<Mathf.Floor(MaxHP-HP); i++)
{
GameObject EmptyHeart = Instantiate(emptyheart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
EmptyHeart.transform.SetParent(GameObject.Find("Hearts").transform);
}
}
Is there a way to instantiate a prefab without making a variable?, or a way to make the variable reference temporary so it only lasts one update?
Thank you for your help in advance!
The problem is that once HP goes below zero, every subsequent update will enter the first if-statement and try to delete the "Hearts" and "Inventory" objects over and over. You can solve this by adding a bool called isDead and change the statement to if (HP <= 0 && !isDead) and then set isDead = true inside the block. This will prevent it from entering it twice.
Frankly though, your way of solving things is entirely backwards. As others have pointed out, deleting and instantiating objects every frame is very inefficient, and Transform.Find is also slow. You don't really need to destroy anything at all - you can rather just have a list of hearts and enable/disable an appropriate amount whenever the HP changes. You can have a single half-heart at the end of the list and enable/disable it when appropriate - if you are using a HorizontalLayoutGroup, it will still align correctly. You might want to make it so that you can only change the HP using a property or function (something like ModifyHealth(float amount)), and put the logic for updating the hearts display in there.

Pause/Resume Action/Animation on Sprite in Cocos2d

Using Cocos2d-x and C++, I'm trying to play and pause an animation for a sprite.
I'm using version 3.15.1 of Cocos2dx.
I have a class called PlayerSprite which is derrived from the cocos2d::Sprite class. Inside PlayerSprite initialization, I've setup my animation with the following code:
SpriteBatchNode* playerSpriteBatch = SpriteBatchNode::create("player.png");
SpriteFrameCache* spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("player.plist");
Vector<SpriteFrame*> animFrames(2);
char str[18] = { 0 };
for (int i = 1; i < 3; i++) {
sprintf(str, "player_idle_%d.png", i);
SpriteFrame* frame = spriteFrameCache->getSpriteFrameByName(str);
animFrames.pushBack(frame);
}
Animation* idleAnim = Animation::createWithSpriteFrames(animFrames, 0.8f);
self->idleAction = self->runAction(RepeatForever::create(Animate::create(idleAnim)));
self->idleAction->setTag(0);
When I run the code, it works fine and the animation loops correctly.
In my void update() method, I am trying to pause/play the action/animation based of weather the player is moving or idle.
I do this with the following code:
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
this->runAction(idleAction);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
When I run this code now, my character falls, and as soon as he hits the gound, I get an error at this->runAction(idleAction); saying:
Access violation reading location 0xDDDDDDE5
I believe this is caused due to this->stopActionByTag(0) deleting the action pointer. I've tried to clone the action to avoid this but have had no success.
I know this is a bit late and you might already have solved this but here goes...
Your problem is that you cannot use one instance of Action (idleAction) multiple times. So, once you have run it and removed it, it is released and cannot be used. So, you have 2 options now,
Either create a new idleAction Action every time before running the action.
Or, have an idleAction retained and don't run it ever. Instead, create a clone of this idleAction and run a new clone each time. i.e.
idleAction->retain();
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
Action idleActionClone = idleAction->clone();
this->runAction(idleActionClone);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
Solution: call retain() to keep your action.
It's a matter of memory management of cocos2d-x.
In create() function of your RepeatForever class (derived from Ref), the reference count is set to 1 and there is a line of code ret->autorelease() before returning the object, which means this object will be released automatically at the end of this frame.
You called runAction() function the same frame you created it, the action is retained by ActionManager, it's reference count set to 2, and then 1 at the end of the frame (autorelease).
After your stopping it, it's released by ActionManager, reference count set to 0 and it's deleted. After this you use your action, it will be an access violation method.
*Edit: don't forget to release the action manually when PlayerSprite is deleted, or it's a leak.
When you stop action it's being recycled from memory. In order to play action once more, you have to recreate it. So you may just make a creator function, which returns your animation. The downside is you're recreating animation each time and it'll also play from the beginning (technically you can rewind it).
But I've developed a simpler technique to pause/resume animations:
Let's say you have an action:
action = MoveBy::create(5.0f, Vec2(50, 100));
Now, you can embed this action into Speed action like this:
action = Speed::create(MoveBy::create(5.0f, Vec2(50, 100)), 1.0f);
1.0f - is speed, so it's normal action rate. Now to pause just call:
action->setSpeed(0.0f);
and to resume:
action->setSpeed(1.0f);
you can also use different speed if you need it for some reason or another ;)

How to check if sprites are hit in special order

I have 3 0f 8 sprites on screen that I would like the reader to hit in order:
touching the egges first, then the sugar and finally the lemon (if done correctly a gold cup appears)
EggsSprite
SugarSprite
LemonSprite
the functions I have work but i would like to know if there an easier way to maintain and extend to other sprites onscreen (I have 8 sprites and hope to produce different "recipies")
ccTouchesBegan I detect touches on the sprites
if(CGRectContainsPoint(EggsSprite.boundingBox, location))
{
EggsSprite_is_hit = YES;
NSLog(#"EggsSprite_is_hit = YES");
}
if(CGRectContainsPoint(SugarSprite.boundingBox, location))
{
SugarSprite_is_hit = YES;
NSLog(#"RunCheckSugar");
[self CheckSugar];
}
if(CGRectContainsPoint(LemonSprite.boundingBox, location))
{
NSLog(#"RunCheckLemon");
LemonSprite_is_hit = YES;
[self CheckLemon];
}
this runs
-(void)CheckLemon
{
NSLog(#"LemonSprite is hit");
if(MeringueUnlocked == YES)
{
NSLog(#"You made a merangue");
Award.opacity=255;
}
else if(MeringueUnlocked == NO)
{
NSLog(#"Incorrect order");
EggsSprite_is_hit = NO;
LemonSprite_is_hit = NO;
SugarSprite_is_hit = NO;
MeringueUnlocked = NO;
}
}
-(void)CheckSugar
{
if(SugarSprite_is_hit == YES && EggsSprite_is_hit== YES)
{
NSLog(#"SugarSprite is hit");
NSLog(#"And Egg Sprite is hit");
SugarSprite_is_hit = NO;
MeringueUnlocked = YES;
}
else if(SugarSprite_is_hit == YES && EggsSprite_is_hit== YES)
{
NSLog(#"SugarSprite not hit ressetting all");
EggsSprite_is_hit = NO;
LemonSprite_is_hit = NO;
SugarSprite_is_hit = NO;
MeringueUnlocked = NO;
}
}
It seems to work ok, but it would be horrible to extend, I cant seem to find any examples on touching sprites in order so any psudo code ideas will be welcome :) as its the approach that Im more stuck on since I'm new to coding.
with thanks :)
N
When your sprites are created, assign to their 'tag' properties order in which they should be hit:
EggsSprite.tag = 1;
SugarSprite.tag = 2;
LemonSprite.tag = 3;
Then make an instance variable to store index of last sprite hit:
int _lastSpriteHitIndex;
and for index of last sprite in your sequence:
int _finalSpriteIndex;
Set its value somewhere in init (or whatever method you use to create your layer):
_finalSpriteIndex = 3;
Then in your touch handler:
// find sprite which was touched
// compare its tag with tag of last touched sprite
if (_lastSpriteHitIndex == touchedSprite.tag - 1)
{
// if it is next sprite in our planned order of sprites,
// store its tag as _lastSpriteHitIndex
_lastSpriteHitIndex = touchedSprite.tag;
}
else
{
// if it's wrong sprite, reset sequence
_lastSpriteHitIndex = 0;
}
if (_lastSpriteHitIndex == _finalSpriteIndex)
{
// Congrats! You hit sprites in correct order!
}
Basically it's a finite-state machine in which hitting sprites in correct order advances machine to next state, and hitting wrong sprite resets machine to initial state. _lastSpriteHitIndex repesents current state, _finalSpriteIndex represents final state.
If you don't want to reset to initial state on wrong sprite hit, just remove else clause - without it machine will simply not advance when hitting wrong sprite.

How to detect touch except the falling bodies from top in cocos2d-x ios game using c++

In my game there are certain zombies coming from top of the screen.I have stored all zombies sprites in an CCArray.Then using foreach loop I am making them falling down.
I just want to perform combo.It means that whenever I kill a zombie on tap, the combo_counter increases.
On killing two consecutive zombies the combo_counter goes to 2 but if I tap at any other location on the screen the combo_counter should go to 0.
So my problem is how to detect whether I have not tapped a zombie and tapped anyother place on the screen.I am attaching my code also of cctouchbegan method
zombies is a CCArray where all zombie sprites are stored
void Level1::ccTouchesBegan(cocos2d::CCSet *pTouch, cocos2d::CCEvent *pEvent)
{
CCTouch* touch = (CCTouch*)(pTouch->anyObject());
CCPoint location = touch->getLocationInView();
location = CCDirector::sharedDirector()->convertToGL(location);
CCObject* touchedzombie;
CCARRAY_FOREACH(zombies, touchedzombie)
{
if(!((CCSprite*) touchedzombie)->isVisible())
continue;
//
if(((CCSprite*)touchedzombie)==zombies->objectAtIndex(0))
{
// if((CCSprite*(touchedzombie)==zombies-))
if(touchedzombie!=NULL&&((CCSprite*)touchedzombie)->boundingBox().containsPoint(location))
{
this->setScoreonGame();
combo_counter++;
CCString *comboString=CCString::createWithFormat("comboX %d",combo_counter);
zombies_left--;
CCLOG("left = %d",zombies_left);
CCSize tt=((CCSprite*)touchedzombie)->getContentSize();
CCPoint pos_of_sprite=((CCSprite*)touchedzombie)->getPosition();
int rand_die1=Level1::random1();
CCString *str = CCString::createWithFormat("z2%d.png", rand_die1);
changedSprite = CCSprite::create(str->getCString());
CCLOG("Inside index 0");
((CCSprite*)touchedzombie)->setVisible(false);
changedSprite->setPositionX(pos_of_sprite.x);
changedSprite->setPositionY(pos_of_sprite.y);
changedSprite->setScaleX(Utils::getScaleX());
changedSprite->setScaleY(Utils::getScaleY());
this->addChild(changedSprite);
combo=CCLabelTTF::create(comboString->getCString(), "HoboStd", 50);
combo->setColor(ccRED);
combo->setPosition((ccp(changedSprite->getContentSize().width*0.50,changedSprite->getContentSize().height*1.05)));
changedSprite->addChild(combo,40);
this->runAction(CCSequence::create(delayAction,
callSelectorAction,
NULL));
this->removeChild( ((CCSprite*)touchedzombie),true);
this->Level1::reloadZombies();
// touchedzombie=NULL;
}
}
if(((CCSprite*)touchedzombie)==zombies->objectAtIndex(3))
{
// if((CCSprite*(touchedzombie)==zombies-))
if(touchedzombie!=NULL&&((CCSprite*)touchedzombie)->boundingBox().containsPoint(location))
{
// iftouched++;
this->setScoreonGame();
combo_counter++;
CCString *comboString=CCString::createWithFormat("comboX %d",combo_counter);
zombies_left--;
CCLOG("left = %d",zombies_left);
CCSize tt=((CCSprite*)touchedzombie)->getContentSize();
CCPoint pos_of_sprite=((CCSprite*)touchedzombie)->getPosition();
int rand_die1=Level1::random1();
CCString *str = CCString::createWithFormat("z2%d.png", rand_die1);
changedSprite3 = CCSprite::create(str->getCString());
// CCLOG("%s",str->getCString());
// CCLOG("Sprite Toucheddd");
CCLOG("Inside index 4");
// CCLog("width= %f height =%f",tt.width,tt.height);
// CCLog("x location =%f y location =%f",location.x,location.y);
// CCLog("Positon of Sprite X=%f Y=%f",pos_of_sprite.x,pos_of_sprite.y);
((CCSprite*)touchedzombie)->setVisible(false);
changedSprite3->setPositionX(pos_of_sprite.x);
changedSprite3->setPositionY(pos_of_sprite.y);
changedSprite3->setScaleX(Utils::getScaleX());
changedSprite3->setScaleY(Utils::getScaleY());
this->addChild(changedSprite3);
combo=CCLabelTTF::create(comboString->getCString(), "HoboStd", 50);
combo->setColor(ccRED);
combo->setPosition((ccp(changedSprite3->getContentSize().width*0.50,changedSprite3->getContentSize().height*1.05)));
changedSprite3->addChild(combo,40);
this->runAction(CCSequence::create(delayAction,
callSelectorAction3,
NULL));
this->removeChild( ((CCSprite*)touchedzombie),true);
this->Level1::reloadZombies();
touchedzombie=NULL;
}
//..upto 9 indexes...
}
}
First of all, it is not neccesary to do this checks : if(((CCSprite*)touchedzombie)==zombies->objectAtIndex(0))
How CCARRAY_FOREACH works, is it takes each object from the provided CCArray and assigns it to your touchedZombie variable - this means that if there are 10 elements in the array, this code will be run 10 times (exactly!). This means that with the first run, you will fall into the first if check (the one with objectAtIndex(0), with the second it will fall into the if check with objectAtIndex(1). Removing this if's not will not only speed up your function, but also tidy it up.
This would save you a lot of space, plus if you wanted to change something you would only have to do it in one place, which is safer.
Ok, to the problem at hand :
I see two solutions to this :
Leaving your code : you should move the combo code from the if blocks, and replace it with a flag. This flag should be set to false at the beginning of ccToucheBegan, and if you you detect a touch on a zombie, set it to true. Then after the CCARRAY_FOREACH block, this flag will tell you if there was a tap on a zombie or not. Change your combo accordingly.
Changing your code : You could also make the zombies CCMenuItemImages - this way they would have a different callback function than the rest of the screen. So whenever the ccTouchBegan method would be fired, you will know that it wasn't a zombie that was touched.
I hope everything is clear, if not - let me know.

How to stop a character from going out of the screen?

i'm making this game where a ninja is supposed to go up and down. I wrote a method for a button to do so but the problem is that when the ninja is at the top of the screen (landscape)
it still goes up when i touch the up button so, i did this
-(void)upPressed:(id)sender
{
if(CGPointEqualToPoint(ninja.position, ccp(0,280)))
{
id standStill = [CCMoveBy actionWithDuration:0 position:ccp(0,0)];
[ninja runAction:standStill];
}else
{
id moveUp = [CCMoveBy actionWithDuration:.1 position:ccp(0,80)];
[ninja runAction:moveUp];
}
}
and the problem still exists. any help?
i.e when the ninja is at (0,280), i want the up button to do nothing
You are testing for equality. This if condition will only be true if the ninja is exactly at {0, 200}.
Try this instead:
if (ninja.position.y < 280)
{
// no need to run an action for this
ninja.position = CGPointZero;
// but you should stop any potentially running (move) action
[ninja stopAllActions];
}
else ...