I just started using the SpriteBuilder with Cocos2d-v3
For the exercise of the SpriteBuilder, I practice in the tutorial of the following WebSite
for the time being.
https://www.makegameswith.us/tutorials/getting-started-with-spritebuilder/collision-detection/
As a flow of the games of the tutorials,at first the catapult and the catapultArm are located on stage,the object(Penguin) which was attached to the arm flies forward when I pull the arm to the rear with a finger and separate a finger from a screen.
When the object(Penguin) hits other objects(is called Seal),output log,"Something collided with a seal!"
In a "Implementing a delegate method" of tutorial, I was going to try to display a letter,"Something collided with a seal!" in Consoll using CCLOG method.
In the Gameplay.m, I implemented ccPhysicsCollisionPostSolve:seal:wildcard method that is called to dispaly a letter "Something collide with a seal!",but this method isn't called when Penguin object hits other objects(seal).
I certainly made Penguin.ccbi and Seal.ccbi in SpriteBuilder,and thier ccbi file is Physics body.
Why ccPhysicsCollisionPostSolve:seal:wildcard method isn't called ?
It's my code that I really implemented as follows
This is Gameplay.m file
#import "Gameplay.h"
#implementation Gameplay{
CCPhysicsNode *_physicsNode;
//To joint betweent catapult and catapultArm
CCNode *_catapultArm;
CCNode *_catapult;
CCPhysicsJoint *_catapultJoint;
//Invisible Physics force
CCNode *_pullbackNode;
CCPhysicsJoint *_pullbackJoint;
//to move catapultArm
CCNode *_mouseJointNode;
CCPhysicsJoint *_mouseJoint;
//to fly penguin
CCNode *_currentPenguin;
CCPhysicsJoint *_penguinCatapultJoint;
//Object
CCNode *_levelNode;
//To Prevent a 'retry' button from moving with a fly penguin
CCNode *_contentNode;
}
//is called when CCB file has completed loading
-(void)didLoadFromCCB{
_physicsNode.collisionDelegate = self;
//tell this scene to accept touches
self.userInteractionEnabled = TRUE;
//loads the Levels/Leve1.ccb we have set up in SpriteBuilder
CCScene *level = [CCBReader loadAsScene:#"Levels/Level1"];
[_levelNode addChild:level];
//visualize physics bodies & joints
_physicsNode.debugDraw = TRUE;
//catapultArm and catapult shall not collide
[_catapultArm.physicsBody setCollisionGroup:_catapult];
[_catapult.physicsBody setCollisionGroup:_catapult];
//create a joint to connect the catapult arm with the catapult
_catapultJoint = [CCPhysicsJoint connectedPivoJointWithBodyA:_catapultArm.physicsBody
bodyB:_catapult.physicsBody
anchorA:_catapultArm.anchorPointInPoints];
//nothing shall collide with our invisible nodes
_pullbackNode.physicsBody.collisionMask = #[];
//nothing shall collide with our invisible nodes
_mouseJointNode.physicsBody.collisionMask = #[];
_pullbackJoint = [CCPhysicsJointconnectedSpringJointWithBodyA:_pullbackNode.physicsBody
bodyB:_catapultArm.physicsBody
anchorA:ccp(0,0)
anchorB:ccp(34,138)
restLength:60.f
stiffness:500.f
damping:40.f
];
}
//called on every touch in this scene (called every touch)
-(void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CCLOG(#"touch Began");
CGPoint touchLcation = [touch locationInNode:_contentNode];
//start catapult dragging when a touch inside of the catapult arm occurs
if(CGRectContainsPoint([_catapultArm boundingBox], touchLcation))
{
//move the mouseJointNode to the touch position
_mouseJointNode.position = touchLcation;
_mouseJoint = [CCPhysicsJoint connectedSpringJointWithBodyA:_mouseJointNode.physicsBody
bodyB:_catapultArm.physicsBody
anchorA:ccp(0,0)
anchorB:ccp(34,138)
restLength:0.f
stiffness:3000.f
damping:150.f
];
//create a penguin from the ccbFile
_currentPenguin = [CCBReader load:#"Penguin"];
CGPoint penguinPosition = [_catapultArm convertToWorldSpace:ccp(34, 138)];
_currentPenguin.position = [_physicsNode convertToNodeSpace:penguinPosition];
//add it to the physics world
[_physicsNode addChild:_currentPenguin];
//we don't want the penguin to rotate in the scoop
_currentPenguin.physicsBody.allowsRotation = FALSE;
//create a joint to keep the penguin fixed to the scoop until the catapult is released
_penguinCatapultJoint =
[CCPhysicsJoint connectedPivoJointWithBodyA:_currentPenguin.physicsBody
bodyB:_catapultArm.physicsBody
anchorA:_currentPenguin.anchorPointInPoints
];
}
}
-(void)touchMoved:(UITouch *)touch withEvent:(UIEvent *)event{
CCLOG(#"MOVING!!!!!!!!!!");
//whenever touches move,update the position of the mousejointNode to touch position
CGPoint touchLocation = [touch locationInNode:_contentNode];
_mouseJointNode.position = touchLocation;
}
-(void)touchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
//when touches end,meaning the user releases their finger,release the catapult
[self releaseCatapult];
}
-(void)touchCancelled:(UITouch *)touch withEvent:(UIEvent *)event{
[self releaseCatapult];
}
//destroy our joint and let the catapult snap when a touch ends
-(void)releaseCatapult{
CCLOG(#"Release");
if(_mouseJoint != nil)
{
//releases the joint and lets the catapult snap back
[_mouseJoint invalidate];
_mouseJoint = nil;
//release the joint and lets the penguin fly
[_penguinCatapultJoint invalidate];
_penguinCatapultJoint = nil;
//after snapping rotation is fine
_currentPenguin.physicsBody.allowsRotation = TRUE;
//follow the flying penguin
CCActionFollow *follow =
}
}
-(void)retry
{
[[CCDirector sharedDirector]replaceScene:[CCBReader loadAsScene:#"Gameplay"]];
}
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair seal:(CCNode *)nodeA
wildcard:(CCNode *)nodeB
{
CCLOG(#"Something collided with a seal!");
}
#end
Most likely you did not set a collision type for the Sealclass.
Double check that you have:
Set the custom class for the Seal in SpriteBuilder to Seal
Have set the collisionType within the didLoadFromCCB method inside the Seal class to "seal"
That should fix you issue.
Also, in Spritebuilder, make sure the seals already loaded in the level (Level1.ccb) have Collision type set to seal.
Related
I have a ball object that falls straight down a level made in SpriteBuilder as a layer. The level is added to the gameplay scene, which has a ccnode (_levelNode) as a child of the physics node. However, when the game runs it either doesn't scroll, or the view is messed up.
My code is:
- (void)didLoadFromCCB {
//_physicsNode.debugDraw = TRUE;
_loadedLevel = [CCBReader load:#"levels/Level1" owner:self];
[_levelNode addChild: _loadedLevel];
}
- (void)onEnter {
[super onEnter];
self.userInteractionEnabled = TRUE;
self.isGamePaused = FALSE;
_followBall = [CCActionFollow actionWithTarget:_ball worldBoundary:[_loadedLevel boundingBox]];
[_physicsNode runAction:_followBall];
}
I just started using SpriteBuilder with cocos2d - v3.
It looks promising and a good tool to have.
But my issue is that I want to create a man like character with all body parts as different sprites and put them into a Physics body using joints. I am struggling to find any example of it using spritebuilder or chipmunk.
Will greatly appreciate any help here. Thx in advance.
EDIT : Here's code for body. I used peevedpenquin example from link
Here is my code for character body. I just added a block as body part to penguin's tail.
#import "PenguinWithBlock.h"
#implementation PenguinWithBlock{
CCNode *_pBlock;
CCNode *_penguinB;
CCPhysicsJoint *_penguinJoint;
CCPhysicsNode *_penguinNode;
}
- (void)didLoadFromCCB {
_penguinNode.debugDraw = TRUE;
[_pBlock.physicsBody setCollisionGroup:_penguinB];
[_penguinB.physicsBody setCollisionGroup:_penguinB];
// create a joint to connect the catapult arm with the catapult
_penguinJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_penguinB.physicsBody bodyB:_pBlock.physicsBody anchorA:_penguinB.anchorPointInPoints];
}
This is Gameplay.m file
#implementation Gameplay {
CCPhysicsNode *_physicsNode;
CCNode *_catapultArm;
CCNode *_catapult;
CCNode *_levelNode;
CCNode *_contentNode;
CCPhysicsJoint *_catapultJoint;
CCNode *_pullbackNode;
CCPhysicsJoint *_pullbackJoint;
CCNode *_mouseJointNode;
CCPhysicsJoint *_mouseJoint;
PenguinWithBlock *_currentPenguin;
CCPhysicsJoint *_penguinCatapultJoint;
CCAction *_followPenguin;
}
// is called when CCB file has completed loading
- (void)didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
_physicsNode.collisionDelegate = self;
CCScene *level = [CCBReader loadAsScene:#"Levels/Level1"];
[_levelNode addChild:level];
// visualize physics bodies & joints
//_physicsNode.debugDraw = TRUE;
// catapultArm and catapult shall not collide
[_catapultArm.physicsBody setCollisionGroup:_catapult];
[_catapult.physicsBody setCollisionGroup:_catapult];
// create a joint to connect the catapult arm with the catapult
_catapultJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_catapultArm.physicsBody bodyB:_catapult.physicsBody anchorA:_catapultArm.anchorPointInPoints];
// nothing shall collide with our invisible nodes
_pullbackNode.physicsBody.collisionMask = #[];
// create a spring joint for bringing arm in upright position and snapping back when player shoots
_pullbackJoint = [CCPhysicsJoint connectedSpringJointWithBodyA:_pullbackNode.physicsBody bodyB:_catapultArm.physicsBody anchorA:ccp(0, 0) anchorB:ccp(34, 138) restLength:60.f stiffness:500.f damping:40.f];
_mouseJointNode.physicsBody.collisionMask = #[];
}
- (void)update:(CCTime)delta
{
if (_currentPenguin.launched) {
// if speed is below minimum speed, assume this attempt is over
if (ccpLength(_currentPenguin.physicsBody.velocity) < MIN_SPEED){
[self nextAttempt];
return;
}
int xMin = _currentPenguin.boundingBox.origin.x;
if (xMin < self.boundingBox.origin.x) {
[self nextAttempt];
return;
}
int xMax = xMin + _currentPenguin.boundingBox.size.width;
if (xMax > (self.boundingBox.origin.x + self.boundingBox.size.width)) {
[self nextAttempt];
return;
}
}
}
- (void)nextAttempt {
_currentPenguin = nil;
[_contentNode stopAction:_followPenguin];
CCActionMoveTo *actionMoveTo = [CCActionMoveTo actionWithDuration:1.f position:ccp(0, 0)];
[_contentNode runAction:actionMoveTo];
}
// called on every touch in this scene
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [touch locationInNode:_contentNode];
// start catapult dragging when a touch inside of the catapult arm occurs
if (CGRectContainsPoint([_catapultArm boundingBox], touchLocation))
{
// move the mouseJointNode to the touch position
_mouseJointNode.position = touchLocation;
// setup a spring joint between the mouseJointNode and the catapultArm
_mouseJoint = [CCPhysicsJoint connectedSpringJointWithBodyA:_mouseJointNode.physicsBody bodyB:_catapultArm.physicsBody anchorA:ccp(0, 0) anchorB:ccp(34, 138) restLength:0.f stiffness:3000.f damping:150.f];
// create a penguin from the ccb-file
_currentPenguin = (PenguinWithBlock*)[CCBReader load:#"PenguinWithBlock"];
// initially position it on the scoop. 34,138 is the position in the node space of the _catapultArm
CGPoint penguinPosition = [_catapultArm convertToWorldSpace:ccp(34, 138)];
// transform the world position to the node space to which the penguin will be added (_physicsNode)
_currentPenguin.position = [_physicsNode convertToNodeSpace:penguinPosition];
// add it to the physics world
[_physicsNode addChild:_currentPenguin];
// we don't want the penguin to rotate in the scoop
_currentPenguin.physicsBody.allowsRotation = FALSE;
// create a joint to keep the penguin fixed to the scoop until the catapult is released
_penguinCatapultJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_currentPenguin.physicsBody bodyB:_catapultArm.physicsBody anchorA:_currentPenguin.anchorPointInPoints];
}
}
- (void)touchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// whenever touches move, update the position of the mouseJointNode to the touch position
CGPoint touchLocation = [touch locationInNode:_contentNode];
_mouseJointNode.position = touchLocation;
}
- (void)launchPenguin {
// loads the Penguin.ccb we have set up in Spritebuilder
CCNode* penguin = [CCBReader load:#"PenguinWithBlock"];
// position the penguin at the bowl of the catapult
penguin.position = ccpAdd(_catapultArm.position, ccp(16, 50));
// add the penguin to the physicsNode of this scene (because it has physics enabled)
[_physicsNode addChild:penguin];
// manually create & apply a force to launch the penguin
CGPoint launchDirection = ccp(1, 0);
CGPoint force = ccpMult(launchDirection, 8000);
[penguin.physicsBody applyForce:force];
// ensure followed object is in visible are when starting
self.position = ccp(0, 0);
CCActionFollow *follow = [CCActionFollow actionWithTarget:penguin worldBoundary:self.boundingBox];
[_contentNode runAction:follow];
}
-(void) touchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
// when touches end, meaning the user releases their finger, release the catapult
[self releaseCatapult];
}
-(void) touchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
// when touches are cancelled, meaning the user drags their finger off the screen or onto something else, release the catapult
[self releaseCatapult];
}
- (void)releaseCatapult {
if (_mouseJoint != nil)
{
// releases the joint and lets the catapult snap back
[_mouseJoint invalidate];
_mouseJoint = nil;
// releases the joint and lets the penguin fly
[_penguinCatapultJoint invalidate];
_penguinCatapultJoint = nil;
// after snapping rotation is fine
_currentPenguin.physicsBody.allowsRotation = TRUE;
_currentPenguin.launched = TRUE;
// follow the flying penguin
_followPenguin = [CCActionFollow actionWithTarget:_currentPenguin worldBoundary:self.boundingBox];
[_contentNode runAction:_followPenguin];
}
}
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair seal:(CCNode *)nodeA wildcard:(CCNode *)nodeB
{
float energy = [pair totalKineticEnergy];
// if energy is large enough, remove the seal
if (energy > 5000.f)
{
[self sealRemoved:nodeA];
}
}
- (void)sealRemoved:(CCNode *)seal {
// load particle effect
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"SealExplosion"];
// make the particle effect clean itself up, once it is completed
explosion.autoRemoveOnFinish = TRUE;
// place the particle effect on the seals position
explosion.position = seal.position;
// add the particle effect to the same node the seal is on
[seal.parent addChild:explosion];
// finally, remove the destroyed seal
[seal removeFromParent];
}
- (void)retry {
// reload this level
[[CCDirector sharedDirector] replaceScene: [CCBReader loadAsScene:#"Gameplay"]];
}
PenguinWithBlock settings in SpriteBuilder:
You can use a "blank" CCNode with all others parts of the body being children of it, maybe even have children of another body parts. The tree of nodes will heavily depend on the body design.
Give a look at this tutorial, as it teaches the basics to do so. Also, SpriteBuilder still doesn't have the feature to create the joints inside it, but you can easily do this programmatically as the tutorial does.
EDIT: This can help you
I'm currently trying out a few things. I already did this tutorial: https://www.makegameswith.us/tutorials/getting-started-with-spritebuilder/
However I'm a little stuck now.
I created a CCNode in SpriteBuilder and added 3 images. carbody.png and 2 wheel.png.
I made all objects physicsObjects.
In code I tried to connect them with joints and move them. However the carBody moves but the wheels keep staying on their place.
#import "Car.h"
#implementation Skater{
CCNode *_wheel1;
CCNode *_wheel2;
CCNode *_carBody;
CCPhysicsJoint *_bodyWheelJoint1;
CCPhysicsJoint *_bodyWheelJoint2;
}
- (id)init {
self = [super init];
if (self) {
[_wheel1.physicsBody setCollisionGroup: _carBody];
[_wheel2.physicsBody setCollisionGroup: _carBody;
[_carBody.physicsBody setCollisionGroup: _carBody];
_bodyWheelJoint1 = [CCPhysicsJoint connectedPivotJointWithBodyA:_wheel1.physicsBody bodyB: _carBody.physicsBody anchorA:_wheel1.anchorPointInPoints];
_bodyWheelJoint2 = [CCPhysicsJoint connectedPivotJointWithBodyA:_wheel2.physicsBody bodyB: _carBody.physicsBody anchorA:_wheel2.anchorPointInPoints];
}
return self;
}
And in Gameplay.m I only did this:
-(void)didLoadFromCCB{
[_car runAction:
[CCActionMoveTo actionWithDuration:10 position:CGPointMake(2000,_car.position.y)]];
}
The carBody moves, the wheels don't...
What am I missing?
Edit:
I made a few changes now but my wheels still keep staying on their place...
#import "Car.h"
#implementation Car{
CCNode *_wheel1;
CCNode *_wheel2;
CCNode *_carBody;
CCPhysicsJoint *_bodyWheelJoint1;
CCPhysicsJoint *_bodyWheelJoint2;
}
- (id)init {
self = [super init];
if (self) {
CCLOG(#"Car created");
[_wheel1.physicsBody setCollisionGroup:_carBody];
[_wheel2.physicsBody setCollisionGroup:_carBody];
[_carBody.physicsBody setCollisionGroup:_carBody];
_bodyWheelJoint1 = [CCPhysicsJoint connectedPivotJointWithBodyA:_wheel1.physicsBody bodyB:_carBody.physicsBody anchorA:_wheel1.anchorPointInPoints];
_bodyWheelJoint2 = [CCPhysicsJoint connectedPivotJointWithBodyA:_wheel2.physicsBody bodyB:_carBody.physicsBody anchorA:_wheel2.anchorPointInPoints];
}
return self;
}
-(void)moveCar:(int)distance{
CCLOG(#"Car should move");
CGPoint launchDirection = ccp(1, 0);
CGPoint force = ccpMult(launchDirection, distance);
[self.physicsBody applyForce:force];
}
#end
You're simply missing the point that move (and other) actions bypass physics. Once a node has a physics body, the use of CCAction* classes that change position and rotation is a no-no, and most others (ie scale) will not be applied to the physics body either but can still be used safely.
To move a physics object, apply an impulse or force to the physics body.
So after taking a little break after being very frustrated for months on end with this issue, I am now back and attempting to solve my issue once and for all.
I’m not going to post code, because the code I have is messy and patchy and is from countless points of view.
My issue is this:
I have a sprite, and his information is stored in a class called player.m. I have a game level, and it is stored in GameLevelLayer.m
I have player.m store velocity, direction, and all the information about the character, and then I have the GameLevelLayer.m implement, move, use bounds to check collisions, etc.
I now want to make the character move using a spritesheet (I know how to do spritesheeting, I just don’t know how to do it while working with two different classes)
My question is, do I create the batchnodes and spritesheet information (CCBatchnodes, caches, etc) and all of the actions and everything in player.m, and then run them in GameLevelLayer.m? Or do I create all that in GameLevelLayer.m
I really need some help here because I’ve been stuck on this for months
EDIT:
Here is where I am, thanks to a suggestion from a community member.
This is my entire player.m (Long story short, I define everything about the player 'you might just want to pay attention to the init, because that seems to be the problem. The other stuff is my physics engine')
#import "Player.h"
#import "SimpleAudioEngine.h"
#import "GameLevelLayer.h"
#implementation Player
#synthesize velocity = _velocity;
#synthesize desiredPosition = _desiredPosition;
#synthesize onGround = _onGround;
#synthesize forwardMarch = _forwardMarch, mightAsWellJump = _mightAsWellJump, isGoingLeft = _isGoingLeft;
#synthesize WalkAction = _WalkAction;
#synthesize isMoving = _isMoving;
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super init]) {
self.velocity = ccp(0.0, 0.0);
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile: #"BankerSpriteSheet_default.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"BankerSpriteSheet_default.png"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <=6; ++i) {
[walkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"banker%d.png", i]]];
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
self = [super initWithSpriteFrameName:#"banker1.png"];
self.WalkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
//[_Banker runAction:_WalkAction];
[spriteSheet addChild:self];
[[CCAnimationCache sharedAnimationCache] addAnimation:walkAnim name:#"walkAnim"];
}
}
return self;
}
-(void)update:(ccTime)dt {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];
CGPoint jumpForce = ccp(0.0, 310.0);
float jumpCutoff = 150.0;
if (self.mightAsWellJump && self.onGround) {
self.velocity = ccpAdd(self.velocity, jumpForce);
if (![defaults boolForKey:#"All Muted"]) {
if (![defaults boolForKey:#"SFX Muted"]) {
[[SimpleAudioEngine sharedEngine] playEffect:#"jump.wav"];
}
}
} else if (!self.mightAsWellJump && self.velocity.y > jumpCutoff) {
self.velocity = ccp(self.velocity.x, jumpCutoff);
}
CGPoint gravity = ccp(0.0, -450.0);
CGPoint gravityStep = ccpMult(gravity, dt);
CGPoint forwardMove = ccp(800.0, 0.0);
CGPoint forwardStep = ccpMult(forwardMove, dt);
self.velocity = ccp(self.velocity.x * 0.90, self.velocity.y); //2
if (self.forwardMarch) {
self.velocity = ccpAdd(self.velocity, forwardStep);
if (!self.isMoving) {
//[self runAction: _WalkAction];
//self.isMoving = YES;
}
} //3
CGPoint minMovement = ccp(0.0, -450.0);
CGPoint maxMovement = ccp(120.0, 250.0);
self.velocity = ccpClamp(self.velocity, minMovement, maxMovement);
self.velocity = ccpAdd(self.velocity, gravityStep);
CGPoint stepVelocity = ccpMult(self.velocity, dt);
if (!self.isGoingLeft) {
self.desiredPosition = ccpAdd(self.position, stepVelocity);
}else{
self.desiredPosition = ccp(self.position.x-stepVelocity.x, self.position.y+stepVelocity.y);
}
}
-(CGRect)collisionBoundingBox {
CGRect collisionBox = CGRectInset(self.boundingBox, 3, 0);
//CGRect collisionBox = self.boundingBox;
//collisionBox = CGRectOffset(collisionBox, 0, -2);
CGPoint diff = ccpSub(self.desiredPosition, self.position);
CGRect returnBoundingBox = CGRectOffset(collisionBox, diff.x, diff.y);
return returnBoundingBox;
}
#end
So now the issue is, how do i get the player to appear in the game like a normal sprite, and then how do I run the animations when the player begins to move forward
Your game architecture is very good! Keep player properties on player and use GameLayer only to send player actions is the best approach.
If your Player Class extends from CCSprite, put everithing related to player in your Player Class: Run animations on it, load the cache on player constructor (use the CCSprite initWithTexture:rect:rotated: or a custom autorelease constructor, like a +(id)player ).
Then, in your GameLevelLayer you will need a CCSpriteBatchNode to add your player to it. You can use this batch node to add other objects that use same spritesheet.
EDIT:
Reviewing your code, your initWithTexture is wrong. The correct is to call [super initWithTexture:]:
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super initWithTexture:texture]) {
Then, in your GameLevelLayer, create your player and add it to a CCSpriteBatchNode:
// *** In GameLevelLayer.m ***
// Create player
Player *player = [Player spriteWithFile:#"player.png"];
// Create batch node
CCSpriteBatchNode *batchNode = [CCSpriteBatchNode batchNodeWithTexture:player.texture];
// Add batch node as child
[self addChild:batchNode];
// Add player as batch node's child
[batchNode addChild:player];
// Set player position
player.position = ccp(100,100);
Your GameLevelLayer needs to do only this to create your Player. And when it detects user input, call methods like "jump", "moveRight", "moveLeft" in your player. Also detect collisions and send actions to player like "die", "getSpecialItem".
// *** In GameLevelLayer.m ***
[player jump];
[player die];
So, your player needs to handle these methods and performs its own logic to do the actions:
// *** In Player.m ***
- (void)moveRight
{
self.position = ccpAdd(self.position, ccp(10,0));
}
I have a very simple level that has a single static body (and associated sprite) in it. When the user touches the body, I remove the sprite, destroy the body and replace it with a dynamic body with a new sprite, and then I transition to a new scene. My code works perfectly the first time it is executed, but then when this scene is reloaded it crashes when the user touches the body.
I store the filename of the .png file that I use for my sprite in the sprite's userData field. Then when the user presses the button (touches the body) it is a simple matter to retrieve it. The problem is occurring on subsequent reloads of the scene when I try to access the sprite's userData field. Instead of holding the filename it is empty (null). This in turn crashes the program when I try to use the filename.
I cannot understand why it works the first time and not the second. I set a breakpoint and watch the filename getting assigned to the sprite's userData field, but it is only retrievable the first time I create the scene. Here is the ccTouchesBegan method. The crash occurs where I try to assign newSpriteFileName because on the second run through oldSpriteFileName is empty.
I have just about gone crazy with this one, but I am sure it is something obvious. As always thanks for the help!!
(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for( UITouch *touch in touches )
{
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
b2Vec2 locationWorld = b2Vec2(touchLocation.x/PTM_RATIO, touchLocation.y/PTM_RATIO);
NSLog(#"Location x = %f, y = %f", touchLocation.x, touchLocation.y);
b2AABB aabb;
b2Vec2 delta = b2Vec2(1.0/PTM_RATIO, 1.0/PTM_RATIO);
aabb.lowerBound = locationWorld - delta;
aabb.upperBound = locationWorld +delta;
SimpleQueryCallback callback(locationWorld);
m_world->QueryAABB(&callback, aabb);
//If they have not pressed a button yet, and this touch was actually inside
//one of the buttons then we will destroy the static button (and sprite)
//that they touched and replace it with a dynamic button
//(and new darker sprite) that falls, spinning off of the screen.
if(!replaceButtonPushedYet && callback.fixtureFound)
{
//Get a reference to the static body of the button they touched and wake it up
b2Body *body = callback.fixtureFound->GetBody();
body->SetAwake(true);
//Get the position of the body (button) they touched and use it to
//position the new button. This value is in Box2D coordinates,
//so when I position the new button I won't divide by PTM_RATIO.
b2Vec2 touchedButtonPosition = body->GetPosition();
//Get the sprite from the button the user pressed
CCSprite *bodySprite = (CCSprite*)body->GetUserData();
//Then extract the file name of the sprite. I assigned this to
//sprite's userData when I loaded the sprite in the method //"placeTheIndividualButton". Now I am going to extract it and
//then replace "(up)" with "(down)" in that string
//and that becomes the name of the sprite for the new (pressed)
//button I am getting ready to create. It is all about the file
//naming conventions!
NSString *oldSpriteFileName = (NSString*)bodySprite.userData;
NSString *newSpriteFileName = [oldSpriteFileName stringByReplacingOccurrencesOfString:#"up" withString:#"down"];
//First remove the sprite tied to the button the user pressed,
//then destroy the body of the button.
[self removeChild:bodySprite cleanup:YES];
body->GetWorld()->DestroyBody(body);
//Set the bool to true to keep this code from executing again.
//This ensures that once they press a button they can't
//press another one.
replaceButtonPushedYet = true;
//Build the new dynamic button that will fall and spin off the screen
b2BodyDef buttonBodyDef2;
b2PolygonShape buttonShape2;
b2FixtureDef buttonShapeDef2;
b2Vec2 vertices2[4];
//load the sprite for the second button (pressed down) and add it to the layer
level_down = [CCSprite spriteWithFile:newSpriteFileName];
level_down.userData=newSpriteFileName;
[self addChild:level_down];
//Define the polygon that forms the second button (pressed down)
buttonBodyDef2.type = b2_dynamicBody;
//Not dividing touchedButtonPosition.x or .y by PTM_RATIO because
//they are already in Box2D coordinates
buttonBodyDef2.position.Set(touchedButtonPosition.x, touchedButtonPosition.y);
buttonBodyDef2.angularVelocity = 1.5;
buttonBodyDef2.userData = level_down;
buttonBody2 = m_world->CreateBody(&buttonBodyDef2);
//Define the vertices for the replacement button
vertices2[0].Set(-94/PTM_RATIO, -32/PTM_RATIO);
vertices2[1].Set(94/PTM_RATIO, -32/PTM_RATIO);
vertices2[2].Set(94/PTM_RATIO, 32/PTM_RATIO);
vertices2[3].Set(-94/PTM_RATIO, 32/PTM_RATIO);
buttonShape2.Set(vertices2, 4);
//Define the shape for the replacement button
buttonShapeDef2.shape = &buttonShape2;
buttonShapeDef2.density = 50.01f;
buttonShapeDef2.friction = 0.75f;
buttonShapeDef2.restitution = 0.1f;
//The static buttons and the dynamic buttons are both in this groupIndex.
//Since it is a negative number they will never collide. If it was
//positive they would always collide.
buttonShapeDef2.filter.groupIndex = -1;
//Put the second button (pressed down) into the world.
buttonBody2->CreateFixture(&buttonShapeDef2);
//This starts a timer that fires every second to call the makeTransition
//method. The code inside that method will check to see if the button
//has fallen off the screen yet. If it has then it will transition to
//the new selected level.
//The colon after makeTransition sends a ccTime (dt). I don't need it,
//but may in the future so I left it in there.
buttonFalling = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeTransition:) userInfo:nil repeats:YES];
}
}
}
Chances are that the NSString is being released when you remove the sprite. Why not just storing the texture name in an instance variable?