I have a very simple level that has a single static body (and associated sprite) in it. When the user touches the body, I remove the sprite, destroy the body and replace it with a dynamic body with a new sprite, and then I transition to a new scene. My code works perfectly the first time it is executed, but then when this scene is reloaded it crashes when the user touches the body.
I store the filename of the .png file that I use for my sprite in the sprite's userData field. Then when the user presses the button (touches the body) it is a simple matter to retrieve it. The problem is occurring on subsequent reloads of the scene when I try to access the sprite's userData field. Instead of holding the filename it is empty (null). This in turn crashes the program when I try to use the filename.
I cannot understand why it works the first time and not the second. I set a breakpoint and watch the filename getting assigned to the sprite's userData field, but it is only retrievable the first time I create the scene. Here is the ccTouchesBegan method. The crash occurs where I try to assign newSpriteFileName because on the second run through oldSpriteFileName is empty.
I have just about gone crazy with this one, but I am sure it is something obvious. As always thanks for the help!!
(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for( UITouch *touch in touches )
{
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
b2Vec2 locationWorld = b2Vec2(touchLocation.x/PTM_RATIO, touchLocation.y/PTM_RATIO);
NSLog(#"Location x = %f, y = %f", touchLocation.x, touchLocation.y);
b2AABB aabb;
b2Vec2 delta = b2Vec2(1.0/PTM_RATIO, 1.0/PTM_RATIO);
aabb.lowerBound = locationWorld - delta;
aabb.upperBound = locationWorld +delta;
SimpleQueryCallback callback(locationWorld);
m_world->QueryAABB(&callback, aabb);
//If they have not pressed a button yet, and this touch was actually inside
//one of the buttons then we will destroy the static button (and sprite)
//that they touched and replace it with a dynamic button
//(and new darker sprite) that falls, spinning off of the screen.
if(!replaceButtonPushedYet && callback.fixtureFound)
{
//Get a reference to the static body of the button they touched and wake it up
b2Body *body = callback.fixtureFound->GetBody();
body->SetAwake(true);
//Get the position of the body (button) they touched and use it to
//position the new button. This value is in Box2D coordinates,
//so when I position the new button I won't divide by PTM_RATIO.
b2Vec2 touchedButtonPosition = body->GetPosition();
//Get the sprite from the button the user pressed
CCSprite *bodySprite = (CCSprite*)body->GetUserData();
//Then extract the file name of the sprite. I assigned this to
//sprite's userData when I loaded the sprite in the method //"placeTheIndividualButton". Now I am going to extract it and
//then replace "(up)" with "(down)" in that string
//and that becomes the name of the sprite for the new (pressed)
//button I am getting ready to create. It is all about the file
//naming conventions!
NSString *oldSpriteFileName = (NSString*)bodySprite.userData;
NSString *newSpriteFileName = [oldSpriteFileName stringByReplacingOccurrencesOfString:#"up" withString:#"down"];
//First remove the sprite tied to the button the user pressed,
//then destroy the body of the button.
[self removeChild:bodySprite cleanup:YES];
body->GetWorld()->DestroyBody(body);
//Set the bool to true to keep this code from executing again.
//This ensures that once they press a button they can't
//press another one.
replaceButtonPushedYet = true;
//Build the new dynamic button that will fall and spin off the screen
b2BodyDef buttonBodyDef2;
b2PolygonShape buttonShape2;
b2FixtureDef buttonShapeDef2;
b2Vec2 vertices2[4];
//load the sprite for the second button (pressed down) and add it to the layer
level_down = [CCSprite spriteWithFile:newSpriteFileName];
level_down.userData=newSpriteFileName;
[self addChild:level_down];
//Define the polygon that forms the second button (pressed down)
buttonBodyDef2.type = b2_dynamicBody;
//Not dividing touchedButtonPosition.x or .y by PTM_RATIO because
//they are already in Box2D coordinates
buttonBodyDef2.position.Set(touchedButtonPosition.x, touchedButtonPosition.y);
buttonBodyDef2.angularVelocity = 1.5;
buttonBodyDef2.userData = level_down;
buttonBody2 = m_world->CreateBody(&buttonBodyDef2);
//Define the vertices for the replacement button
vertices2[0].Set(-94/PTM_RATIO, -32/PTM_RATIO);
vertices2[1].Set(94/PTM_RATIO, -32/PTM_RATIO);
vertices2[2].Set(94/PTM_RATIO, 32/PTM_RATIO);
vertices2[3].Set(-94/PTM_RATIO, 32/PTM_RATIO);
buttonShape2.Set(vertices2, 4);
//Define the shape for the replacement button
buttonShapeDef2.shape = &buttonShape2;
buttonShapeDef2.density = 50.01f;
buttonShapeDef2.friction = 0.75f;
buttonShapeDef2.restitution = 0.1f;
//The static buttons and the dynamic buttons are both in this groupIndex.
//Since it is a negative number they will never collide. If it was
//positive they would always collide.
buttonShapeDef2.filter.groupIndex = -1;
//Put the second button (pressed down) into the world.
buttonBody2->CreateFixture(&buttonShapeDef2);
//This starts a timer that fires every second to call the makeTransition
//method. The code inside that method will check to see if the button
//has fallen off the screen yet. If it has then it will transition to
//the new selected level.
//The colon after makeTransition sends a ccTime (dt). I don't need it,
//but may in the future so I left it in there.
buttonFalling = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeTransition:) userInfo:nil repeats:YES];
}
}
}
Chances are that the NSString is being released when you remove the sprite. Why not just storing the texture name in an instance variable?
Related
I can't figure out how to handle with tile map layer and other node. I want to make another one layer(CCNode) for such things as menu button, score, joystick that must always stay fixed(not scrolled), when the whole map is scrolled.
self.theMap=[CCTiledMap tiledMapWithFile:#"map.tmx"];
self.bgLayer=[theMap layerNamed:#"bg"];
[self addChild:theMap z:-1];
CCNode *joystickNode;
joystickNode=[CCNode node];
[bgLayer.parent addChild:joystickNode z:2];
upArrowFrame=[CCSpriteFrame frameWithImageNamed:#"uparrow.png"];
upArrow=[CCButton buttonWithTitle:#"" spriteFrame:upArrowFrame];
[upArrow setTarget:self selector:#selector(upArrowPressed)];
upArrow.position= ccp(190,190);
[joystickNode addChild:upArrow z:2];
Now upArrow is not visible on the screen at all. If I add to self instead of joystickNode, it will appear.
I can't even understand, what parent should new CCNode have. Can anyone explain it to me? I also tried to add new CCNode as a child of self and theMap.
EDIT: Oops, it's actually moving the camera. How to implement it in this case?
-(void)setCenterOfScreen :(CGPoint) position {
CGSize screenSize=[[CCDirector sharedDirector]viewSize];
int x=MAX(position.x, screenSize.width/2);
int y=MAX(position.y, screenSize.height/2);
x= MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y= MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint =ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.position=difference;
}
- (void)update:(CCTime)dt{
[self setCenterOfScreen:hero.position];
}
Currently, in cocos2d, I have a an app that does the following:
Initiate with a Blank Screen.
When I tap the screen, I get a circle to pop-up. As I hold the circle, the circle will continue to grow at a constant rate. However, despite the fact that the sprite is growing, the box2d physical body isn't, which means that the sprite will not collide with other bodies. I been trying to figure out a way to change the radius that scales with the sprite but no such question exist here for cocos2d. I have noticed other box2d for things other than cocos2d but I am having a hard time translating them over.
//smile.position = ccp(touchLocation.x, touchLocation.y);
smile.scale = .05;
[self addChild:smile];
// b2BodyDef smileBodyDef;
smileBodyDef.type = b2_dynamicBody;
smileBodyDef.position.Set(touchLocation.x/PTM_RATIO, touchLocation.y/PTM_RATIO);
smileBodyDef.userData = smile;
smileBody = world->CreateBody(&smileBodyDef);
//Radius
b2CircleShape smileCircleShape;
int radius = 80;
//Fixture
smileFixtureDef.shape = &smileCircleShape;
smileFixtureDef.density = 0.00f;
smileFixtureDef.friction = .2f;
smileBody->CreateFixture(&smileFixtureDef);
if (CGRectContainsPoint(smileRect, touchLocation)) {
growForever = [CCRepeatForever actionWithAction: [CCScaleBy actionWithDuration: .5 scale: 1.2]];
[growForever setTag:1];
[smile runAction:growForever];
Each time you want to change your radius, grab the shape object associated with the b2Fixture that you created for your body, and then set the new value accordingly:
fixture->GetShape()->m_radius = new_radius/PTM_RATIO;
I have issue in jumping sprite body. Following is the update method for jumping sprite body
-(void) update: (ccTime) dt
{
world->Step(dt, 10, 10);
// BOOL worldAsleep=true;
for(b2Body *b=world->GetBodyList();b;b=b->GetNext())
{
if(b->GetUserData() !=NULL)
{
CCSprite *myActor=(CCSprite *)b->GetUserData();
myActor.position=ccp(b->GetPosition().x *PTM_RATIO, b->GetPosition().y *PTM_RATIO);
b->SetTransform(b2Vec2(b->GetPosition().x,b->GetPosition().y), b->GetAngle());
if (fire.active==YES)
{
id jump=[CCJumpBy actionWithDuration:1 position:ccp(b->GetPosition().x, b->GetPosition().y) height:100 jumps:1];
[myActor runAction:jump];
b2Vec2 force = b2Vec2(0,350);
}
}
}
}
Here fire.active is a button for jumping sprite and ccjumpto method is used for jump sprite but what to do to jump body.
For jump sprite body i was used ApplyLinearImpuls but it can not jump like sprite type.
I want to jump both sprite and body same like only sprite jump
Thanks for replay
hey you jump body according to button clicked you put below code when button clicked:
b2Vec2 locationWorld;
locationWorld = b2Vec2(0.0f,8.0f);
double Force= _body->GetMass();
_body->ApplyLinearImpulse(Force*locationWorld, _body->GetWorldCenter());
I've been studying this for hours (2 days, actually) and just cannot figure out what is wrong. The touches are accepted and processing, but the isTouchHandled test is triggering TRUE prematurely; as if a different bounding box was touched than the one that is touched. I have several non-overlapping CCSprite buttons, with each pointed to in the levelButtons array. Iterate through to see which one is touched; but it's always the wrong one.
Does the CGRectContainsPoints method get thrown off if these sprites are in their own layer, which is then in another layer? In other words, is CGRectContainsPoints using raw equality of pixel locations as reported by position? If so, a sprite's position relative to the entire screen is different than its reported position if it is a child, which is relative to the parent. Maybe this has something to do with it? My array and the tags of its contents are correctly lining up, I've logged and checked that many times; it appears to be the bounding box check.
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CCLOG (#"levelButtons size:%i",[self.levelButtons count]);
BOOL isTouchHandled = NO;
for (int i=0;i<25;i++){
CCSprite*temp=(CCSprite*)[self.levelButtons objectAtIndex:i];
CCLOG(#"iteration temp.tag: %i for object: %i",temp.tag,i);
isTouchHandled= CGRectContainsPoint([temp boundingBox], [[CCDirector sharedDirector] convertToGL:[touch locationInView: [touch view]]]);
if (isTouchHandled) {
CCLOG(#"level touched: %i",temp.tag);
break;
}
}
return isTouchHandled;
}
UPDATE: Incidentally, I also just subclassed CCSprite and add the touche methods to the individual sprites in this fashion ,taking my array of sprites out of the picture. The results were the same, so I suspect the CGRectContainsPoints is not properly working when your rect is a child of other children, the coordinates are not being reported correctly, I suspect.
I think it may be problem with an array that you are getting sprites. Any way , this is how i am using the code for getting the sprite tag.
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
for(int i1=0;i1<=25;i1++)
{
CCSprite *sprite1 = (CCSprite *)[self getChildByTag:i1];
if(CGRectContainsPoint([sprite1 boundingBox], location))
{
//Your Code
break;
}
}
I solved this by creating a new CGRect for the CGRectContainsPoint test, and translating the bounding box into the actual onscreen rectangle; the bounding box test will not work on its own if it is located on a child sprite (or layer). It returns its local position only, relative to the parent.
I have one question when infinite background scrolling is done, is the object remain fixed(like doodle in doodle jump, papy in papi jump) or these object really moves.Is only background move or both (background and object )move.plz someone help me.I am searching for this solution for 4/5 days,but can't get the solution.So plz someone help me. And if object does not move how to create such a illusion of object moving.
If you add the object to the same layer as the scrolling background, then it will scroll as the background scrolls.
If your looking for an effect like the hero in doodle jump, you may want to look at having two or more layers in a scene.
Layer 1: Scrolling Background Layer
Layer 2: Sprite layer
SomeScene.m
CCLayer *backgroundLayer = [[CCLayer alloc] init];
CCLayer *spriteLayer= [[CCLayer alloc] init];
[self addChild:backgroundLayer z:0];
[self addChild:spriteLayer z:1];
//Hero stays in one spot regardless of background scrolling.
CCSprite *squidHero = [[CCSprite alloc] initWithFile:#"squid.png"];
[spriteLayer addChild:squidHero];
If you want objects to scroll with the background add it to the background layer:
//Platform moves with background.
CCSprite *bouncePlatform= [[CCSprite alloc] initWithFile:#"bouncePlatform.png"];
[backgroundLayer addChild:bouncePlatform];
Another alternative is to use a CCFollow action. You would code as if the background is static (which it will be) and the player is moving (which it will be), but add a CCFollow action to the player. This essentially moves the camera so that it tracks your player.
You can also modify the classes so that you can get the CCFollow action to follow with an offset (i.e., so the player is not in the middle of the screen) as well as to have a smoothing effect to it, so that when the player moves, the follow action is not jerky. See the below code:
*NOTE I am using cocos2d-x, the c++ port. The methods are similar in cocos2d, and you should be able to modify these to fit the cocos2d syntax. Or search around -- I found these for cocos2d and then ported to c++.
//defines the action to constantly follow the player (in my case, "runner.p_sprite is the sprite pointing to the player)
FollowWithOffset* followAction = FollowWithOffset::create(runner.p_sprite, CCRectZero);
runAction(followAction);
And separately, I have copied the class definition for CCFollow to create my own class, CCFollowWithAction. This also has a smoothing effect (you can look this up more online) so that when the player moves, the actions are not jerky. I modified "initWithTarget," to take into account an offset, and "step," to add a smoothing action. You can see the modifications in the comments below.
bool FollowWithOffset::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCAssert(pFollowedNode != NULL, "");
pFollowedNode->retain();
m_pobFollowedNode = pFollowedNode;
if (rect.equals(CCRectZero))
{
m_bBoundarySet = false;
}
else
{
m_bBoundarySet = true;
}
m_bBoundaryFullyCovered = false;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);
//m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f);
m_obHalfScreenSize = CCPointMake(m_obFullScreenSize.x/2 + RUNNER_FOLLOW_OFFSET_X,
m_obFullScreenSize.y/2 + RUNNER_FOLLOW_OFFSET_Y);
if (m_bBoundarySet)
{
m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x);
m_fRightBoundary = -rect.origin.x ;
m_fTopBoundary = -rect.origin.y;
m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y);
if(m_fRightBoundary < m_fLeftBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;
}
if(m_fTopBoundary < m_fBottomBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2;
}
if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) )
{
m_bBoundaryFullyCovered = true;
}
}
return true;
}
void FollowWithOffset::step(float dt)
{
CC_UNUSED_PARAM(dt);
if(m_bBoundarySet){
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(m_bBoundaryFullyCovered)
return;
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary),
clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary)));
}
else{
//custom written code to add in support for a smooth ccfollow action
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
CCPoint moveVect = ccpMult(ccpSub(tempPos,m_pTarget->getPosition()),0.25); //0.25 is the smooth constant.
CCPoint newPos = ccpAdd(m_pTarget->getPosition(), moveVect);
m_pTarget->setPosition(newPos);
}
}