I add a couple of nodes, like this:
SKShapeNode* node= [SKShapeNode shapeNodeWithRectOfSize:rect.size]; // always 32x32
node.fillColor= [UIColor darkGrayColor];
node.strokeColor= [UIColor lightGrayColor];
node.position= position;
node.physicsBody= [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(rect.size.width - 2.0, rect.size.height - 2.0)];
node.physicsBody.categoryBitMask= catSomeNode;
node.physicsBody.contactTestBitMask= catSomeOtherNode;
node.physicsBody.dynamic= YES;
node.physicsBody.usesPreciseCollisionDetection= YES;
node.physicsBody.affectedByGravity= NO;
node.physicsBody.collisionBitMask= 0;
[self addChild:node];
NSLog(#"Adding node at position %#", NSStringFromCGPoint(node.position));
e.g. for the first shape:
2015-11-14 16:17:25.162 MyApp[13479:632495] Adding node at position {16, 16}
Later, I will try to animate the shape when it is touched. To determine the direction of the animation, I need to find out the position of the node.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
if (!(node.physicsBody.categoryBitMask & catSomeNode))
return;
if ([node isKindOfClass:[SKShapeNode class]])
{
// Here, the position is different
}
}
The changed position according to the debugger is:
(lldb) p position
(CGPoint) $0 = (x = 15.999996185302734, y = 15.999996185302734)
This will mess up my logic, because i cannot easily compare positions.
2 questions:
1) Positioning
Is it correct, that positioning is always based on the node center, unlike e.g. UIView? This means essentially when I want to place a node at 0/0, I will have to set the position to width/2 / height/2, right?
This feels a bit cumbersome, but well, I can work with that.
2) Touch detection
As you can see in my example, the position of the brick was changed. Why is that? After [self addChild:node]; the position will remain at 16/16, but after the touch detection it is changed to 15.999996185302734 / 15.999996185302734.
Whats wrong? I even saved the pointer of the node as class member, to check the position (in case nodeAtPoint returns a different object). But still the position is messed up.
[edit] Did some more tests. I saved the pointer to the SKShapeNode added above in my class, and I put a debug message in the scene's update: method, which prints the position of the node. This is what happens:
2015-11-14 18:39:07.560 MyApp[14414:696141] Node position {16, 16}
2015-11-14 18:39:07.701 MyApp[14414:696141] Node position {15.999999046325684, 15.999999046325684}
2015-11-14 18:39:07.866 MyApp[14414:696141] Node position {15.999998092651367, 15.999998092651367}
2015-11-14 18:39:07.951 MyApp[14414:696141] Node position {15.999997138977051, 15.999997138977051}
2015-11-14 18:39:08.019 MyApp[14414:696141] Node position {15.999996185302734, 15.999996185302734}
There is no code executed between the first update: and the second one. Also I did not tap, so no touch detection was done.
This leads me to believe I have some sort of initialization issue, so the scene is somewhat changed as soon as the app is loaded, or the scene becomes visible. I thought I already have this covered, because the node is added in the - (void)didMoveToView:(SKView *)view method.
Related
Two likely related things here:
1) I can draw a box and add to child from my SKScene impelmentation file with self, self.scene, and myWorld, but not with an SKSprite node's scene property.
SKSpriteNode *bla = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(100, 100)];
[self.scene addChild:bla]; // If I use bla.scene, it doesn't work. self, self.scene, myworld all do work though.
bla.position = CGPointMake(0, 0);
bla.zPosition = 999;
2) I've seen the related questions here and here, but I'm trying to add a joint during gameplay (grabbing a rope). This method gets called after doing some sorting in `didBeginContact:
-(void)monkey:(SKPhysicsBody *)monkeyPhysicsBody didCollideWithRope:(SKPhysicsBody *)ropePhysicsBody atPoint:(CGPoint)contactPoint
{
if (monkeyPhysicsBody.joints.count == 0) {
// Create a new joint between the player and the rope segment
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:playerPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
}
}
I've got showPhyiscs enabled on the scene, so I can see that the joint is ending up in a totally wacky place. Unfortunately, I don't know how to apply the linked solutions since I'm not adding the SKSpriteNodes in this method, they already exist, so I can't just flip the order of position and physicsBody.
I've tried every permutation I could for both of the convertPoint methods. Nothing worked. My best guess is that physicsWorld is using some wacky coordinate system.
Method members of SKPhysicsWorld that relate to position (CGPoint) or frame (CGRect) are to be in scene coordinates. Scene coordinates reference the point {0,0} as the bottom left corner and is consistent throughout SpriteKit.
The scene property of your object named bla will be nil when bla is first created and is set by the scene when added to it.
[bla.scene addChild:bla]; // this won't do anything as scene will be nil when first created
It looks as though convertedRopePosition is being assigned an incorrect value because the second member you're passing into, - (CGPoint)convertPoint:(CGPoint)point fromNode:(SKNode *)node , is the scene when it should be another node in the same node tree as this node. where this node is the caller (in this case the SKScene subclass).
Try replacing the line-
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
with-
CGPoint convertedRopePosition = [self convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node];
I came up with a janky work around for this problem. It turns out that the coordinate system offset for physicsWorld was likely due to an anchor difference. Changing the anchors of every related thing made no difference, and you can't change the anchor of the physicsWorld directly, so I ended up adding half of the scene width and half of the scene height to the anchor position of my joint. That got it to show in the right place and behave normally.
This problem persisted once side scrolling was factored in. I've posted other questions here with this same problem but I'll include th
I added the following convenience method to my GameScene.m. It essentially takes the place of the seemingly useless convertTo built in method.
-(CGPoint)convertSceneToFrameCoordinates:(CGPoint)scenePoint
{
CGFloat xDiff = myWorld.position.x - self.position.x;
CGFloat yDiff = myWorld.position.y - self.position.y;
return CGPointMake(scenePoint.x + self.frame.size.width/2 + xDiff, scenePoint.y + self.frame.size.height/2 + yDiff);
}
I use this method to add joints. It handles all of the coordinate system transformations that need to be dealt with that lead to the issue raised in this question. For example, the way I add joints
CGPoint convertedRopePosition = [self convertSceneToFrameCoordinates:ropePhysicsBody.node.position];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
So I'm trying to implement a method into a game where if a player has their finger touching the screen at lets say 50 on the y axis or lower, an SKAction will run to move the character sprite down, and if their finger is touching above the 50, another action will run moving the sprite up. I don't know how you have it recognize a touch and hold though. Help please.
You will need to capture the touch event of the user in order to pull this off. I suggest checking out the excellent tutorial provided by RayWenderlich at: Animating Textures and Moving Them With Touch Events
If you don't have time for that this is what your code might look like:
First the knowledge that you will need is that the two most generic touch events are the:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
and the:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Basically the touches began method is called when the user places their finger on the screen whereas the touches ended method will be triggered when the user takes their finger off the screen.
For this example I will use touchesEnded, but feel free to change it if you want, its as easy as switching the "Ended" with "Began".
All of the code I am about to show you will take place within the scenes implementation file:
If you just want the sprite to move up when above a certain point and down when below a certain point use the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//used to hold the location to travel
CGPoint moveLocation;
//check if the Y location is less than 50
if(location.y < 50)
{
//the the location to the bottom of the screen
moveLocation = CGPointMake(sprite.position.x, 0);
}
//and then if its more than 50
else
{
//set the locaiton to the top of the screen
moveLocation = CGPointMake(sprite.position.x, self.frame.size.height);
}
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
And there you have it. The only flaw is that it will always take 5 seconds to reach the location regardless of your distance from it, read on if you would like hints on how to correct that, or if you would like to see how to make the sprite travel to any touched location on the screen.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
That code will make it so that for 5 seconds the sprite will move to the location touched on the screen. Just remember to replace the instances of "sprite" with the name of your SKSpriteNode variable. For more complex movement try the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//set the velocity of the subject
float velocity = self.frame.size.width/3.0;
//The above will ensure that it will take the sprite 3 seconds to travel the distance of the screen
//Determine the difference between the point touched and the sprites position
CGPoint moveDifference = CGPointMake(location.x - sprite.position.x, location.y - sprite.position.y);
//Use pythagorean theorem to figure out the actual length to move
float distanceToMove = sqrtf(moveDifference.x * moveDifference.x + moveDifference.y*moveDifference.y);
//Use the distance to travel and the velocity to determine how long the sprite should move for
float moveDuration = distanceToMove / velocity;
//and finally move the sprite
if(sprite actionForKey:#"moving")
{
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:moveDuration];
[sprite runAction:moveAction withKey:#"moving"];
}
That will set a velocity for the sprite, determine the length of the travel, and how long it will take to get to the new location.
If you want to involve textures I highly suggest reading the link I provided.
Upon request I would be glad to offer examples using forces to control movement speed as well.
I hope that helps, let me know if there are any problems I wasn't in a position where I could run the code but I am sure that it is fine.
i followed this wonderful guide about mario-style game:
http://www.raywenderlich.com/62053/sprite-kit-tutorial-make-platform-game-like-super-mario-brothers-part-2
however, i wanted to convert the movement controls to arrow keys, implemented by SKSpriteNodes with names that are detected by:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = YES;
self.rightOriginalTouchLocation = location;
...
}
}
self.player.moveRight is a boolean value (much like moveForward in the guide), that tells the character at update to move.
it is terminated at:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = NO;
}
...
}
however, i encounter the following problem - when i start the touch on the arrow, drag it outside the arrow, and then release the tap, it is not recognized as 'touch ended' for the arrow node (and it doesn't stop moving because of that).
i tried to solve it in many ways (even calculating touch move distance from original location and see if its too far, then cancel movement), but i always manage to reproduce the constant motion problem.
the issue lies with the fact that i can tap two arrows at the same time, so it is not enough to remember the last node tapped.
since i want to allow movement for different directions at the same time, i cant stop all movements in case one button is dismissed. i need to specifically know which button was released so i can stop that direction's movement only.
do you have any ideas for me? should i implement it in another way considering i want the arrow keys, or is there a method to detect which node is released even though it is not at its original location (of the tap)?
Thank you very much!
if anyone is interested about the issue - i had a problem where touching and moving from SKSpriteNode didnt call the touchesEnded for that SKSpriteNode (since the 'release' of the touch was not in the node).
i solved it by keeping the CGPoint of the touch at touchesBegan, and when touchesEnded called, i calculated distances to nearest key using simple distance function:
-(int)calculateDistanceWithPoints:(CGPoint)point1 andPoint:(CGPoint)point2 {
float d = sqrtf(pow((point1.x - point2.x), 2) + pow((point1.y - point2.y), 2));
return (int)d;
}
and then, in touchesEnded, i checked to which key the distance is minimal(i have 3 keys, so which key is most likely to be the key that was 'released') - and did the action required for that key release.
Issue: A SKShapeNode sometimes doesn't draw the entire path. If it's moving each frame some frames it doesn't draw. In my real case scenario I'm using a very complex 10+ sided shape but for this demo I'm just doing a triangle. See screenshots for example (http://imgur.com/a/50mHA).
Code: See this in action by adding this to the basic template, or any scene, and move the mouse (finger) around. Some shapes show this glitching more than others but if you move around enough you'll see it on every shape.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInNode:self];
[self enumerateChildNodesWithName:#"path" usingBlock:^(SKNode *node, BOOL *stop) {
[node removeFromParent];
}];
CGMutablePathRef p = CGPathCreateMutable();
CGPathMoveToPoint(p, NULL, location.x-80, location.y-130);
CGPathAddLineToPoint(p, NULL, location.x+80, location.y);
CGPathAddLineToPoint(p, NULL, location.x-80, location.y+130);
CGPathCloseSubpath(p);
SKShapeNode *shape = [SKShapeNode node];
shape.name = #"path";
shape.path = p;
shape.fillColor = [UIColor redColor];
[self addChild:shape];
}
Any idea what's going on?
Edit: I wanted to add that the overall effect I'm trying to accomplish is dynamic lighting in a scene with physics bodies and a player (light source). I have used the information from http://ncase.me/sight-and-light/ to create a polygon. However as the player runs through the level, 99% of the time it works fine, except for that 1% where the shape glitches and doesn't draw completely. Which is what I'm showing in the example here.
OK, what you're doing here is very inefficient which is possibly why you're getting this effect.
What you should do is create a single shape node and store it in a property...
#property (nonatomic, strong) SKShapeNode *theShapeNode;
and when the scene loads you can create it once...
// in some method that runs once when the scene loads...
// path is relative to the shape node not the the scene it is in.
// by MOVING the shapeNode the path moves too.
CGMutablePathRef p = CGPathCreateMutable();
CGPathMoveToPoint(p, NULL, 0, -260);
CGPathAddLineToPoint(p, NULL, 160, 0);
CGPathAddLineToPoint(p, NULL, 0, 260);
CGPathCloseSubPath(p);
self.theShapeNode = [SKShapeNode node];
self.theShapeNode.path = p;
self.theShapeNode.fillColor = [UIColor redColor];
[self addChild:theShapeNode];
Then in the touches moved method...
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInNode:self];
self.theShapeNode.position = location;
}
This means that each time the touch moves you're just repositioning the node that already exists. You're not having to create the path each time.
I am creating a simple Sprite Kit game however when i am adding the PhysicsBody to one of my sprites it seems to be going in the wrong position. i know that it is in the wrong position as i have have set
skView.showsPhysics = YES;
and it is showing up in the wrong position.
The Square in the bottom corner is the physics body for the first semicircle. I am using a square at the moment just for testing purposes.
My app includes view following and follows my main sprite when it moves. I implemented this by following apples documentation and creating a 'myworld' node and creating all other nodes from that node.
myWorld = [SKNode node];
[self addChild:myWorld];
semicircle = [SKSpriteNode spriteNodeWithImageNamed:#"SEMICRICLE.png"];
semicircle.size = CGSizeMake(semicircle.frame.size.width/10, semicircle.frame.size.height/10);
semicircle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:semicircle.frame.size];
semicircle.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
semicircle.physicsBody.dynamic = YES;
semicircle.physicsBody.collisionBitMask = 0;
semicircle.name = #"semicircle";
[myWorld addChild:semicircle];
To centre on the node I call these methods
- (void)didSimulatePhysics
{
[self centerOnNode: [self childNodeWithName: #"//mainball"]];
}
- (void) centerOnNode: (SKNode *) node
{
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(node.parent.position.x - cameraPositionInScene.x, node.parent.position.y - cameraPositionInScene.y);
}
I don't know if the my world thing makes any difference to the SkPhysics body...
SKPhysicsBody starts at coordinates 0,0 which is at the bottom left hand corner. If you make the area smaller, as you did by width/10 and height/10, you decrease the size but from the bottom left.
I think what you are looking for is bodyWithRectangleOfSize:center: which allows you to manually set the center from which you base your physics body area on.
Update:
Based on what I understand, your smallest semi circle pic size is the same as the screen size. I would suggest you modify the image size to something like the example I have. You can then set the sprite's position as required and set the physics body to the half of the image containing your semi circle.
Your centerOnNode call should be put in the didEvaluateActions function instead of the didSimulatePhysics function. This is because you need to move the world before the physics are drawn so that they stay in sync. Similar question found here: https://stackoverflow.com/a/24804793/5062806