How to make a specific object defy gravity? (SpriteKit / Objective-C) - ios

I'm working on a piece of code, and I want there to be gravity in the view but I want specific projectiles to defy gravity and fly across the screen. I know to get rid of the gravity on the whole view its just:
self.physicsWorld.gravity = CGVectorMake(0, 0);
But as stated I want gravity on the scene.
So I'm wondering if there is a way to take the gravity off one specific item? (i.e. the SKSpriteNode _debris item in my case)

By setting the physicsBody to not be affected by gravity.
E.g.
myNoGravityObject.physicsBody.affectedByGravity = NO;
See the documentation of SKPhysicsBody.

If you don't want a node to interact with physics calculations at all, but still want it to have a physicsBody (i.e. to make it begin to interact with things later on, or to check for collisions with other nodes), you can set
node.physicsBody.dynamic = NO;
This will cause the node to ignore gravity, as well as collisions, impulses, and the like. If you are setting up the contact delegate, note that at least one node in any given contact must be dynamic for the contact delegate to be notified of the event.

Related

GKGoal Avoid Boundary Obstacles

I am seeming to have difficulty keeping my sprite nodes inside a map boundary that I have set up in the following way:
I have an SKNode *enemy that moves around an SKScene by goals and behaviors courtesy of GameplayKit from iOS 9. Currently the node wanders, and avoids obstacles that are defined GKPolygonObstacle objects. I have my bitmask set up so that any obstacle is deemd category wall, which the node is told to collide with (AKA disallow passing through).
In my didBeginContact:(SKPhysicsContact *)contact I am handling these collisions. All is working exactly as planned and there are no issues when manually moving this enemy.
However, the problem begins when I have the enemy wander around the scene through an SKGoal *wanderGoal set up as
// Low --> 0.5 Lowest --> 0.25
enemy.wanderGoal = [GKGoal goalToWander:low];
[enemy.agent.behavior setWeight:lowest forGoal:enemy.wanderGoal];
// Add obstacles to avoid for each of the inner map nodes
// that act as impassible areas for the enemy to pass through.
NSArray *obstacles = [SKNode obstaclesFromNodePhysicsBodies:innerMapArray];
/** This goal does not change --> enemies will ALWAYS
avoid the obstacles in the level map. This should always
be set to the highest priority of the enemy goals
*/
enemy.avoidGoal = [GKGoal goalToAvoidObstacles:obstacles maxPredictionTime:10];
// Highest --> 250
[enemy.agent.behavior setWeight:highest forGoal:enemy.avoidGoal];
The enemy moves randomly as expected, and seems to avoid the obstacles most of the time...yet on the occasion the enemy does in fact pass through the map barrier. Let me expand on this:
The map barrier is essentially a rectangle, and I have set up 2 nodes that act as a shell that represent the left half and the right half of this shape. These nodes have a thickness of 100 points so they are not simply CGPath refs but rather shapes. They act as these two brackets act: [] (my halves are flush touching at the middle).
My question is such:
How can I prevent the enemy from passing through these obstacles with using either physics with the bitmask categories, or by the goal behavior? (Or both together)
I understand that while a GKGoal is a suggested behavior it does not guarantee this goal to be reached (but it can be ~95% sure it wont happen as Apple's AgentsCatalog shows). But, what I don't understand is why the enemy does not simply "slide" along the edge of the boundary as the goal tells it to (try to) "pass" through such boundary.
This "sliding" behavior does happen when I manually move the enemy around the scene, and as I attempt to move it passed a boundary deemed an obstacle, it slides towards the direction that I am having it move (just as any video game does that has map boundaries).
SO
If someone can help me prevent this from happening / explain how to approach preventing the enemy from ever passing through these (supposedly) "impassible" areas, I would be most appreciative.
FYI: If you are unclear with what I am describing / how I have created any objects, please ask and I will provide more info to help.

SpriteKit - Making a sprite defy gravity (like a balloon)

Does anyone have any idea how I can make my SKSpriteNode defy gravity? I thought of inverting the default gravity but realise I also need things to fall too!
It seems like it should be easy but reading through the documentation I can’t see how I would do it!
Thanks
Update: In iOS 8 / OS X Yosemite (10.10), physics fields provide an elegant solution to these sorts of problems. Here's a quick take on using them to add buoyancy (for a specific set of nodes) to your scene.
Create an SKFieldNode with the linearGravityFieldWithVector constructor, providing a vector that's the opposite of gravity, and add the field node to your scene.
Set the fieldBitMask on your balloons to something unique.
Set the categoryBitMask on the field to something that overlaps with the balloons' fieldBitMask, but that does not overlap with the fieldBitMask of any other bodies.
Now, the balloons will rise or hold steady, but other objects will fall. Tweaking the field's strength will let you tune whether the balloons' buoyancy is perfectly balancing gravity (so that they float in place, but are disturbed when touched), or slightly more or less than gravity (so that they slowly rise or fall).
By default, a field is infinite, covering the whole scene, but you can change that with the field's region property to limit it to a portion of the scene. (This is useful if you want to simulate buoyancy in water — once an object rises past the top of the field at the water's surface, it falls back in.)
Also, if you want variable buoyancy as per #TheisEgeberg's answer, you can control its variation over distance with the falloff property.
In iOS 7 / OS X Mavericks (10.9), or if you want more precise control over which forces apply where and when, you can use the approach from my original answer below.
If you want an object to really float like a balloon — that is, to be buoyant, affected by gravity but also counteracting it — you'll need to apply a force to it on every frame (i.e. in your update: method).
Beware scaling: gravity is a constant acceleration, but if you're applying a force to counteract gravity, a force is proportional to mass. To make a vector that perfectly balances gravity for use in applyForce:, you'll need to:
scale the gravity vector by {-1,-1,-1} to point in the opposite direction
scale by the mass of the body you're applying the force to (F = ma, where gravity or anti-gravity is a).
scale by 150 — there's a bug where the SKPhysicsWorld.gravity property isn't in the same units as applyForce:. (If you turn SKPhysicsWorld gravity off and use SKFieldNode gravity instead, you don't need to do this.)
Unlike turning off affectedByGravity and applying an action to make the balloon rise, this approach works well with the rest of the physics sim. With a balancing force exactly equal to gravity, the balloon will float in place — after colliding with other things it'll return to equilibrium. If the balancing force is greater than gravity, the balloon will rise, but its rise will be hindered by other bodies in its way.
First off, an SKSpriteNode isn't affected by gravity at all. It is the SKPhysicsBody that belongs to the node that is affected by gravity.
Second...
myNode.physicsBody.afectedByGravity = NO;
:D
If you want it to rise upwards then you can add an action to it...
SKAction *moveAction = [SKAction moveByX:0 y:-10 duration:1];
SKAction *repeatingAction = [SKAction repeatActionForever:moveAction];
[myNode runAction:repeatingAction];
As many have said it's about counterforce.
So yes, you can apply a counterforce to the balloon to make it go upwards.
But to make it look like a balloon you need to understand what makes a balloon go up: Air pressure. Since the helium or whatever light gas you are using is lighter than air it will start to go up, or in other words the heavier air will go under the balloon. It's like a piece of wood in water, the heavier water will go under the wood, till the wood is soaked and gets even heavier than the water.
So what you should do is to make the counterforce adapt to the height of the balloon, the higher it gets the less pressure you apply upwards. This is a way to simulate buoyancy.
Here’s my answer:
- (MyClass *)newRisingObject
{
MyClass *risingObject = [MyClass spriteNodeWithImageNamed:#"image"];
[risingObject setPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame))];
[risingObject setName:#"name"];
[risingObject setUserInteractionEnabled:YES];
// Physics
//
[risingObject setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:risingObject.size.width / 2.0f]];
[risingObject.physicsBody setRestitution:1.0f];
[risingObject.physicsBody setFriction:0.0f];
[risingObject.physicsBody setLinearDamping:0.0f];
[risingObject.physicsBody setAngularDamping:0.0f];
[risingObject.physicsBody setMass:1.3e-6f];
return risingObject;
}
.
- (void)didMoveToView:(SKView *)view
{
/* Setup your scene here */
MyClass *risingObject = [self newRisingObject];
[self addChild:risingObject];
}
.
- (void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
[self enumerateChildNodesWithName:#"name"
usingBlock:^(SKNode *node, BOOL *stop)
{
// Push object up
//
[node.physicsBody applyImpulse:CGVectorMake(0.0f, node.physicsBody.mass * OBJECT_ACCELERATION)];
}];
}

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.

Create one or multiple gravitational points in sprite-kit scene

As this question, I want to add some gravity in my sprite-kit scene. But the difference is my scene objects should be attracted to a point (at the middle of the scene). Or to multiple points.
I'm pretty sure I found something like that on the web, but I can't find it again.
I there a common way to acheive this, or should I calculate my own forces in the update: method?
Thanks
I believe all you have to do is add gravity field nodes, and then turn OFF the gravity of the physicsWord so that your objects don't fall "down". You might have to set up some invisible borders to prevent the objects from orbiting the gravity field nodes, or possibly set a really friction.
You can also set the field strength of the gravity field nodes, which may save you from having to calculate the forces manually.
To turn off gravity (swift notation):
physicsWorld.gravity = CGVectorMake(0, 0)

How would I build pool-cues / simplified pinball-style plungers with SpriteKit?

I'm working on a game in which the user should be able to trigger 'rods' that come out from the edge of the screen to displace elements on screen (balls). These projectiles roughly resemble pool-cues. Or perhaps pinball plungers, except that they start from the 'loaded' position (mostly offscreen), and when triggered, they eject out, then quickly retreat.
I'm unclear how I should build these with Sprite Kit.
The game uses the PhysicsEngine, and the onscreen balls should be effected both by gravity AND they should be displaced when they collide with the rods. However the rods should neither be effected by gravity, not displaced when they collide with the balls -- they should simply retreat regardless of whether they've made contact with the balls.
I realize I can set the affectedByGravity property for the rods. However because they will still displace slightly when they collide with the balls. How can I 'fix' or 'peg' them in place? Do I need to use an SKPhysicsSlidingJoint? If so, has anyone encountered any examples online? Is there a simpler way to do this?
A related physics engine, Box2D distinguishes static, kinematic, and dynamic bodies.
Kinematic bodies can move and will collide with other objects, but they are themselves not affected by dynamic bodies or forces like gravity. Thus, consider setting rod.dynamic = NO; but animate it with actions. See also here in the reference for SKPhysicsBody.

Resources