Only one sprite on the scene at once Cocos2D 3.x - ios

The sprite appears every time the screen is touched, then it shoots to the desired area. How would i make it that only one sprite will be on the scene at a time until it exits the scene or hits an object? (even though the screen is touched multiple times)
This is the projectile code
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// 1
CGPoint touchLocation = [touch locationInNode:self];
// 2
CGPoint offset = ccpSub(touchLocation, _player.position);
float ratio = offset.y/offset.x;
int targetX = _player.contentSize.width/2 + self.contentSize.width;
int targetY = (targetX*ratio) + _player.position.y;
CGPoint targetPosition = ccp(targetX,targetY);
// 3
CCSprite *projectile = [CCSprite spriteWithImageNamed:#"projectile.png"];
projectile.position = _player.position;
projectile.physicsBody = [CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints];
projectile.physicsBody.collisionGroup = #"playerGroup";
projectile.physicsBody.collisionType = #"projectileCollision";
[_physicsWorld addChild:projectile];
// 4
CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.5f position:targetPosition];
CCActionRemove *actionRemove = [CCActionRemove action];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove]]];
[[OALSimpleAudio sharedInstance] playEffect:#"pew-pew-lei.caf"];
}

If I understood your point correctly , you could simply add a flag , to be able to notice when there's already a sprite on the scene. Just declare on your class a
BOOL isSpritePresent;
Initialize it on your class custom id method.
-(id)init {
self=[super init];
isSpritePresent=NO;
return self; }
And then on the start of TouchBegan add something like
if(isSpritePresent){
return; //As there's already an sprite on the scene.
}
And at the end
isSpritePresent=YES;
And finally when the arrow or w/e it is reached its target , call a method to reset the Boolean.
Or... If you're looking forward to doing things simpler and you believe you have an specific time to let the user shoot again just add a delay after the other actions as ..
CCActionDelay *delay = [CCActionDelay actionWithDuration:1.2f];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove,delay]]];

Related

Handling Thousand of SKSpriteNodes in a scene

I am building a game using sprite kit and its a game where you send balls into a bucket and grow the bucket. As the buckets grow the balls (SKSpriteNodes) stay on the scene. Im trying to see how to keep high performance while managing thousands of nodes. Any idea how i can do this? After 700 or so the FPS in simulator goes below 10 tps.
Here is my code from my scene. Any help is appreciated.
//
// GameScene.m
//
#import "GameScene.h"
#implementation GameScene
#synthesize _flowIsON;
NSString *const kFlowTypeRed = #"RED_FLOW_PARTICLE";
const float kRED_DELAY_BETWEEN_PARTICLE_DROP = 0.1; //delay for particle drop in seconds
static const uint32_t kRedParticleCategory = 0x1 << 0;
static const uint32_t kInvisbleWallCategory = 0x1 << 1;
NSString *const kStartBtn = #"START_BTN";
NSString *const kLever = #"Lever";
NSString *const START_BTN_TEXT = #"Start Game";
CFTimeInterval lastTime;
-(void)didMoveToView:(SKView *)view {
[self initializeScene];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:kStartBtn]) {
[node removeFromParent];
//initalize to ON
_flowIsON = YES;
//[self initializeScene];
} else if ([node.name isEqualToString:kLever]) {
_leverNode = (SKSpriteNode *)node;
[self selectNodeForTouch:location];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPoint previousPosition = [touch previousLocationInNode:self];
CGPoint translation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);
[self panForTranslation:translation];
}
-(void)update:(CFTimeInterval)currentTime {
float deltaTimeInSeconds = currentTime - lastTime;
//NSLog(#"Time is %f and flow is %d",deltaTimeInSeconds, _flowIsON);
if ((deltaTimeInSeconds > kRED_DELAY_BETWEEN_PARTICLE_DROP) && _flowIsON) {
[self startFlow:kFlowTypeRed];
//only if its been past 1 second do we set the lasttime to the current time
lastTime = currentTime;
}
}
- (void) initializeScene {
SKLabelNode *startBtn = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
startBtn.text = START_BTN_TEXT;
startBtn.name = kStartBtn;
startBtn.fontSize = 45;
startBtn.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:startBtn];
//init to flow off
_flowIsON = NO;
// Set physics body delegate
self.physicsWorld.contactDelegate = self;
self.shouldRasterize = YES;
self.view.showsDrawCount = YES;
self.view.showsQuadCount = YES;
//Set collision mask for invisible wall
_nonWallNode = (SKSpriteNode *) [self.scene childNodeWithName:#"NonWall"];
_nonWallNode.physicsBody.categoryBitMask = kInvisbleWallCategory;
_nonWallNode.physicsBody.collisionBitMask = kRedParticleCategory;
_nonWallNode.physicsBody.contactTestBitMask = kRedParticleCategory | kInvisbleWallCategory;
}
- (void) startFlow:(NSString *)flowKey {
// //SKSpriteNode *redParticleEmitter = [SKSpriteNode spriteNodeWithImageNamed:#"RedFlowParticles"];
//
// SKShapeNode *redParticleEmitter = [[SKShapeNode alloc] init];
//
// CGMutablePathRef myPath = CGPathCreateMutable();
// CGPathAddArc(myPath, NULL, 0,0, 15, 0, M_PI*2, YES);
// redParticleEmitter.path = myPath;
//
// redParticleEmitter.lineWidth = 1.0;
// redParticleEmitter.fillColor = [SKColor blueColor];
// redParticleEmitter.strokeColor = [SKColor whiteColor];
// redParticleEmitter.glowWidth = 0.5;
//
// //set size to 20px x 20px
// //redParticleEmitter.size = CGSizeMake(10, 10);
SKSpriteNode *redParticleEmitter = [SKSpriteNode spriteNodeWithImageNamed:#"RedFlowParticles"];
//set size to 20px x 20px
redParticleEmitter.size = CGSizeMake(10, 10);
SKPhysicsBody *redParticleEmitterPB = [SKPhysicsBody bodyWithCircleOfRadius:redParticleEmitter.frame.size.width/2];
redParticleEmitterPB.categoryBitMask = kRedParticleCategory;
redParticleEmitterPB.collisionBitMask = kRedParticleCategory;
redParticleEmitterPB.contactTestBitMask = kRedParticleCategory | kInvisbleWallCategory;
//set this to 5% of the width of the scene
redParticleEmitter.position = CGPointMake(self.frame.size.width*0.05, self.frame.size.height);
redParticleEmitter.physicsBody =redParticleEmitterPB;
redParticleEmitter.name = #"RedParticle";
[self addChild:redParticleEmitter];
}
- (void)selectNodeForTouch:(CGPoint)touchLocation {
//1
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
//2
if(![_leverNode isEqual:touchedNode]) {
[_leverNode removeAllActions];
[_leverNode runAction:[SKAction rotateToAngle:0.0f duration:0.1]];
_leverNode = touchedNode;
//3
if([[touchedNode name] isEqualToString:kLever]) {
SKAction *sequence = [SKAction sequence:#[[SKAction rotateByAngle:degToRad(-4.0f) duration:0.1],
[SKAction rotateByAngle:0.0 duration:0.1],
[SKAction rotateByAngle:degToRad(4.0f) duration:0.1]]];
[_leverNode runAction:[SKAction repeatActionForever:sequence]];
}
}
}
float degToRad(float degree) {
return degree / 180.0f * M_PI;
}
- (CGPoint)boundLayerPos:(CGPoint)newPos {
CGSize winSize = self.size;
CGPoint retval = newPos;
retval.x = MIN(retval.x, 0);
retval.x = MAX(retval.x, -[self size].width+ winSize.width);
retval.y = [self position].y;
return retval;
}
- (void)panForTranslation:(CGPoint)translation {
CGPoint position = [_leverNode position];
if([[_leverNode name] isEqualToString:kLever]) {
[_leverNode setPosition:CGPointMake(position.x + translation.x, position.y + translation.y)];
}
// else {
// CGPoint newPos = CGPointMake(position.x + translation.x, position.y + translation.y);
// [_background setPosition:[self boundLayerPos:newPos]];
// }
}
# pragma mark -- SKPhysicsContactDelegate Methods
- (void)didBeginContact:(SKPhysicsContact *) contact {
if (([contact.bodyA.node.name isEqualToString:#"RedParticle"] && [contact.bodyB.node.name isEqualToString:#"NonWall"]) ||
([contact.bodyB.node.name isEqualToString:#"RedParticle"] && [contact.bodyA.node.name isEqualToString:#"NonWall"])) {
//NSLog(#"Red particle Hit nonwall");
//contact.bodyA.node.physicsBody.pinned = YES;
//once red particle passes the invisible wall we need to stop it from going back through the wall
}
}
- (void)didEndContact:(SKPhysicsContact *) contact {
//NSLog(#"didEndContact called");
if (([contact.bodyA.node.name isEqualToString:#"RedParticle"] && [contact.bodyB.node.name isEqualToString:#"NonWall"]) ||
([contact.bodyB.node.name isEqualToString:#"RedParticle"] && [contact.bodyA.node.name isEqualToString:#"NonWall"])) {
//NSLog(#"Red particle left");
contact.bodyB.collisionBitMask = kRedParticleCategory | kInvisbleWallCategory;
//once red particle passes the invisible wall we need to stop it from going back through the wall
}
}
#end
Try this:
Create an additional sprite node on screen to display all your static balls as a whole (explained below).
Create an array of CGPoint to keep track of the positions of all balls that stopped.
At regular intervals, check all active ball sprites to see which ones have come to a stop.
For each ball that has stopped, remove that srpite from the scene and instead add its position (CGPoint) to the array described in #2.
Render an image consisting of one ball instance at each position in the array, and assign that image (texture) to the sprite node described in #1.
Go back to #3 and repeat.
Note: I haven't used SpriteKit for a while and I'm not sure how to implement point #5, but it shouldn't be too difficult. SKEffectNode has an option (shouldRasterize) to cache its appearance -i.e., render once and reuse the same image on all subsequent frames.
Regarding the "regular intervals" described in step #3, the actual value (for example, every 10 frames) will depend on your measured performance and the dynamics of your actual game; you need to find it yourself. If it is too often, the overhead of rendering the static balls texture over and over will cause a performance hit. Too far apart, and you will spend more frames than necessary rendering many still, separate sprites that could have otherwise been "grouped".
Alternative Solution:
Instead of removing the sprites from screen when each ball becomes static, you could instead move them into a different container node (as children of it), and have that node be rasterized instead of rendering anew each frame.
This keeps each ball as a separate SKSpriteNode instance (even when the ones that are stopped) and allows for SpriteKit physics bodies (not sure if sprites with different parents can collide with each other, though. Never used SpriteKit physics).
In any case, the performance hit due to collision detection will increase with the number of balls, independent of whether you draw them each frame or not.
I don't know exactly what optimizations SpriteKit's physics does (e.g., prunning, etc.), but the naïve approach to collision between n objects is to test each object against every other object, so the worse case is O(n^2).
Final Thoughts:
Because you can safely assume that still balls do not move anymore, the "group" of still balls remains in the same shape all along (until new balls stop and are added, that is).
Ideally, you could calculate the "envelope" (a possibly non-convex polygon, with rounded corners) and collision-test the moving balls against that. Still not a trivial task, but at least it helps you skip collision-testing against the static balls in the inside of the group, which should never collide anyway (they are "shielded" by the balls in the boundary of the group).
Well your problem here is all those physics bodies, you have a 1000 sprites checking 1000 other sprites whether or not they need to be colliding. One way you can make this a little faster is to break your screen into sub sets and having your nodes collission detection only check the surrounding neighbor quadrants and its own for sprites. EG. break the screen into 9 sections, the top left section has its own bit mask, and can only collide with sprites in the top left,middle top, middle center, and let center sections. If this sprite moves to the middle top section, its category becomes middle top, and will only check sprites in the top left, middle top, top right, left center, middle center, and right center. The less checks the nodes have to make the better.

Make a sprite only jump once

Working with Xcode, Spritebuilder and Cocos2d, I am trying to let the players sprite only jump 1 single time. So when the player taps the screen, the sprite jumps, and only when it has landed, the player should again be able to jump again. I haven't found any clear explanations so far, so hence my question.
Here's my code
Edit so that the code only lets the sprite jump once:
static const CGFloat scrollSpeed = 0.4;
BOOL isInAir;
#implementation GameScene
{
CCSprite *player1;
CCPhysicsNode *physicsNode1;
CCNode *ground1;
CCNode *ground2;
}
- (void)didLoadFromCCB
{
self.userInteractionEnabled = TRUE;
grounds = #[ground1, ground2]
}
- (void)update:(CCTime)delta
{
// moves the player to the right
player1.position = ccp(player1.position.x + delta * scrollSpeed, player1.position.y);
// move the camera with the player
physicsNode1.position = ccp(physicsNode1.position.x - (scrollSpeed *delta), physicsNode1.position.y);
// loop the ground
for (CCNode *ground in grounds)
{
// get the world position of the ground
CGPoint groundWorldPosition = [physicsNode1 convertToWorldSpace:ground.position];
// get the screen position of the ground
CGPoint groundScreenPosition = [self convertToNodeSpace:groundWorldPosition];
// if the left corner is one complete width off the screen, move it to the right
if (groundScreenPosition.x <= (-1 * ground.contentSize.width))
{
// puts the ground piece that is about to leave the screen behind the last one
ground.position = ccp(ground.position.x + 600, ground.position.y);
//NSLog(#"player1 pos: %#", NSStringFromCGPoint(ground.position));
}
}
// clamp velocity
float yVelocity = clampf(player1.physicsBody.velocity.y, -1 * MAXFLOAT, 200.f);
player1.physicsBody.velocity = ccp(0, yVelocity);
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
if (isInAir == NO)
{
[player1.physicsBody applyImpulse:ccp(0, 150)];
isInAir = YES;
}
}
- (BOOL) ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair player1:(CCNode *)player1 ground: (CCNode *)ground
{
NSLog(#"player is touching the ground");
isInAir = NO;
return YES;
}
#end
You can use a BOOL. Here is my code for a jump on touch:
BOOL isInAir;//Make this global.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
SKSpriteNode *node = [self nodesAtPoint:[touch locationInNode:self]];
if (isInAir == NO)
{
CGFloat impulseX = 0.0f;
CGFloat impulseY = 600.0f;
[node.physicsBody applyImpulse:CGVectorMake(impulseX, impulseY) atPoint:node.position];
isInAir = YES;
//other stuff
}
}
Then when you contact ground, set isInAir to NO.

Having trouble adding multiple physics objects in Xcode cocos2d

I'm new to cocos2d and objective-c. I know I'm missing something here, but I just can't find the solution. Hope someone can help....
My goal is to be able to click any of the sprites that I've placed on the screen. To start, I've put 2 sprites on the screen. (Each sprite is in the shape of a star).
The problem is, only the second sprite placed on the screen is clickable. My guess is that when I call addNewStar, it replaces _star with the latest star sprite, and takes the previous _star out of the physics node. I want all the stars I add to be in the physics node and be clickable. No clue how to do this.
Here is my code...hopefully someone can point out my mistake(s)!
#implementation MainScene {
CCSprite *_star;
CCPhysicsNode *_physicsNode;
CCNode *_ground;
CCLabelTTF *_scoreLabel;
BOOL _gameOver;
CCButton *_restartButton;
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
[self addNewStar];
[self addNewStar];
// set collision txpe
_ground.physicsBody.collisionType = #"level";
// set this class as delegate
_physicsNode.collisionDelegate = self;
}
-(void)addNewStar {
//This successfully loads my star onto the screen
_star = (Star *)[CCBReader load:#"Star"];
_star.physicsBody.collisionGroup = #"starGroup";
_star.physicsBody.collisionType = #"star";
_star.position = ccp(200,300);
[_physicsNode addChild:_star];
}
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair star:(CCNode *)star level:(CCNode *)level {
[self gameOver];
return TRUE;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
float ranNum1 = (arc4random_uniform(10000));
float ranNum2 = (arc4random_uniform(10000));
float sideForce = ranNum1 - ranNum2;
if (!_gameOver) {
CGPoint touchLocation = [touch locationInNode:_physicsNode];
if(CGRectContainsPoint([_star boundingBox], touchLocation))
{
[_star.physicsBody applyImpulse:ccp(sideForce, 1000.f)];
[_star.physicsBody applyAngularImpulse:2500.f];
}
}
}
- (void)restart {
CCScene *scene = [CCBReader loadAsScene:#"MainScene"];
[[CCDirector sharedDirector] replaceScene:scene];
}
- (void)gameOver {
if (!_gameOver) {
_gameOver = TRUE;
_restartButton.visible = TRUE;
_star.rotation = 90.f;
_star.physicsBody.allowsRotation = FALSE;
[_star stopAllActions];
CCActionMoveBy *moveBy = [CCActionMoveBy actionWithDuration:0.2f position:ccp(-2, 2)];
CCActionInterval *reverseMovement = [moveBy reverse];
CCActionSequence *shakeSequence = [CCActionSequence actionWithArray:#[moveBy, reverseMovement]];
CCActionEaseBounce *bounce = [CCActionEaseBounce actionWithAction:shakeSequence];
[self runAction:bounce];
}
}
#end
You only have one pointer to one star. So when you test if the star was touched in your touches began method then of course only the second one would do anything since your sole pointer is only pointing at the most recently created star.
To answer your question, the simple approach you could take is to add each star to an NSArray. Then in your touches began method you can loop over the array and see which star was touched.
Example:
// Recommend using properties over i-vars
#property (nonatomic, strong) NSMutableArray* starList;
// In init or onEnter...
self.starList = [NSMutableArray array];
// Each time you create a new star...
Star* star = ...;
[self addChild:star];
[self.starList addObject:star];
// In your touches began...
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
...
for (Star* star in self.starList)
{
// If star is touched...
// ...add your impulses, etc
}
}
This is touchBegan from my project. Maybe you can change code little bit for your need. What you can see here? After getting location of touch code searching b2Body which was touched. In your example you need to search your _physicsNode for _child that was touched.
for(UITouch *touch in allTouches)
{
CGPoint location = [touch locationInView:touch.view];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 worldLoc = b2Vec2(ptm(location.x), ptm(location.y));
// SEARCH YOUR CHILD FROM NODE HERE
for (b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetType() == b2_dynamicBody)
if (b->IsBullet() == false)
{
for (b2Fixture *f = b->GetFixtureList(); f; f = f->GetNext())
{
// Hit!
if (f->TestPoint(worldLoc))
{
//do stuff
}
break;
}
}
}
}

Getting location of section of sprite

I'm using IOS7's sprite kit and using NSSpriteNode with default anchor of (.5, .5) [center]. The sprite will be rotating around a lot, is there a way to get the location relative to the anchor point? Like, if I'm looking for the sprites top-center anchorpoint of (.5,1) or bottom-center (.5, 0)? This way I can always get the same location of a part of a sprite however it is rotated?
self.player = [SKSpriteNode spriteNodeWithImageNamed:#"ship.png"];
self.player.anchorPoint = CGPointMake(.5, 1);
CGPoint p = [self.player convertPoint:self.player.position toNode:self]];
NSLog(#"x=%f,y=%f", p.x, p.y);
self.player.anchorPoint = CGPointMake(.5, 0);
CGPoint p = [self.player convertPoint:self.player.position toNode:self]];
NSLog(#"x=%f,y=%f", p.x, p.y);
This ends up yielding the same point even though I'm changing my anchor point to be different parts. I know this is a bad idea to change the anchor point, but trying to give an idea of what I'm trying to do.
I'd like a method something like:
CGPoint imageTopLoc = [self.player getLocationUsingAnchorPoint:CGPointMake(.5, 1)];
CGPoint imageBottomLoc = [self.player getLocationUsingAnchorPoint:CGPointMake(.5, 0)];
// calculate vector of direction between of two points... (next)
Thanks,
Chris
This code returns the coordinates / touch point inside a node, regardless of the node's rotation. (It's based upon the SpriteKit Game template.)
Is that what you are looking for?
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKSpriteNode *ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
ship.name = #"ship";
ship.position = CGPointMake(100, 100);
ship.zRotation = M_PI * 0.5;
[self addChild:ship];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *ship = [self childNodeWithName:#"ship"];
CGPoint p = [self convertPoint:location toNode:ship];
NSLog(#"x=%f,y=%f", p.x, p.y);
}
}

Stoping the movement of a sprite- Box2D (cocos2d)

I am in the process of making a game and I need an object to move only when the buttons are pressed. I have a method that begins the movement, and so far I am ending the movement of an object by having destroying the body of the object. The problem that I have is that I can't move the object again since the program will now crash. I'm wondering if there is a way to recreate the body once its destroyed since my current method of checking if the body still exists isn't working.
Here is my code.
NSMutableArray *spaceObjectsArray;
#pragma mark - HelloWorldLayer
#interface HelloWorldLayer()
-(void) initPhysics;
-(void) addNewSpriteAtPosition:(CGPoint)p;
-(void) createMenu;
#end
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(void)gameLogic:(ccTime)delta
{
[self addSpaceObjects];
}
-(void) addSpaceObjects
{
_spaceObject = [CCSprite spriteWithFile:#"blueDot.jpg"];
//create spaceObject body
b2BodyDef spaceObjectbBodyDef;
spaceObjectbBodyDef.type=b2_dynamicBody;
spaceObjectbBodyDef.userData = _spaceObject;
//make the location of spaceObject
CGSize winSize = [CCDirector sharedDirector].winSize;
int minX= _spaceObject.contentSize.width/2;
int maxX = winSize.width - _spaceObject.contentSize.width/2;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
_spaceObject.position = ccp(actualX, winSize.height + _ship.contentSize.height);
spaceObjectbBodyDef.position.Set(actualX/PTM_RATIO, (winSize.height+_ship.contentSize.height)/PTM_RATIO);
_spaceObjectBody= _world->CreateBody(&spaceObjectbBodyDef);
//create spaceObject shape
b2PolygonShape spaceObjectShape;
spaceObjectShape.SetAsBox(_spaceObject.contentSize.width/PTM_RATIO/2, _spaceObject.contentSize.height/PTM_RATIO/2);
//create spaceObject fixture
b2FixtureDef spaceObjectShapeDef;
spaceObjectShapeDef.shape= &spaceObjectShape;
spaceObjectShapeDef.density = 2;
spaceObjectShapeDef.restitution =0;
spaceObjectShapeDef.friction=0;
_spaceObjectFixture = _spaceObjectBody->CreateFixture(&spaceObjectShapeDef);
[self addChild:_spaceObject];
_spaceObject.tag=1;
[spaceObjectsArray addObject:_spaceObject];
//aply force on the object
int randomValue = ((arc4random() % 5) *-1);
b2Vec2 force = b2Vec2(0,randomValue);
_spaceObjectBody ->ApplyLinearImpulse(force, _spaceObjectBody->GetPosition());
}
init method that contains the body creations and definitions
-(id) init
{
if( (self=[super init])) {
CGSize s = [CCDirector sharedDirector].winSize;
//create spaceShip sprite and add it to the layer
_ship = [CCSprite spriteWithFile:#"theShip.gif" ];
_ship.position = ccp(s.width/2, 1.25*_ship.contentSize.height);
[self addChild:_ship];
//create the world
b2Vec2 gravity = b2Vec2_zero;
_world = new b2World(gravity);
//create ship body
b2BodyDef shipBodyDef;
shipBodyDef.type = b2_dynamicBody;
shipBodyDef.position.Set((s.width/2)/PTM_RATIO, (1.25*_ship.contentSize.height)/PTM_RATIO);
shipBodyDef.userData = _ship;
if(_shipBody == NULL){
_shipBody =_world->CreateBody(&shipBodyDef);
}
//create ship shape
b2PolygonShape shipShape;
shipShape.SetAsBox(_ship.contentSize.width/PTM_RATIO/2, _ship.contentSize.height/PTM_RATIO/2);
//create Ship definition and add to body
b2FixtureDef ShipShapeDef;
ShipShapeDef.shape = &shipShape;
ShipShapeDef.density = 3;
ShipShapeDef.friction =0;
ShipShapeDef.restitution =0;
_shipFixture = _shipBody->CreateFixture(&ShipShapeDef);
//make the paddles
//bottom left one
_paddle1 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomOfScreenX = 0 + _paddle1.contentSize.width/2;
int bottomOfScreenY = 0+_paddle1.contentSize.height/2;
_paddle1.position = ccp(bottomOfScreenX,bottomOfScreenY);
[self addChild:_paddle1];
//bottom right one
_paddle2 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomRightOfScreenX = s.width - _paddle2.contentSize.width/2;
_paddle2.position = ccp(bottomRightOfScreenX, bottomOfScreenY);
[self addChild:_paddle2];
//continuously spawn spaceObjects
[self schedule:#selector(gameLogic:) interval:1];
// enable events
self.touchEnabled = YES;
// init physics
[self schedule:#selector(tick:)];
}
return self;
}
-(void)tick:(ccTime) delta
{//this method is to simulate physics and to test for the position of where objects should be if force has been applied to them
_world->Step(delta, 8, 8);
for (b2Body *b=_world->GetBodyList(); b; b=b->GetNext()){
if (b->GetUserData() != NULL){
CCSprite *shipData = (CCSprite *)b->GetUserData();
shipData.position = ccp(b->GetPosition().x *PTM_RATIO, b->GetPosition().y *PTM_RATIO);
}
}
}
The paddles to move the ship are touched logic
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Set up a way for touches to be turned into locations onscreen
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location ];
//check to see if Left Paddle is being pressed
if (CGRectContainsPoint([_paddle1 boundingBox], location)){
b2Vec2 force = b2Vec2(-5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody ->GetPosition());
}
if (CGRectContainsPoint([_paddle2 boundingBox], location)){
b2Vec2 force = b2Vec2(5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody->GetPosition());
}
}
The paddle box is no longer being touched logic
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_world->DestroyBody(_shipBody);
}
-(void) dealloc
{
delete _world;
_world = NULL;
[super dealloc];
}
#end
Destroying the body to end the movement of it is not the best solution. You should only remove the body if you really don't want it to be part of the simulation any more.
There are several options for stopping the body's movement:
1 - Set its linear velocity to 0. This will bring it to an immediate stop. If something else is pushing on it (e.g. contact wit body), you will have to decide what to do.
body->SetLinearVelocity(b2Vec2(0,0)));
2 - Set its linear/angular damping to 0. This will dissipate the momentum it has so it will slowly stop. The factor you use should be greater than 0. The body will come to a halt more quickly with larger values and it will be resistant to movement from other bodies (if they bump it, it will slow down and stop again). Remember to turn the linear/angular damping back to 0 when you want the body to start moving.
body->SetLinearDamping(0.2);
body->SetAngularDamping(0.2);
3 - Give it a target position to seek to and set the position as the place you want it to be. This is basically a feedback control loop where you are applying a force to get it to move towards where you want the body to stay. It can be used to make a body follow paths, etc. The code below is part of a larger code base (you can see it here), but you should be able to get the general idea. This function applies thrust to the object so that it pushes towards a target.
void MovingEntity::ApplyThrust()
{
// Get the distance to the target.
b2Vec2 toTarget = GetTargetPos() - GetBody()->GetWorldCenter();
toTarget.Normalize();
b2Vec2 desiredVel = GetMaxSpeed()*toTarget;
b2Vec2 currentVel = GetBody()->GetLinearVelocity();
b2Vec2 thrust = GetMaxLinearAcceleration()*(desiredVel - currentVel);
GetBody()->ApplyForceToCenter(thrust);
}

Resources