SpriteKit multidirectional scrolling - ios

I am developing a space shooter with SpriteKit for testing purposes.
The game field is 700 x 700 pixels/points large. obviously is does not fit into a iPhone screen. That means I need some sort of scrolling that affects players + enemies + bullet + asteroids. I searched google, but most subjects on scrolling refer to Cocos2D and nearly always to Cocos2D specific features, not provided by sprite kit. So the thing is this is my first game so I am not sure what the right way is to implement multidirectional scrolling. I hope you guys can give me a solution and/or hints/tutorials or anything else that helps me :D

Create your "spaceship", create a camera node and in the updates method make your camera always center on the spaceship.
Something similar to this question: How to make camera follow SKNode in Sprite Kit?
-(void)centerOnNode:(SKNode*)node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
cameraPositionInScene.x = 0;
node.parent.position = CGPointMake(node.parent.position.x - cameraPositionInScene.x, node.parent.position.y - cameraPositionInScene.y);
}
Then you'll have to figure out your own game mechanics on how the ship moves but if you simply imply the ship follows your finger you can use SKActions to animate to finger location. Example: https://stackoverflow.com/a/19172574/525576
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint diff = CGPointMake(location.x - _myPlayer.position.x, location.y - _myPlayer.position.y);
CGFloat angleRadians = atan2f(diff.y, diff.x);
[_myPlayer runAction:[SKAction sequence:#[
[SKAction rotateToAngle:angleRadians duration:1.0],
[SKAction moveByX:diff.x y:diff.y duration:3.0]
]]];
}
}
}

Solved it, thanks to ray wenderlich
CGSize winSize = self.size;
int x = MAX(player.position.x, winSize.width / 2 );
int y = MAX(player.position.y, winSize.height / 2 );
x = MIN(x, worldSize.width - winSize.width / 2 );
y = MIN(y, worldSize.height - winSize.height/ 2 );
CGPoint actualPosition = CGPointMake(x, y);
CGPoint centerOfView = CGPointMake(winSize.width/2, winSize.height/2);
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
world.position = viewPoint;

Related

Sprite Kit - Determine vector of swipe gesture to flick sprite

I have a game where circular objects shoot up from the bottom of the screen and I would like to be able to swipe them to flick them in the direction of my swipe. My issue is, I don't know how to calculate the vector/direction of the swipe in order to get the circular object to get flicked in the proper direction with the proper velocity.
The static vector "(5,5)" I am using needs to be calculated by the swipe speed and direction of the swipe. Also, I need to make sure that once I make first contact with the object, it no longer happens, as to refrain from double hitting the object. Here's what I am doing currently:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
[node.physicsBody applyImpulse:CGVectorMake(5, 5) atPoint:location];
}
}
Here's an example of how to detect a swipe gesture:
First, define instance variables to store the starting location and time .
CGPoint start;
NSTimeInterval startTime;
In touchesBegan, save the location/time of a touch event.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Avoid multi-touch gestures (optional) */
if ([touches count] > 1) {
return;
}
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
// Save start location and time
start = location;
startTime = touch.timestamp;
}
Define parameters of the swipe gesture. Adjust these accordingly.
#define kMinDistance 25
#define kMinDuration 0.1
#define kMinSpeed 100
#define kMaxSpeed 500
In touchesEnded, determine if the user's gesture was a swipe by comparing the differences between starting and ending locations and time stamps.
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
// Determine distance from the starting point
CGFloat dx = location.x - start.x;
CGFloat dy = location.y - start.y;
CGFloat magnitude = sqrt(dx*dx+dy*dy);
if (magnitude >= kMinDistance) {
// Determine time difference from start of the gesture
CGFloat dt = touch.timestamp - startTime;
if (dt > kMinDuration) {
// Determine gesture speed in points/sec
CGFloat speed = magnitude / dt;
if (speed >= kMinSpeed && speed <= kMaxSpeed) {
// Calculate normalized direction of the swipe
dx = dx / magnitude;
dy = dy / magnitude;
NSLog(#"Swipe detected with speed = %g and direction (%g, %g)",speed, dx, dy);
}
}
}
}
There is another way to do it, you can add a pan gesture and then get the velocity from it:
First add pan gesture in your view:
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[self.view addGestureRecognizer:gestureRecognizer];
Then handle the gesture:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint location = [recognizer locationInView:recognizer.view];
if ([_object containsPoint:location]){
self.movingObject = YES;
<.. object start moving ..>
}
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
if (!self.movingObject)
return;
CGPoint translation = [recognizer translationInView:recognizer.view];
object.position = CGPointMake(object.position.x + translation.x, object.position.y + translation.y);
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (!self.movingObject)
return;
self.movingObject = NO;
float force = 1.0f;
CGPoint gestureVelocity = [recognizer velocityInView:recognizer.view];
CGVector impulse = CGVectorMake(gestureVelocity.x * force, gestureVelocity.y * force);
<.. Move object with that impulse using an animation..>
}
}
In touchesBegan save the touch location as a CGPoint you can access throughout your app.
In touchesEnded calculate the distance and direction of your initial touch (touchesBegan) and ending touch (touchesEnded). Then apply the appropriate Impulse.
To refrain from double hitting, add a bool canHit that you set to NO when the impulse is applied and set back to YES when you are ready to hit again. Before applying the impulse, make sure canHit is set to YES.

Using gestures with SpriteKit to Drag, rotate and scale multiple sprite separately at the same time

I have created an iOS app in which I need to be able to move, rotate and scale a sprite ( I am using Apple's Sprite Kit) at the same time. For the most part I have this working. I currently can touch with 1 finger and move the sprite, and if I use two fingers I can scale and rotate the sprite. To do this I am using UIPanGestureRecognizer, UIPinchGestureRecognizer and UIRotateGestureRecognizer. That works fine. What I would like is, while I am dragging, rotating and scaling a one sprite with my right hand, I can take my left hand and drag rotate and scale a different sprite independently of the other sprite.
Currently I am using iOS gestures to move, rotate and scale the sprites. I used code very close to what I found on Ray Wenderlich's website in his Drag and Drop Sprites tutorial for Sprite Kit. http://www.raywenderlich.com/44270/sprite-kit-tutorial-how-to-drag-and-drop-sprites. The part I am using is near the bottom when he start to use UIPanGestureRecognizers instead of just the touch method.
Like I said the Gestures work fine on one sprite at a time. How do I make it work on more than one sprite?
For instance for the UIPanGesturRecognizer I add the code below:
- (void)didMoveToView:(SKView *)view {
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:gestureRecognizer];
}
Then I have a method for that called gestureRecognizer below:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation]; // This just returns the node that has been touched
} 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) {
[_selectedNode removeAllActions];
}
}
Finally there is the method that moves the sprite:
- (void)panForTranslation:(CGPoint)translation {
if([[_selectedNode name] isEqualToString:kAnimalNodeName]) {
CGPoint position = [_selectedNode position];
// Set variable for the point to move selected node
CGPoint movePoint = CGPointMake(position.x + translation.x, position.y + translation.y);
[_selectedNode setPosition:newPos];
}
}
Now the example code is showing only the methods for the UIPanGestureRecognizer but I also have similar methods for the rotate and pinch gestures. All of this code is in my scene class.
Thank you for the help.
Well, the tutorial you posted pretty much shows how to do it...
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
}
- (void)selectNodeForTouch:(CGPoint)touchLocation {
//The below statement assigns touchedNode from a sprite that contains touchLocation
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
//2
if(![_selectedNode isEqual:touchedNode]) {
[_selectedNode removeAllActions];
[_selectedNode runAction:[SKAction rotateToAngle:0.0f duration:0.1]];
_selectedNode = touchedNode;
//the below if statement determines what SKNode is to be given a SKAction
if([[touchedNode name] isEqualToString:kAnimalNodeName]) {
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]]];
[_selectedNode runAction:[SKAction repeatActionForever:sequence]];
}
}
}
So if you want to apply the action to multiple nodes, simply give them the same name. I suggest naming your nodes, put them in an array, and then iterate through them checking if they have the same name.
If you have any further questions please comment.
UPDATE: 1
- (void)panForTranslation:(CGPoint)translation {
//Once again you would do the same thing. Just give the nodes the same name.
if([[_selectedNode name] isEqualToString:kAnimalNodeName]) {
CGPoint position = [_selectedNode position];
// Set variable for the point to move selected nodes
CGPoint movePoint = CGPointMake(position.x + translation.x, position.y + translation.y);
[_selectedNode setPosition:newPos];
}
I'm also using that tutorial to do something similar. The sample code relies on an instance variable, selectedNode, that represents the one node that is selected.
To make this work for multiple nodes, I would recommend using an NSMutableArray of selectedNodes, or subclassing SKSpriteNode to store whether or not it is currently "selected". Good luck!

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

touchesMoved, how do I make my view catch up - and stay under the finger?

I have written some code that restricts the movement of a box (UIView) to a grid.
When moving the box, the movement is locked to the grid and the box starts getting behind your finger if you drag diagonally or really fast.
So what is the best way to write a method that makes the box catch up and get back under the finger - it must move on the same path as your finger - and it must also not move through other boxes, so it needs collision detection - so I just can't do an Animate to new center point.
Any suggestions?
This is current code in use:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
lastLocation = location;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
CGPoint offset = CGPointMake(self.center.x + location.x - lastLocation.x, self.center.y + location.y - lastLocation.y);
CGPoint closestCenter = [self closestCenter:offset];
CGRect rect = CGRectMake(offset.x - (self.size.width / 2), offset.y - (self.size.height / 2), self.size.width, self.size.height);
if (fabsf(closestCenter.x - offset.x) < fabsf(closestCenter.y - offset.y)) {
offset.x = closestCenter.x;
}
else {
offset.y = closestCenter.y;
}
// Do collision detection - removed for clarity
lastLocation = location;
self.center = offset;
}
Don't use a relative offset movement. Instead, use the actual touch location as the desired position and then bound (modify) it based on the grid restrictions. In this way you won't get any lag behind the touch.
From your collision detection I guess there is a path that must be followed and a naive implementation will 'jump' the view to the touch across boundaries. A simple solution to this is to limit jumping to a maximum of half a grid square (so the user must bring the touch back to the view if they drop it).

Can't move sprites connected by joints to the right location

I have a marionette connected by SKPhysicsJoints. I want it to behave so when I touch a certain point in the scene, the marionette's head moves to that point. Problem is, when I do it, the head moves TOWARDS that point, but does not reach it. If I keep clicking the same point, it will get a bit closer each time.
I have set affectedByGravity=NO on all the SKSpriteNodes that make up the marionette.
I am moving the sprite just by doing
head.position = (the position of the mouse)
and it works fine as long as the head isn't connected to the rest of the marionette by a joint.
So it's kind of like the rest of the sprites are weighing down the head. I wish I knew more about this, theres just so little info on Sprite Kit and not a lot handles this, from what I can find.
edit: Since I was asked, here is a sample project that demonstrates the problem I am having:
-(void) didMoveToView:(SKView *)view
{
self.physicsWorld.speed = 1;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
_head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
_chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck.png"];
_head.position = CGPointMake(512, 380);
_chest.position = CGPointMake(512, 290);
_head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_head.size];
_head.physicsBody.mass = 1;
_head.physicsBody.dynamic = YES;
_chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_chest.size];
_chest.physicsBody.mass = 1;
_chest.physicsBody.dynamic = YES;
[self addChild:_head];
[self addChild:_chest];
CGPoint chestJointPinAnchor = CGPointMake(_chest.position.x, _chest.position.y+39);
SKPhysicsJointPin *chestJointPin = [SKPhysicsJointPin jointWithBodyA:_head.physicsBody bodyB:_chest.physicsBody anchor:chestJointPinAnchor];
[self.physicsWorld addJoint:chestJointPin];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_head.physicsBody.affectedByGravity = NO;
_chest.physicsBody.affectedByGravity = NO;
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
_head.position = positionInScene;
}
Just set your head to: _head.physicsBody.dynamic = NO, while the chest remains dynamic: _chest.physicsBody.dynamic = YES.
You'll find the head now moves exactly to the touch location, dragging the chest along.

Resources