Transform a physics body using Sprite Kit - ios

I am making a 2D game where a character stands stationary at the left hand side of the screen and objects fly towards him from the right. He needs to be able to slap these flying enemies down. I have a sprite animation that contains the "slap" animation frames. During the slap, his body moves slightly and his arm rotates from resting on the ground, to fully extended above his head and then slaps down to the ground. Here is what it looks like:
SumoSmash Animation GIF
For these purposes I have a class called SumoWarrior which is a SKSpriteNode subclass. The SumoWarrior sprite node has a child sprite node called warriorArm. My idea was to have the main sumo warrior sprite node display the animation, and to use this warriorArm sprite node only for the purposes of a physics body in the shape of the warriors arm. I need to somehow rotate this arm body to follow the sprite animation, in order to detect collisions with the flying objects.
Here is how the arm is created:
sumoWarrior.warriorArm = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"warriorArm"]];
sumoWarrior.warriorArm.position = CGPointMake(15, 25);
sumoWarrior.warriorArm.anchorPoint = CGPointMake(0.16, 0.7);
sumoWarrior.warriorArm.texture = nil;
sumoWarrior.warriorArm.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:[sumoWarrior createArmBody]];
sumoWarrior.warriorArm.physicsBody.mass = 9999;
sumoWarrior.warriorArm.physicsBody.categoryBitMask = CollisionPlayer;
sumoWarrior.warriorArm.physicsBody.collisionBitMask = CollisionEnemy;
sumoWarrior.warriorArm.physicsBody.contactTestBitMask = CollisionEnemy | CollisionAlly;
sumoWarrior.warriorArm.physicsBody.allowsRotation = YES;
sumoWarrior.warriorArm.physicsBody.dynamic = YES;
Is it possible to rotate and extend this arm somehow, in order for it to follow the animation precisely? Is it also possible to alter the main physics body of the warrior (which is just a polygon around the body, without the arm) so that IT also follow the animation? Or am I completely missing the way that this should be done?

I used Texture Packer for the texture and animation. I created an Texture Atlas called sumoAnimations and Texture Packer created a .h file for me which I then imported into the project.
You can get a free copy if you do not already have it.
Before I launch into the code, you might want to reconsider using the animation you have. Going by your previous comments the only relevant frames in the animation are frame 15, 16 and 17. I am not even sure about frame 17 because the sumo already has his hand down. That only give you 3 frames which by the animation you provided is equal to 0.1 seconds as each frame has a time of 0.05 seconds.
Take a look at the 3 pics I included to see what I mean. You might want to consider getting a new animation or allowing for greater time in between frames. I used 0.25 seconds per frame so you can see it more clearly. You can change it to anything you like.
As for the player being missing and being hit, you can create a clearColor sprite rect around the player (behind the arm of course) to detect contact of a missed object.
#import "MyScene.h"
#import "sumoAnimation.h"
#interface MyScene()<SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKSpriteNode *sumo;
SKSpriteNode *arm;
SKAction *block0;
SKAction *block1;
SKAction *block2;
SKAction *block3;
SKAction *block4;
SKAction *slapHappy;
SKAction *wait0;
SKAction *wait1;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
sumo = [SKSpriteNode spriteNodeWithTexture:SUMOANIMATION_TEX_SUMO_001];
sumo.anchorPoint = CGPointMake(0, 0);
sumo.position = CGPointMake(0, 0);
[self addChild:sumo];
arm = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(34, 14)];
arm.anchorPoint = CGPointMake(0, 0);
arm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(34,14) center:CGPointMake(17, 7)];
arm.physicsBody.dynamic = NO;
slapHappy = [SKAction animateWithTextures:SUMOANIMATION_ANIM_SUMO timePerFrame:0.25];
// start the animation
block0 = [SKAction runBlock:^{
[sumo runAction:slapHappy];
}];
// time until frame 15 is reached
wait0 = [SKAction waitForDuration:3.50];
// add arm at frame 15 positon
block1 = [SKAction runBlock:^{
arm.position = CGPointMake(205, 125);
arm.zRotation = 1.3;
[self addChild:arm];
}];
// wait until next frame
wait1 = [SKAction waitForDuration:0.25]; // time in between frames
// move arm and rotate to frame 16 position
block2 = [SKAction runBlock:^{
arm.position = CGPointMake(224, 105);
arm.zRotation = 0.4;
}];
// move arm and rotate to frame 17 position
block3 = [SKAction runBlock:^{
arm.position = CGPointMake(215, 68);
arm.zRotation = -0.65;
}];
// remove arm from view
block4 = [SKAction runBlock:^{
[arm removeFromParent];
}];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[sumo runAction:[SKAction sequence:#[block0, wait0, block1, wait1, block2, wait1, block3, wait1, block4]]];
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
#end
Updated based on additional comments
The pic below outlines my suggestion. Add a physics body rect for the frames the sumo is swatting. This will allow you to not have to deal with adding a body for every frame in the precise position. It will also make the swatting more effective.
Your object can still fall to the ground and have the crushed animation play. Remember that your sumo animation moves very fast and the player will not see precise locations for each frame.
Your idea of having the arm "push" the object would take a much more precise animation. Something like the arm's position changing by a single increment. Then you would have to precisely position a body on the hand. I am not saying its impossible but its certainly A LOT of work and difficult to do.

Related

How to get correct zRotation during [SKAction rotateBy:duration:]

I'm running a rotateBy action that's repeated forever for a node, and occasionally I want to get the node's current rotation. But zRotation always return 0. How can I get the correct zRotation for this node?
Edit: so finally what I'm doing is creating a custom rotation action instead of using Sprite Kit's default action. A lot more complicated and hard to extend but it does what I want and can be encapsulated in each node's own class.
I believe the reason why you are getting 0 is because you are using animation. The properties of nodes undergoing animation are not updated at each step in the simulation. You will need to manually rotate the node in the update method or create a physics body and set the angular velocity. Using real-time motion will allow you to constantly monitor the properties of you nodes at each step in the simulation.
You can see an example of simulating rotation using real-time motion (albeit more complex) in my answer here.
If you paste the following into a new project, you should see the zRotation property change over time. With an 180 degree/second rotational rate and 60 FPS, the zRotation changes by ~3 degrees per update.
#implementation GameScene {
SKSpriteNode *sprite;
}
-(void)didMoveToView:(SKView *)view {
self.scaleMode = SKSceneScaleModeResizeFill;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = CGPointMake (CGRectGetMidX(view.frame),CGRectGetMidY(view.frame));
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
[self addChild:sprite];
}
-(void)didEvaluateActions {
// Wrap at 360 degrees
CGFloat angle = fmod(sprite.zRotation*180/M_PI, 360.0);
NSLog(#"%g",angle);
}
#end

sprite kit - Sprite randomly drifting upwards after rotation

For the sake of simplicity, the scene has a circle sprite and a square sprite. The square sprite is the child of an SKNode that follows around the circle sprite so that rotation of the square always happens around the circle. However, when the node is rotated, the square randomly drifts upwards. This behavior stops once the rotation makes its way all the way around. Here is the code:
_square = [SKSpriteNode spriteNodeWithImageNamed:#"square"];
_circle = [SKSpriteNode spriteNodeWithImageNamed:#"circle"];
_circle.position = CGPointMake(-200, 300);
[self addChild:_circle];
_rotateNode = [SKNode node];
_rotateNode.position = CGPointMake(300, 300);
[self addChild:_rotateNode];
[_rotateNode addChild:_square];
SKAction *moveBall = [SKAction moveTo:CGPointMake(1200, 300) duration:12];
[_circle runAction:moveBall];
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKAction *rotate = [SKAction rotateByAngle:-M_PI_2 duration:0.2];
[_rotateNode runAction:rotate];
}
-(void)update:(CFTimeInterval)currentTime {
_rotateNode.position = _circle.position;
double xOffset = (300 -_circle.position.x);
double yOffset = (300 - _circle.position.y);
_square.position = CGPointMake(xOffset, yOffset);
}
Anyone know why this is happening?
If I understand the question, you are asking why the square moves in unexpected ways? The circle is moving, but on every frame you are setting the rotateNode's position to be the same as the circle, and they both have the same parent: self, so we can ignore circle for any debugging because it just executes its action every frame to get a new position.
The oddness, most likely, comes in because you are manually repositioning rotateNode, which would then move all of its children, including square. But in every frame you are also repositioning square manually. So rotateNode rotates, changes the position of square because it rotated, and then you reposition square to have a fixed offset. Further complicated depending on whether your goal is to position the square in world space or in parent space. But it's not clear what your goal is there.

Sprites are removed by themselfs as soon as they pass the screen height

I'm currently trying SpriteKit for first time and using some actions. I was under the impression that every node/sprite you create you would need to remove it but for some reason they are removed by themselfs as soon as they pass the screen height. Is this a normal behavior in SpriteKit?
Here is the code.
I'm basically using an action to animate a sprite across the screen (from bottom to top in protrait-mode), the funny thing is that the node count stays at 0 no matter how many sprites I create, it actually counts them but it subtracts them as soon as they pass the screen height.
-(void) shoot
{
bullet = [SKSpriteNode spriteNodeWithImageNamed:#"bullet"];
bullet.position = CGPointMake(airPlane.position.x, airPlane.position.y + 50);
[self addChild:bullet];
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height + 20) duration:0.5];
[bullet runAction:moveBullet];
}
If I change my action to where the sprite doesn't cross the screen height then the node count keeps the count.
Change this line...
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height + 20) duration:0.5];
... to this
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height) duration:0.5];
Again Is this a normal behavior in SpriteKit?
Thanks a lot
The node count displayed on the lower right corner is just an indication of the nodes which are being rendered on the screen. They are not an indication of the total nodes in the scene.
Use the following code
[skView setValue:#(YES) forKey:#"_showsCulledNodesInNodeCount"];
At the same place where you call
skView.showsFPS = YES;
skView.showsNodeCount = YES;
Also, please have a look at this answer.
that can be a memory leak because sprite kit holds textures in the memory for further use for better result remove it from the screen when its passing out of the screen and set it manually to nil
for eg
[self enumerateChildNodesWithName:name usingBlock:^(SKNode *node, BOOL *stop) {
if(node.x>900)
{
//remove all the action
[node removeAllActions];
//remove node from parent
[node removeFromParent];
//set nil//not required set automatically to nil when node is remove from parent
node=nil;
}
}];

Sprite Kit, force stop on physics

I'm creating an ongoing learning project (basically I code while learning to use the framework in hope it will be useful for me and for someone else) for Sprite Kit (you can find it here if you are interested) but I'm facing some performance problems with my code.
The project puts cubes on the screen and make them falling. Here's the class that creates the piece
// The phisics for our falling piece:
// It will be a square, subject to gravity of 5: check common.h (9.8 is way too much for us)
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
self.physicsBody.dynamic = YES;
self.physicsBody.mass = 100;
self.physicsBody.collisionBitMask = piecesCollisionBitmask;
self.physicsBody.allowsRotation = NO;
and here is the scene file with the loop.
//schedule pieces
SKAction *wait = [SKAction waitForDuration:1];
SKAction *pieceIsFalling = [SKAction runBlock:^{
FLPPiece *piece = [[FLPPiece alloc] init];
[self addChild:piece];
}];
SKAction *fallingPieces = [SKAction sequence:#[wait,pieceIsFalling]];
[self runAction:[SKAction repeatActionForever:fallingPieces]];
I suspect that physics is behind my frame rate drop. I would like to stop physics execution on node while keeping it on the screen as soon as it collides with something else.
Is that possible? How can I do that?

How to do a camera movement in spriteKit

I have created a SKSpriteNode for a camera with a physic body size of 0.0 , to avoid unwanted collisions and a world node:
-(void)createSceneContents {
SKNode *world = [SKNode node];
world.name = #"world";
self.anchorPoint = CGPointMake(0.1, 0);
SKSpriteNode *camera = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(300, 300)];
camera.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(0, 0)];
camera.physicsBody.affectedByGravity = NO;
camera.physicsBody.usesPreciseCollisionDetection = NO;
camera.physicsBody.categoryBitMask = noColisions;
camera.alpha = 0.5;
camera.zPosition = 1;
camera.name = #"cam";
[self addChild:world];
[world addChild:camera];
I've tried a little tutorial to add a camera in a spriteKit platform game, but i can't even move the view, i don't know hoy to access to the property that move the view. Anybody knows what am i doing wrong?
Here's my code:
-(void)didSimulatePhysics
{
//I've tried with #"cam" and #"hero"
[self centerOnNode: [self childNodeWithName:#"world"]];
}
-(void)centerOnNode:(SKNode *) camera {
CGPoint cameraPositionInScene = [camera.scene convertPoint:camera.position fromNode:camera.parent];
[self.parent setPosition:CGPointMake(
camera.parent.position.x - cameraPositionInScene.x,
camera.parent.position.y - cameraPositionInScene.y
)];
}
In the example from Apple's Documentation, which you are following the camera node isn't an SKSprite, it's an SKNode. I think that will fix your problem.
To answer the question from the title, what you're essentially doing is attaching a world node to the scene. Inside this node, all the sprites are placed. As a child to the world node you add another node for the camera.
This gives you three distinct coordinate systems. Imagine, three sheets of paper, the bottom most one is your world, ie the layer with all the sprites. On top of that is a small piece of paper that represents the camera. Above all of this you have a transparent box that represents your viewing area.
The way it's set up it's impossible to move the top most transparent viewing layer. Instead, what you're doing is moving the point that's sits on top of the world layer and then sliding the world layer to that point.
Now imagine, in the paper scenario, this is a 2D scrolling world where you can only go left and right. Now take the camera point and put it all the way to the right most side of the viewing area. Now, take the world layer and drag it to the left until the camera is in the center of the non-moveable viewing area. That is more or less, what's happening.
In Apple's Adventure sample game they don't move the camera but the "World" SKNode which is the top one.
Excerpt from Apple docs on how they do it:
In Adventure all world-related nodes, including background tiles,
characters, and foliage, are children of a world node, which in turn
is a child of the scene. We change the position of this top-of-tree
world node within the scene to give the effect of moving a camera
across the level. By contrast, the nodes that make up the HUD are
children of a separate node that is a direct child of the scene rather
than of the world node, so that the elements in the HUD don’t move
when we “move the camera.”
Read about it more here
to add the previous answers , you should center on your camera , not the world..
so instead of
[self centerOnNode: [self childNodeWithName:#"world"]];
you should use
[self centerOnNode: [self childNodeWithName:#"cam"]];
and dont forget to change your camera to SKNode instead of SKSprite.
.. and for testing, add a moveTo action on your camera node , move it around back and forth to check if your camera centering works. I recommend putting the call in the touchesbegan
example (put this on your scene where your camera is) :
Put these before the #implementation
#interface yourClassNameHere() // edit this to your own class name
#property SKNode *theWorld;
#property SKNode *theCamera;
#property BOOL cameraRunning;
#end
As you see above, i put the nodes (world and camera) on property of this class, so i dont refer them with node name like you did on your post..
Put this on the Implementation section
// Process Camera centering
-(void) didSimulatePhysics {
[self centerOnNode:self.theCamera];
}
-(void) centerOnNode: (SKNode *) node {
CGPoint pos = [node.scene convertPoint:node.position fromNode:node.parent];
CGPoint p = node.parent.position;
node.parent.position = CGPointMake(p.x - pos.x, p.y-pos.y);
}
// .. Move the camera around when you touch , to see if it works..
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.cameraRunning) {
self.cameraRunning = YES;
SKAction *moveUp = [SKAction moveByX:0 y:500 duration:3];
SKAction *moveDown = [SKAction moveByX:0 y:-500 duration:3];
SKAction *moveLeft = [SKAction moveByX:-500 y:0 duration:3];
SKAction *moveRight = [SKAction moveByX:500 y:0 duration:3];
SKAction *sequence = [SKAction sequence:#[moveUp, moveRight,moveDown,moveLeft]];
[self.theCamera runAction:sequence];
} else {
self.cameraRunning = NO;
[self.theCamera removeAllActions];
self.theCamera.position = CGPointZero;
}
}
regards
PS: do you want anchor point 0,0 or 1,1 ? check your anchor point setting there
If you want to move the view, just move the camera:
// Center the view at 100, 0
camera.position = CGPointMake(100, 0);
Here's a slightly longer example here on how to set up a 2D camera system in SpriteKit (in Swift, not ObjC, but easily translated).

Resources