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

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

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.

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.

How to do impulse physics

Okay guys, I have two sprites one the enemy who moves to pick up items around the map as the items appear and it does that using SKActions then also I have the wall which he shouldn't go through. anyway. all I am trying to do is to make him bounce out if he hits the wall. How would I go about that?
Here is the code below:
// for the Enemy wall I have this code:
- (void) EnemyBelongings {
EnemyWall = [SKSpriteNode spriteNodeWithImageNamed:#"EnemyWall#2x"];
EnemyWall.name = #"hisWall";
EnemyWall.position = CGPointMake(512, 260);
EnemyWall.xScale = 0.09;
EnemyWall.yScale = 0.09;
EnemyWall.physicsBody.categoryBitMask = PhysicsCategoryEnemyWall;
EnemyWall.physicsBody.contactTestBitMask = PhysicsCategoryEnemy;
EnemyWall.physicsBody.collisionBitMask = 0;
[self addChild:EnemyWall];
}
// For the enemy character I have this code
- (void) Enemy {
_Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
_Enemy.position = CGPointMake(520, _Enemy.size.height/1.50);
_Enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width];
_Enemy.physicsBody.usesPreciseCollisionDetection = YES;
_Enemy.physicsBody.categoryBitMask = PhysicsCategoryEnemy;
_Enemy.physicsBody.contactTestBitMask = PhysicsCategoryEnemyWall;
_Enemy.physicsBody.collisionBitMask = 0;
[self addChild:_Enemy];
}
// For the enemy movement I have this code
- (void) EnemySpawner {
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
[_Enemy runAction:wait];
SKAction *actionXMove2 = [SKAction moveToX:ForEnemy.position.x duration:0.14];
SKAction *actionYMove2 = [SKAction moveToY:ForEnemy.position.y duration:0.14];
[_Enemy runAction:actionYMove2];
[_Enemy runAction:actionXMove2];
}];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if (contact.bodyA.categoryBitMask == PhysicsCategoryEnemy && contact.bodyB.categoryBitMask
== PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyA.node; // a is the enemy here
// Code here
SKNode *wall = contact.bodyB.node; // b is the enemy's wall here
// Code here
}
else if (contact.bodyB.categoryBitMask == PhysicsCategoryEnemy &&
contact.bodyA.categoryBitMask == PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyB.node;
// Code here
SKNode *wall = contact.bodyA.node;
// Code here
}
}
You need to applyImpulse on the node's physicsBody to make it simulate naturally and interact with other physicsBodies.
Please look at my answer here on how to do the same.
Copy the rwAdd, rwSub, rwMult, rwLength and the rwNormalize methods from this tutorial by Ray Wenderlich.
Then, try using this code:
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
CGPoint offset = rwSub(location, ForEnemy.position);
CGPoint direction = rwNormalize(offset);
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
[_Enemy.physicsBody applyImpulse:impulseVector];
}];
PhysicsWorld should simulate bounces automatically. Just set your nodes' physics properties accordingly. Look for the 'restitution' property. It will determine the bounciness of your objects.
And don't use SKAction to move your enemies. This will just "teleport" your nodes around. They don't get to have any real velocity in the physicsWorld (and hence don't bounce). Instead, apply forces to your bodies (or set velocity vector manually).

Sprite-Kit Pinch to Zoom Problems UIPinchGestureRecognizer

I've been working on this code for quite a while now but it just feels like one step forward and two steps back. I'm hoping someone can help me.
I'm working with Sprite Kit so I have a Scene file that manages the rendering, UI and touch controls. I have an SKNode thats functioning as the camera like so:
_world = [[SKNode alloc] init];
[_world setName:#"world"];
[self addChild:_world];
I am using UIGestureRecognizer, so I add the ones I need like so:
_panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:_panRecognizer];
_pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(handlePinch:)];
[[self view] addGestureRecognizer:_pinchRecognizer];
The panning is working okay, but not great. The pinching is the real problem. The idea for the pinching is to grab a point at the center of the screen, convert that point to the world node, and then move to it while zooming in. Here is the method for pinching:
-(void) handlePinch:(UIPinchGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
_tempScale = [sender scale];
}
if (sender.state == UIGestureRecognizerStateChanged) {
if([sender scale] > _tempScale) {
if (_world.xScale < 6) {
//_world.xScale += 0.05;
//_world.yScale += 0.05;
//[_world setScale:[sender scale]];
[_world setScale:_world.xScale += 0.05];
CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];
//crazy method why does this work
CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale)*-1), (newWorldPoint.y*_world.yScale)*-1);
//why does the duration have to be exactly 0.3 to work
SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
[_world runAction:moveToCenter];
}
} else if ([sender scale] < _tempScale) {
if (_world.xScale > 0.5 && _world.xScale > 0.3){
//_world.xScale -= 0.05;
//_world.yScale -= 0.05;
//[_world setScale:[sender scale]];
[_world setScale:_world.xScale -= 0.05];
CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];
//crazy method why does this work
CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale - _initialScreenSize.width)*-1), (newWorldPoint.y*_world.yScale - _initialScreenSize.height)*-1);
SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
[_world runAction:moveToCenter];
}
}
}
if (sender.state == UIGestureRecognizerStateEnded) {
[_world removeAllActions];
}
}
I've tried many iterations of this, but this exact code is what is getting me the closest to pinching on a point in the world. There are some problems though. As you get further out from the center, it doesn't work as well, as it pretty much still tries to zoom in on the very center of the world. After converting the center point to the world node, I still need to manipulate it again to get it centered properly (the formula I describe as crazy). And it has to be different for zooming in and zooming out to work. The duration of the move action has to be set to 0.3 or it pretty much won't work at all. Higher or lower and it doesn't zoom in on the center point. If I try to increment the zoom by more than a small amount, it moves crazy fast. If I don't end the actions when the pinch ends, the screen jerks. I don't understand why this works at all (it smoothly zooms in to the center point before the delay ends and the screen jerks) and I'm not sure what I'm doing wrong. Any help is much appreciated!
Take a look at my answer to a very similar question.
https://stackoverflow.com/a/21947549/3148272
The code I posted "anchors" the zoom at the location of the pinch gesture instead of the center of the screen, but that is easy to change as I tried it both ways.
As requested in the comments below, I am also adding my panning code to this answer.
Panning Code...
// instance variables of MyScene.
SKNode *_mySkNode;
UIPanGestureRecognizer *_panGestureRecognizer;
- (void)didMoveToView:(SKView *)view
{
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:_panGestureRecognizer];
}
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(-translation.x, translation.y);
_mySkNode.position = CGPointSubtract(_mySkNode.position, translation);
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// No code needed for panning.
}
}
The following are the two helper functions that were used above. They are from the Ray Wenderlich book on Sprite Kit.
SKT_INLINE CGPoint CGPointAdd(CGPoint point1, CGPoint point2) {
return CGPointMake(point1.x + point2.x, point1.y + point2.y);
}
SKT_INLINE CGPoint CGPointSubtract(CGPoint point1, CGPoint point2) {
return CGPointMake(point1.x - point2.x, point1.y - point2.y);
}

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