Cocos2d: CCSprite initWithFile in CCSprite subclass crashes - objective-c

I have cocos2d project with custom CCSprite subclass:
MyCustomSprite.h:
#import "cocos2d.h"
#interface MyCustomSprite : CCSprite
#end
MyCustomSprite.m:
#import "MyCustomSprite.h"
#implementation MyCustomSprite
- (id)init
{
self = [super initWithFile:#"index.png"];
return self;
}
#end
For some strange reason, this code will crash with "EXC_BAD_ACCESS".
But in spite of this, if i init super as ususal and then write code from CCSprite's initWithFile and initWithTexture, it will work fine:
self = [super init];
if (self) {
// Code from CCSprite.m - initWithFile
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage: #"index.png"];
CGRect rect = CGRectZero;
rect.size = texture.contentSize;
// Code from CCSprite.m - iniWithTexture
[self setTexture:texture];
[self setTextureRect:rect];
return self;
}
What's the reason that the first example crashes, and second not and what's the difference between them?
Thanks for your answers!

Ok, the reason is bad CCSprite design. If we look to CCSprite.h, we can find:
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
NSAssert(texture!=nil, #"Invalid texture for sprite");
// IMPORTANT: [self init] and not [super init];
if( (self = [self init]) )
}
And thats the reason. Instead of [super init] this method calls [self init], and creates recursion ([self init]-[super InitWithFile:]-[self initWithTexture]-[self init]-...).
.
So, the simplest way to solve this problem - just re-name your init method to something else (for example "initialize") and call it instead of init: [[MyCustomSprite
alloc] initialize].

Another approach that may work is just replace your code by this one:
- (id)init
{
self = [super init];
if (self != nil)
{
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:#"my_image_name.png"];
CGRect rect = CGRectZero;
if (texture != nil)
{
rect.size = texture.contentSize;
}
[self setDisplayFrame:[CCSpriteFrame frameWithTexture:texture rect:rect]];
}
return self;
}
And a simpler way, if you are using CCSpriteFrameCache, would be just setting the display frame by
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"my_image_name.png"]
Hope to help somebody!

Related

initWithCoder not working as expected in Xcode 6?

I am using Sprite Kit engine. I used to write code in initWithSize but since the update, initWithSize is not called automatically, instead viewDidLoad is called. I got to know that instead of initWithSize, initWithCoder had to be used, so i changed the code from:
-(id) initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor blueColor];
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
paddle.position = CGPointMake(150, 100);
paddle.physicsBody.dynamic = NO;
[self addChild:paddle];
}
return self;
}
to:
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
self.backgroundColor = [SKColor blueColor];
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
paddle.position = CGPointMake(150, 100);
paddle.physicsBody.dynamic = NO;
[self addChild:paddle];
}
return self;
}
Now, the color of the scene turns blue, but i am not able to add sprites to the scene. Neither do I get any error while building.
Are there some changes that I should make in initWithCoder? (If yes, please specify and elaborate)
Can initWithCoder be used with EXACTLY the same code as we did in initWithSize?
PS: Heres the full code I have in GameScene.m and I do not have any other scene.
#import "GameScene.h"
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
paddle.position = CGPointMake(150, 100);
paddle.physicsBody.dynamic = NO;
[self addChild:paddle];
}
return self;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
And I just copied and pasted the code from initWithSize to initWithCoder. It doesn't seem to create the paddle object.
Please help!
It may be a method signature mismatch?
Instead of instancetype it is defined as: - (id)initWithCoder:(NSCoder *)decoder
I was just looking up if I could have the type safety of instance type in my keyed coding implementations and found your question.
Others say it works. It would be worth a test.
Why does initWithCoder not return instancetype?

Expected member name, ';' at end of list, and require a specifier?

I have the following code:
#implementation GameScene {
-(id)init {
self = [super init]
if (self != nil) {
BackgroundLayer *backgroundLayer = [BackgroundLayer node];
[self addChild:backgroundLayer z:0];
GameplayLayer *gameplayLayer = [GameplayLayer node];
[self addChild:gameplayLayer z:5];
}
return self;
}
#end
Ive done it like this before, But ever since I updated xCode I have been recieving the following error messaged all pinned to the -(id) line.
Type name requires a specifier or qualifier
Expected member name or ';' after declaration specifiers
and
Expected ';' at end of declaration list
Im not sure what to do from here, cant seem to fix these!
You shouldn't have an opening brace after #implementation unless you define extra variables in a block, like this:
#implementation GameScene {
int _variable;
}
- (id)init {
...
}
...
#end
The class methods (init, etc) should not be enclosed in braces, so the fix to your compiler error is to simply remove the opening brace.
You have an open { on your GameScene implementation line.
change your code to
#implementation GameScene
-(id)init {
self = [super init];
if (self != nil) {
BackgroundLayer *backgroundLayer = [BackgroundLayer node];
[self addChild:backgroundLayer z:0];
GameplayLayer *gameplayLayer = [GameplayLayer node];
[self addChild:gameplayLayer z:5];
}
return self;
}
#end
It's simple enough. I solved this issue by placing appropriate "{" and "}" braces in code.
Your code should be as
#implementation GameScene
-(id)init {
self = [super init]
if (self != nil) {
BackgroundLayer *backgroundLayer = [BackgroundLayer node];
[self addChild:backgroundLayer z:0];
GameplayLayer *gameplayLayer = [GameplayLayer node];
[self addChild:gameplayLayer z:5];
}
return self;
}
#end

Object Not initializing

So I am trying to make a Circle class in Objective-C. I know Java and OOP, and a little bit of Objective-C, but I cannot figure out why my class will not initialize. Here is the Circle.h:
#import <Foundation/Foundation.h>
#import "Image.h"
#interface Circle : NSObject
{
Image *img;
CGPoint pos;
}
-(id) init;
-(void) draw;
#end
and here is the Circle.m file:
#import "Circle.h"
#implementation Circle
-(id) init{
self = [super init];
if (self != nil) {
img = [[Image alloc] initWithImage:[UIImage imageNamed:#"circle.png"]];
pos = CGPointMake((CGFloat)200, (CGFloat)200);
}
img = [[Image alloc] initWithImage:[UIImage imageNamed:#"circle.png"]];
pos = CGPointMake((CGFloat)200, (CGFloat)200);
return self;
}
-(void) draw{
[img renderAtPoint: pos centerOfImage: YES];
}
#end
Then in my main class I call:
//player is the name of the circle object (Circle *player;)
[player init];
And in my render method I call:
[player draw];
When I run my app, the image will not draw, any help?
You probably want to say:
player = [[Circle alloc] init];
Rather than just [player init], since the latter will do nothing if player is nil (the default value for members).

Exc_Bad_Access on main function and unable to pass instance variables through classes

Updated post
Now, I have a EXC_BAD_ACCESS on the main 2 times out of 7 and I don't know why the heightOfPumpView result is 0 from the pumpCustomView class when the result of pumpViewHeight is 607.
PumpViewController.m
#import "PumpViewController.h"
#import "PumpModel.h"
#import "PumpCustomView.h"
#implementation PumpViewController
#synthesize labels;
#synthesize heightOfPumpView;
- (id)init
{
if (self = [super init])
{
labels = [[PumpModel alloc]init];
PumpCustomView* pumpView = [PumpCustomView alloc];
heightOfPumpView = [pumpView pumpViewHeight];
[labels pumpCreateLabel:heightOfPumpView];
labelsArray = [[NSMutableArray alloc]initWithArray:[labels labelsGroup]];
[labels release];
if (labelsArray!=nil)
{
[pumpView addSubview:[labelsArray objectAtIndex:2]];
}
[labelsArray release];
[pumpView release];
}
return self;
}
-(void) dealloc
{
[super dealloc];
}
#end
PumpModel.m
#import "PumpModel.h"
#import "PumpViewController.h"
#import "PumpCustomView.h"
#implementation PumpModel
#synthesize labelsGroup;
-(id)init
{
self = [super init];
return self;
}
-(void)pumpCreateLabel:(float)pumpViewHeight
{
theNumberOfPump = 8;
PumpViewController* pumpViewControllerAlloc = [PumpViewController alloc];
labelsGroup = [[NSMutableArray alloc]init];
for (int i = 0;i < theNumberOfPump; i++)
{
int pumpViewHeight = [pumpViewControllerAlloc heightOfPumpView];
int pumpViewWidthA = 259;
int resultHeight = pumpViewHeight/theNumberOfPump;
CGFloat resultWidth = pumpViewWidthA/2;
positionChart[i] = resultHeight * i;
newLabel[i] = [[NSTextField alloc] init] ;
[newLabel[i] setIntValue:i];
newLabel[i].frame = CGRectMake(resultWidth, positionChart[i], 300, 100);
newLabel[i].font= [NSFont fontWithName:#"Arial" size:12];
newLabel[i].textColor= [NSColor blackColor];
newLabel[i].backgroundColor= [NSColor whiteColor];
[labelsGroup addObject:newLabel[i]];
[newLabel[i] release];
NSLog(#"%# %d",[[labelsGroup objectAtIndex:i] stringValue],positionChart[i]);
}
[pumpViewControllerAlloc release];
}
-(void) dealloc
{
[labelsGroup release];
[super dealloc];
}
You shouldn't send messages to the object before [super init], e.g.:
- (id)init
{
if (self = [super init])
{
[self setNumberOfPump:8];
}
return self;
}
This is also true for:
-(id)initWithNumberOfPump:(int)numberOfPump
{
if (self = [super init]) {
theNumberOfPump = numberOfPump;
[self pumpCreateLabel];
}
return self ;
}
If you have a crash, post the backtrace of the crash.
Looking at your setNumberOfPump: method, it seems quite wrong.
labels is allocated, then released, likely leaving the instance variable as a dangling reference that'll crash later
labelsArray is leaked
your dealloc doesn't release any memory
You should try running Build and Analyze on your code, fixing any errors. The above issues combined with comments regarding the init patterns indicates that you should likely review the Objective-C documentation to gain a better understanding of both initialization and memory management patterns.

How to get CCScrollLayer to scroll?

I am trying to get CCScrollLayer to work in my cocos2d app, but it won't scroll.
Here is what I did:
Using the cocos2d template, created a iOS cocos2d project. Added the CCScrollLayer files, and imported into my HelloWorldLayer class. Added a method "layerWithLevelName" to create the layers.
In init, I created a few layers for testing. Below is the code for my HelloWorldLayer implementation. It is very basic, as I am just trying to get some layers to scroll.
#
// HelloWorldLayer implementation
#import "HelloWorldLayer.h"
#import "CCScrollLayer.h"
#implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
// return the scene
return scene;
}
-(CCLayer*) layerWithLevelName:(NSString*)name number:(int)number screenSize:(CGSize)screenSize
{
CCLayer *layer = [[[CCLayer alloc] init]autorelease];
int largeFont = [CCDirector sharedDirector].winSize.height / 9;
CCLabelTTF *layerLabel = [CCLabelTTF labelWithString:name fontName:#"Marker Felt" fontSize:largeFont];
layerLabel.position = ccp( screenSize.width / 2 , screenSize.height / 2 + 10 );
layerLabel.rotation = -6.0f;
layerLabel.color = ccc3(95,58,0);
[layer addChild:layerLabel];
return layer;
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init]))
{
// ask director the the window size
NSMutableArray* _layers = [[NSMutableArray alloc]init];
CGSize screenSize = [CCDirector sharedDirector].winSize;
for (int i=0; i<3; i++)
{
int number = i;
NSString* name = [[NSString alloc]initWithFormat:#"level%d",i];
CCLayer* layer = [self layerWithLevelName:name number:number screenSize:screenSize];
[_layers addObject:layer];
[name release];
}
// Set up the swipe-able layers
CCScrollLayer *scroller = [[CCScrollLayer alloc] initWithLayers:_layers
widthOffset:230];
[self addChild:scroller];
// [scroller selectPage:0];
[scroller release];
[_layers release];
}
return self;
}
- (void) dealloc
{
[super dealloc];
}
#end
If you run the code, you will find that you can see the levels / scroll layer --> It seems to have loaded properly. But you can't scroll. What have I forgotten to do? What am I doing wrong?
EDIT: I am using cocos2d-iphone 1.1beta2
It seems that this is an issue with 1.1beta2.
A temporary workaround is to set scroller.stealTouches = NO;
I don't know what other side effects this has though.
EDIT:
Fix: Use updated CCTouchDispatcher files:
https://github.com/cocos2d/cocos2d-iphone/blob/develop/cocos2d/Platforms/iOS/CCTouchDispatcher.m
and corresponding .h file
https://github.com/cocos2d/cocos2d-iphone/blob/develop/cocos2d/Platforms/iOS/CCTouchDispatcher.h