Reputation: 1503
I've some code that worked perfectly fine in iOS 8, however since iOS 9 a few SKFieldNodes
behave very strangely. The image below shows a scene with two SKFieldNodes
(marked with red sprites) and a SKEmitterNode
emitting gold-ish particles.
In iOS 8, the fields would attract the particles and "suck" them towards the magnet's tips. Since iOS 9, the particles accelerate towards the upper right corner, like illustrated by the red arrows. Also, I had to increase the strength
of the fields to even make the effect visible.
Here's the corresponding code:
SKSpriteNode* magnet = [SKSpriteNode spriteNodeWithImageNamed:@"StarMagnet_main"];
//Add field node
SKFieldNode* field = [SKFieldNode radialGravityField];
field.strength = 10.0;
field.falloff = 0.1;
//Add a red circle to visualize the node
SKShapeNode* debugNode = [SKShapeNode shapeNodeWithCircleOfRadius:20.0];
debugNode.fillColor = [UIColor redColor];
[field addChild:debugNode];
field.position = CGPointMake(magnet.size.width * 0.3, magnet.size.height * 0.45);
field.categoryBitMask = LECategoryDust;
[magnet addChild:field];
//Add second field
field = [field copy];
field.position = CGPointMake(-field.position.x, field.position.y);
[magnet addChild:field];
//Add particle emitter
SKEmitterNode* emitter = [SKEmitterNode emitterNamed:@"MagneticParticleEmitter"];
emitter.fieldBitMask = LECategoryDust;
emitter.position = CGPointMake(0, magnet.size.height/2.0);
[magnet addChild:emitter];
It seems as if the field nodes were located somewhere near the upper right corner, yet the red circle child nodes show different.
Upvotes: 2
Views: 321
Reputation: 1503
After thoroughly investigating the issue, I conclude that it is a SpriteKit bug introduced with iOS 9. Below you can find a test scene that consists of a SKEmitterNode
, SKFieldNode
and two SKShapeNodes
marking the initial viewport and the field's position. All nodes are descendants of a rootNode
used to translate the "world".
Although the field's position does not change, the center of its effects does.
The force calculations are only correct if the SKEmitterNode's
targetNode
property is set to be the scene
, which is not always desired.
The yellow circle marks the field's position. Yet, particles are drawn towards the upper right corner. Here are some positions at this point:
Root scene pos: 160x, 284y | parent pos: 160x, 284y
Field scene pos: 80x, 142y | parent pos: -80x, -142y
Target scene pos: 80x, 142y | parent pos: -80x, -142y
As you can see the field and the yellow circle have the same position (and parent).
When moving the scene (that is, the rootNode
only), the particles are drawn towards another point that moves closer to the lower left corner while translating into the opposite position.
Again some positions after translating:
Root scene pos: 84x, 149y | parent pos: 84x, 149y
Field scene pos: 4x, 7y | parent pos: -80x, -142y
Target scene pos: 4x, 7y | parent pos: -80x, -142y
The field's position has not changed, yet the center of gravity did.
To better illustrate the behaviour, I've uploaded a screencast: https://youtu.be/EJs4vPYMndU
Anyone willing to try this out on their own is welcome, here's the code of the test scene used in this experiment:
#import "LEDebugScene.h"
#import "LEPhysicsCategories.h"
@implementation LEDebugScene
SKNode* rootNode;
SKFieldNode* fieldNode;
SKShapeNode* targetPosShape;
- (void) didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor blackColor];
// This will be the desired field's position (in parent space)
CGPoint fieldPosition = CGPointMake(-self.size.width * 0.25, -self.size.height * 0.25);
// 1. Set up a root node to move around to translate the "world"
rootNode = [SKNode node];
rootNode.position = CGPointMake(self.size.width/2.0, self.size.height/2.0);
[self addChild:rootNode];
// 2. Mark the target position of the field with a circle
targetPosShape = [SKShapeNode shapeNodeWithCircleOfRadius:20.0];
targetPosShape.fillColor = [UIColor yellowColor];
targetPosShape.strokeColor = [UIColor brownColor];
[rootNode addChild:targetPosShape];
targetPosShape.position = fieldPosition;
// 3. Mark the initially visible portion of the scene
SKShapeNode* initialViewportShape = [SKShapeNode shapeNodeWithRect: CGRectMake(-self.size.width/2.0,
-self.size.height/2.0,
self.size.width,
self.size.height)];
initialViewportShape.fillColor = [UIColor clearColor];
initialViewportShape.strokeColor = [UIColor redColor];
initialViewportShape.lineWidth = 3.0;
[rootNode addChild:initialViewportShape];
// 4. Add an emitter at the center of the initial viewport
NSString* emitterPath = [[NSBundle mainBundle] pathForResource:@"DebugEmitter" ofType:@"sks"];
SKEmitterNode* emitterNode = [NSKeyedUnarchiver unarchiveObjectWithFile:emitterPath];
emitterNode.fieldBitMask = LECategoryDust;
emitterNode.particlePositionRange = CGVectorMake(self.size.width, self.size.height);
emitterNode.position = CGPointZero;
[rootNode addChild:emitterNode];
// 5. Create a field at the desired position
fieldNode = [SKFieldNode radialGravityField];
fieldNode.userData = [NSMutableDictionary dictionary];
fieldNode.categoryBitMask = LECategoryDust;
fieldNode.falloff = 1.8;
fieldNode.minimumRadius = 20.0;
fieldNode.strength = 2.5;
fieldNode.position = fieldPosition;
[rootNode addChild:fieldNode];
}
- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//Get the touch movement vector
UITouch* touch = [touches anyObject];
CGPoint prevLoc = [touch previousLocationInNode:self];
CGPoint location = [touch locationInNode:self];
CGVector delta = CGVectorMake(location.x - prevLoc.x, location.y - prevLoc.y);
//Translate the world by repositioning the root node
rootNode.position = CGPointMake(rootNode.position.x + delta.dx, rootNode.position.y + delta.dy);
CGPoint rootScenePos = [self convertPoint:rootNode.position fromNode:rootNode.parent];
CGPoint fieldScenePos = [self convertPoint:fieldNode.position fromNode:fieldNode.parent];
CGPoint targetShapeScenePos = [self convertPoint:targetPosShape.position fromNode:targetPosShape.parent];
NSLog(@"");
NSLog(@"Root scene pos: %.0fx, %.0fy | parent pos: %.0fx, %.0fy",
rootScenePos.x, rootScenePos.y, rootNode.position.x, rootNode.position.y);
NSLog(@"Field scene pos: %.0fx, %.0fy | parent pos: %.0fx, %.0fy",
fieldScenePos.x, fieldScenePos.y, fieldNode.position.x, fieldNode.position.y);
NSLog(@"Target scene pos: %.0fx, %.0fy | parent pos: %.0fx, %.0fy",
targetShapeScenePos.x, targetShapeScenePos.y, targetPosShape.position.x, targetPosShape.position.y);
}
@end
Despite trying for hours, I did not manage to compensate for the calculation errors by repositioning the field node. If anyone does, it would be great if they could share it.
EDIT: I also filed a bug report (23063175).
Upvotes: 0