I created a subclass called Obstacle of SKSpriteNode and implement the physics body in the init:
-(id)initWithHeight:(NSInteger)height flipped:(BOOL)flipped
{
if (self = [super initWithImageNamed: #"obstacle.png"])
{
[self setName: #"obstacle"];
[self setSize: CGSizeMake(OBSTACLE_WIDTH, height)];
[self setPosition: CGPointZero];
// rotate if needed
if (flipped)
{
[self runAction: [SKAction rotateByAngle: 3.14 duration: 0.0f]];
}
// physics
SKPhysicsBody* pb = [SKPhysicsBody bodyWithRectangleOfSize: self.size];
pb.dynamic = NO;
pb.affectedByGravity = NO;
[pb setCategoryBitMask: obstacleCategory];
[pb setContactTestBitMask: playerCategory];
[self setPhysicsBody: pb];
}
return self;
}
Then in my scene I have a method that spawns 2 obstacles at a time ever couple of seconds.
Obstacle *ob0 = [[Obstacle alloc] initWithHeight: h0 flipped: NO];
[bottom setPosition: CGPointMake(x0, y0)];
Obstacle *ob1 = [[Obstacle alloc] initWithHeight: h1 flipped: YES];
[top setPosition: CGPointMake(x1, y1)];
NSArray* objs = #[ob0, ob1];
for (Obstacle* o in objs)
{
[self addChild: o];
[o runAction: [SKAction
moveToX: -OBSTACLE_WIDTH duration: SPAWN_SPEED*2]
completion:^{
[o removeFromParent];
}];
}
After running for a random period of time (between a few seconds and a few minutes), the app will crash with
malloc: *** error for object 0x16553120: pointer being freed was not allocated
The error gets thrown on the following line in the first chunk of code
SKPhysicsBody* pb = [SKPhysicsBody bodyWithRectangleOfSize: self.size];
I've tried moving the physics implementation out of the subclass and into the scene after the Obstacle is instantiated, but it throws the same error.
Edit:
The debugger gave me new insight to my problem:
*** Terminating app due to uncaught exception 'Cant add body,
already exists in a world', reason: 'Cant add body <SKPhysicsBody>
type:<Rectangle> representedObject:[<SKSpriteNode> name:'obstacle'
texture:[<SKTexture> 'obstacle.png' (50 x 200)] position:{400, 446}
size:{50, 244} rotation:0.00], already exists in a world'
Do SKPhysicsNodes need to have a unique name maybe?
I found the solution! Because I was generating a random number for hight and subtracting that number from the height of the view for the other obstacle, sometimes the height was a negative value which would cause the error in creating the physics body. I hope this helps someone else down the line, cause I was banging my head over it all day
Related
This seems like a huge glitch in my opinion.
I have an SKScene overlaying a SceneKit scene. In my app, a method is called to update progress on a "progress bar" (really just an SKShapeNode with a really low height). This method is called updateSaveDataNotification and takes an NSNotification. I called [self childNodeWithName:] to access a child node. The problem is, when it's called, self has no children. Even though it really has three children.
I'm changing my code so they're variables in the implementation so I can animate their scale that way, but why would this behavior even exist? Why would self have zero children in this instance?
EDIT: the progress bar is simply an SKShapeNode. Here's the code I used:
#implementation {
SKShapeNode *progressBar;
float progress;
}
…
-(void)load {
[[NSNotificationCenter defaultCenter] addObserver:self name:#"anyName" selector:#selector(rcvNotification:) object:nil];
progress = 0.0;
…
progressBar = [SKShapeNode shapeNodeWithRectOfSize:CGSizeMake(self.size.width * 0.9, 2)
progressBar.xScale = 0;
progressBar.alpha = 0;
progressBar.fillColor = [UIColor whiteColor];
progressBar.strokeColor = [UIColor whiteColor];
progressBar.position = CGPointMake(self.size.width / 2, self.size.height * 0.1);
progressBar.name = #"progressBar";
…
[self addChild:progressBar]
[progressBar runAction:[SKAction fadeAlphaTo:1 duration:1]];
}
// The notification has one KVP in its user info dictionary. This is "someKey."
// "someKey"'s float value is added to "progress."
-(void)rcvNotification:(NSNotification*)notification {
progress += [[notification.userData valueForKey:#"someKey"] floatValue];
// [self updateUI];
// note: if I call updateUI like that, self will have zero children regardless of whether or not it actually has more.
// a remedy:
[self runAction:[SKAction waitForDuration:0] completion:^{
[self updateUI];
}];
}
-(void)updateUI {
NSLog(#"count of children: %lu", self.children.count);
[progressBar runAction:[SKAction scaleXTo:progress duration:0.5] completion:
// code to run when finished animating change
// also logic for reaching 100% progress.
}];
}
My predicament was, for a solid hour, not being able to re-scale that progress bar. I figured out self temporarily had 0 children, so I made it run a 0-second animation. I want to figure out why this odd behavior occurs.
For some reason my remove function on my CCNode is not working after the animation is run.
I am adding a CCNode on click, running a animation then running the remove function.
// loads the Coin
CCNode* coin = [CCBReader load:#"heros/coin"];
coin.name =#"coin";
// position
coin.position = ccpAdd(_touchNode.position, ccp(0, 0));
//Add to Parent
[_touchNode addChild:coin z:-1];
id action1 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 200))];
id action2 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 100))];
CCActionCallFunc *callAfterMoving = [CCActionCallFunc actionWithTarget:self selector:#selector(cleanupSprite:)];
[coin runAction: [CCActionSequence actions:action1, action2, callAfterMoving, nil]];
using the following delete function produces a crash error with This node does not contain the specified child
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChild:inSprite cleanup:YES];
}
Using the following delete also doesn't work
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChildByName:#"coin" cleanup:YES];
}
self does not contain coin, _touchNode does. Do this in your callback :
[_touchNode removeChildByName:#"coin" cleanup:YES];
I'm trying to create a moving background on top of a still background using a NSTimer, it works fine until I call the command to add another child. Here is the error I'm getting.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: <SKSpriteNode> name:'(null)' texture:[<SKTexture> 'groundGrass.png' (53 x 71)] position:{159, 35} size:{808, 71}
Here is my code to move the ground:
-(void)moveGroundGrass{
groundGrass.position = CGPointMake(groundGrass.position.x -1, groundGrass.position.y);
if (groundGrass.position.x < 160){
[self addChild:groundGrass];
}
Here is the rest:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
background.xScale = 0.9;
background.yScale = 1.1837f;
[self addChild:background];
groundGrass = [SKSpriteNode spriteNodeWithImageNamed:#"groundGrass"];
groundGrass.position = CGPointMake (404, 35);
[self addChild:groundGrass];
moveGround = [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:#selector(moveGroundGrass) userInfo:nil repeats:YES];
}
return self;
}
When groundGrass.png moves past the point on screen where it is supposed to add another child node, it crashes...
You arent creating a new groundGrass in the moveGroundGrass method. You need to create a new one like you did in the initWithSize:
In an Obj-C you create an object like this: object *name [object new]; where object can be f.e. UIView.
If you want to have easy access to the objects you are creating you can add them into an NSMutableArray but if you only want to add them and then do nothing to them(not recommended) you can just create it and then add it to the view.
I have a simple game that I've been working on, a side scrolling space adventure where you save cats. A very simple game, but I'm running across an issue with. My game starts up normally, showing the main menu scene. Hit play, and it transititons to the game scene without issue, however, I'm finding that the first time I press start to begin the game, I get a bit of loading lag. This lag only appears the very first time you load the game.
Upon hitting start, the following method is called:
-(void)nonplayerSprites:(StartReason)endReason{
if (endReason == kStartGame){
// [self playMusic:#"Electrodoodle.mp3"];
[self spawnPlayer];
[self setupUI];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(enemySpawn) onTarget:self],
[SKAction waitForDuration:2.0]]]]];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(spawnCat) onTarget:self],
[SKAction waitForDuration:1.5]]]]];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(powerUpSpawnCheck) onTarget:self],[SKAction
waitForDuration:2.0]]]]];
} else if (endReason == kEndGame){
[self removeAllActions];
} else if (endReason == kRestartGame){
MainGame * myScene =
[[MainGame alloc] initWithSize:self.size];
SKTransition *reveal =
[SKTransition fadeWithDuration:0.5];
[self.view presentScene: myScene transition: reveal];
}
}
I have four sprite nodes that it calls, a UFO, a cat, the player rocket and a randomized power up. Two SKLabelNodes for player score and life total are called via the spawnUI method, and Additionally, music will play. Although I currently have it disabled.
The enemy and friendly nodes are all called using a similar method:
-(void)enemySpawn
{
int enemyChoice = [self getRandomNumberBetween:0 to:1];
if (enemyChoice == 0){
_enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
[_enemy setScale:1.0];
}else if (enemyChoice == 1){
_enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_alt"];
[_enemy setScale:0.50];
}
_enemy.anchorPoint = CGPointZero;
_enemy.zPosition = 2.0;
_enemy.name = #"rocket";
_enemy.position = CGPointMake(self.size.width + _enemy.size.width, [self
getRandomNumberBetween:0 to:250]);
SKAction *enemyMove = [SKAction sequence:#[[SKAction moveToX:-40 duration:[self
getRandomNumberBetween:2 to: 5]],
[SKAction removeFromParent]]];
[_worldNode addChild:_enemy];
[_enemy runAction:enemyMove];
}
I feel like I know what the issue is, the game data is being loaded into ram for the first time, but how do I avoid the pause while this loads? I wonder if I'm doing something wrong, because it doesn't seem like I'm loading enough to cause a noticable 1 to 3 second pause upon hitting the start button. The second time through, say after the player dies and restarts, the pause is gone and everything plays out smoothly. I apologize if I'm leaving out any important information, I'm pretty new at SpriteKit.
Anyone have some thoughts?
Thanks in advance.
I had the same problem.
It seems that when you use [SKSpriteNode spriteNodeWithImageNamed:#"enemy"] or *[SKSpriteNode spriteNodeWithImageNamed:#"enemy_alt"]* or any other texture loading method, texture is not stored in buffers for quick access so first call of those methods is giving you lags.
The solution is next :
Add properties for your game objects textures
#property(nonatomic,strong) SKTexture *enemyTexture;
.
.
.
add method to your scene subclass:
-(void)preloadTextures
{
self.enemyTexture = [SKTexture textureWithImageNamed:#"enemy"];
.
.
.
.
[SKTexture preloadTextures:#[self.enemyTexture,...] withCompletionHandler:^{
NSLog(#"Textures preloaded");
}];
}
Now call that method before presenting
MainGame * myScene =
[[MainGame alloc] initWithSize:self.size];
SKTransition *reveal =
[SKTransition fadeWithDuration:0.5];
[myScene preloadTextures];
[self.view presentScene: myScene transition: reveal];
Hope it will do the trick for you too.
Situation:
I'm getting some mysterious crashing shortly after a CCCallFunc. In short, we have a button. The button has a tag to identify it later. When the button is pressed, we run some actions to animate it, and when the animation is done, we CCCallFunc another method to transition to another scene. We crash shortly after the CCCallFunc. Source and errors below.
Point Of Crash (in cocos2d source):
// From CCActionInstant.m of cocos2d
-(void) execute
{
/*** EXC_BAD_ACCESS on line 287 of CCActionInstant.m ***/
[targetCallback_ performSelector:selector_];
}
#end
Snapshot of Thread 1:
My Code:
Below is some source taken from MenuLayer.m (a simple menu to display a button).
// from MenuLayer.m
// …
#implementation MenuLayer
-(id) init{
if((self=[super init])) {
/****** Create The Play Button (As a CCMenu) ********/
CCSprite *playSprite = [CCSprite spriteWithFile:#"playbutton.png"];
CCMenuItemSprite *playItem = [CCMenuItemSprite itemFromNormalSprite:playSprite selectedSprite:nil target:self selector:#selector(animateButton:)];
playItem.tag = 3001;
playItem.position = ccp(160.0f, 240.0f);
CCMenu *menu = [CCMenu menuWithItems:playItem, nil];
menu.position = ccp(0.0f, 0.0f);
[self addChild:menu z:0];
}
}
// ...
- (void)animateButton:(id)sender{
/*** Run an animation on the button and then call a function ***/
id a1 = [CCScaleTo actionWithDuration:0.05 scale:1.25];
id a2 = [CCScaleTo actionWithDuration:0.05 scale:1.0];
id aDone = [CCCallFunc actionWithTarget:self selector:#selector(animationDone:)];
[sender runAction:[CCSequence actions:a1,a2,aDone, nil]];
}
- (void)animationDone:(id)sender{
/*** Identify button by tag ***/
/*** Call appropriate method based on tag ***/
if([(CCNode*)sender tag] == 3001){
/*** crashes around here (see CCActionInstant.m) ***/
[self goGame:sender];
}
}
-(void)goGame:(id)sender{
/*** Switch to another scene ***/
CCScene *newScene = [CCScene node];
[newScene addChild:[StageSelectLayer node]];
if ([[CCDirector sharedDirector] runningScene]) {
[[CCDirector sharedDirector] replaceScene:newScene]];
}else {
[[CCDirector sharedDirector] runWithScene:newScene];
}
}
Use CCCallFuncN instead of CCCallFun.
CCCallFuncN passes the Node as parameter, the problem with CCCallFun is that you are loosing reference of the node.
I test your code with CCCallFuncN and works ok.
Just a hunch. Besides checking for memory leaks, try to schedule a selector with a 0 second interval instead of directly sending the goGame message. I have a suspicion that director's replaceScene causes a cleanup of the scene and all objects associated with it. That in turn could leave the CCCallFunc action in an undefined state. Although normally it works fine - which is to say that this is just another indication about something sketchy, memory- respectively object-lifetime-management-wise.
Btw, if you support iOS 4 as a minimum, use CCCallBlock instead of CCCallFunc. That's safer and cleaner.