I am making a platforming game in Swift with SpriteKit involving a main character that jumps around. However, once the level loads the player immediately falls through the ground. You can see it in action here.
I am using SKTilemapNode to create the ground, and looping through the tiles when a level loads to create an SKPhysicsBody on a child node of the tile map. This is very similar to what is demoed in the "What's new in SpriteKit" video at WWDC 2016:
So, here we've got a little platform that I built. A little guy that can run around. And you can see that I got the parallax scrolling going on in the background. And you'll note that I'm colliding with the tiles here. And I achieve this by leveraging custom user data that we can put on each of our tiles. Here, I'll show you in our tile set. Select one of the variants here.
And you can see that we have some user data over here. And I just have a value called edgeTile which is a Boolean, and I set to 1.
So, in code, I'm going through the tile map in our platform demo here, and I'm looking for all of these edge tiles.
And whenever I find one, I create some physics data to allow the player to collide with it.
My function to create a physics body based off of an SKTilemapNode is as follows:
extension SKTileMapNode {
//In order for this to work, edge tile definitions must have the "edge" property in user data
func createPhysicsBody() -> SKPhysicsBody {
var physicsBodies = [SKPhysicsBody]()
for row in 0 ..< self.numberOfRows {
for column in 0 ..< self.numberOfColumns {
if self.tileDefinition(atColumn: column, row: row)?.userData?["edge"] != nil {
physicsBodies.append(SKPhysicsBody(rectangleOf: self.tileSize, center: self.centerOfTile(atColumn: column, row: row)))
}
}
}
let body = SKPhysicsBody(bodies: physicsBodies)
body.affectedByGravity = false
body.isDynamic = false
body.allowsRotation = false
body.pinned = true
body.restitution = 0
body.collisionBitMask = 0b1111
body.categoryBitMask = 0b1111
body.contactTestBitMask = 0b1000
return body
}
func initializePhysicsBody() {
let node = SKNode()
node.name = "Tilemap"
node.physicsBody = createPhysicsBody()
addChild(node)
}
}
So, in my scene setup all I have to do is call tileMap.initializePhysicsBody() to do everything that I need.
The SKPhysicsBody for my player is as follows:
let rect = CGSize(width: 16 * xScale, height: 24 * yScale)
let physics = SKPhysicsBody(rectangleOf: rect)
physics.isDynamic = true
physics.allowsRotation = false
physics.pinned = false
physics.affectedByGravity = true
physics.friction = 0
physics.restitution = 0
physics.linearDamping = 0
physics.angularDamping = 0
physics.density = 100
physics.categoryBitMask = 0b0001
physics.collisionBitMask = 0b0001
physics.contactTestBitMask = 0b0011
physics.usesPreciseCollisionDetection = true
physicsBody = physics
I'm not sure what the problem is here, but if I set the SKTilemapNode's physics body to be dynamic, it works. This is how I had the game working up until this point, however, this creates a lot of jitter in the ground because it's moving as a result of the player hitting it. So, thanks for reading this far at least, and any suggestions would be appreciated.
EDIT.
I think the ERROR Here is not using UInt 32
body.categoryBitMask: UInt32 = 2
body.collisionBitMask: UInt32 = 1
body.contactTestBitMask: UInt32 = 1
And Player
physics.categoryBitMask: UInt32 = 1
physics.collisionBitMask: UInt32 = 2
physics.contactTestBitMask: UInt32 = 2
This should definitely Work
Also try this way for the tileMapNode (given below) rather than creating the extension. This was given in a apple developer forum by dontangg
self.tileMap = self.childNode(withName: "Tile Map") as? SKTileMapNode
guard let tileMap = self.tileMap else { fatalError("Missing tile map for the level") }
let tileSize = tileMap.tileSize
let halfWidth = CGFloat(tileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(tileMap.numberOfRows) / 2.0 * tileSize.height
for col in 0..<tileMap.numberOfColumns {
for row in 0..<tileMap.numberOfRows {
let tileDefinition = tileMap.tileDefinition(atColumn: col, row: row)
let isEdgeTile = tileDefinition?.userData?["edgeTile"] as? Bool
if (isEdgeTile ?? false) {
let x = CGFloat(col) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = playerCollisionMask | wallCollisionMask
tileNode.physicsBody?.categoryBitMask = wallCollisionMask
tileMap.addChild(tileNode)
}
}
}
Related
I'm trying to make a sidescroller using SKTileMapNode and SKTileMaps, but I'm having trouble with the physics bodies of individual tiles. I create them in a loop as described in this link:
https://forums.developer.apple.com/thread/50043
However my player node passes through the ground. What am I doing wrong?
guard let landBackground = childNode(withName: "ground")
as? SKTileMapNode else {
fatalError("Background node not loaded")
}
self.landBackground = landBackground
self.tileSize = landBackground.tileSize
for x in 0...(landBackground.numberOfColumns-1){
for y in 0...(landBackground.numberOfRows-1){
let tile = landBackground.tileDefinition(atColumn: x, row: y)
if(tile != nil){
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = self.landBackground.centerOfTile(atColumn: x, row: y)
tileNode.position.x -= (tileSize.width * 0.5)
tileNode.position.y -= (tileSize.height * 0.5)
tileNode.physicsBody = SKPhysicsBody(rectangleOf: tileSize, center: tileNode.position)
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = playerCategory|groundCategory
//tileNode.physicsBody?.contactTestBitMask = playerCategory
tileNode.physicsBody?.categoryBitMask = groundCategory
landBackground.addChild(tileNode)
}
}
}
SKPhysicsBody(rectangleOf: tileSize, center: tileNode.position)
This is probably not correct. The documentation for the call says:
The center of the square in the owning node’s coordinate system.
This means you should probably use .zero. The physics body is always assumed to be at the same location as the node it belongs to, unless specifically offset. You should never use scene coordinates for this.
Also make sure you have showsPhysics set to true on your SKView so you can see where they really are.
I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.
I've been asked to simplify this question, so that's what I'm doing.
I'm struggling in SpriteKit's physic joints (and possibly physic body properties). I tried every single subclass and many configurations but seams like nothing works or I'm doing something wrong.
I'm developing Snake game. User controls head of snake which should move at constant speed ahead and user can turn it clockwise or anticlockwise. All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago.
I think for this game the Pin joint should be the answer, which anchor point is exactly in the centre between elements.
Unfortunately the result is not perfect. The structure should make the perfect circle, but it doesn't. I'm attaching the code, and gif showing the current effect. Is anyone experience enough to give me any suggestion what properties of physic body and or joints should are apply here for desired effect?
My code:
class GameScene: SKScene {
private var elements = [SKNode]()
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
let dummyTurnNode = SKNode()
dummyTurnNode.position = CGPoint(x: size.width / 2 - 50, y: size.height / 2)
let dummyTurnBody = SKPhysicsBody(circleOfRadius: 1)
dummyTurnBody.isDynamic = false
dummyTurnNode.physicsBody = dummyTurnBody
addChild(dummyTurnNode)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
let label = SKLabelNode(text: "A")
label.fontSize = 10
label.fontName = "Helvetica-Bold"
element.addChild(label)
if index == 0 {
element.fillColor = UIColor.blue()
body.velocity = CGVector(dx: 0, dy: 30)
let dummyTurnJoint = SKPhysicsJointPin.joint(withBodyA: dummyTurnBody, bodyB: body, anchor: dummyTurnNode.position)
physicsWorld.add(dummyTurnJoint)
} else {
body.linearDamping = 1
element.fillColor = UIColor.red()
let previousElement = elements[index - 1]
let connectingJoint = SKPhysicsJointPin.joint(withBodyA: previousElement.physicsBody!, bodyB: body, anchor: CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index) + CGFloat(15)))
physicsWorld.add(connectingJoint)
}
}
}
override func update(_ currentTime: TimeInterval) {
let head = elements.first!.physicsBody!
var velocity = head.velocity
velocity.normalize()
velocity.multiply(30)
head.velocity = velocity
}
}
extension CGVector {
var rwLength: CGFloat {
let xSq = pow(dx, 2)
let ySq = pow(dy, 2)
return sqrt(xSq + ySq)
}
mutating func normalize() {
dx /= rwLength
dy /= rwLength
}
mutating func multiply(_ factor: CGFloat) {
dx *= factor
dy *= factor
}
}
"All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago."
You should note that with Physics joints you are likely going to have variance no matter what you do. Even if you have it close to perfect you'll have rounding errors under the hood making the path not exact.
If all the tail parts are equal you can also use a different approach, this is something I've done for a comet tail. Basically the idea is that you have an array of tail objects and per-frame move move the last tail-object always to the same position as the head-object. If the head-object has a higher z-position the tail is drawn below it.
If you need to keep your tail in order you could vary the approach by storing an array of head-positions (per-frame path) and then place the tail objects along that path in your per-frame update call to the snake.
See my code below for example:
These are you head-object variables:
var tails = [SKEmitterNode]()
var tailIndex = 0
In your head init function instantiate the tail objects:
for _ in 0...MAX_TAIL_INDEX
{
if let remnant = SKEmitterNode(fileNamed: "FireTail.sks")
{
p.tails.append(remnant)
}
}
Call the below per-frame:
func drawTail()
{
if tails.count > tailIndex
{
tails[tailIndex].resetSimulation()
tails[tailIndex].particleSpeed = velocity() / 4
tails[tailIndex].emissionAngle = zRotation - CGFloat(M_PI_2) // opposite direction
tails[tailIndex].position = position
tailIndex = tailIndex < MAX_TAIL_INDEX ? tailIndex + 1 : 0
}
}
The resulting effect is actually really smooth when you call it from the scene update() function.
i want to be able move the ball with pause every 20 pixels moved i tried this one but didn't do nothing, the ball stays at the point where it started it doesn't move to the end of the screen,i know i didn't put the waitForDuration , because i wanted to check if it will move or not
func spwan()
{
let ball:SKSpriteNode = SKScene(fileNamed: "Ball")?.childNodeWithName("ball") as! SKSpriteNode
ball.removeFromParent()
self.addChild(ball)
ball.name = "spriteToTrack"
ball.zPosition = 0
ball.position = CGPointMake(1950, 1000)
var num:CGFloat = ball.position.x
let a1 = SKAction.moveToX(num - 20, duration: 10)
// i want to get to -50 let a1 = SKAction.moveToX(-50 , duration: 10)
let minus = SKAction.runBlock{
num -= 20
}
let sq1 = SKAction.sequence([a1,minus])
ball.runAction(SKAction.repeatAction(sq1, count: 10)
}
The ball should move 20 pixels at least in the above code, but 20 pixels in 10 seconds might seen like a standstill. Anyways I think you've overcomplicated things quite a bit by using moveToX rather than moveBy:, so (with a bit of rejigging) you're probably better of with something like this:
func spawn() {
let x: CGFloat = 1950
let xDelta: CGFloat = -20
let xDestination: CGFloat = -50
let repeats = Int((x - xDestination)/fabs(xDelta))
let move = SKAction.moveBy(CGVectorMake(xDelta, 0), duration: 2) // made it a lot quicker to show that stuff is happening
move.timingMode = .EaseInEaseOut
let ball:SKSpriteNode = SKScene(fileNamed: "Ball")?.childNodeWithName("ball") as! SKSpriteNode
ball.removeFromParent()
ball.name = "spriteToTrack"
ball.position = CGPointMake(x, 1000)
addChild(ball)
ball.runAction(SKAction.repeatAction(move, count: repeats))
}
I am generating Y position for SKSpriteNode. I need to generate y position, but it should not be in the position previously generated image, should not overlap. How would I be able to do this in Swift in SpriteKit?
for _ in 1...3 {
let lower : UInt32 = 100
let upper : UInt32 = UInt32(screenSize.height) - 100
let randomY = arc4random_uniform(upper - lower) + lower
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
prekazka = obstacle.copy() as! SKSpriteNode
prekazka.name = "prekazka"
prekazka.position = CGPoint(x: 0, y: Int(randomY))
prekazka.zPosition = 10;
prekazka.size = CGSize(width: screenSize.width / 7, height: screenSize.height / 7)
prekazka.physicsBody = SKPhysicsBody(texture: obstaclesTexture, size: obstacle.size)
prekazka.physicsBody?.dynamic = false
prekazka.physicsBody?.categoryBitMask = PhysicsCatagory.obstacle
prekazka.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
prekazka.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
self.addChild(prekazka)
}
To avoid both overlapping sprites and different positions you need to take the height of the sprite into consideration. Something like this perhaps:
let spriteHeight = 60 // or whatever
let lower = 100
let upper = Int(screenSize.height) - 100
let area = upper - lower
let ySpacing = area/spriteHeight
var allowedYs = Set<CGFloat>()
let numberOfSprites = 3
while allowedYs.count < numberOfSprites {
let random = arc4random_uniform(UInt32(spriteHeight))
let y = CGFloat(random) * CGFloat(ySpacing)
allowedYs.insert(y)
}
for y in allowedYs {
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
prekazka = obstacle.copy() as! SKSpriteNode
prekazka.name = "prekazka"
prekazka.position = CGPoint(x: 0, y: y)
// etc...
}
I would be tempted to generate the sprite's position and then loop over all existing sprites in the scene and see if their frame overlaps with the new sprites frame:
let lower : UInt32 = 100
let upper : UInt32 = UInt32(screenSize.height) - 100
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
// Generate a position for our new sprite. Repeat until the generated position cause the new sprite’s frame not to overlap with any existing sprite’s frame.
repeat {
let randomY = arc4random_uniform(upper - lower) + lower
prekazka.position = CGPoint(x: 0, y: Int(randomY))
} while checkForOverlap(prekazka) = true // If it overlaps, generate a new position and check again
//Function to see if an SkSpriteNode (passed as parameter node) overlaps with any existing node. Return True if it does, else False.
Func checkForOverlap(SKSpriteNode: node) ->> bool {
for existingNode in children { // for every existing node
if CGRectIntersectsRect(existingNode.frame, node.frame) {return True} // return True if frames overlap
}
return false //No overlap
}
(Writing this from memory, so may need a bit of fiddling to work).
Another approach would be to generate and position all the nodes with physics bodies, and set the scene up so that all nodes collide with all others. Then allow the physics engine to handle any collisions which would cause it to move the nodes around until none are overlapping. At this point, your could remove all the physics bodies.