cocos2d Sprite contentSize Problem - iphone

I've defined a sprite using the spriteWithFile method, providing a 120px by 30px .png
Sprite *trampoline = [Sprite spriteWithFile:#"trampoline.png"];
[self addChild:trampoline];
When I add this to my Layer and position it, it is where I expect it to be on the screen.
trampoline = [Trampoline node];
trampoline.position = ccp(160,15);
[self addChild:trampoline z:0 tag:1];
However, it seems to have no contentSize. The following NSLog statement:
NSLog(#"Content Size x:%f, y:%f", trampoline.contentSize.width,trampoline.contentSize.height);
Gives the following read out:
2009-07-10 18:24:06.385 TouchSprite[3251:20b] Content Size x:0.000000, y:0.000000
Am I missing something? Shouldn't that be 120.000000 by 30.000000
Any help would be greatly appreciated.
Regards,
Rich

Are these lines part of the Trampoline class?
Sprite *trampoline = [Sprite spriteWithFile:#"trampoline.png"];
[self addChild:trampoline];
From my limited experience with cocos2d, contentSize of a Sprite seems to only apply to the content that actually belongs to the Sprite, and not all children of that Sprite. As a result, in your example above, asking for the contentSize in your log statement won't work, since there isn't any content added to the Trampoline node. However, if you were to override the contentSize method inside your Trampoline class to return the contentSize of the Sprite that actually loaded the graphic, that should work.
Here's a snippet of a Sprite I'm using in a game I'm currently working on that illustrates what I'm talking about:
- (id) init
{
self = [super init];
if (self != nil)
{
self.textLabel = [Label labelWithString:#"*TEXT*"
fontName:#"Helvetica"
fontSize:18];
[textLabel setRGB:0 :0 :0];
textLabel.transformAnchor = CGPointZero;
textLabel.position = CGPointZero;
self.transformAnchor = CGPointZero;
[self addChild:textLabel];
}
return self;
}
//
- (CGSize) contentSize
{
return textLabel.contentSize;
}
This comes from a class that extends Sprite. Until I added the override for contentSize, asking for it from another class would give me the same results your seeing. Now that I'm telling it return the content size of the textLabel, it's working just like I'd expect it to.

I assume Trampoline inherits from Sprite, which then inherits from Node. You are over-writing trampoline with [Trampoline node] which creates a node ... but is the Trampoline implementation overriding the node method to initialize your sprite file into the Trampoline node?
I think you are just getting an empty Node class back from the line:
trampoline = [Trampoline node];

Related

Adding a Hud Layer to Scene Cocos2d-3

To keep it simple, what is the easiest way to make the default [ Menu ] in default HelloWorld Scene (for example) as it's own layer. Issue I'm having now is that the scene is completely black, with nothing showing up!
GameLayer node:
- (id)init
{
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
self.metaLayer = [theMap layerNamed:#"Meta"];
metaLayer.visible = NO;
CCTiledMapObjectGroup *objects = [theMap objectGroupNamed:#"mainChar"];
NSMutableDictionary *startPoint = [objects objectNamed:#"startPosition"];
int x = [[startPoint valueForKey:#"x"] intValue];
int y = [[startPoint valueForKey:#"y"] intValue];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(x,y);
[self addChild:mainChar];
[self addChild:theMap z:-1];
[self setCenterOfScreen: mainChar.position];
return self;
}
HudLayer node
-(id)init
{
CGSize winSize = [[CCDirector sharedDirector] viewSize];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
[backButton setTarget:self selector:#selector(onBackClicked:)];
[self addChild:backButton];
return self;
}
Scene
+ (GameScene *)scene
{
return [[self alloc] init];
}
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.gameLayer = [GameLayer node];
[self addChild:gameLayer z:-1];
//self.contentSize = self.gameLayer.contentSize;
hudLayer = [HudLayer node];
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer z:1];
return self;
}
From the OP I take that you have two issues with one being that the HUD is not static (i.e. it is moving as your map moves which you don't want) and that it is not positioning at the top of the screen.
Looking at the position issue first, your position is set to normalized. Since the scene's content size has been made to be the size of your map, which I take is larger than your screen, then this is why it is showing up at the top right of the map and not the screen. To fix this don't do normalized positioning. If you want to be able to still express the position in the 0 to 1 range, use (remove the line that sets the position type to normalized also):
CGSize winSize = [[CCDirector sharedDirector] viewSize];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
If your map is 10,000 x 10,000 then using the normalized positioning like you are will set the button to (8,500, 9,500) rather than the top of the screen.
Looking at the static issue next, from the looks of it you have the Hello World scene that you are adding everything to right? It also looks like you are moving the Hello World scene with a call to:
[self setCenterOfScreen: player.position];
What you want to do instead is this, you first have a scene:
HelloWorldScene* scene;
And to this scene you are adding two "main" layers with one being your gameplay layers as children of a main gameplay layer and the other being your HUD layer, which for example could look something like:
GameplayLayer* gameLayer;
HudLayer* hudLayer;
[scene addChild:gameLayer];
[scene addChild:hudLayer];
When the player moves (or camera or whatever), what should be moving is the game's layer, not the root Hello World scene. Moving the root scene will move all of its children, which includes the hud. That is not what you want.
When I worked on, for example, the Goldfish Mysteries app (https://itunes.apple.com/us/app/finn-friends-mysteries/id740040227?mt=8) I had essentially layers for:
Story (comprised of multiple sub-layers like bg, characters, etc)
Text (highlighted text that plays along with the narration audio)
HUD (comprised of multiple sub-layers)
Whenever there is movement on the story level it occurs on the story layer. When the HUD appears then the text and story layers are paused recursively (i.e. them and all of their children) along with the narration audio if any is playing but the HUD layer remains untouched. Resuming consists of resuming story, text, and any playing narration. I don't remember off the top if dropping the hud moved down the story and text layers in this specific app since I don't have my iPad in front of me, but i have done apps in the past where dropping the hud shifted all other layers. In that situation and for a simple app it would be fine to move the scene since the scene would only be shifted enough to show the TOC (in this type of app for example). What you look to be doing is moving the entire scene with the player's movement, which is not what you really wanted to do by the looks of it.
Either way you want a clear separation between layers and operations that are meant to only happen on specific layers should be directed only towards those layers.
Hope this helped.
UPDATE (Edited):
Based on your new edited OP, you have a new problem that you've introduced. For the hud you shouldn't have to set the layer's position since inside the hud layer everything is already being laid out relative to the screen anyways. So what you should have instead is:
hudLayer = [HudLayer node];
[self addChild:hudLayer z:1];
The second issue is that you are not properly writing your init methods for the game and hud layer classes. The init should look like:
- (instanceType)init
{
self = [super init];
if (self)
{
// Do your init stuff...
}
return self;
}
You are never calling:
self = [super init];

Multiple sprites with same SKPhysics body object

I am building an airport simulation game using sprite kit. My layout for the game states intact before adding SKPhysics body and once SKPhysicsbody is set for nodes, my sprite nodes goes wary.
This is what I am adding to scene without SKPhysicsBody. Imagine a airport with many gates and flights standing next to the gates. That is what I am trying to achieve with below code.
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
allPlanes = [[NSMutableArray alloc]init];
// setting gravity to 0
[[self physicsWorld] setGravity:CGVectorMake(0, 0)];
[[self physicsWorld] setContactDelegate:self];
[self setBackgroundColor:[UIColor whiteColor]];
// Below method will provide runway and other static objects that can be seen in an airport
[self setupAirport];
/* This is where I am setting up by plane sprites.This method will provide a node which is added to the scene. As you can notice, the sprites are added at specific coordinates until they fill the screen. Imagine three or four gates with flights standing by those gates. That is what I am trying to achieve with below while loop */
int xval = 20;
while (xval < kScreenWidth)
{
[self setupFlightAtPoint:xval];
xval = xval + 60;
}
}
return self;
}
Now code for methods [self setupFlightAtPoint:xPos]
-(void) setupFlightAtPoint:(CGFloat ) xPos
{
// Below code will provide a static gate like object.gateNode is of type Gate class which is a subclass of SKNode
gateNode = [[Gate node] newGate];
[gateNode setPosition:CGPointMake(xPos, kScreenHeight * 0.37)];
[self addChild:gateNode];
// Below code provide plane node and positions it near the gate object.Plane is subclass of SKNode
Plane *plane = [[Plane alloc]init];
imageNode = [plane newPlane];
imageNode.planeIdentifier = xPos;
[imageNode setPosition:CGPointMake(gateNode.frame.origin.x + 12, gateNode.frame.origin.y+15)];
[allPlanes addObject:imageNode];
[self addChild:imageNode];
}
Plane object method
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
return self;
}
Till now everything looks fine.Please see attached image called scene1 to see what I see with above code.
Now my problem begins here when I am trying to set physics body to my plane sprites. In my "newPlane" method, I am adding below code
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
[spriteNode setPhysicsBody:planePhysics];
[[spriteNode physicsBody] setAffectedByGravity:NO];
return self;
}
After setting Physicsbodies, my scene looks like this
only one plane sprite is seen in my scene now and I am not able to figure out why?
Try initializing and assigning the physics body before adding the sprite as child:
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
spriteNode.position = CGPointMake(100, 200);
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
spriteNode.physicsBody = planePhysics;
spriteNode.physicsBody.affectedByGravity = NO;
[self addChild:spriteNode];
return self;
}
I also converted the code to use dot notation, I find this easier to type and read.

CCFollow with CCParticleSystem

I'm trying to follow some sprite by some instance of CCParticleSystem using CCFollow. I don't want to make instance of CCParticleSystem a child of sprite, because I want it to be displayed some time after sprite removed.
When moving sprite from bottom-left to top-right corner my ParticleSystem moves from center to bottom-left corner. I can't understand why does it happen.
Here is sample code:
-(id) init
{
if( (self=[super init]) ) {
CCSprite *someSprite = [CCSprite spriteWithFile:#"Icon.png"];
[self addChild:someSprite];
id action = [CCMoveTo actionWithDuration:5 position:ccp(480,320)];
[someSprite runAction:action];
CCParticleSystemQuad *effect = [CCParticleMeteor node];
// effect.positionType = kCCPositionTypeFree;
// effect.positionType = kCCPositionTypeRelative;
// effect.positionType = kCCPositionTypeGrouped; changing of positionType to any of this options does not make any sense
[effect runAction:[CCFollow actionWithTarget:someSprite]];
[self addChild:effect];
}
return self;
}
It's a case of bad documentation.
I tried this myself and got the same result. By looking at the code and the fragment of documentation that exists, the CCFollow action is intended to be used only on fullscreen nodes like CCLayer, CCScene and perhaps CCNode. I would document it as follows:
CCFollow is an action for scrolling the entire screen by allowing the
layer or scene follow another node, such as a player object. It's an
alternative for moving the Camera object or updating the position
manually.

Cocos2d - how to make individual particles follow the layer, not the emitter?

I have a CCSprite and a CCParticleSystemQuad that are both children of the CCLayer. In my update method, I set the emitter's position to that of the sprite, so it tracks the sprite around. The smoke puff fall out the bottom of the sprite like you'd expect and even though you move the sprite around, the smoke appears to be part of the background layer.
The problem come if I match up their rotations. Now, for example if my sprite is rocking back and forth, the puffs of smoke swing in an arc and appear attached to the sprite.
How can I make the puffs of smoke continue along the parent layer in a straight line and not rotate with the sprite? They don't translate with the sprite when I move it, so why do they rotate with it?
EDIT: adding code...
- (id)init
{
if (!(self = [super init])) return nil;
self.isTouchEnabled = YES;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
sprite = [CCSprite spriteWithFile:#"Icon.png"]; // declared in the header
[sprite setPosition:ccp(screenSize.width/2, screenSize.height/2)];
[self addChild:sprite];
id repeatAction = [CCRepeatForever actionWithAction:
[CCSequence actions:
[CCRotateTo actionWithDuration:0.3f angle:-45.0f],
[CCRotateTo actionWithDuration:0.6f angle:45.0f],
[CCRotateTo actionWithDuration:0.3f angle:0.0f],
nil]];
[sprite runAction:repeatAction];
emitter = [[CCParticleSystemQuad particleWithFile:#"jetpack_smoke.plist"] retain]; // declared in the header - the particle was made in Particle Designer
[emitter setPosition:sprite.position];
[emitter setPositionType:kCCPositionTypeFree]; // ...Free and ...Relative seem to behave the same.
[emitter stopSystem];
[self addChild:emitter];
[self scheduleUpdate];
return self;
}
- (void)update:(ccTime)dt
{
[emitter setPosition:ccp(sprite.position.x, sprite.position.y-sprite.contentSize.height/2)];
[emitter setRotation:[sprite rotation]]; // if you comment this out, it works as expected.
}
// there are touches methods to just move the sprite to where the touch is, and to start the emitter when touches began and to stop it when touches end.
I found the answer on a different site - www.raywenderlich.com
I don't know why this is true, but it seems that CCParticleSystems don't like to be rotated while you move them around. They don't mind changing their angle property. Actually, there may be cases where you want that behavior.
Anyway I made a method that adjusts the emitter's angle property and it works fine. It takes your touch location and scales the y component to be the angle.
- (void)updateAngle:(CGPoint)location
{
float width = [[CCDirector sharedDirector] winSize].width;
float angle = location.x / width * 360.0f;
CCLOG(#"angle = %1.1f", angle);
[smoke_emitter setAngle:angle]; // I added both smoke and fire!
[fire_emitter setAngle:angle];
// [emitter setRotation:angle]; // this doesn't work
}
CCSprite's anchorPoint is {0.5f, 0.5f), while the emitter descends directly from CCNode, which has an anchorPoint of {0.0f, 0.0f}. Try setting the emitter's anchorPoint to match the CCSprite's.

Problem animating a Parallax Layer in Cocos2d-iPhone

I'm trying to implement my own parallax scrolling in Cocos2d since the built in parallax functionality isn't exactly what I'm looking for (it scales the layers, which is not what I want)
So, my own Parallax Layers overwrite the Layers setPosition method like this
- (void) setPosition:(CGPoint)point
{
[super setPosition:CGPointMake(point.x / pRatio, point.y / pRatio)];
}
pRatio is an int that reduces the amount the layer moves by, thus creating the kind of Parallax effect I'm looking for. And it works perfectly like this.
However, on occasion I need to animate a Layer to a specific position using MoveTo, so I created this method which responds to an NSNotiification
- (void) gotoStartPosition:(NSNotification *)notification
{
CGPoint startPos = CGPointFromString([notification object]);
id moveToStart = [MoveTo actionWithDuration:1 position:startPos];
[self runAction:moveToStart];
}
Which works until I set the pRatio to anything above 1. If the pRatio is higher than 1, then gotoStartPosition makes the layer jump to CGPoint(0,0) and animates into position from there.
I can't figure out at what point the Layers position is being set to 0,0, and I don't understand why it's only doing this when pRatio is higher than 1.
I suspect this is going to be something painfully simple, but I think my brain is fried. Any help, greatly appreciated.
UPDATE: I'm a doofus. What's happening is obvious. The Animation first sets the layers position to it's current position. But I've told the layer to divide any setPosition coordinates by pRatio. So it's not going to CGPoint(0,0) but something like CGPoint(0.032, 0). I've fixed it like this:
- (void) setPosition:(CGPoint)point
{
if(!animating){
[super setPosition:CGPointMake(round(point.x / pRatio), round(point.y / pRatio))];
}else{
[super setPosition:point];
}
}
- (void) gotoStartPosition:(NSNotification *)notification
{
animating = YES;
CGPoint startPos = CGPointFromString([notification object]);
CGPoint pStart = CGPointMake(startPos.x / pRatio, startPos.y / pRatio);
id moveToStart = [MoveTo actionWithDuration:1 position:pStart];
id endAnim = [CallFunc actionWithTarget:self selector:#selector(endAnimation)];
[self runAction:[Sequence actions: moveToStart, endAnim, nil]];
}
- (void) endAnimation
{
animating = NO;
}
Which isn't elegant, but it works. I'm closing this question.