I am trying to create nodes, that are put into an array, and then are added to the scene with their physics bodies.
Here is the code for creating the initial sprites:
let name = createTarget()
let targetNode = SKSpriteNode(imageNamed: name)
targetNode.name = name
chickenNodes.append(targetNode)
targetNode.position = generateRandomLocation()
let range = SKRange(lowerLimit: targetNode.position.y, upperLimit: targetNode.position.y)
let lockToCenter = SKConstraint.positionY(range)
targetNode.constraints = [lockToCenter]
if movingItems { animateTargets(targetNode) }
Once all of these nodes are in the array, I add them in didMove to a background node, fgNode, in the scene, like this:
for chicken in chickenNodes {
let texture = SKTexture(imageNamed: chicken.name!)
chicken.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
chicken.physicsBody?.isDynamic = true
chicken.physicsBody?.affectedByGravity = true
chicken.physicsBody?.allowsRotation = false
chicken.physicsBody?.linearDamping = 0.0
chicken.physicsBody?.restitution = 1.0
chicken.physicsBody?.friction = 0.0
fgNode.addChild(chicken)
}
When I view the physics through the scene, the physics bodies keep falling away from the sprite (as though they're responding to the gravity in the scene); and the sprite is just locked where it is. How do I ensure that the physicsBody sticks to the sprite?
Related
In my original build, my sprites were static, so to make the collision physics as accurate as I could rather than use rectangleOfSize for SKPhysicsBody passing in the node, I used this tool to define a path SpriteKit's SKPhysicsBody with polygon helper tool.
However now I'm animating the sprites so that they move back and forth during gameplay (obviously my physics path remains static given the above) so my physicsbody no longer matches what the player sees on screen.
The helper tool seemed like a bit of hack that Apple would eventually fix in the API, has there been anything recent in SpriteKit that would help me out here so that I can pass in the node and define a precise physicsbody rather than the hard-coded approach? If not, any other alternatives?
Not sure how performant this is but you can pre-generate the physics bodies for each texture then animate the sprite along with its physicsBody using a custom action.
Here is an example:
func testAnimation() {
var frameTextures = [SKTexture]()
var physicsBodies = [SKPhysicsBody]()
for index in 1...8 {
// The animation has 8 frames
let texture = SKTexture(imageNamed: "guy\(index)")
frameTextures.append(texture)
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.affectedByGravity = false
physicsBodies.append(physicsBody)
}
let sprite = SKSpriteNode(texture: frameTextures[0])
let framesPerSecond = 16.0
let animation = SKAction.customAction(withDuration: 1.0, actionBlock: { node, time in
let index = Int((framesPerSecond * Double(time))) % frameTextures.count
if let spriteNode = node as? SKSpriteNode {
spriteNode.texture = frameTextures[index]
spriteNode.physicsBody = physicsBodies[index]
}
})
sprite.run(SKAction.repeatForever(animation));
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)
}
I am trying to make a SKSpriteNode come from a SKShapeNode. When the below code runs, the projectiles are appearing, but they will originate from a different point on the screen, not the player location.
Here is my shoot function that is in my Player Class.
func shoot() {
let newProjectile = Projectile()
newProjectile.position = self.position
self.addChild(newProjectile)
let action = SKAction.moveTo(CGPointMake(
600 * -cos(newProjectile.zRotation - 1.57079633) + newProjectile.position.x,
600 * -sin(newProjectile.zRotation - 1.57079633) + newProjectile.position.y
), duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
newProjectile.runAction(SKAction.sequence([action, actionMoveDone]))
}
Here is my Projectile Class :
class Projectile : SKSpriteNode {
let Texture = SKTexture(imageNamed: "image.png")
static var counter : Int = 0
init(){
//super.init()
super.init(texture: Texture, color: UIColor.whiteColor(), size: CGSize(width: radius * 2, height: radius * 2))
self.name = "projectile-" + NSUUID().UUIDString
self.physicsBody = SKPhysicsBody(circleOfRadius: radius)
self.physicsBody?.categoryBitMask = GlobalConstants.Category.projectile
self.physicsBody?.collisionBitMask = GlobalConstants.Category.projectile
self.physicsBody?.contactTestBitMask = GlobalConstants.Category.projectile | GlobalConstants.Category.wall
self.zPosition = GlobalConstants.ZPosition.projectile
}
}
you probably need to add your projectiles to the scene instead of the player itself. you're adding the projectiles to your player.
you should add the projectile to the parents scene. something like this.
self.scene!.addChild(newProjectile)
If you want the projectiles to be children of the player, then the projectile's position must be expressed in terms of the player i.e. A position of (0, 0) will place the projectile at the player's anchor point.
If you want the projectiles to be children of the scene, then the projectile's position will be expressed in terms of the scene i.e. a position equal to the player's position will be needed to put the projectile on top of the player.
You combined the two, causing the projectile to be placed with the (x,y) position of the player in the scene, but relatIve to the player, which means that wherever the player is compared to the scene's origin, then that's where the projectile will appear relative to the player.
I know that this question has been "asked" here but it was not helpful, nor did it cover what I am asking.
That being said, I want to know how to achieve the following:
I need a node that has 2 physics bodies: 1) a smaller body (200x200) that represents the node's frame, and 2) a larger body (500x500) that represents an outer perimeter of an area that will act as a "detection" boundary. The outer body will not physically impact colliding objects, it just needs to register that an object has encountered that boundary line. Below is a simple idea of what I am describing:
My initial approach was to use SKPhysicsBody bodyWithBodies:<NSArray> but that would have just created the overlap body as the smaller rect.
So I figured I would have to create an SKSpriteNode for the smaller body (the black square), and another SKSpriteNode for the larger body (the blue square), and add it as a child to the smaller square (or vice versa).
My problem is that I can't seem to get the outer boundary to allow another object to pass through while registering that interaction. The closest I have come is getting a movable node to hit the boundary, then continually get pushed back and forth along the line of the 500x500 boundary as long as I attempt to pass through the boundary. I have been able to make the moving node UNABLE to pass through the 500x500, but this is quite the opposite of what I need.
Here is the code that I am using, perhaps someone can see something I am doing wrong:
/// TESTING
// Detection body - 500x500
SKSpriteNode *dbody = [SKSpriteNode spriteNodeWithImageNamed:#"object500"];
dbody.position = CGPointMake(500, 100);
dbody.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:dbody.frame];
dbody.physicsBody.categoryBitMask = detection;
dbody.physicsBody.collisionBitMask = 0;
dbody.physicsBody.contactTestBitMask = player;
// Node body - 200x200
SKSpriteNode *testBoundary = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
testBoundary.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:testBoundary.frame];
testBoundary.physicsBody.categoryBitMask = object;
testBoundary.physicsBody.contactTestBitMask = player;
testBoundary.position = dbody.position;
// Add boundary as child to main node
[testBoundary addChild:dbody];
// Add to scene
[chapterScene addChild:dbody];
All of this is inside LevelScene.m, an SKScene set with <SKPhysicsContactDelegate> in the header. Also, I have defined chapterScene as such:
// Set up main chapter scene
self.anchorPoint = CGPointMake(0.5, 0.5); //0,0 to 1,1
chapterScene = [SKNode node];
[self addChild:chapterScene];
, with chapterScene defined at the top of LevelScene.
#interface LevelScene () {
#pragma 1 Scene objects
SKNode *chapterScene;
}
If someone can help me figure out what I am doing wrong, or perhaps an alternative approach that would give me what I am looking for I would appreciate it.
NOTE: My backup solution would be seemingly expensive in terms of CPU and memory, but I believe it would work nonetheless. I would have a BOOL isDetected to represent if the moving player node IS within the 500x500 area, then in update I would monitor the distance to a smaller 200x200 rect boundary of the center of the 500x500 node. But I would much rather use the 2 SKPhysicsBody's as I am sure it can be done and would be much easier to implement.
UPDATE:
here is my moving character (a separate SKNode class called Character.m)
- (void)createCharacterWithName:(NSString *)charName {
character = [SKSpriteNode spriteNodeWithImageNamed:charName];
[self addChild:character];
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:character.frame.size.width/2.0];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = YES;
// Define physics body relationships
self.physicsBody.categoryBitMask = player;
self.physicsBody.collisionBitMask = player;
self.physicsBody.contactTestBitMask = detection;
}
Here is an image of my simulator (it is a little different, in terms of sizes, as my description. My description was for ease of understanding not the exact scaling of the nodes)
this is working for me. sorry i didnt write it on obj c. just a lot faster for me in swift
import SpriteKit
let CategoryOuter:UInt32 = 1 << 0
let CategoryInner:UInt32 = 1 << 1
let CategoryTester:UInt32 = 1 << 2
class GameScene: SKScene, SKPhysicsContactDelegate {
let outerSprite = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(100, 100))
let innerSprite = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(40, 40))
let tester = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(10, 10))
override init(size: CGSize) {
super.init(size: size)
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVectorMake(0, 0)
outerSprite.position = CGPointMake(size.width/2, size.height/2)
outerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: outerSprite.size)
outerSprite.physicsBody!.dynamic = false
outerSprite.physicsBody!.categoryBitMask = CategoryOuter
outerSprite.physicsBody!.collisionBitMask = 0
outerSprite.physicsBody!.contactTestBitMask = CategoryTester
innerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: innerSprite.size)
innerSprite.physicsBody!.dynamic = false
innerSprite.physicsBody!.categoryBitMask = CategoryInner
innerSprite.physicsBody!.collisionBitMask = CategoryTester
innerSprite.physicsBody!.contactTestBitMask = CategoryTester
outerSprite.addChild(innerSprite)
addChild(outerSprite)
tester.physicsBody = SKPhysicsBody(rectangleOfSize: tester.size)
tester.physicsBody!.categoryBitMask = CategoryTester
tester.physicsBody!.collisionBitMask = CategoryInner
addChild(tester)
}
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (CategoryOuter | CategoryTester) {
print("outer collision")
}
else if collision == (CategoryInner | CategoryTester) {
print("inner collision")
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let location = touch.locationInNode(self)
tester.position = location
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have a wrapper class to create a label node with some subnodes. This is the init for the class:
let label = SKLabelNode(text: word)
let background = SKShapeNode(rectOfSize: size, cornerRadius: 0.5)
let mainNode = SKSpriteNode(color: color, size: background.frame.size)
label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center
label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center
label.addChild(background)
label.addChild(mainNode)
mainNode.zPosition = 1
background.zPosition = 2
label.zPosition = 5
self.node = label
In my main scene I instantiate the node, set its physics body and set the gravity for the scene:
self.physicsWorld.gravity = CGVectorMake( 0, -10 );
let node = MyNode()
node.node.zPosition = 99
node.node.position.y = endOfScreenTop
if let name = node.node.name as String!, let sprite = node.node.childNodeWithName(name) as? SKSpriteNode {
node.node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.node.physicsBody!.mass = 1.0;
node.node.physicsBody!.angularDamping = 0.0;
node.node.physicsBody!.linearDamping = 0.0;
node.node.physicsBody!.friction = 0.0;
node.node.physicsBody!.affectedByGravity = true
node.node.physicsBody!.dynamic = true
node.node.physicsBody!.allowsRotation = false
}
addChild(node.node)
I have 2 problems:
The label appears behind the SKSpriteNode, even though the zPosition should place it at the front
The SKShapeNode (that's just an insets to smooth the corners) doesn't appear at all
None of the nodes are affected by gravity
PS. Even changing the following to true doesn't change anything:
skView.ignoresSiblingOrder = false
PS2. the MyNode class has these properties
var node: SKLabelNode
var speed: CGFloat = 1.0
var word: String
var color: UIColor
var kind: Kind
Assuming that skView.ignoresSiblingOrder = true in the view controller (the default setting), some thoughts...
When ignoresSiblingOrder is set to true, Sprite Kit ignores sibling relationships when determining the order in which to draw nodes. It does not, however, affect the parent-sibling draw order.
In either mode, the parent is drawn first and then the child nodes are rendered. Normally, the children appear on top of the parent (if they're at the same location) regardless of the zPositions of the parent and children! The exception is if a child's zPosition is less than zero. In this case, the child will appear beneath its parent (even if the parent's zPosition is less than the child).
It doesn't appear that you are setting node.node.name nor the name of the sprite node. If the names aren't set, the if condition will fail and the physics body won't be attached to node.node.
The default settings for shapes only draws a white border around its path (in this case, a rectangle). You should see this as an white outline around the sprite node. Setting background.fillColor = SKColor.greenColor() will show the SKShapeNode filled with green.
i am trying to make collision of two objects, but "func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact)" is not being called.
my code is,
let carbonNode = SCNNode(geometry: carbonAtom())
carbonNode.position = SCNVector3Make(-6, 8, 0)
let coneAtomNode = SCNNode(geometry: coneAtom())
pinNode = coneAtomNode
pinNode.physicsBody = SCNPhysicsBody.dynamicBody()
pinNode.physicsBody?.restitution = 0.9;
pinNode.categoryBitMask = 0x4;
pinNode.physicsBody?.collisionBitMask = ~(0x4);
coneAtomNode.position = SCNVector3Make(-6, -15, 0)
scene.rootNode.addChildNode(coneAtomNode)
balloonNode = carbonNode
sceneView.scene = scene
sceneView.scene?.physicsWorld.contactDelegate = self
pinNode.runAction(SCNAction.repeatAction(SCNAction.moveTo(SCNVector3Make(-6, 10+5, 0), duration: 1.5), count: 1), completionHandler: {
})
You can't move "dynamic" bodies programmatically (i.e no action, no animation and no manual updates of position/rotation/scale). You can either move dynamic bodies with forces or use a kinematicBody instead.
Kinematic bodies behave just like static bodies but you can move them programmatically.
Also if you want to get physics contacts between two nodes, the two nodes need to have a physicsBody.