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.
Related
I am trying to make a program where you are allowed to select between an option of shapes, and then drawing it. To allow for multiple shapes I created a vector of a class which creates shapes (Shapes are set up with the chosen function). My problem is the mouse click is too long, so it assigns it to everything in the vector, so you can't create a new shape. Is there a problem in my logic, or is there a problem in the code?
Here is my attempt:
for (auto& it : onCanvas) {
if (Mouse::isButtonPressed(Mouse::Left)) {
if (mousepointer.getGlobalBounds().intersects(circleOption.getGlobalBounds())) {
it.chosen(circles);
}
if (mousepointer.getGlobalBounds().intersects(rectOption.getGlobalBounds())) {
it.chosen(rectangle);
}
if (mousepointer.getGlobalBounds().intersects(triOption.getGlobalBounds())) {
it.chosen(triangles);
}
if (mousepointer.getGlobalBounds().intersects(it.shape.getGlobalBounds()) || it.dragging) {
it.shape.setPosition(mousepointer.getPosition());
it.dragging = true;
}
}
if (!Mouse::isButtonPressed) {
it.dragging = false;
}
win.draw(it.shape);
}
Your source-code is a bit incomplete (what is onCanvas and mousepointer). But I guess the problem is that this snippet is called multiple times while your mouse is clicked. To avoid that you can do two thing.
In the first solution you use events, so you only add shapes when the state of the mousebutton changes (you can additionally listen to the MouseButtonReleased to simulate a full click):
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.mouseButton.button == sf::Mouse::Left)
{
// Hit Detection
}
}
or second solution you remember the last state of the button (probably do the mouse check once outside of the for loop):
bool mouse_was_up = true;
if (mouse_was_up && Mouse::isButtonPressed(Mouse::Left)) {
mouse_was_up = false;
for (auto& it : onCanvas) {
// Hit Detection
}
}
else if (!Mouse::isButtonPressed(Mouse::Left))
mouse_was_up = true;
I would rather stick to the first solution because when your click is too short and your gameloop is in another part of the game logic, you can miss the click.
I am trying to make my basic loading screen transition over to game level screen. So what i wanted to do is, once the loading screen is active (or has appeared onscreen), I want at this point to start loading my game state. What it is doing at the moment is loading everything at the start, and this does take a while.
So currently my project starts off with a main menu. Then when i press enter, its starts the loading screen. I have my manual state change using keypresses like so:
void Game::update()
{
static bool enterPreviouslyPressed = false;
static bool escapePreviousPressed = false;
const Uint8 *keys = SDL_GetKeyboardState(NULL);
if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(GameState))
{
fsm->setState(menuState);
}
else if (keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
fsm->setState(loadingState);
}
else if ((keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed) && typeid(*fsm->getState()) == typeid(LoadScreenState))
{
fsm->setState(gameState);
}
else if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
exit(0);
}
enterPreviouslyPressed = keys[::SDL_SCANCODE_RETURN] != 0;
escapePreviousPressed = keys[::SDL_SCANCODE_ESCAPE] != 0;
fsm->update();
}
I did this to initially does this so i could change states manually to check that everything works. I was wondering if there was an easy(ish) way, like boolean flags for example or another simpler way to do this. I wasn't able find any tutorials online so wondering if someone knows the best solution as to how to do this. I did see a question on here, kindda similar but I wasn't sure if it answered my question as the person did this in threads which I am not familiar with how to implement. Apologies if I dont seem to have the logic correct - so please advise otherwise.
Looks fairly standard, except I would simplify it by keeping two keyboard state variables declared as class variables, like:
const Uint8 *curKeys = SDL_GetKeyboardState(NULL), *prevKeys;
// ...
void Game::update() {
prevKeys = curKeys;
curKeys = = SDL_GetKeyboardState(NULL);
//and so then compare curKeys to prevkeys
//and ditch the booleans
// ...
}
I'm trying to make a click and drag selection system in c++ and SDL2 like the kind used in real time strategy games. You click with the mouse and drag it over what you want to select. How can I go about making it?
Edit: I know how to handle the mouse inputs. I have used a Rect structure to track the size of the selection zone. However while the zone draws correctly, the objects inside the zone don't react at all. However individually clicking on them works fine every time.
I guess my question is what is the best way to check for a group selection vs individual object selection?
You have to check what's inside the rect you are gonna drag (the test depends on the shapes that are included) and the drag those shapes aswell
I can describe how i have implemented this.
In the event handling routine, do something like the code below. I think the method names explain quite well what's happening and how i'm thinking (this is copied from my hobby-hack-RTS-engine which is based on SDL2):
case SDL_MOUSEBUTTONDOWN:
{
// Calculate index, x and y for the tile that was clicked in the map.
int iClick = m_Map.getTileIndex(event.button.x, event.button.y);
if(iClick >= 0)
{
int xClick = m_Map.getTileX(iClick);
int yClick = m_Map.getTileY(iClick);
if((int)event.button.button == 1)
{
// Unmark all MO..
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
it->unMark();
}
activateFraming(event.button.x, event.button.y);
}
else
{
...
}
}
break;
}
case SDL_MOUSEBUTTONUP:
{
if((int)event.button.button == 1)
{
int endClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int endClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int startClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
int startClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
if(endClickX > 0 && endClickY > 0 && startClickX > 0 && startClickY > 0)
{
for(int x = std::min(startClickX, endClickX); x <= std::max(startClickX, endClickX); x++)
{
for(int y = std::min(startClickY, endClickY); y <= std::max(startClickY, endClickY); y++)
{
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
if(it->getTileX() == x && it->getTileY() == y)
{
it->mark();
}
}
}
}
}
deactivateFraming();
}
else
{
...
}
break;
}
My selectable objects are stored as std::list<MGMovingObject> in m_MO.
The idea is that i save tile coordinates of the start of the selection frame and the end of the selection frame. I then iterate over the selectable objects and detect the ones inside the selection frame. I select these (mark()) and when i iterate over the objsects at a later stage, say during rendering, i can read out if they are selected (isMarked()).
If you want to steal code or ideas, here is the actual source file i copied it from: https://github.com/qmargyl/mgframework/blob/master/src/mgframework/mgframework.cpp
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.
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.