Handling Thousand of SKSpriteNodes in a scene - ios

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.

Related

How to make SKSpriteNode rotate in the direction of touching?

Could you please help me with the problem I have?
define CC_RADIANS_TO_DEGREES(ANGLE) ((ANGLE) * 57.29577951f) // PI
-(void)didMoveToView:(SKView )view {
/ Setup your scene here */
_skyColor = [SKColor colorWithRed:113.0/255.0 green:197.0/255.0 blue:207.0/255.0 alpha:1.0];
[self setBackgroundColor:_skyColor];
//Setup the array to hold the walking frames
NSMutableArray *padlingFrames = [NSMutableArray array];
//Load the TextureAtlas for the bear
SKTextureAtlas *kajakAnimatedAtlas = [SKTextureAtlas atlasNamed:#"KajakImages"];
//Load the animation frames from the TextureAtlas
long numImages = kajakAnimatedAtlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"kajak_0%d", i];
SKTexture *temp = [kajakAnimatedAtlas textureNamed:textureName];
[padlingFrames addObject:temp];
}
_kajakPadlingFrames = padlingFrames;
//Create kajak sprite, setup position in middle of the screen, and add to Scene
SKTexture *temp = _kajakPadlingFrames[0];
_kajak = [SKSpriteNode spriteNodeWithTexture:temp];
_kajak.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[_kajak setScale:0.2];
[self addChild:_kajak];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent )event {
/ Called when a touch begins */
CGPoint touchLocation = [[touches anyObject] locationInNode:self];
[self updateRotate:touchLocation];
}
(void)updateRotate:(CGPoint)touchLocation
{
float deltaX = touchLocation.x - _kajak.position.x;
float deltaY = touchLocation.y - _kajak.position.y;
float angle = atan2f(deltaY, deltaX);
SKAction *action = [SKAction rotateByAngle: CC_RADIANS_TO_DEGREES(angle) duration: 5.0];
[_kajak runAction:action];
}
The kayak is not rotate in the direction the I touch. It is some thing I missing? Please help me. Tank you in advance
The zero angle for SKSpriteNodes is pointing up (as opposed to standard trig functions that treat zero angle as pointing right). I had to create a special angle calculation function to take that into account in my game (see below, in Swift).
You may also need to make sure that the coordinate system you are using for the positions is the same for the sprite and the touch location. From your code (i'm not very comfortable with Obj-C) I believe they are both base on the scene's bounds but I'm not certain.
// angle between two points
// ------------------------
// SpriteKit's zero angle is pointing up
// atan2 returns an angle pointing right
// we're usung sprite kit's conventions so removing 90° from the result
//
func spriteAngleFrom(start:CGPoint, to finish:CGPoint) -> CGFloat
{
let deltaX = finish.x - start.x
let deltaY = finish.y - start.y
return atan2(deltaY,deltaX) - CGFloat.pi/2
}
You also need to use rotateToAngle, not rotateByAngle. And I would suggest you also specify shortestUnitArc : true.

Can't prevent the sprite from moving out of the screen

I want to keep my sprite just moving in the range of the screen. So I create a edge loop by bodyWithEdgeLoopFromRect, also the collision bit mask has been set to make them collide with each other.
static const uint32_t kRocketCategory = 0x1 << 0;
static const uint32_t kEdgeCategory = 0x1 << 6;
I use the pan recognizer to move the sprite and here is the current code that sets the properties of all the things.
- (void)didMoveToView:(SKView *)view
{
// Pan gesture
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[self.view addGestureRecognizer:self.panRecognizer];
// Edge
self.backgroundColor = [SKColor blackColor];
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = kEdgeCategory;
self.physicsBody.contactTestBitMask = kRocketCategory;
self.physicsBody.collisionBitMask = kRocketCategory;
self.physicsBody.usesPreciseCollisionDetection = YES;
self.physicsBody.restitution = 0;
// Rocket
SKTexture *texture = [SKTexture textureWithImageNamed:#"Rocketship-v2-1"];
texture.filteringMode = SKTextureFilteringNearest;
self.rocketSprite = [SKSpriteNode spriteNodeWithTexture:texture];
self.rocketSprite.position = CGPointMake(CGRectGetMidX(self.scene.frame), CGRectGetMaxY(self.scene.frame)*0.3); // 30% Y-axis
self.rocketSprite.name = #"rocketNode";
self.rocketSprite.xScale = 2;
self.rocketSprite.yScale = 2;
self.rocketSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.rocketSprite.size];
self.rocketSprite.physicsBody.categoryBitMask = kRocketCategory;
self.rocketSprite.physicsBody.contactTestBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.collisionBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.usesPreciseCollisionDetection = YES;
self.rocketSprite.physicsBody.allowsRotation = NO;
self.rocketSprite.physicsBody.restitution = 0;
[self addChild:self.rocketSprite];
}
Pan recognizer code:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.selectedRocket = self.rocketSprite;
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
[self panForTranslation:translation];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
}
}
- (void)panForTranslation:(CGPoint)translation
{
CGPoint position = self.selectedRocket.position;
if ([self.selectedRocket.name isEqualToString:#"rocketNode"]) {
self.selectedRocket.position = CGPointMake(position.x + translation.x, position.y);
}
}
Now the problem is when I move the sprite (rocketship) slowly, the edge will stop the sprite going out of the screen. However, when I move the sprite very quickly, the sprite will rush out the range. See the animation below. I have read some solutions to the similar problem but I still don't know what's wrong with my code. Do the edge loop and collision bit mask not enough for the situation here?
Your attempt is similar to what I've found when searching online, and having read what you've tried it looks like you've pretty much covered most of the more common problems (Not using SKAction, for instance).
I don't currently use a contact Bitmask to handle collisions on the side of my scene, but I do use a loop that actively checks positions.
Here's the method I use to reposition objects: (Repositions everything, you can specify it to move only the rocket.
-(void)repositionObjects
{
for (CXSpriteNode *i in self.sceneObjects) // CXSpriteNode is a certain subclass
{
CGPoint position = i.position;
if (position.x > self.background.size.width || position.x < 0)
{
CGPoint newPosition;
if (position.x > self.background.size.width)
{
newPosition = CGPointMake(self.background.size.width-1, position.y);
} else {
newPosition = CGPointMake(1, position.y);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
if (position.y > self.background.size.height || position.y < 0)
{
CGPoint newPosition;
if (position.y > self.background.size.height)
{
newPosition = CGPointMake(self.background.size.height-1, 1);
} else {
newPosition = CGPointMake(position.x, 1);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
}
}
This gets called from the SKScene loop as such:
-(void)update:(NSTimeInterval)currentTime
{
[self repositionObjects];
}
Now, it's evidently not as elegant as your desired outcome, and I have a feeling it might still induce flickering, but I'd give it a try anyways.
Also, it might be worth trying to disable/cancel the gesture once it goes out of range momentarily to stop repeated swipes that may cause flickering.
Hope this helps.
Use SpriteKit actions instead of directly modifying the position of the sprite.
What is happening is that the recognizer isn't sending updates fast enough. The sprite's bounds never intersect with the edge in any one frame.
In one update, the recognizer says that the touch is within the bounds of the edge loop. But in the next update, the touch has moved way beyond the edge loop. This leaves no frame in which the sprite's bounds intersect the edge, so no collision gets detected.
I do not use sprite kit in daily life, but you could for example solve it the hard way by adding something like this in your sprite's update method
(Pseudo code)
CGFloat x = clamp(self.position.x, minXValue, maxXValue);
CGFloat y = clamp(self.position.y, minYValue, maxYValue);
self.position = CGPointMake(x, y);

Spritekit: move SKSpritekitNode in arc with SKPhysicsBody

I'm trying to move an SKSpriteNode in an arc with physics in spritekit like so:
But am unsure as to which physic I should apply to it (applyImpulse, applyForce, applyTorque).
Currently using applyTorque, the code does not actually work, and produces no movement on the object:
_boy.physicsBody.velocity = CGVectorMake(1, 1);
CGVector thrustVector = CGVectorMake(0,100);
[_boy.physicsBody applyTorque:(CGFloat)atan2(_boy.physicsBody.velocity.dy, _boy.physicsBody.velocity.dx)];
applyTorque is not the right method for this. Torque is a twisting force that causes your node to rotate around its center point.
There is no one easy command for what you are looking to do. You also fail to mention your method of movement for your node. Apply force, impulse, etc... You are going to have to come up with a hack for this one.
The sample project below does what you are looking for and it will point you in the right direction. You are going to have to modify the code to suit your specific project's needs though.
Tap/click the screen once to start moving the node and tap/click the screen a second time to start to 90 degree movement change.
#import "GameScene.h"
#implementation GameScene {
int touchCounter;
BOOL changeDirection;
SKSpriteNode *node0;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor whiteColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(50, 50)];
node0.position = CGPointMake(150, 200);
node0.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node0.size];
node0.physicsBody.affectedByGravity = NO;
[self addChild:node0];
touchCounter = 0;
changeDirection = NO;
}
-(void)update:(CFTimeInterval)currentTime {
if(changeDirection)
[self changeMovement];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touchCounter++;
for (UITouch *touch in touches) {
if(touchCounter == 1)
[node0.physicsBody applyImpulse:CGVectorMake(25, 0)];
if(touchCounter == 2)
changeDirection = YES;
}
}
-(void)changeMovement {
if(node0.physicsBody.velocity.dy<200) {
[node0.physicsBody applyImpulse:CGVectorMake(-0.1, 0.1)];
} else {
changeDirection = NO;
node0.physicsBody.velocity = CGVectorMake(0, node0.physicsBody.velocity.dy);
}
}

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);
}

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

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]]];

Resources