I'm encountering a very weird error causing my app to crash. I have created a custom SKSpriteNode called heroSpriteNode. Here is my initialization:
(id) init
{
if (self = [super init])
{
[self loadAnimations];
SKTexture *temp = self.gorillaStandingFrames[0];
self = [heroSpriteNode spriteNodeWithTexture:temp];
[self setUpHeroDetails];
}
return self;
}
The error is occurring when attempting to run an action on "self" the custom SkSpriteNode, like this:
-(void)jump
{
NSLog(#"Jump Tap!");
if(self.isOnTheGround)
{
self.physicsBody.velocity = CGVectorMake(0, 0);
self.physicsBody.angularVelocity = 0;
self.timedJump=NO;
[self.physicsBody applyImpulse:CGVectorMake(0, 350)];
[self removeActionForKey:#"walkingInPlaceBear"];
[self jumpingGorilla2]; // HERE IS THE ERROR
self.isOnTheGround=NO;
self.isRunning=NO;
self.isStanding=NO;
self.jumpFixTimer = [NSTimer scheduledTimerWithTimeInterval:0.4 target:self selector:#selector(flipTimedJumpBoolean) userInfo:nil repeats:NO];
}
}
I know this animation (and other animations I run) is the issue, because when I comment out that line the game runs fine.
Here is how I am loading the textures and creating an action:
-(void)loadJumpAnimation2
{
NSMutableArray *jumpFrames = [NSMutableArray array];
SKTextureAtlas *gorillaAnimatedAtlas = [SKTextureAtlas atlasNamed:#"mainCharJump"];
int numImages = gorillaAnimatedAtlas.textureNames.count;
for (int i=2; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"j%d", i];
SKTexture *temp = [gorillaAnimatedAtlas textureNamed:textureName];
[jumpFrames addObject:temp];
}
self.gorillaFlyingJump = jumpFrames;
}
-(void)jumpingGorilla2
{
[self runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:self.gorillaFlyingJump
timePerFrame:0.25f
resize:NO
restore:YES]] withKey:#"jump"];
return;
}
When it crashes there is no crash report in the logger, only a popup message in Xcode that is of type: 9 SIGKILL. It seems like this could be a memory issue related to how the animation frames are being stored? The animations ran fine on a standard SkSpriteNode, then after refactoring much of the code into a custom SkSpriteNode, I can no longer apply animations. Any ideas? Thank you.
UPDATE:
I've created another class to try to isolate the issue I'm having, I will post the code below with comments.
Here is the entire test class I created:
#import "heroTestNode.h"
#implementation heroTestNode
- (id) init
{
if (self = [super init])
{
[self loadAnimations];
SKTexture *temp = self.gorillaWalkingFrames[0];
self = [heroTestNode spriteNodeWithTexture:temp];
}
return self;
}
-(void)loadAnimations
{
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *gorillaAnimatedAtlas = [SKTextureAtlas atlasNamed:#"mainCharRun2"];
int numImages = gorillaAnimatedAtlas.textureNames.count;
NSLog(#"Here are how many animation frames: %d", numImages);
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"r%d", i];
SKTexture *temp = [gorillaAnimatedAtlas textureNamed:textureName];
[walkFrames addObject:temp];
}
self.gorillaWalkingFrames = walkFrames;
}
-(void)walkingGorilla // running this is causing the SIGKILL 9 crash
{
[self runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:self.gorillaWalkingFrames
timePerFrame:0.1
resize:NO
restore:YES]] withKey:#"test"];
return;
}
#end
Here is the interface:
#import <SpriteKit/SpriteKit.h>
#interface heroTestNode : SKSpriteNode
#property NSMutableArray *gorillaWalkingFrames;
-(void)walkingGorilla;
#end
And finally, here is how I am instantiating it:
heroTestNode *test = [[heroTestNode alloc] init];
test.position = CGPointMake((self.frame.size.width/2)+30,self.frame.size.height/3);
[self addChild:test];
[test walkingGorilla]; // this is causing the SIGKILL 9 crash
This causes the it to freeze "compressing" the view into the bottom left corner of my iPad and going unresponsive. I have no idea what would be causing this.
Related
I am new to the iOS native game development. I am trying to create animation using some frames for Ideal state for the character. I am following tutorial from the Ray's Website. Look like I am doing everything fine but I can't see animation in working. Only first frame (default) is visible all the time. I debug the code & it's indeed visiting blinking player method where all 3 frames are also present, but nothing happening on screen.
It will be great if someone can guide/help me identify the issue.
#implementation GameScene
{
NSArray *_playerBlinkFrames;
}
// Player
SKNode *_player;
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self createPlayer];
}
return self;
}
- (void) createPlayer
{
SKTextureAtlas *playerAnimatedAtlas = [SKTextureAtlas atlasNamed:#"Assets"];
_player = [SKNode node];
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Idle"];
[_player addChild:sprite];
[sprite setName:#"Ball"];
NSMutableArray *blinkFrames = [NSMutableArray array];
SKTexture *temp = [playerAnimatedAtlas textureNamed:#"Idle.png"];
SKTexture *temp2 = [playerAnimatedAtlas textureNamed:#"Blink.png"];
SKTexture *temp3 = [playerAnimatedAtlas textureNamed:#"LookRight.png"];
[blinkFrames addObject:temp];
[blinkFrames addObject:temp2];
[blinkFrames addObject:temp3];
_playerBlinkFrames = blinkFrames;
_player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:_player];
[self blinkingPlayer];
}
-(void)blinkingPlayer
{
[_player runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:_playerBlinkFrames
timePerFrame:0.3f
resize:NO
restore:YES]] withKey:#"blinkingInPlacePlayer"];
return;
}
Thanks.
Try this:
// Player
SKSpriteNode *_player;
//..//
SKTextureAtlas *playerAnimatedAtlas = [SKTextureAtlas atlasNamed:#"Assets"];
NSMutableArray *blinkFrames = [NSMutableArray array];
SKTexture *temp = [playerAnimatedAtlas textureNamed:#"Idle.png"];
SKTexture *temp2 = [playerAnimatedAtlas textureNamed:#"Blink.png"];
SKTexture *temp3 = [playerAnimatedAtlas textureNamed:#"LookRight.png"];
[blinkFrames addObject:temp];
[blinkFrames addObject:temp2];
[blinkFrames addObject:temp3];
_playerBlinkFrames = blinkFrames;
SKTexture *tempSprite = _playerBlinkFrames[0];
_player = [SKSpriteNode spriteNodeWithTexture:tempSprite];
I've not tested it, but i think you have create a SKNode player covered by a spritenode.
let me know
I'm trying to create 10 balloons in my app. I create a random CGPoint to set my node's position and check, in case that already exist a node at this point I try again.
I don't know why, the balloons keep being placed over another balloon.
Any ideia of what I am doing wrong?
Here is my code:
-(void)gerarBaloes:(int)quantidade
{
balloons = [NSMutableArray new];
u_int32_t maxHorizontal = 900;
u_int32_t minHorizontal = 100;
u_int32_t maxVertical = 650;
u_int32_t minVertical = 100;
for (int i=1; i<= quantidade; i++) {
CGPoint posicao = CGPointMake(arc4random_uniform(maxHorizontal - minHorizontal + 1) + minHorizontal, arc4random_uniform(maxVertical - minVertical + 1) + minVertical);
while (![self validPosition:posicao]) {
posicao = CGPointMake(arc4random_uniform(maxHorizontal - minHorizontal + 1) + minHorizontal, arc4random_uniform(maxVertical - minVertical + 1) + minVertical);
}
balao* nBalao = [[balao alloc]initWithPosition:posicao];
SKLabelNode* numero = [[SKLabelNode alloc]initWithFontNamed:#"Arial Rounded MT Bold"];
numero.text = [NSString stringWithFormat:#"%d",i];
numero.zPosition = 1;
nBalao.body.zPosition = 0;
[nBalao addChild:numero];
nBalao.body.name = #"balloon";
[balloons addObject:nBalao];
[self addChild:nBalao];
}
}
And this is my validation method:
-(BOOL)validPosition:(CGPoint)position
{
NSArray* nodes = [self nodesAtPoint:position];
NSLog(#"total de nodes %d",nodes.count);
if (nodes.count > 0) {
for (SKNode* n in nodes) {
if ([n isKindOfClass:[balao class]]) {
return NO;
}
}
return YES;
}else{
return YES;
}
}
Balao.m class:
#implementation balao
-(id)initWithPosition:(CGPoint)pos
{
if (self = [super init]) {
self.position = pos;
int n = arc4random_uniform(4);
_balloonAtlas = [SKTextureAtlas atlasNamed:[NSString stringWithFormat:#"balloon%d",n]];
_explosionFrames = [NSMutableArray array];
int numImages = _balloonAtlas.textureNames.count;
for (int i=1; i<= numImages; i++){
NSString *textureName = [NSString stringWithFormat:#"balloon_%d",i];
SKTexture *temp = [_balloonAtlas textureNamed:textureName];
[_explosionFrames addObject:temp];
}
SKTexture *textInicial = [_explosionFrames[0] copy];
[_explosionFrames removeObjectAtIndex:0];
self.body = [SKSpriteNode spriteNodeWithTexture:textInicial];
[self addChild:_body];
}
return self;
}
- (void)explodeBalloon
{
SKAction* explosao = [SKAction animateWithTextures:_explosionFrames timePerFrame:0.02f resize:NO restore:YES];
[self.body runAction:explosao completion:^(void){
[self removeFromParent];
}];
[self runAction:[SKAction playSoundFileNamed:#"popSound.mp3" waitForCompletion:NO]];
}
This is how the balloons appears:
Edit:
I tried the suggested method, with CGRectCointainsPoint, but it didn't work too. :/
Checking for nodes at a given point is not enough to prevent overlap. You need to check whether the point falls inside the frame of another balloon. You can modify the function like this
-(BOOL)validPosition:(CGPoint)position
{
NSArray* nodes = [self children];
if (nodes.count > 0) {
for (SKNode* n in nodes) {
if ([n isKindOfClass:[balao class]]) {
if (CGRectContainsPoint([n getBalloonFrame], position)) // Changed line
{
return NO
}
}
}
return YES;
}
return YES;
}
CGRectContainsPoint checks whether a given rectangle contains a point.
Inside balao class define the following function. Also note the changed line in the above code.
-(void)getBalloonFrame
{
return CGRectOffset(self.body.frame, self.frame.origin.x, self.frame.origin.y)
}
I have an NSArray that I fill with SKTexture to animate an animal leaping:
#interface PSFrogNode ()
#property (strong, nonatomic) NSArray *leapFrames;
#end
#implementation PSFrogNode {
}
- (instancetype)init
{
self = [super init];
if (self) {
SKTextureAtlas *frogLeapAtlas = [SKTextureAtlas atlasNamed:kFrogLeapAtlas];
int numImages = frogLeapAtlas.textureNames.count;
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:numImages];
for (int i=1; i <= numImages/2; i++) {
NSString *textureName = [NSString stringWithFormat:#"frogLeap%d", i];
SKTexture *temp = [frogLeapAtlas textureNamed:textureName];
[tempArray addObject:temp];
}
self.leapFrames = [NSArray arrayWithArray:tempArray];
self = [PSFrogNode spriteNodeWithTexture:[self.leapFrames objectAtIndex:0]];
[self setUserInteractionEnabled:NO];
}
return self;
}
- (void)animateFrogLeap
{
[self runAction:[SKAction animateWithTextures:self.leapFrames timePerFrame:0.09 resize:NO restore:YES]];
}
The array self.leapFrames is allocated in the init method but when the scene calls the animateFrogLeap method it's empty (meaning nil).
Why is that?
You want to create the array outside of the init method and then pass it in:
When creating the view
SKTextureAtlas *frogLeapAtlas = [SKTextureAtlas atlasNamed:kFrogLeapAtlas];
int numImages = frogLeapAtlas.textureNames.count;
NSMutableArray *images = [NSMutableArray arrayWithCapacity:numImages];
for (int i=1; i <= numImages/2; i++) {
NSString *textureName = [NSString stringWithFormat:#"frogLeap%d", i];
SKTexture *temp = [frogLeapAtlas textureNamed:textureName];
[images addObject:temp];
}
PSFrogNode *node = [[PSFrogNode alloc] initWithImages:images];
// Now use the node
PSFrogNode.h
-(instancetype)initWithImages:(NSArray *)images;
PSFrogNode.m
#interface PSFrogNode ()
#property (nonatomic, copy) NSArray *leapFrames;
#end
#implementation PSFrogNode
-(instancetype)initWithImages:(NSArray *)images
{
self = [self initWithTexture:[images firstObject]];
if (self) {
_leapFrames = [images copy];
[self setUserInteractionEnabled:NO];
}
return self;
}
As mentioned in my comment, the below line in your original implementation would mean that self would be reassigned, so the line before it (assigning the array to the property) would then be meaning less.
self = [PSFrogNode spriteNodeWithTexture:[self.leapFrames objectAtIndex:0]];
I'm loading an animation using textures from a TextureAtlas.
But the animation does not appear on screen.
I appears only when I NSLog the Textures! Looks like a bug.
Does somebody made similar experience?
SKTextureAtlas *atlas = [SKTextureAtlas atlasWithRetinaCorrection:spriteSheetName_];
if ([atlas.textureNames count] == 0) return;
NSArray *r = [self getSprites:WEAPON_FIREANIMATION ofDict:atlas.textureNames];
if ([r count] == 0)return;
SKSpriteNode *firstSprite = (SKSpriteNode*)[self childNodeWithName:tFIRSTSPRITE];
[firstSprite removeActionForKey:aFIRE_ANIM];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
int k =0;
if (self.weaponAnimDuration == 0)
{
k = (self.weaponShootInterval / 0.1f)/[r count];
} else
{
k = (self.weaponAnimDuration / 0.1f)/[r count];
}
for (int i = 0; i<k; i++)
{
for (NSString *sname in r)
{
[walkAnimFrames addObject:[SKTexture textureWithImageNamed:sname]];
}
}
NSLog(#"%#",walkAnimFrames);
SKAction *weaponAnim = [SKAction animateWithTextures:walkAnimFrames timePerFrame:0.1f];
[firstSprite runAction:weaponAnim withKey:aFIRE_ANIM];
#import "SKTextureAtlas+MySKTextureAtlas.h"
#implementation SKTextureAtlas (MySKTextureAtlas)
+(SKTextureAtlas*)atlasWithRetinaCorrection:(NSString*)name
{
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00)
{
name = [NSString stringWithFormat:#"%##2x",name];
}
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"atlasc"];
if (path == Nil) return Nil;
return [SKTextureAtlas atlasNamed:name];
}
#implementation NSArray (Tools)
-(NSArray*)animationTextureFrames
{
NSMutableArray *rr = [NSMutableArray new];
for (NSString *name in self)
{
SKTexture *tex = [SKTexture textureWithImageNamed:name];
[rr addObject:tex];
}
return rr;
}
I found the bug!
I had to preload the textures.
[SKTexture preloadTextures:explosionTextures withCompletionHandler:^(void){}];
This is how I do mine, and havent had any issues yet.
NSMutableArray *textures = [NSMutableArray arrayWithCapacity:5];
for (int i = 1; i < 5; i++) {
NSString *textureName = [NSString stringWithFormat:#"rocket%d", i];
SKTexture *texture = [SKTexture textureWithImageNamed:textureName];
[textures addObject:texture];
}
self.rocketAnimation = [SKAction animateWithTextures:textures timePerFrame:0.1];
// add animation to my sprite
[self.projectile runAction:[SKAction repeatActionForever:self.rocketAnimation]];
// add spite to screen etc...
[self addChild:self.projectile];
See this post, there is a bug when images have a lot of transparent space using texture atlas.
SKTexture returns wrong size?
NSLog is very slow compared to most other operations. Just inserting that call adds a significant pause to the main thread of your app. I'm guessing that your texture loading is sending tasks off to the GPU. Without the NSLog call the GPU may not have enough time to complete the tasks. Maybe you can preload the textures.
Go to you'r Project and follow these steps:- 1> Go to Build Settings, 2> Search for Enable Texture Atlas Generation and select YES
I add one sprite per body and run animation, but when I try to remove this sprite after reloading scene(calling afterLoadProcessing once again) - removes the only first with animation. I'm new to iOS & Cococs2D programming, please help me with advice.
Here is my code:
-(void)afterLoadProcessing:(b2dJson*)json
{
[super afterLoadProcessing:json];
[self removeChild:enemysprite cleanup:YES];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"player_enemy.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"player_enemy.png"];
[self addChild:spriteSheet];
NSMutableArray *enemyGusenitsaAnimFrames = [NSMutableArray array];
for (int i = 31; i <=36; ++i) {
[enemyGusenitsaAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"%d.png",i]]];
}
CCAnimation *gusenitsaMovAnim = [CCAnimation animationWithSpriteFrames:enemyGusenitsaAnimFrames delay:0.1f];
_enemyGusenitsaMovement = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:gusenitsaMovAnim]];
_enemyGusenitsaMovement.tag = 109;
std::vector<b2Body*> enemySprites;
json->getBodiesByName("enemy", enemySprites);
for (int i = 0; i < enemySprites.size(); i++) {
b2Body *bod = enemySprites[i];
enemysprite = [[CCSprite alloc] init];
[enemysprite stopAllActions];
enemysprite.scale = 0.04;
[self addChild:enemysprite z:50];
bod->SetUserData(enemysprite);
[[enemysprite runAction:[_enemyGusenitsaMovement copy]] autorelease];
}
}
Instead of use [self removeChild:enemysprite cleanup:YES];, you can remove all children
[self removeAllChildrenWithCleanup:YES];
Or loop all bodies and remove it :
for (int i = 0; i < enemySprites.size(); i++) {
b2Body *bod = enemySprites[i];
CCSprite *sprite = (CCSprite *)bod->GetUserData();
[self removeChild:sprite];
}