Getting location of section of sprite - ios

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

Related

How to draw a line in SpriteKit efficiently

In my SpriteKit scene, user should be able to draw line with his/her finger. I have a working solution, if the line is long, FPS gets down to 4-6 and the line starts to get polygonal, as the image below:
To draw myline (SKShapeNode*), I collect points of touches movement in an NSMutableArray* noteLinePoints in this way
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
SKNode *node = [self nodeAtPoint:touchPoint];
if(noteWritingActive)
{
[noteLinePoints removeAllObjects];
touchPoint = [[touches anyObject] locationInNode:_background];
[noteLinePoints addObject:[NSValue valueWithCGPoint:touchPoint]];
myline = (SKShapeNode*)node;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myline)
{
CGPoint touchPoint = [[touches anyObject] locationInNode:_background];
[noteLinePoints addObject:[NSValue valueWithCGPoint:touchPoint]];
[self drawCurrentNoteLine];
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if(myline)
{
myline.name = #"note";
myline = nil;
}
NSLog(#"touch ended");
}
and I draw the line in this way
- (CGPathRef)createPathOfCurrentNoteLine
{
CGMutablePathRef ref = CGPathCreateMutable();
for(int i = 0; i < [noteLinePoints count]; ++i)
{
CGPoint p = [noteLinePoints[i] CGPointValue];
if(i == 0)
{
CGPathMoveToPoint(ref, NULL, p.x, p.y);
}
else
{
CGPathAddLineToPoint(ref, NULL, p.x, p.y);
}
}
return ref;
}
- (void)drawCurrentNoteLine
{
if(myline)
{
SKNode* oldLine = [self childNodeWithName:#"line"];
if(oldLine)
[self removeChildrenInArray:[NSArray arrayWithObject:oldLine]];
myline = nil;
myline = [SKShapeNode node];
myline.name = #"line";
[myline setStrokeColor:[SKColor grayColor]];
CGPathRef path = [self createPathOfCurrentNoteLine];
myline.path = path;
CGPathRelease(path);
[_background addChild:myline];
}
}
How can I fix this problem? because all subsequent lines are all polygonal only, I think because the fps is very low and sampling rate of touches automatically get also very low...
Please note that for the test I used an iPad 3 (my app needs to be working from iPad 2 model with iOS7)
Do not constantly create new SKShapeNodes, there is a bug which is causing your slowdown. Instead, only use 1 SKShapeNode (Or create a bunch but reuse them), and append the path with new info (So there is no need to constantly add the myline to the background)
Alternative:
Use 1 community SKSkapeNode for rendering of the path, then convert the SKShapeNode to a texture with view.textureFromNode, then add an SKSpriteNode with this new texture instead of the shape node

Can't seem to figure what's wrong here

I've used this same exact code before, and it worked perfect every time. However, I'm trying to use it to move the paddle sprite along a horizontal path at position 100 for y and it updates with the location of my touch but for some reason it's not moving at all. I can't spot what's wrong. Can someone take a look and let me know?
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newPosition = CGPointMake(location.x, 100);
//stop the paddle from going too far
if (newPosition.x < self.paddle.size.width/ 2) {
newPosition.x = self.paddle.size.width/ 2;
}
if (newPosition.x > self.size.width - (self.paddle.size.width/ 2)) {
newPosition.x = self.size.width - (self.paddle.size.width/ 2);
}
self.paddle.position = newPosition;
}
}
-(void) addPlayer:(CGSize)size {
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
//resize sprite
paddle.size = CGSizeMake(125, 31.25);
//position sprite
paddle.position = CGPointMake(size.width/2, 100);
//add physics body to paddle
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
//change to static so wont be moved by physics
paddle.physicsBody.dynamic = NO;
//add sprite to scene
[self addChild:paddle];
}
-(instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]){
self.backgroundColor = [SKColor colorWithRed:(29.0f/255) green:(29.0f/255) blue:(29.0f/255) alpha:1.0];
//change gravity
self.physicsWorld.gravity = CGVectorMake(0, 0);
//add physics body to scene
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self addPlayer:size];
[self addBricks:size];
[self addBall:size];
}
return self;
}

SpriteKit multidirectional scrolling

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;

Sprite Kit Joints

I am trying to make an animated character that can reach out for things on the screen, without moving his torso.
If he can not reach than his hand should go the maximal amount in this direction and then be counteracted by the physical limitation of his static torso.
So in my code the green rectangle (hand) moves where I click and the torso follows.
I would like to be able to make the red rect (body) stationary and make the green rectangle "point" at the touch point position.
I have tried to apply a fixed joint to between the scene and the red body rectangle, but this did not seem to work.
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.affectedByGravity = NO;
body.physicsBody.dynamic = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width / 2 + self.hand.size.width / 2, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = NO;
self.scene.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x + body.size.width / 2, body.position.y)]];
[self.hand runAction:[SKAction moveByX:100 y:10 duration:0.1]];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
[self.hand runAction:[SKAction moveTo:location duration:0.1]];
}
First of all, you don't really need to use physics here, if you only want the arm to point at the touched location.
Just change the arm node's anchorPoint to the shoulder (where you would put the pin on the joint) and rotate it around that point (the point is held in a helper shoulderNode, so that it's easy to convert to its coordinates later). You can calculate the rotation angle using atan2f. Here's the code:
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width*0.5, body.position.y);
self.hand.anchorPoint = CGPointMake(0, 0.5);
[self addChild:self.hand];
SKNode *shoulderNode = [SKNode node];
shoulderNode.position = self.hand.position;
shoulderNode.name = #"shoulderNode";
[self addChild:shoulderNode];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
CGPoint locationConv = [self convertPoint:location toNode:[self childNodeWithName:#"shoulderNode"]];
self.hand.zRotation = (atan2f(locationConv.y, locationConv.x));
}
On the other hand, if you ever need to use physics bodies here, it's probably a bad idea to use SKActions to move the arm, and you also might want to set body.physicsBody.dynamic = NO to make the torso static. Then, make sure you add the pin joint correctly, and set its frictionTorque property to a high value to get less dangling. One of the ways to make the hand point at the right location is its velocity vector set in the update method - just use the converted location coordinates (here they are relative to the body node's centre). Full example code:
- (void) createSceneContent {
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.dynamic = NO;
body.physicsBody.affectedByGravity = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 150)];
// experiment with the anchorPoint for different results
self.hand.anchorPoint = CGPointMake(0.5, 0);
self.hand.position = CGPointMake(body.position.x, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = YES;
self.hand.physicsBody.affectedByGravity = NO;
SKPhysicsJointPin *pinHand = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x, body.position.y-5)];
[self.physicsWorld addJoint:pinHand];
// experiment with the friction torque value to achieve desired results
pinHand.frictionTorque = 1.0;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
location = [touch locationInNode:self];
}
-(void)update:(CFTimeInterval)currentTime {
CGPoint locationConv = [self convertPoint:location toNode:body];
// experiment with the multiplier (here: 10) for faster/slower movement
self.hand.physicsBody.velocity = CGVectorMake(locationConv.x*10, locationConv.y*10);
}
Hope that helps!

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