My sprite kit game has been having a memory problem and i have tried everything i know of to fix it but still nothing. A little background on the issue; the first time you play the game, memory is fine, it stays completely stable. But when you die and hit play again, that's when the memory starts creeping up. It seems that the more you play again, the faster the memory creeps up. You can be on any scene and the memory being used will still climb. Here's a picture showing instruments, http://postimg.org/image/5olu8h7tp/.
Here's the code i have:
creates the object:
-(void)createObstacle0 {
int yMin = (CGRectGetMidY(self.frame)+190);
int yMax = (CGRectGetMidY(self.frame)+270);
CGPoint startPoint = CGPointMake(-20, yMin + arc4random_uniform(yMax - yMin));
SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithImagedNamed:#"obstacle"];
obstacle.position = CGPointMake(startPoint.x, startPoint.y);
obstacle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:21];
obstacle.physicsBody.categoryBitMask = enemyCategory;
obstacle.physicsBody.contactTestBitMask = playerCategory;
obstacle.physicsBody.dynamic = NO;
obstacle.name = #"obstacle";
[self addChild:obstacle];
SKAction *move = [SKAction moveTo:CGPointMake(340, startPoint.y) duration:minTime +arc4random_uniform(maxTime - minTime)];
SKAction* removeObstacle = [SKAction removeFromParent];
[obstacle runAction:[SKAction sequence:#[move, removeObstacle]] completion:^{
score++;
scorelabel.text = [NSString stringWithFormat:#"%lu",(unsigned long)score];
}];
float randomNum = arc4random_uniform(3.0) + 0.2;
[self performSelector:#selector(createObstacle0) withObject:nil afterDelay:randomNum];
}
Collision detector and scene transition:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (playerCategory | enemyCategory)) {
playerAlive = NO;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:score forKey:#"playerScore"];
[defaults synchronize];
int highScore = [[[NSUserDefaults standardUserDefaults] objectForKey:#"High Score"] integerValue];
if (highScore){
if (highScore < score){
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:score] forKey:#"High Score"];
}
}
else{
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:score] forKey:#"High Score"];
}
[[NSUserDefaults standardUserDefaults] synchronize];
SKTransition *reveal = [SKTransition fadeWithDuration:0.75];
MGLCreateGameOverScene *scene = [MGLCreateGameOverScene sceneWithSize:self.view.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[self.view presentScene:scene transition:reveal];
}
}
To remove remaining nodes:
-(void)update:(CFTimeInterval)currentTime {
if (playerAlive == NO) {
[self enumerateChildNodesWithName:#"obstacle" usingBlock:^(SKNode *node, BOOL *stop) {
[node removeFromParent];
}];
}
}
As i said before, the first time you play, memory stays completely stable. It's only after the first time that memory starts to rise. It appears that the more you revisit the main game scene, the faster the memory climbs. I really need help solving this, my game has been down for two weeks now and i really want it back up. I appreciate all help.
Your problem is with the line:
[self performSelector:#selector(createObstacle0) withObject:nil afterDelay:randomNum];
This method actually retains self (by creating a timer) until after the selector is performed. What you need to do is add to the method didBeginContact: the following line before changing scenes:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(createObstacle0) object:nil];
This will destroy the timer that is retaining self and calling that method unendingly.
You can check that this works by adding a call to NSLog(#"%s", __PRETTY_FUNCTION__); in the dealloc method of your scene.
Related
I am using AVCapture to capture video and save it. But I need to provide zooming option like pinch to zoom or through a zoom button. Also video should be saved in exactly in same manner in which it is being displayed, I mean when zoomed in, it should be saved zoomed. Any help, Link is appreciated. My code for setting up AVCapture session is:
- (void)setupAVCapture{
session = [[AVCaptureSession alloc] init];
session.automaticallyConfiguresApplicationAudioSession=YES;
[session beginConfiguration];
session.sessionPreset = AVCaptureSessionPresetMedium;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
captureVideoPreviewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:captureVideoPreviewLayer];
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
NSLog(#"ERROR: trying to open camera: %#", error);
}
[session addInput:input];
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
[session addOutput:movieFileOutput];
[session commitConfiguration];
[session startRunning];
}
I faced the same problem also, and I have solved it using these two steps:
Add a PinchGestureRecognizer event something like that in your Camera Preview View Controller
- (IBAction)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer{
if([gestureRecognizer isMemberOfClass:[UIPinchGestureRecognizer class]])
{
effectiveScale = beginGestureScale * ((UIPinchGestureRecognizer *)gestureRecognizer).scale;
if (effectiveScale < 1.0)
effectiveScale = 1.0;
CGFloat maxScaleAndCropFactor = [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] videoMaxScaleAndCropFactor];
if (effectiveScale > maxScaleAndCropFactor)
effectiveScale = maxScaleAndCropFactor;
[CATransaction begin];
[CATransaction setAnimationDuration:.025];
[self.previewView.layer setAffineTransform:CGAffineTransformMakeScale(effectiveScale, effectiveScale)];
[CATransaction commit];
if ([[self videoDevice] lockForConfiguration:nil]) {
[[self videoDevice] setVideoZoomFactor:effectiveScale];
[[self videoDevice] unlockForConfiguration];
}}}}
** Note that the key method for persisting the zoom level for video device is [device setVideoZoomFactor:]
2- In the IBAction of the Record Button , add this code to capture the video ( recording ) then to save the recorded video in the a certain path with certain name
- (IBAction)recordButtonClicked:(id)sender {
dispatch_async([self sessionQueue], ^{
if (![[self movieFileOutput] isRecording])
{
[self setLockInterfaceRotation:YES];
if ([[UIDevice currentDevice] isMultitaskingSupported])
{
// Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until the app returns to the foreground unless you request background execution time. This also ensures that there will be time to write the file to the assets library when the app is backgrounded. To conclude this background execution, -endBackgroundTask is called in -recorder:recordingDidFinishToOutputFileURL:error: after the recorded file has been saved.
[self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]];
}
// Update the orientation on the movie file output video connection before starting recording.
// Start recording to a temporary file.
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[#"movie" stringByAppendingPathExtension:#"mov"]];
[[self movieFileOutput] startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
}
else
{
[[self movieFileOutput] stopRecording];
}
});
}
I hope that helps you
Add UIPinchGestureRecognizer object to your and handle callback like this:
- (void) zoomPinchGestureRecognizerAction: (UIPinchGestureRecognizer *) sender {
static CGFloat initialVideoZoomFactor = 0;
if (sender.state == UIGestureRecognizerStateBegan) {
initialVideoZoomFactor = _captureDevice.videoZoomFactor;
} else {
CGFloat scale = MIN(MAX(1, initialVideoZoomFactor * sender.scale), 4);
[CATransaction begin];
[CATransaction setAnimationDuration: 0.01];
_previewLayer.affineTransform = CGAffineTransformMakeScale(scale, scale);
[CATransaction commit];
if ([_captureDevice lockForConfiguration: nil] == YES) {
_captureDevice.videoZoomFactor = scale;
[_captureDevice unlockForConfiguration];
}
}
}
I am currently coding a game for the iPhone and I sometimes get random crashes with the error in the title.
I've researched quite a bit and it probably has to do with leaking(?) memory issues. The people I asked told me, that it may has to do with adding a nil object. I set up exception breakpoints and NSZombies but neither of those recognize the crash, nor give me the exact code line where the crash occurs.
After some crash-testing I noticed that most of the time it happens when either the touchesBegan method or the didBeganContact method is active.
Here's the code of the touchesBegan method and the didBeginContact method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (touchEnabled == YES){
firstTouch++;
if (firstTouch == 1){
SKAction* removeFade = [SKAction removeFromParent];
SKAction* startFade = [SKAction fadeAlphaTo:0 duration:0.5];
SKAction* fadeSeq = [SKAction sequence:#[startFade, removeFade]];
[startScreen runAction:fadeSeq completion:^{
startScreenRemoved = YES;
[self gameCountdown];
touchToShoot = YES;
}];
}
if (firstTouch == 2){
weaponActivated = YES;
}
if (gameOver == YES){
[self removeAllActions];
[self removeAllChildren];
[bgPlayer stop];
touchToShoot = NO;
SKScene* gameScene = [[GameScene alloc] initWithSize:self.size];
SKTransition *doors = [SKTransition doorsOpenVerticalWithDuration:1];
[self.view presentScene:gameScene transition:doors];
gameOver = NO;
}
}
if (touchToShoot == YES) {
[self weaponParticle];
touchToShoot = NO;
[self performSelector:#selector(enableTouchToShoot) withObject:nil afterDelay:0.3]; //-enableTouchToShoot{} = touchToShoot = YES;
}
//Pause Button
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"pauseButton"]) {
[self pauseMenu];
}
else if ([node.name isEqualToString:#"continue"]) {
self.paused = NO;
pauseBG.hidden = YES;
pauseText.hidden = YES;
backm.hidden = YES;
cont.hidden = YES;
//Damit unsichtbare Buttons nicht während dem Spiel gedrückt werden
backm.zPosition = -100;
cont.zPosition = -100;
}
else if ([node.name isEqualToString:#"backtomenu"]) {
self.paused = NO;
[self displayAlert];
}
}
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (contact.bodyA.categoryBitMask == shipCategory) {
//Explosion Animation
SKAction* wait =[SKAction waitForDuration:0.5];
SKAction* fadeOut = [SKAction scaleTo:0.0 duration:1];
remove = [SKAction removeFromParent];
explodeSequenceGO =[SKAction sequence:#[fadeOut,wait,remove]];
ExplosionPath = [[NSBundle mainBundle] pathForResource:#"Explosion" ofType:#"sks"];
Explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:ExplosionPath];
Explosion.position = CGPointMake(contact.bodyA.node.position.x, contact.bodyA.node.position.y);
[self addChild:Explosion];
[Explosion runAction:explodeSequenceGO];
[contact.bodyA.node removeFromParent];
[contact.bodyB.node removeFromParent];
[shipSmoke removeFromParent];
[player1 removeFromParent];
touchEnabled = NO;
[self gameOver];
}
if (contact.bodyA.categoryBitMask == enemyCategory || contact.bodyB.categoryBitMask == enemyCategory) {
//Explosion Animation
SKAction* wait =[SKAction waitForDuration:0.5];
SKAction* fadeOut = [SKAction scaleTo:0.0 duration:1];
remove = [SKAction removeFromParent];
explodeSequenceGO =[SKAction sequence:#[fadeOut,wait,remove]];
ExplosionPath = [[NSBundle mainBundle] pathForResource:#"Explosion" ofType:#"sks"];
Explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:ExplosionPath];
Explosion.position = CGPointMake(contact.bodyA.node.position.x, contact.bodyA.node.position.y);
[Explosion runAction:explodeSequenceGO];
[self addChild:Explosion];
[contact.bodyA.node removeFromParent];
[contact.bodyB.node removeFromParent];
hitCount++;
[self scoreChange:100];
}
if (hitCount>39) {
[self eLevel2];
}
}
Does anyone see a fault? I greatly appreciate any tip, since I am searching for this bug for weeks....
EDIT: The crash just points to the "main" function, which doesn't help at all
And in each of the Thread "actions" it just points to Assembly(?) code:
And like I said, I tried to analyze the crashes by various debug tools (NSZombies, MemoryTool, Exceptional Breakouts, etc.) but none of them give me useful infos. When the app crashes, the debugging tools just stop recording, but they don't show me any faults or crash results.
I know that this is a few months old, but I too was experiencing the EXC_BAD_ACCESS error when transitioning from one scene to another. After reading multiple posts, and commenting out almost every line of code in my game, what fixed it for me was the removing all of the objects from the existing scene before returning from the method and bang error was gone.
- (void)returnToMenuScene {
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
MenuScene *menuScene = [MenuScene sceneWithSize:self.scene.size];
[self.scene.view presentScene:menuScene transition:reveal];
[self.gameScene removeAllChildren];
return;
}
- (GameScene *)gameScene {
return (GameScene *)self.scene;
}
I hope this helps someone else in the future
One thing I found early on is that transitioning between scenes caused crashes if the original scene was killed too early by SpriteKit. - Crazy talk I know.
To help this I created a base class scene which included a cleanup op.
-(void)cleanUp
{
[self cleanUpChildrenAndRemove:self];
}
- (void)cleanUpChildrenAndRemove:(SKNode*)node {
for (SKNode* child in node.children) {
[self cleanUpChildrenAndRemove:child];
}
[node removeFromParent];
}
Then when i transition scenes, I let the old scene live for a while before destroying it.
Notice the use of an action on the old scene to clean itself up later on.
Call me paranoid, delusional, but it fixed my crash. I also needed to call cleanup when I received Terminate events on the App.
if( oldscene!=nil )
{
SKTransition* doors = [SKTransition doorsOpenVerticalWithDuration:1.5];
doors.pausesIncomingScene = NO;
[[self view] presentScene:newscene transition:doors];
SKNode* dummynode = [SKNode node];
// wait for the transition to complete before we give up the referene to oldscene
[dummynode runAction:[SKAction waitForDuration:2.0] completion:^{
if ([oldscene isKindOfClass:[MyBaseScene class]])
{
// failing to clean up nodes can result in crashes... nice.
[((MyBaseScene*)oldscene) cleanUp];
}}];
[oldscene addChild:dummynode];
}
else
{
[[self view] presentScene:newscene];
}
Trying to diagnose why my game is building up memory, causing it to eventually slow down to the point of unresponsiveness. Based on previously asked questions, i think i might know what's causing it, not sure though, so i will just post what i think it might be.
[self performSelector:#selector(createObstacle0) withObject:nil afterDelay:1.0];
[self performSelector:#selector(score0) withObject:nil afterDelay:1];
....
-(void)createObstacle0 {
int yMin = (CGRectGetMidY(self.frame)+190);
int yMax = (CGRectGetMidY(self.frame)+270);
CGPoint startPoint = CGPointMake(-20, yMin + arc4random_uniform(yMax - yMin));
SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithImageNamed:#"obstacle"];
obstacle.position = CGPointMake(startPoint.x, startPoint.y);
obstacle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:21.5];
obstacle.physicsBody.categoryBitMask = enemyCategory;
obstacle.physicsBody.contactTestBitMask = playerCategory;
obstacle.physicsBody.dynamic = NO;
obstacle.name = #"obstacle0";
[self addChild:obstacle];
[obstacle runAction:[SKAction moveTo:CGPointMake(340, startPoint.y) duration:minTime + arc4random_uniform(maxTime - minTime)]];
float randomNum = arc4random_uniform(3.0) + 0.1;
[self performSelector:#selector(createObstacle0) withObject:nil afterDelay:randomNum];
}
-(void)score0 {
[self enumerateChildNodesWithName:#"obstacle0" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x > 330) {
score++;
scorelabel.text = [NSString stringWithFormat:#"%lu",(unsigned long)score];
[node removeFromParent];
}
}];
[self performSelector:#selector(score0) withObject:nil afterDelay:.1];
}
I read here that perferomSelector can cause some memory problems. For me, after playing my game just once, i can sit at the main menu and both cpu and memory will just continue to rise. No idea what's going on and appreciate all help. Is the code i have here to blame?
I have code as the following:
- (void)setPosts:(NSArray *)posts
{
_posts = posts;
int totalHeight = 0;
for (TumblrPost *post in posts) {
totalHeight += post.thumbH;
}
dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_async(mainQ, ^{
int cumulativeY = 0;
int postCount = 0;
for (TumblrPost *post in posts) {
NSArray* array = [[NSBundle mainBundle] loadNibNamed:#"ThumbnailView" owner:nil options:nil];
ThumbnailView* thumbnail = [array objectAtIndex:0];
thumbnail.frame = CGRectMake(0,cumulativeY,0,0);
thumbnail.userInteractionEnabled = YES;
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showMainImage:)];
[thumbnail addGestureRecognizer:gesture];
[self.multiThumbnailView addSubview:thumbnail];
[thumbnail loadUrl:post.url];
cumulativeY+=100;//post.thumbH;
if(postCount >=2)
break;
postCount++;
}
NSLog(#"Set posts method");
});
}
- (void)showMainImage:(UITapGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateChanged || gesture.state == UIGestureRecognizerStateEnded)
{
int thumbIndex = [self.multiThumbnailView.subviews indexOfObject:gesture.view];
self.selectedPost = (TumblrPost*)[self.posts objectAtIndex:thumbIndex];
[self performSegueWithIdentifier:#"ShowMainPost" sender:self];
}
}
multiThumbnailView is a UIView which I have in my storyboard and ThumbnailView is an xib/class combination which is a 100x100 square with a label in it that says 'test'.
When I run my code I get three boxes in a vertical line but the gesture recogniser doesn't fire when I click on my sub views. Everything has userInteractionEnabled ticked. I tried making a test gesturerecognizer on the main multiThumbnailView and that worked.
Please help!
My guess is that ThumbnailView is a subclass of UIImageView - which by default sets its userInteractionEnabled to NO.
Make sure that you set userInteractionEnabled = YES on every ThumbnailViewthat you want to intercept taps.
EDIT:
In addition, you set the frame of the ThumbnailView to be with size of (0,0).
That means that this view is basically invisible and thus won't intercept touches.
And finally, please don't do:
ThumbnailView* thumbnail = [array objectAtIndex:0];
Instead you can check the count of the array or simply:
ThumbnailView* thumbnail = [array lastObject];
I am using Cocos2d engine and I've faced a strange problem.
I have a sprite. Also, I have 2 animations for that sprite. And I want to play one animation when app loads and second, after ccTouchevent is called.
walkAnim = [CCAnimation animation];
dropAnim = [CCAnimation animation];
for( int q=1;q<12;q++){
[walkAnim addFrameWithFilename: [NSString stringWithFormat:#"walkforward_%.2d.png", q]];
[dropAnim addFrameWithFilename: [NSString stringWithFormat:#"drop_%.2d.png", q]];
}
action = [CCAnimate actionWithAnimation:walkAnim];
action.duration = 2;
id act = [CCRepeatForever actionWithAction:action];
[sprite runAction:act];
So, here we see an animating sprite.
[sprite stopAllActions]; //and here my torture begins
I have tried many ways of creating an action:
I've tried to add another AnimateAction, tried to replace the current animation, but everything results in a crash.
[action setAnimation:dropAnim];
and
CCAnimate* animat = [[CCAnimate alloc]initWithDuration:30 animation:dropAnim restoreOriginalFrame:YES];
and
id action = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:dropAnim]];
[player1 runAction:action];
The crash is in [CCAnimate actionWithAnimation:]
+(id) actionWithAnimation: (CCAnimation*)anim
{
return [[[self alloc] initWithAnimation:anim restoreOriginalFrame:YES] autorelease];
}
Thanks!
To launch an action from another method, you have to retain the action Eg: [action retain];
To launch an action from another method, you have to retain the action
Eg: [action retain]; and then do not forget to release it
-(void)create{
for( int q=1;q<12;q++){
[playerWalkAnim addFrameWithFilename: [NSString stringWithFormat:#"walkforward_%.2d.png", q]];
}
playerAction = [CCAnimate actionWithAnimation:playerWalkAnim];
playerAction.duration = 2;
[playerAction retain];
}
-(void)launch{
[player1 runAction:playerAction];
}