Resize SKSpriteNodes with their SKPhysicsBody in SpriteKit - ios

I was wondering what the appropriate way was to resize an SKSpriteNode WITH its SKPhysicalBody in SpriteKit. At first I thought I could use SKActions to do resizeToX/Y and that would work. I tried doing this like:
SKAction *action = [SKAction resizeToY:whateverFloat completion:^{
SKPhysicalBody *newBody = [SKPhysicalBody bodyWithRectangleOfSize:spriteNode.size];
spriteNode.physicalBody = newBody;
}];
NOTE : I typed this from memory, its not the exact code, so please ignore syntax errors. I also tried moving around the variables just in case to see what happens. For example, I tried instantiating newBody outside the completion block. I tried assigning the newBody outside the completion block. I tried to update the categoryBitMask and all the same contactBitMasks again. Nothing works.
The closest I got it to working was when I didn't add a new physicalBody, the SKSpriteNode would resize, but its physicalBody would stay the same. If I add a new physicalBody, the whole SKSpriteNode disappears. I don't understand.
After searching online for a solution using SKAction, I found many different threads saying "SKActions don't work well with physics. Avoid mixing them." If I have to avoid mixing them, then how do I grow and shrink an SKSpriteNode the correct way and update its physical body while getting rid of its old one? The physics and collision logic from SpriteKits physics stuff is very important in my game.

Related

How to stop object after fixed distance/time when using applyImpulse (Swift, iOS)?

In Swift, is it possible to get an object to stop moving after a fixed distance/time when using applyImpulse on an object? One option is to use SKAction.runSequence and do something like this:
applyImpulse
waitForDuration
object.physicsBody?.velocity = CGVectorMake(0, 0)
This seems clunky, however.
The other option is to move the object manually through the position property, but this causes us to lose the built-in physics effects, which we hope to retain.

IOS Objective-C moving multiple objects with just the UIKit

The problem I am facing is that I have absolutely no idea how to create multiple balls that move with their own speed, have their own location. That kind of thing. When I click the screen, a ball should start moving up. When I click again, another ball should start moving up. You get the idea. I've looked at several source codes and tutorials but I really don't understand how this is achieved?
You should probably be doing this with SpriteKit (see video)
But if you want to keep it really simple, your approach could be something like:
Create a Ball class (object) (this could, if you want, contain the image of the ball)
Initiate a new Ball object on every touch: Ball *ball = [Ball alloc] init]];
Start animating the newly created ball (using basic animation for instance)

Checking/removing SKScene sprite children easily/efficiently?

Working on an iOS game using SpriteKit. My background is made up of map tiles (essentially an infinite map, procedurally generated).
Our system is designed to manage "chunks" of the map, and we only load chunks near the player. Since SpriteKit requires we add SKSpriteNodes, we no longer have clean control over "unloading" sprites for chunks/tiles that are no longer near the player.
I realize SpriteKit won't actually render things off-screen, but it's going to kill performance if we can't remove sprites no longer needed, or check if a chunk/tile is already added.
Since SKNodes doesn't respond to isEqual:, I only see two ways to do this:
Give each sprite a name with their chunk/tile coordinate, and check this name each update
Maintain a separate array of loaded tiles and check that instead
Is there any easier way of checking/removing if a sprite has been added already? Maybe a partial string match on sprite name?
I'm not sure that using SpriteKit is the best solution (Xcode simulator seems to drag at 30fps, have yet to test on a real device). We originally built this game in Java and we're rendering our own textures - hence only what was loaded and could be fed into opengl manually.
-(void) renderToScene:(SKScene *)scene {
for( Chunk *chunk in loadedChunks ){
for( Tile *tile in [chunk getTiles] ){
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tileTexture];
sprite.name = #"Tile";
sprite.position = CGPointMake(realX,realY);
[scene addChild:sprite];
}
}
}
What will in fact kill your framerate is frequently creating and removing nodes - you should have a pool of sprites that you re-use rather than recreate.
Just update the sprite's texture, position and other attributes when reusing one of those you no longer need. A common use case is to have enough tiles to span the entire screen plus one row and one column, so that when an entire row or column has moved outside the screen you can reposition it at the other side with new textures according to the map data.
If i'm understanding what you're asking correctly. You want to properly remove a node/sprite from the scene if its no longer within view.
You should just be able to call the [self removeFromParent] method to remove in whenever its outside the bounds of the screen. Remember you can call this method on any object as long as its a child.
For instance if i had character,
SKSpriteNode *character;
SKSpriteNode *sword;
//all the rest of the properties are up to you.
if my character picked up a sword in the game and after a certain time period the sword is no longer accessible. I would do:
[character addChild:sword];
which would add it to the character.
[sword removeFromParent];
when i no longer need the sword to be apart of its parent.

Change position of a SKSpriteNode that has a physicsBody

I can't change the position of a SKSpriteNode by changing
self.position = newPosition;
It doesn't work anymore if it has a physicsBody.
A workaround I got is:
self.myStar.physicsBody = nil;
[self.myStar changePosition];
self.myStar.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.myStar.size.width/2];
Or
[self runAction:[SKAction moveTo:newPosiion duration:0.0]];
But #2 isn't smooth. It needs to pop up on another position without a moving action.
I had the same problem. Apparently you cannot explicitly set the position of a Sprite node once it has a PhysicsBody. I solved it by temporarily removing the sprite node's PhysicsBody.
CGFloat yPosition = 400.f;
SKPhysicsBody* goldfishPhysicsBody = _goldfish.physicsBody;
_goldfish.physicsBody = nil;
_goldfish.position = CGPointMake(_goldfish.position.x, yPosition);
_goldfish.physicsBody = goldfishPhysicsBody;
Yes, you can!
I'm not sure what exactly you're doing and where exactly you run this code and what the observed effect is that you meant by "can't change position" but changing a node's position always works, whether the node has a physicsBody or not, and whether the physicsBody is dynamic or static. I verified this in a simple test project with dynamic set to both YES and NO, with both circle and edge loop body types, using both position property and move actions.
You can change the node's position either by setting the position property directly or by using a move action - both variants work. If you use a 0 duration for the move action, it effectively becomes the same as setting the position property.
So whatever problem you're observing, it's not because you can't generally change a node's position once it has a physicsBody. That's absolutely not the case.
I'm guessing you may have run into one of the following problems:
node is already running a different move action, overriding your position changes
you were looking at the wrong node (use logs to verify actual position of the node in question)
you change position elsewhere, for example setting the node's position every frame thus invalidating any other position change
if it's not one of the above, then possibly something else I couldn't think of ...
I had a problem like this where I tried to update position inside didBeginContact:, but it was not having any effect, I think because the physics simulation immediately clobbers the value.
My solution was to cache the new position in a property on my scene and then update the position of the node next time update: was called on my scene.
[SKAction moveTo:newPosition duration:0.0] worked for me, too, and there was no popping. I haven't decided yet which is more elegant.
I think the problem you are having is that in order for you to control a phsyics body, you need to set it to dynamic.
self.myStar.physicsBody.dynamic = NO;
If the dynamic property is set to YES, then the physics engine is in control of it's movement.
As noted in the comments, setting dynamic to NO shouldn't restrict movement via SKAction or position property. However, if it is set to YES, it's possible that something in the physics engine is affecting your attempt to move the node.
If you want the movement to not pop, then you need to set a duration higher than zero to your SKAction or it will pop as you have described. Setting duration to 0.0 is the same as just changing the position.
For example :
[self runAction:[SKAction moveTo:newPosition duration:1.0]];
will move the node to the new position over 1 second.
I ran into a similar problem. When using SKAction, even with duration set to 0.0 I got strange behaviours especially when two SKActions had been triggered at the same time.
I tried setting position directly but as mentioned by others this doesn't work when using the SKPhysicsContactDelegate.
However for me it worked to remove the node from its parent, I then set the new position, and other things I want to change, and then I add the node again to its former parent.
It's not ideal but in some cases it might help.
As an example with the SKPhysicsContactDelegate method didBegin:
func didBegin(_ contact: SKPhysicsContact) {
guard let node = contact.bodyB.node else { return }
node.removeFromParent()
node.position = CGPoint(x: 10, y: 10)
addChild(node)
}
Seems similar to SKSPriteNode position changes to 0,0 for no reason
As stated in answer and comments of this question, it seems you must either set the position before setting the physicsBody and/or set the physicsBody after adding the node to your scene.
I've had this problem in several scenarios and I haven't found out what exactly causes it to fail sometimes.
Yes you can, but it has its price.
I think you have to make a decision: Either let the position being calculated by the PhysicsEngine OR set the position on your behalf.
Look, for an object in a physics world, there is no magical movement from 'outside', there is only forces, collisions, drifts, ... and that will lead to any position. You can not manipulate the position without putting forces on some related nodes.
And in the moment you try BOTH, having a physicsBody (e.g. on your player), but also try to move them around by setting position manually or running actions for moving, YOU LEAVE the world of physics. Your player will be MOVED through walls and so on, against all physics rules.
So, to want an object being manipulated by the physics engine on the one hand and also to want "positioning" by code is kind a contradiction.
There are - of course ways - to combine both ways (e.g. the mentioned 'workaround' by temporarily removing the physicsBody), but this has also its price and has to be sequentially. You can not have both ways at the very same time.

precision of subtracting from a float

i just started using SpriteKit and i'm discovering the API. i have an object to move on screen when a button is pressed and held. so what i did is set an NSTimer while the user is still holding and keep calling the same method to move the object every 0.1 seconds. the object does move but while moving at some point it like doesn't show a smooth transition horizontally. so i used NSLog to look at the objects position and i released that it's subtracting 10 points rather than 0.5. here is the code:
SKAction *moveLeft = [SKAction moveToX:((CGFloat)[[self childNodeWithName:#"Hero"] frame].origin.x)-0.5 duration:0.1];
[[self childNodeWithName:#"Hero"] runAction:moveLeft];
how can i fix that? besides, i realised in the console that the coordinate is like 150.000000 not like 150 or 150.0. could that be the problem?
Another question for the ones who worked with SpriteKit before!!
in a gameplay scene and you want to add a button you add it in the scene class or in the view controller?
i found a solution, i'm using this right now:
SKAction *moveLeft = [SKAction moveByX:1 y:0 duration:0];
it gives more precise results.
It sounds like you are compounding SKAction tweens, setting a new one before the old finishes. It's also possible you are creating timers and not killing them ? Can't be sure without seeing the implementation.
One way to solve the problem is to set a SKAction in the desired direction when the touch begins, and then removing the action when the touch ends. So you will only ever have one SKAction running at a time.
Without seeing your code that creates the NSTimer and how it handles them it's hard to determine exactly what is going on, so I'm basing my answer on what information you have given us. This is one possible scenario.

Resources