SkShapeNode physicsbody weird behaviour - swift

I created a subclass of SKNode:
class SquareDrop: SKNode {
init(image: SKSpriteNode) {
super.init()
self.setScale(0.3
//Set the starting position of the node
self.position = CGPoint(x: 0.5, y: UIScreen.main.bounds.height/2)
//Apply a physics body to the node
self.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "square"), size: CGSize(width: image.size.width * 0.3, height: image.size.height * 0.3))
self.addChild(image)
}
I created an instance in GameScene class and I set up it:
func spawnRain()
squareDrop = SquareDrop(image: SKSpriteNode(imageNamed: "square"))
squareDrop?.physicsBody?.linearDamping = 5
squareDrop?.physicsBody?.restitution = 0
self.addChild(squareDrop!)
}
It is the result:
It works like aspected, but instead of using an image I wanted to draw a square:
func spawnRain() {
//Here I did not use the class that I had created previously
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 100, height: 100), cornerRadius: 5).cgPath
shape.position = CGPoint(x: frame.midX - shape.frame.width/2 , y: UIScreen.main.bounds.height/2)
shape.fillColor = UIColor.red
shape.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100))
addChild(shape)
}
It doesn't seem like I aspected (the squares that I created have a strange behaviour). Any hints?
I am sorry for the question, but I am a noob of SpriteKit.

Related

Why does my SKPhysicsBody not adjust when I change my SKNode's anchor point?

I create gems from an SKTexture (below), then assign it a physics body. When the user touches and moves the gem, I change it's actor point so that the gem isn't hidden under the users finger:
.anchorPoint = CGPoint(x: 0.5, y: 0)
However when I have showsPhysics set to true, I can see that the physics body is still at the gem's original anchor point (see attached image). Is this a bug? Is there some additional method I need to call?
super.init(texture: skTexture, color: .clear, size: .zero)
self.isHidden = true
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.size = CGSize(width: myGV.gemSize.width, height: myGV.gemSize.height)
self.zPosition = theZ.gem
self.position = CGPoint(x: 0, y: 0 )
self.name = fileName
self.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: fileName), size: CGSize(width: myGV.gemSize.width, height: myGV.gemSize.height))

Programmatically drawing custom oval shape on screen swift

How can i draw custom oval shape like in below image in swift (not swiftUI).
Thank you in advance
I have tried to clone similar view using UIView and was able to create similar UI as you have stated on screenshot
And here is my code snippet
let width: CGFloat = view.frame.size.width
let height: CGFloat = view.frame.size.height
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let rect = CGRect(x: width / 2 - 150, y: height / 2.5 - 100, width: 300, height: 400)
let circlePath = UIBezierPath(ovalIn: rect)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = UIColor.red.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
Hope this helps you. Good day.
You can draw oval path like this
class CustomOval: UView {
override func drawRect(rect: CGRect) {
var ovalPath = UIBezierPath(ovalIn: rect)
UIColor.gray.setFill()
ovalPath.fill()
}
}

Create a UIView with rounded bottom edge

I want make view this Bottom roundedenter image description here
I think this is really close to what you want:
The rectView is your initial view;
You can play with the + 50 of quad curve height to adjust the curve
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
rectView.backgroundColor = .red
view.addSubview(rectView)
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: rectView.frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: rectView.frame.width, y: rectView.frame.height), controlPoint: CGPoint(x: rectView.frame.width / 2 , y: rectView.frame.height + 50 ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
rectView.layer.insertSublayer(shapeLayer, at: 0)
rectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
rectView.layer.cornerRadius = 10
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25
Maybe, even better:
You can create your view:
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(rectView)
Extend UIView
extension UIView {
func addBottomArc(ofHeight height: CGFloat, topCornerRadius: CGFloat) {
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: frame.width, y: frame.height), controlPoint: CGPoint(x: frame.width / 2 , y: frame.height + height ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.cornerRadius = topCornerRadius
}
}
So you can set the height and the corner radius like this:
rectView.addBottomArc(ofHeight: 50, topCornerRadius: 15)
and than add the shadow you need to your view:
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25

Centering a UIBezierPath

I have a UIBezierPath thats a rounded square. It somewhat looks like this:
Here's my code for the shape:
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x:(0), y: (0), width: (250), height: (400)), cornerRadius: 64).cgPath
shape.position = CGPoint(x: 0, y: 0)
print(shape.position)
shape.fillColor = UIColor.white
shape.strokeColor = UIColor.white
shape.lineWidth = 5
addChild(shape)
I want to center it in the middle of the screen, but using
shape.position = CGPoint(x: self.frame.width, y: self.frame.height)
doesn't work. Thanks!
I suggest that you center the UIBezierPath within the shape node. For example,
let width:CGFloat = 250
let height:CGFloat = 400
shape.path = UIBezierPath(roundedRect: CGRect(x:-width/2, y: -height/2, width: width, height: height), cornerRadius: 64).cgPath
You can center the shape in the scene by setting its position to (0, 0).

Does a parent have its children's physicsBodies?

I was wondering what the physicsBody of a parent would be if all I do is this:
let combinedBodies = SKSpriteNode()
addChild(combinedBodies)
//Create its children like this as an example:
let child = SKSpriteNode(color: UIColor.red, size: CGSize(width: 10, height: 20))
child.physicsBody = SKPhysicsBody(rectangleOf: child.size)
child.physicsBody?.categoryBitMask = collisionType.child.rawValue
combinedBodies.addChild(child)
allBodies.append(child.physicsBody?)
Would the physicsBody of combinedBodies be the combination of all its children's physicsBodies, or would it have none and I would have to manually add it? Also, what would the categoryBitMask of the parent be? Would it be collisionType.child.rawValue if all of its children had that category, or would I manually have to give it its own category?
Edit: I have used this:
combinedBodies.physicsBody = SKPhysicsBody(bodies: )
I have a list where I store all the children's physicsBodies and then put that list into this command (I keep updating the combinedBodies.physicsBody when new and old children get deleted). However, now I have the problem that the physicsbodies of the parent aren't where the original physicsbodies of the children were (they are at different positions on the screen), they are in the middle of the screen. Is there a way to fix this?
I have also changed the physicsBody of the combinedBodies like this:
combinedBodies.physicsBody = SKPhysicsBody(bodies: allBodies)
combinedBodies.physicsBody?.isDynamic = true
combinedBodies.physicsBody?.categoryBitMask = collisionType.combinedBodies.rawValue
combinedBodies.physicsBody?.collisionBitMask = 0
combinedBodies.physicsBody?.contactTestBitMask = 0
combinedBodies.physicsBody?.affectedByGravity = false
combinedBodies.physicsBody?.friction = 0.0
Please note that all the code in this question is basically my code, just that the new children are created through an iteration with different positions, rotations, and colors.
EDIT WITH EXAMPLE CODE: I have been able to recreate my issue in a test program which I have added here (the allLine is the parent of ScreenLine and line in this code).
class GameScene: SKScene, SKPhysicsContactDelegate {
enum type: UInt32 {
case line = 1
case screenLine = 2
case allLine = 4
}
override func didMove(to view: SKView) {
let allLine = SKSpriteNode()
addChild(allLine)
var points = [CGPoint]()
var bodies = [SKPhysicsBody]()
points = [CGPoint(x: 0, y: 300), CGPoint(x: -200, y: 100)]
let width = 5.0
let height = {() -> Double in
let distX = abs(points[1].x - points[0].x)
let distY = abs(points[1].y - points[0].y)
let dist = sqrt((distX * distX) + (distY * distY))
return Double(dist)
}()
let ScreenLine = SKSpriteNode(color: UIColor.init(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0), size: CGSize(width: width, height: height))
ScreenLine.position = CGPoint(x: ((points[1].x + points[0].x)/2), y: ((points[1].y + points[0].y)/2))
let rotation = {() -> CGFloat in
let m = (points[1].y - points[0].y)/(points[1].x - points[0].x)
let angle = CGFloat(Double.pi/2) - atan(m)
return -angle
}()
ScreenLine.zRotation = rotation
ScreenLine.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: CGPoint(x: 0, y: 0))
ScreenLine.physicsBody?.contactTestBitMask = type.screenLine.rawValue
ScreenLine.physicsBody?.collisionBitMask = 0
ScreenLine.physicsBody?.contactTestBitMask = 0
ScreenLine.physicsBody?.isDynamic = true
ScreenLine.physicsBody?.affectedByGravity = false
allLine.addChild(ScreenLine)
bodies.append(ScreenLine.physicsBody!)
points = [CGPoint(x: -100, y: 300), CGPoint(x: -300, y: 100)]
let line = SKSpriteNode(color: UIColor.init(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0), size: CGSize(width: width, height: height))
line.position = CGPoint(x: ((points[1].x + points[0].x)/2), y: ((points[1].y + points[0].y)/2))
line.zRotation = rotation
line.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: CGPoint(x: 0, y: 0))
line.physicsBody?.contactTestBitMask = type.screenLine.rawValue
line.physicsBody?.collisionBitMask = 0
line.physicsBody?.contactTestBitMask = 0
line.physicsBody?.isDynamic = true
line.physicsBody?.affectedByGravity = false
allLine.addChild(line)
bodies.append(line.physicsBody!)
allLine.physicsBody = SKPhysicsBody(bodies: bodies)
allLine.physicsBody?.isDynamic = true
allLine.physicsBody?.categoryBitMask = type.allLine.rawValue
allLine.physicsBody?.collisionBitMask = 0
allLine.physicsBody?.contactTestBitMask = 0
allLine.physicsBody?.affectedByGravity = false
allLine.physicsBody?.friction = 0.0
}
}
UPDATE: Now by centering the physicsBody of the children to where the node is (not CGPoint.zero), but where I place the child on the screen, the physicsBody is in the right position when I add it to the list.
I set the line and screen line physicsBody positions like this:
ScreenLine.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: ScreenLine.position)
line.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: line.position)
PROBLEM THAT I'M CURRENTLY TRYING TO FIGURE OUT:
This makes the physicsBodies of allLine centred. However, they aren't rotated like they should be. They don't preserve their rotations. How do I fix this??
Thanks for any answers and help :D