i am using spacemanager with chipmunk physics for my game.
What i am trying to do is hit a ball to a pillar and make the ball disappear. i am recreating the ball in different location so the user can hit another pillar. i have the ball shape and ball sprite as member variable.
Here is the code for creating ball, pillar shapes etc.
ball = [smgr addCircleAt:cpv(1000,10) mass:0.5 radius:35];
ball->collision_type = kBallCollisionType;
ballSprite = [cpCCSprite spriteWithShape:ball file:#"head.png"];
//[ballSprite autoFreeShape];
[self addChild:ballSprite];
ballSprite.spaceManager = smgr;
//ballSprite.ignoreRotation = NO;
cpShape *dome = [smgr addRectAt:cpv(400,500) mass:1 width:400 height:100 rotation:0];
dome->collision_type = kRectCollisionType;
cpCCSprite *dome1 = [cpCCSprite spriteWithShape:dome file:#"001.png"];
[self addChild:dome1];
dome1.spaceManager = smgr;
cpShape *pillarone = [smgr addRectAt:cpv(300,300) mass:1 width:45 height:194 rotation:0];
pillarone->collision_type = kRectCollisionType;
cpCCSprite *pillar1 = [cpCCSprite spriteWithShape:pillarone file:#"004.png"];
[self addChild:pillar1];
pillar1.spaceManager = smgr;
cpShape *pillartwo = [smgr addRectAt:cpv(500,300) mass:1 width:45 height:194 rotation:0];
pillartwo->collision_type = kRectCollisionType;
cpCCSprite *pillar2 = [cpCCSprite spriteWithShape:pillartwo file:#"004.png"];
[self addChild:pillar2];
pillar2.spaceManager = smgr;
cpShape *staticground = [smgr addRectAt:cpv(510,25) mass:1 width:0 height:0 rotation:0];
cpCCSprite *staticground1 = [cpCCSprite spriteWithShape:staticground file:#"grass1-1024.png"];
[self addChild:staticground1 z:1 tag:0];
[smgr addCollisionCallbackBetweenType:kRectCollisionType
otherType:kBallCollisionType
target:self
selector:#selector(handleCollisionWithFragmentingRect:arbiter:space:)];
AND HERE IS THE CODE FOR COLLISION HANDLING.
- (void) handleCollisionWithFragmentingRect:(CollisionMoment)moment arbiter:(cpArbiter*)arb space:(cpSpace*)space{
if (moment == COLLISION_POSTSOLVE)
{
[self removeChild:ball->data cleanup:YES];
[smgr removeAndFreeShape:ball];
ball = [smgr addCircleAt:cpv(1000,10) mass:0.5 radius:35];
ball->collision_type = kBallCollisionType;
ballSprite = [cpCCSprite spriteWithShape:ball file:#"head.png"];
[self addChild:ballSprite];
ballSprite.spaceManager = smgr;
}
}
When the first ball hits the pillar it disappears fine. but for the second and some times third ball i pick and hit the pillar it crashes with error as follows.
Chipmunk warning: Cannot remove a constraint that was not added to the space. (Removed twice maybe?)
Failed condition: cpArrayContains(space->constraints, constraint)
i am not sure where i went wrong, can anyone help please.
Thanks.
I think you need to add teh ball to be removed to a "garbage" array, and not remove it on the handleCollisionWithFragmentingRect function. For all safety measures, include the
- (void) update : (ccTime) dt
{
for (cpShape *shape in _garbage)
{
// Remove shape
[self removeChild:shape->data cleanup:YES];
[smgr removeAndFreeShape:shape];
// Add new ball
ball = [smgr addCircleAt:cpv(1000,10) mass:0.5 radius:35];
ball->collision_type = kBallCollisionType;
ballSprite = [cpCCSprite spriteWithShape:ball file:#"head.png"];
[self addChild:ballSprite];
ballSprite.spaceManager = smgr;
}
...
}
And then you would change the handle function to this:
- (void) handleCollisionWithFragmentingRect:(CollisionMoment)moment arbiter:(cpArbiter*)arb space:(cpSpace*)space
{
if (moment == COLLISION_POSTSOLVE)
{
// SHOULDNT BE A DIRECT REFERENCE TO MEMBER VARIABLE BALL.
// SHOULD BE ADDED FROM INFORMATION WITHIN THE PARAMETERS OF THIS FUNCTION.
_garbage.addObject(ball);
}
}
Related
I have some sprite-sheet that i have to animate forever , and i would like to add it as a CCLayer
to my scene .
Later on , i have to move this whole animation sprite on the screen.
So, for example, i have some animation of a dog walking, from sprite sheet, this one is running forever. than i want to be able to move this dog on screen while animating.
What is the best way to do this ? (or the right way)
This is how i animate the frames :
CCSprite *boom;
boom = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"%#_00000.png",file]];
boom.position=touch;
[self addChild:boom];
NSMutableArray *animFrames = [NSMutableArray array];
for(int i = 0; i < 5; i++)
{
CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:
#"%#_0000%i.png",file,i]];
[animFrames addObject:frame];
}
CCAnimation* boxAnimation = [CCAnimation animationWithSpriteFrames:animFrames delay:0.075f];
CCAnimate * boxAction = [CCAnimate actionWithAnimation:boxAnimation];
CCAction *call=[CCCallBlock actionWithBlock:^{[self removeFromParentAndCleanup:YES];}];
CCAction * sequence=[CCSequence actions:boxAction,[CCHide action],call,nil];
[boom runAction:sequence];
return self;
How would you move this whole thing ?
There are a few ways to do this. If you are not preocupied with collision detection, then one way would be to :
CGPoint egressPosition = ccp(0,0); // figure this out in your app
float moveDuration = 1.5f ; // whatever time you compute for desired speed and distance
id move = [CCMoveTo actionWithDuration:moveDuration position:egressPosition];
id spawn = [CCSpawn actions:sequence,move,nil];
[boom runAction:spawn];
otherwise, using your code as is
[self schedule:#selector(moveBox:)]; // optional, you could do this in update method
[boom runAction:sequence];
-(void) moveBoom:(CCTime) dt {
CGPoint newPosition;
delta = ccp(dt*speedX,dt*speedY); // crude , just to get the idea
newPosition = ccpAdd(boom.position,delta);
// here you can figure out collisions at newPosition before the collision
// and do whatever seems appropriate
boom.position = newPosition;
}
So after taking a little break after being very frustrated for months on end with this issue, I am now back and attempting to solve my issue once and for all.
I’m not going to post code, because the code I have is messy and patchy and is from countless points of view.
My issue is this:
I have a sprite, and his information is stored in a class called player.m. I have a game level, and it is stored in GameLevelLayer.m
I have player.m store velocity, direction, and all the information about the character, and then I have the GameLevelLayer.m implement, move, use bounds to check collisions, etc.
I now want to make the character move using a spritesheet (I know how to do spritesheeting, I just don’t know how to do it while working with two different classes)
My question is, do I create the batchnodes and spritesheet information (CCBatchnodes, caches, etc) and all of the actions and everything in player.m, and then run them in GameLevelLayer.m? Or do I create all that in GameLevelLayer.m
I really need some help here because I’ve been stuck on this for months
EDIT:
Here is where I am, thanks to a suggestion from a community member.
This is my entire player.m (Long story short, I define everything about the player 'you might just want to pay attention to the init, because that seems to be the problem. The other stuff is my physics engine')
#import "Player.h"
#import "SimpleAudioEngine.h"
#import "GameLevelLayer.h"
#implementation Player
#synthesize velocity = _velocity;
#synthesize desiredPosition = _desiredPosition;
#synthesize onGround = _onGround;
#synthesize forwardMarch = _forwardMarch, mightAsWellJump = _mightAsWellJump, isGoingLeft = _isGoingLeft;
#synthesize WalkAction = _WalkAction;
#synthesize isMoving = _isMoving;
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super init]) {
self.velocity = ccp(0.0, 0.0);
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile: #"BankerSpriteSheet_default.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"BankerSpriteSheet_default.png"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <=6; ++i) {
[walkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"banker%d.png", i]]];
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
self = [super initWithSpriteFrameName:#"banker1.png"];
self.WalkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
//[_Banker runAction:_WalkAction];
[spriteSheet addChild:self];
[[CCAnimationCache sharedAnimationCache] addAnimation:walkAnim name:#"walkAnim"];
}
}
return self;
}
-(void)update:(ccTime)dt {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];
CGPoint jumpForce = ccp(0.0, 310.0);
float jumpCutoff = 150.0;
if (self.mightAsWellJump && self.onGround) {
self.velocity = ccpAdd(self.velocity, jumpForce);
if (![defaults boolForKey:#"All Muted"]) {
if (![defaults boolForKey:#"SFX Muted"]) {
[[SimpleAudioEngine sharedEngine] playEffect:#"jump.wav"];
}
}
} else if (!self.mightAsWellJump && self.velocity.y > jumpCutoff) {
self.velocity = ccp(self.velocity.x, jumpCutoff);
}
CGPoint gravity = ccp(0.0, -450.0);
CGPoint gravityStep = ccpMult(gravity, dt);
CGPoint forwardMove = ccp(800.0, 0.0);
CGPoint forwardStep = ccpMult(forwardMove, dt);
self.velocity = ccp(self.velocity.x * 0.90, self.velocity.y); //2
if (self.forwardMarch) {
self.velocity = ccpAdd(self.velocity, forwardStep);
if (!self.isMoving) {
//[self runAction: _WalkAction];
//self.isMoving = YES;
}
} //3
CGPoint minMovement = ccp(0.0, -450.0);
CGPoint maxMovement = ccp(120.0, 250.0);
self.velocity = ccpClamp(self.velocity, minMovement, maxMovement);
self.velocity = ccpAdd(self.velocity, gravityStep);
CGPoint stepVelocity = ccpMult(self.velocity, dt);
if (!self.isGoingLeft) {
self.desiredPosition = ccpAdd(self.position, stepVelocity);
}else{
self.desiredPosition = ccp(self.position.x-stepVelocity.x, self.position.y+stepVelocity.y);
}
}
-(CGRect)collisionBoundingBox {
CGRect collisionBox = CGRectInset(self.boundingBox, 3, 0);
//CGRect collisionBox = self.boundingBox;
//collisionBox = CGRectOffset(collisionBox, 0, -2);
CGPoint diff = ccpSub(self.desiredPosition, self.position);
CGRect returnBoundingBox = CGRectOffset(collisionBox, diff.x, diff.y);
return returnBoundingBox;
}
#end
So now the issue is, how do i get the player to appear in the game like a normal sprite, and then how do I run the animations when the player begins to move forward
Your game architecture is very good! Keep player properties on player and use GameLayer only to send player actions is the best approach.
If your Player Class extends from CCSprite, put everithing related to player in your Player Class: Run animations on it, load the cache on player constructor (use the CCSprite initWithTexture:rect:rotated: or a custom autorelease constructor, like a +(id)player ).
Then, in your GameLevelLayer you will need a CCSpriteBatchNode to add your player to it. You can use this batch node to add other objects that use same spritesheet.
EDIT:
Reviewing your code, your initWithTexture is wrong. The correct is to call [super initWithTexture:]:
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super initWithTexture:texture]) {
Then, in your GameLevelLayer, create your player and add it to a CCSpriteBatchNode:
// *** In GameLevelLayer.m ***
// Create player
Player *player = [Player spriteWithFile:#"player.png"];
// Create batch node
CCSpriteBatchNode *batchNode = [CCSpriteBatchNode batchNodeWithTexture:player.texture];
// Add batch node as child
[self addChild:batchNode];
// Add player as batch node's child
[batchNode addChild:player];
// Set player position
player.position = ccp(100,100);
Your GameLevelLayer needs to do only this to create your Player. And when it detects user input, call methods like "jump", "moveRight", "moveLeft" in your player. Also detect collisions and send actions to player like "die", "getSpecialItem".
// *** In GameLevelLayer.m ***
[player jump];
[player die];
So, your player needs to handle these methods and performs its own logic to do the actions:
// *** In Player.m ***
- (void)moveRight
{
self.position = ccpAdd(self.position, ccp(10,0));
}
I've created a quiz game, that implements a time bar. In the first play it's all right, but if, after gameover, the player tap "restart", the game goes on properly, but the time bar disappears!
Here my code from GameOverLayer to Game:
-(void) restart {
[[CCDirector sharedDirector] replaceScene:[HelloWorldLayer node]];
}
Here the function to create a new question
-(void)creaDomanda{
//bar
CCProgressFromTo *to1 = [CCProgressFromTo actionWithDuration:MaxTime from:100 to:0];
bar = [CCProgressTimer progressWithFile:#"barra.png"];
bar.type = kCCProgressTimerTypeHorizontalBarLR;
[bar setPosition:ccp(size.width - 250 , size.height - 18)];
int randomValue = (arc4random() % 4) + 1;
NSString *stringa = [NSString stringWithFormat:#"Domanda%i", randomValue];
dictionary = [plistData objectForKey:stringa];
domanda = [dictionary valueForKey:#"Titolo"];
labelDomanda = [CCLabelTTF labelWithString:domanda fontName:#"Marker Felt" fontSize:24];
labelDomanda.position = ccp( size.width /2 , 400 );
[self addChild: labelDomanda];
int rispostaEsatta = [[dictionary valueForKey:#"Soluzione"] intValue];
menu = [CCMenu menuWithItems:nil];
for (int i = 1; i<5;i++)
{
if(rispostaEsatta == i){
item = [CCMenuItemFont itemFromString:[dictionary valueForKey:
[NSString stringWithFormat:#"Risposta%i",i] ]
target:self selector:#selector(corretto)];
}else{
item = [CCMenuItemFont itemFromString:[dictionary valueForKey:
[NSString stringWithFormat:#"Risposta%i",i] ]
target:self selector:#selector(sbagliato)];
}
[menu addChild:item];
}
//[..]
[self addChild:menu];
[self addChild:bar];
[bar runAction:to1];
}
And here one of the correct/wrong method (similar) that after all, create a new question:
-(void)sbagliato{
CCLOG(#"Sbagliato");
if (menu) [self removeChild:menu cleanup:YES];
if (labelDomanda) [self removeChild:labelDomanda cleanup:YES];
if (bar) [self removeChild:bar cleanup:YES];
labelRisultato = [CCLabelTTF labelWithString:#"Hai sbagliato!" fontName:#"Marker Felt" fontSize:24];
[labelRisultato setColor:ccc3(255, 1, 1)];
labelRisultato.position = ccp(size.width / 2, 280);
[self addChild:labelRisultato];
[self gameOver:2 punteggio:0];
// Richiamiamo il metodo per eliminare la label dopo 0,3 secondi
[self performSelector:#selector(eliminaLabel) withObject:nil afterDelay:0.5];
increment = increment - 20;
[pointLabel setString: [NSString stringWithFormat: #"Punti: %i", increment]];
// new question
[self performSelector:#selector(creaDomanda) withObject:nil afterDelay:0.5];
}
Can anyone explain to me please why when I restart the time bar desappers?
Thank You
My best guess:
The CCProgressFromTo action is still running. Since it progresses down to 0, the CCProgressTimer eventually doesn't display any part of it anymore. This may continue even if you run another CCProgressFromTo action on the progress timer.
Solution: be sure to stop any running CCProgressFromTo actions before running another.
If that doesn't fix it, then I imagine the CCProgressTimer needs to be reset by setting percentage back to 100.
I haven't seen easy examples about rotaion around a specified point. I tried something like this but it doesn't work:
//CCNode *node is declared
//In a function of a subclass of CCSprite
- (void)moveWithCicrlce
{
anchorNode = [CCNode node];
anchorNode.position = ccpSub(self.position, circleCenter);
anchorNode.anchorPoint = circleCenter;
[anchorNode runAction:[CCRotateBy actionWithDuration:1 angle:90]];
[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:[CCCallFunc actionWithTarget:self selector:#selector(rotate)], [CCDelayTime actionWithDuration:0.1], nil]]];
}
- (void)rotate
{
self.position = ccpAdd(anchorNode.position, anchorNode.anchorPoint);
}
Here's how you can rotate a node (sprite etc) around a certain point P (50,50) with a radius (distance from P) of 100:
CCNode* center = [CCNode node];
center.position = CGPointMake(50, 50);
[self addChild:center];
// node to be rotated is added to center node
CCSprite* rotateMe = [CCSprite spriteWithFile:#"image.png"];
[center addChild:rotateMe];
// offset rotateMe from center by 100 points to the right
rotateMe.position = CGPointMake(100, 0);
// perform rotation of rotateMe around center by rotating center
id rotate = [CCRotateBy actionWithDuration:10 rotation:360];
[center runAction:rotate];
My approximate solution:
#interface Bomb : NSObject {
CCSprite *center;
}
...
#end
and some methods:
- (void)explode
{
BombBullet *bullet = [BombBullet spriteWithFile:#"explosion03.png"];
[[[CCDirector sharedDirector] runningScene] addChild:bullet];
center = [CCSprite spriteWithTexture:bullet.texture];
center.position = explosionPoint;
center.anchorPoint = ccp(-0.5, -0.5);
center.visible = NO;
[[[CCDirector sharedDirector] runningScene] addChild:center];
[center runAction:[CCRotateBy actionWithDuration:1 angle:360]];
CCCallFunc *updateAction = [CCCallFuncN actionWithTarget:self selector:#selector(update:)];
[bullet runAction:[CCRepeatForever actionWithAction:[CCSequence actions:updateAction, [CCDelayTime actionWithDuration:0.01], nil]]];
}
- (void)update:(id)sender
{
BombBullet *bombBullet = (BombBullet *)sender;
bombBullet.rotation = center.rotation;
bombBullet.position = ccpAdd(center.position, center.anchorPointInPoints);
bombBullet.position = ccpAdd(bombBullet.position, ccp(-bombBullet.contentSize.width / 2, -bombBullet.contentSize.height / 2));
bombBullet.position = ccpRotateByAngle(bombBullet.position, center.position, bombBullet.rotation);
}
of course I should add sprite deleting.
I'm trying to make a effect using the CCBitmapFontAtlas, here is what I want:
The string say "ABCDEFG" being dispayed one by one, each one won't be displayed
until the one before is completely displayed.
And here is what I tried:
-(id) init
{
if( (self=[super init] )) {
label = [CCBitmapFontAtlas bitmapFontAtlasWithString:#"ABC" fntFile:#"bitmapFontTest.fnt"];
[self addChild:label];
CGSize s = [[CCDirector sharedDirector] winSize];
label.position = ccp(s.width/2, s.height/2);
label.anchorPoint = ccp(0.5f, 0.5f);
label.visible = NO; //hide it first
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
return self;
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCSprite AChar = (CCSprite) [label getChildByTag:0];
CCSprite BChar = (CCSprite) [label getChildByTag:1];
CCSprite CChar = (CCSprite) [label getChildByTag:2];
id fade_in = [CCFadeIn actionWithDuration:3];
label.visible = YES;
[AChar runAction:fade_in];
[BChar runAction:fade_in];
[CChar runAction:fade_in];
return YES;
}
The effect is the "ABC" will fade in once I touched the screen, then I tried to use the
CallFuncND to call the next string to fade in while the current string is displayed.
But this seems to make things very complex.
Is there a easier way to get this effect done?
Any suggestion will be appreciate.
I feel like you are going in the right direction with this one. You could have each letter be a separate sprite and store them in an array and then run them each one by one.
The call function can be started by:
[self displayNextSprite:spriteArray nextIndex:0];
And the function is:
// Warning, This assumes you are not passing it an empty array, you may want to put in a check for that
-(void)displayNextSprite:(NSMutableArray*)spriteArray nextIndex:(NSUInteger)nextIndex
{
CCSprite *nextSprite = [spriteArray objectAtIndex:nextIndex];
id action1 = [CCFadeIn actionWithDuration:3];
// or = [CCPropertyAction actionWithDuration:3 key:#"opacity" from:0 to:255];
// The last letter
if(nextIndex == ([spriteArray count] - 1))
{
[nextSprite runAction:action1];
}
else // continue to the next letter
{
id callFunc = [CCCallFunc actionWithTarget:self selector:#selector(displayNextSprite:spriteArray nextIndex:nextIndex+1)];
id sequence = [CCSequence actionOne:action1 two:callFunc];
[nextSprite runAction:sequence];
}
}