SpriteKit - Collision detection not working - ios

i've got a problem with the collisions.
adding physicbody depending on tilemap:
guard let tilemap = childNode(withName: "LevelGround") as? SKTileMapNode else { return }
let tileSize = tilemap.tileSize
let halfWidth = CGFloat(tilemap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(tilemap.numberOfRows) / 2.0 * tileSize.height
for row in 0..<tilemap.numberOfRows {
for col in 0..<tilemap.numberOfColumns {
if tilemap.tileDefinition(atColumn: col, row: row) != nil {
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(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.affectedByGravity = false
tileNode.physicsBody?.collisionBitMask = Physics().player | Physics().bullet
tileNode.physicsBody?.categoryBitMask = Physics().ground
tileNode.physicsBody?.contactTestBitMask = Physics().ground | Physics().player
tileNode.physicsBody?.friction = 0.1
tileNode.physicsBody?.restitution = 0
tileNode.strokeColor = .white
tileNode.name = "Ground"
self.world.addChild(tileNode)
}
}
}
Let the bullet spawn on tapping the action2 button:
if self.ctrlAction2.contains(touch.location(in: cam)) {
let bullet = SKShapeNode(ellipseOf: CGSize(width: 5, height: 4))
bullet.fillColor = .orange
bullet.physicsBody = SKPhysicsBody(edgeLoopFrom: bullet.path!)
bullet.physicsBody?.affectedByGravity = true
bullet.physicsBody?.categoryBitMask = Physics().bullet
bullet.physicsBody?.collisionBitMask = Physics().enemy | Physics().ground
bullet.physicsBody?.contactTestBitMask = Physics().bullet | Physics().enemy | Physics().ground
bullet.position.y = charakter.position.y + charakter.size.height / 7
bullet.physicsBody?.isDynamic = true
bullet.name = "bullet"
if facing == "right" {
bullet.position.x = charakter.position.x + charakter.size.width / 1.9
self.world.addChild(bullet)
bullet.run(SKAction.moveBy(x: 2000, y: 0, duration: 4))
} else if facing == "left" {
bullet.position.x = charakter.position.x - charakter.size.width / 1.9
self.world.addChild(bullet)
bullet.run(SKAction.moveBy(x: -2000, y: 0, duration: 4))
}
}
The bullet collides with enemies, the enemies collides with the player and the player collides with the ground - everything works, except the collision between bullet and ground.
Thank you for your help in advance
Pascal

Related

SpriteKit SKTileMapNode same physics body for multiple tiles

The following code is correctly adding physicsBodies for every "wall" tile in my SKTileMapNode. However, I would like to group them all under the same node with one physicsBody for the entire group. How do I achieve that?
private func setupTileMap() {
let tileSize = self.tileMap.tileSize
let halfWidth = CGFloat(self.tileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(self.tileMap.numberOfRows) / 2.0 * tileSize.height
for column in 0..<self.tileMap.numberOfColumns {
for row in 0..<self.tileMap.numberOfRows {
let tileDefinition = self.tileMap.tileDefinition(atColumn: column, row: row)
let tileX = CGFloat(column) * tileSize.width - halfWidth
let tileY = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.strokeColor = .clear
tileNode.position = CGPoint(x: tileX, y: tileY)
let physicsBodyCenter = CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0)
let physicsBody = SKPhysicsBody(rectangleOf: tileSize, center: physicsBodyCenter)
physicsBody.isDynamic = false
tileNode.physicsBody = physicsBody
if tileDefinition?.name == Nodes.wall.rawValue {
physicsBody.categoryBitMask = CategoryMask.wall.rawValue
}
self.tileMap.addChild(tileNode)
}
}
}

Move sprite connected with pin join by touch?

I have a sprite node connected to another sprite node with a pin joint, anchored as such. I am trying to simulate this, where the green sprite is being moved with a finger:
https://i.stack.imgur.com/limMh.gif
This is what I have so far, but it's not rotating correctly:
body.name = "body"
body.size = CGSize(width: 10, height: 70)
body.physicsBody = SKPhysicsBody(rectangleOf: body.size)
body.physicsBody?.isDynamic = false
self.addChild(body)
leftArm.name = "leftArm"
leftArm.size = CGSize(width: 50, height: 50)
leftArm.position = CGPoint(x: -24, y: 0)
leftArm.physicsBody = SKPhysicsBody(rectangleOf: leftArm.size)
leftArm.physicsBody?.isDynamic = true
self.addChild(leftArm)
let leftArmBodyJoint = SKPhysicsJointPin.joint(withBodyA: leftArm.physicsBody!,
bodyB: body.physicsBody!,
anchor: CGPoint(x: self.body.frame.midX, y: self.body.frame.midY))
physicsWorld.add(leftArmBodyJoint)
let location = touches.first?.location(in: self)
let nodeTouched = atPoint(location!)
if nodeTouched.name == "leftArm" {
print("Began touch on left arm")
touchedLeftArm = nodeTouched
isFingerOnLeftArm = true
self.startPoint = touches.first!.location(in: self)
}
if isFingerOnLeftArm{
let newPoint = touches.first!.location(in: self)
let calculatedPoint = CGPoint(x: newPoint.x - startPoint.x, y: newPoint.y - startPoint.y)
leftArm.position = CGPoint(x: leftArm.position.x + calculatedPoint.x, y: leftArm.position.y + calculatedPoint.y)
let radians = atan2(newPoint.x - leftArm.position.x, newPoint.y - leftArm.position.y)
leftArm.zRotation = -radians//this rotates the player
self.startPoint = newPoint
}
Thanks for any help.

Move one Node to touch.location when already a few Nodes in scene with same name

I created a function that spawns a SKSpriteNode with name Ball and I created another function to animate it with 2 different colors. After they spawned I need to move one Ball to my touch.location, but when I create this it always takes the last spawned Node to move the location. So I just need to move one of a few balls that are currently in the scene and not the last one added.
Can someone please help me. Thanks in advance
// Spawn forever
let action = SKAction.sequence([SKAction.run(spawnRandomBall), SKAction.wait(forDuration: 1.2)])
run(SKAction.repeatForever(action))
}
//-------------------------------------------------------------------------------------------------//
func randomLocation (){
let randomNumber = Int(arc4random() % 2) // 0 and 1
if randomNumber == 0 {
roodball.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 20))
blauwball.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 20))
} else {
roodball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
blauwball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
}
}
//.....................................................................................................//
func spawnRandomBall (){
let randomNumber = Int(arc4random() % 2) // 0 and 1
if randomNumber == 0 {
spawnRoodBall()
} else {
spawnBlauwBall()
}
roodball.physicsBody = SKPhysicsBody(circleOfRadius: roodball.size.height / 2)
roodball.physicsBody?.allowsRotation = false
roodball.physicsBody?.affectedByGravity = false
roodball.physicsBody?.friction = 0
roodball.physicsBody?.restitution = 1
roodball.physicsBody?.linearDamping = 0
roodball.physicsBody?.angularDamping = 0
roodball.physicsBody?.velocity = CGVector(dx: 1, dy: 1)
blauwball.physicsBody = SKPhysicsBody(circleOfRadius: blauwball.size.height / 2)
blauwball.physicsBody?.allowsRotation = false
blauwball.physicsBody?.affectedByGravity = false
blauwball.physicsBody?.friction = 0
blauwball.physicsBody?.restitution = 1
blauwball.physicsBody?.linearDamping = 0
blauwball.physicsBody?.angularDamping = 0
blauwball.physicsBody?.velocity = CGVector(dx: 1, dy: 1)
randomLocation()
}
//.....................................................................................................//
func spawnRoodBall () {
roodball = SKSpriteNode(imageNamed: "BalRood")
roodball.position = CGPoint(x: 360, y: self.frame.midY - 38)
roodball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roodball.size = CGSize(width: 50, height: 50)
roodball.run(SKAction.animate(with: roodTextureArray, timePerFrame: 1.3))
roodball.zPosition = 40
let wait = SKAction.wait(forDuration: 6.5)
let remove = SKAction.removeFromParent()
roodball.run(SKAction.sequence([wait, remove]), withKey: "waitRemove")
self.addChild(roodball)
}
//...................................................................................................//
func spawnBlauwBall () {
blauwball = SKSpriteNode(imageNamed: "BalBlauw")
blauwball.position = CGPoint(x: 360, y: self.frame.midY - 38)
blauwball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
blauwball.size = CGSize(width: 50, height: 50)
blauwball.run(SKAction.animate(with: blauwTextureArray, timePerFrame: 1.3))
blauwball.zPosition = 40
let wait = SKAction.wait(forDuration: 6.5)
let remove = SKAction.removeFromParent()
blauwball.run(SKAction.sequence([wait, remove]), withKey: "waitRemove")
self.addChild(blauwball)
}

Swift UITableView zigzag borders

I am trying to achieve this aspect for an UITableView : https://www.dropbox.com/s/bcp86myyjgek1kt/Screenshot%202016-11-04%2014.04.14.png?dl=0 and I am stuck.
I followed Atul Manwar's answer :
func applyZigZagEffect(givenView: UIView) {
let width = givenView.frame.size.width
let height = givenView.frame.size.height
let givenFrame = givenView.frame
let zigZagWidth = CGFloat(7)
let zigZagHeight = CGFloat(5)
let yInitial = height-zigZagHeight
var zigZagPath = UIBezierPath()
zigZagPath.moveToPoint(CGPointMake(0, 0))
zigZagPath.addLineToPoint(CGPointMake(0, yInitial))
var slope = -1
var x = CGFloat(0)
var i = 0
while x < width {
x = zigZagWidth * CGFloat(i)
let p = zigZagHeight * CGFloat(slope)
let y = yInitial + p
let point = CGPointMake(x, y)
zigZagPath.addLineToPoint(point)
slope = slope*(-1)
i++
}
zigZagPath.addLineToPoint(CGPointMake(width, 0))
var shapeLayer = CAShapeLayer()
shapeLayer.path = zigZagPath.CGPath
givenView.layer.mask = shapeLayer
}
The result is not the one I am looking for, because I only obtain the bottom border: Achieved using Atul's answer and I have no clue how to change it for both borders ( bottom and top ).
Tried with images, but is not scaled correctly, and I find this solution better, but I am not able to produce the effect for top border.
Thanks!
I had been working on your question and this are my results, use this code,
func applyZigZagEffect(givenView: UIView) {
let width = givenView.frame.size.width
let height = givenView.frame.size.height
let givenFrame = givenView.frame
let zigZagWidth = CGFloat(7)
let zigZagHeight = CGFloat(5)
var yInitial = height-zigZagHeight
var zigZagPath = UIBezierPath(rect: givenFrame)
zigZagPath.move(to: CGPoint(x:0, y:0))
zigZagPath.addLine(to: CGPoint(x:0, y:yInitial))
var slope = -1
var x = CGFloat(0)
var i = 0
while x < width {
x = zigZagWidth * CGFloat(i)
let p = zigZagHeight * CGFloat(slope)
let y = yInitial + p
let point = CGPoint(x: x, y: y)
zigZagPath.addLine(to: point)
slope = slope*(-1)
i += 1
}
zigZagPath.addLine(to: CGPoint(x:width,y: 0))
yInitial = 0 + zigZagHeight
x = CGFloat(width)
i = 0
while x > 0 {
x = width - (zigZagWidth * CGFloat(i))
let p = zigZagHeight * CGFloat(slope)
let y = yInitial + p
let point = CGPoint(x: x, y: y)
zigZagPath.addLine(to: point)
slope = slope*(-1)
i += 1
}
var shapeLayer = CAShapeLayer()
shapeLayer.path = zigZagPath.cgPath
givenView.layer.mask = shapeLayer
}
I hope this helps you, this code works and was tested
Edited
With this method you can get curved zigzag instead of lines
class func pathSemiCirclesPathForView(givenView: UIView, ciclesRadius:CGFloat = 4, circlesDistance : CGFloat = 3, top:Bool = true, bottom:Bool = true ) ->UIBezierPath
{
let width = givenView.frame.size.width
let height = givenView.frame.size.height
let semiCircleWidth = CGFloat(ciclesRadius*2)
let semiCirclesPath = UIBezierPath()
semiCirclesPath.move(to: CGPoint(x:0, y:0))
if(bottom) {
var x = CGFloat(0)
var i = 0
while x < width {
x = (semiCircleWidth) * CGFloat(i) + (circlesDistance * CGFloat(i))
let pivotPoint = CGPoint(x: x + semiCircleWidth/2, y: height)
semiCirclesPath.addArc(withCenter: pivotPoint, radius: ciclesRadius, startAngle: -180 * .pi / 180.0, endAngle: 0 * .pi / 180.0, clockwise: true)
semiCirclesPath.addLine(to: CGPoint(x: semiCirclesPath.currentPoint.x + circlesDistance, y: height))
i += 1
}
}
else {
semiCirclesPath.addLine(to: CGPoint(x: 0, y: height))
semiCirclesPath.addLine(to: CGPoint(x: width, y: height))
}
semiCirclesPath.addLine(to: CGPoint(x:width,y: 0))
if(top) {
var x = CGFloat(width)
var i = 0
while x > 0 {
x = width - (semiCircleWidth) * CGFloat(i) - (circlesDistance * CGFloat(i))
let pivotPoint = CGPoint(x: x - semiCircleWidth/2, y: 0)
semiCirclesPath.addArc(withCenter: pivotPoint, radius: ciclesRadius, startAngle: 0 * .pi / 180.0, endAngle: -180 * .pi / 180.0, clockwise: true)
semiCirclesPath.addLine(to: CGPoint(x: semiCirclesPath.currentPoint.x - circlesDistance, y: 0))
i += 1
}
}
semiCirclesPath.close()
return semiCirclesPath
}
RESULTS
In case somebody else will need it somewhere, I am posting here the result I was looking for with #Reinier Melian 's help. I will soon post another version, customising only the first and last cell of a UITableView.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var customView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.applyZigZagEffect(givenView: customView)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pathZigZagForView(givenView: UIView) ->UIBezierPath
{
let width = givenView.frame.size.width
let height = givenView.frame.size.height
let givenFrame = givenView.frame
let zigZagWidth = CGFloat(7)
let zigZagHeight = CGFloat(5)
var yInitial = height-zigZagHeight
let zigZagPath = UIBezierPath(rect: givenFrame.insetBy(dx: 5, dy: 5))
zigZagPath.move(to: CGPoint(x:0, y:0))
zigZagPath.addLine(to: CGPoint(x:0, y:yInitial))
var slope = -1
var x = CGFloat(0)
var i = 0
while x < width
{
x = zigZagWidth * CGFloat(i)
let p = zigZagHeight * CGFloat(slope) - 5
let y = yInitial + p
let point = CGPoint(x: x, y: y)
zigZagPath.addLine(to: point)
slope = slope*(-1)
i += 1
}
zigZagPath.addLine(to: CGPoint(x:width,y: 0))
yInitial = 0 + zigZagHeight
x = CGFloat(width)
i = 0
while x > 0
{
x = width - (zigZagWidth * CGFloat(i))
let p = zigZagHeight * CGFloat(slope) + 5
let y = yInitial + p
let point = CGPoint(x: x, y: y)
zigZagPath.addLine(to: point)
slope = slope*(-1)
i += 1
}
zigZagPath.close()
return zigZagPath
}
func applyZigZagEffect(givenView: UIView)
{
let shapeLayer = CAShapeLayer(layer: givenView.layer)
givenView.backgroundColor = UIColor.clear
shapeLayer.path = self.pathZigZagForView(givenView: givenView).cgPath
shapeLayer.frame = givenView.bounds
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.masksToBounds = true
shapeLayer.shadowOpacity = 1
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: 0, height: 0)
shapeLayer.shadowRadius = 3
givenView.layer.addSublayer(shapeLayer)
}
}
Result
Here is the code in a Swift 3 playground if anyone wants to check out the solution in action:
https://gist.github.com/thexande/9f93b3c899af63108415936bf13a43da

Create parallax clouds with random Y coordinate using SKSpriteNode and SKAction in Swift

I'm trying to create a parallax cloud effect with different speeds and sizes but i'm having a hard time when trying to add a different Y coordinate, not always in the same Y for all the clouds.
So I have 5 clouds, with its respective X and Y coordinate, and I use the SKAction.moveByX() function.
All the clouds starts from for example FRAME_WIDTH + 200 (out of bounds) and they end at -100. After that I reset the X to FRAME_WIDTH + 200 and do a "forever" sequence.
That was working perfectly, but I would like to add a random Y coordinate every time the animation finishes.
I was able to do it, but that would only change the Y coordinate once.
How can I achieve this?
Here's my current code:
func addClouds() {
let cloud1 = SKSpriteNode(imageNamed: "cloud1")
let cloud2 = SKSpriteNode(imageNamed: "cloud2")
let cloud3 = SKSpriteNode(imageNamed: "cloud3")
let cloud4 = SKSpriteNode(imageNamed: "cloud4")
let cloud5 = SKSpriteNode(imageNamed: "cloud5")
cloud1.zPosition = ZIndexPosition.CLOUD
cloud1.position = CGPoint(x: self.FRAME_WIDTH - 100, y: self.FRAME_HEIGHT / 2 - 150)
cloud1.size = CGSize(width: 44, height: 14)
cloud2.zPosition = ZIndexPosition.CLOUD
cloud2.position = CGPoint(x: self.FRAME_WIDTH + 150, y: self.FRAME_HEIGHT - 100)
cloud2.size = CGSize(width: 104, height: 16)
cloud3.zPosition = ZIndexPosition.CLOUD
cloud3.position = CGPoint(x: self.FRAME_WIDTH - 50, y: self.FRAME_HEIGHT - 200)
cloud3.size = CGSize(width: 8, height: 6)
cloud4.zPosition = ZIndexPosition.CLOUD
cloud4.position = CGPoint(x: self.FRAME_WIDTH + 200, y: self.FRAME_HEIGHT / 2 - 50)
cloud4.size = CGSize(width: 116, height: 32)
cloud5.zPosition = ZIndexPosition.CLOUD
cloud5.position = CGPoint(x: self.FRAME_WIDTH, y: self.FRAME_HEIGHT - 250)
cloud5.size = CGSize(width: 24, height: 6)
let resetCloud1YPos = SKAction.moveToY(self.randomCloud1, duration: 0)
let resetCloud2YPos = SKAction.moveToY(self.randomCloud2, duration: 0)
let resetCloud3YPos = SKAction.moveToY(self.randomCloud3, duration: 0)
let resetCloud4YPos = SKAction.moveToY(self.randomCloud4, duration: 0)
let resetCloud5YPos = SKAction.moveToY(self.randomCloud5, duration: 0)
let resetCloud1Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 100, duration: 0)
let resetCloud2Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 150, duration: 0)
let resetCloud3Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 50, duration: 0)
let resetCloud4Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 200, duration: 0)
let resetCloud5Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 20, duration: 0)
let moveCloud1 = SKAction.moveToX(-100, duration: 28)
let cloud1Sequence = SKAction.sequence([moveCloud1, resetCloud1Pos, resetCloud1YPos])
let cloud1Forever = SKAction.repeatActionForever(cloud1Sequence)
let moveCloud2 = SKAction.moveToX(-100, duration: 24)
let cloud2Sequence = SKAction.sequence([moveCloud2, resetCloud2Pos, resetCloud2YPos])
let cloud2Forever = SKAction.repeatActionForever(cloud2Sequence)
let moveCloud3 = SKAction.moveToX(-100, duration: 35)
let cloud3Sequence = SKAction.sequence([moveCloud3, resetCloud3Pos, resetCloud3YPos])
let cloud3Forever = SKAction.repeatActionForever(cloud3Sequence)
let moveCloud4 = SKAction.moveToX(-100, duration: 13)
let cloud4Sequence = SKAction.sequence([moveCloud4, resetCloud4Pos, resetCloud4YPos])
let cloud4Forever = SKAction.repeatActionForever(cloud4Sequence)
let moveCloud5 = SKAction.moveToX(-100, duration: 18)
let cloud5Sequence = SKAction.sequence([moveCloud5, resetCloud5Pos, resetCloud5YPos])
let cloud5Forever = SKAction.repeatActionForever(cloud5Sequence)
cloud1.runAction(cloud1Forever)
cloud2.runAction(cloud2Forever)
cloud3.runAction(cloud3Forever)
cloud4.runAction(cloud4Forever)
cloud5.runAction(cloud5Forever)
self.addChild(cloud1)
self.addChild(cloud2)
self.addChild(cloud3)
self.addChild(cloud4)
self.addChild(cloud5)
}
randomCloud1,2,3,4 and 5 is just a random generation within the screen height bounds.
Thanks in advance.
If I understand what you're trying to accomplish, the following code should help you:
// ViewController.swift
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//create and position a test SKSpriteNode
let cloud = SKSpriteNode()
cloud.color = SKColor.blueColor()
cloud.position = CGPoint(x: self.frame.size.width - 100, y: 400)
cloud.size = CGSize(width: 44, height: 14)
self.addChild(cloud)
//pass the SKSpriteNode into the moveNode function
self.moveNode(cloud)
}
//reposition the node
func repositionNode(node: SKSpriteNode) {
print("repositioning node")
node.position = CGPointMake(self.frame.size.width - 100, self.randomCloudPosition())
print(node.position.y)
self.moveNode(node)
}
//move the node again
func moveNode(node: SKSpriteNode) {
node.runAction(SKAction.moveToX(-100, duration: 28), completion: {
self.repositionNode(node)
})
}
//return a random number between 300 and 799 (change this for your specific case)
func randomCloudPosition() -> CGFloat {
return CGFloat(arc4random_uniform(500) + 300) //should be between 300 and 799
}
}
You should be able to create your five clouds (or however many) and then just pass each one into moveNode and the animation will continue indefinitely

Resources